Java深入集合--linkedHashMap

来源:互联网 发布:php手册 pdf 编辑:程序博客网 时间:2024/05/19 18:47

原文:http://greemranqq.iteye.com/blog/1931880

LinkedHashMap 源码介绍

 

一、介绍:

     LinkedHashMap 和hashMap 功能类似,都是维护的键值对集合,连遍历 以及方法都类似,唯一的区别在于

hashMap  里面的元素是根据hash值来决定存放位置的,是无序的,而LinkedHashMap 维护的是一个按顺序存放的双向链表,是有序的。

      所谓的双向链表其实是链表的一种。链表:相当于元素 A->B->C ,也就是我可以通过A 找到B ,从而找到C,可以单向移动。而双向链表:A<->B<->C ,也就是说我可以从B  找到 A 和 C,可以双向移动。

 

二、源码分析:

       

Java代码 复制代码 收藏代码
  1. public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>  

    我们可以看到 这东西是继承的HashMap 的,说明拥有hashMap 的功能,那么我们具体来看看 不同在哪里呢?

 

   1. 构造函数:

    

Java代码 复制代码 收藏代码
  1. public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {  
  2.         super(initialCapacity, loadFactor);  
  3.         this.accessOrder = accessOrder;  
  4. }  
  5.   
  6.  public LinkedHashMap(int initialCapacity, float loadFactor) {  
  7.         super(initialCapacity, loadFactor);  
  8.         accessOrder = false;  
  9. }  
  10.   
  11.  public LinkedHashMap(int initialCapacity) {  
  12.     super(initialCapacity);  
  13.         accessOrder = false;  
  14. }  
  15. 我们看到3个构造函数,其实都是访问的super(..),也就是hashMap 的构造,区别就在accessOrder   
  16. 来看看它是什么?  

 

 

Java代码 复制代码 收藏代码
  1.  //The iteration ordering method for this linked hash map: <tt>true</tt>  
  2.  //for access-order, <tt>false</tt> for insertion-order.  
  3. // 简单说就是这个用来控制元素的顺序,  
  4. // true: 是访问的顺序,也就是谁最先访问,就排在第一位  
  5. // false:存放顺序,就是你put 元素的时候的顺序  
  6. private final boolean accessOrder;  

  这里稍后再看怎么用的,我们先来分析核心内部类 Entry 类,这几乎是所有map 都需要的东西。

 

Java代码 复制代码 收藏代码
  1. private static class Entry<K,V> extends HashMap.Entry<K,V> {  
  2.        // These fields comprise the doubly linked list used for iteration.  
  3.        // 这里我们看到,Entry<K,V>  类里面多了了两个属性,专门来方便我们进行链表的前后移动        // 的  
  4.        Entry<K,V> before, after;  
  5.        // 这里调用了hashMap 的entry 构造,说明还是用的HashMap 的Entry  
  6. Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {  
  7.            super(hash, key, value, next);  
  8.        }  
  9.       // ..剩下的稍后说  

   

 

 

Java代码 复制代码 收藏代码
  1. 继续来看这些属性怎么利用起来的。对于集合,我们肯定要关注他的 存放元素 和 取元素的方法啦:  
  2. 当我们找遍整个类的时候发现,没有找到put 方法- -。但是LinkedHashMap 肯定是可以调用put的,因为继承了hashMap,  
  3. 那我们把 hashMap 的put方法先取出来吧  

 

Java代码 复制代码 收藏代码
  1. public V put(K key, V value) {  
  2.        if (key == null)  
  3.            return putForNullKey(value);  
  4.        int hash = hash(key.hashCode());  
  5.        // 这里都是调用 hashMap 计算位置什么的,前面hashMap 已经分析过啦  
  6.        int i = indexFor(hash, table.length);  
  7.        for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  8.            Object k;  
  9.            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  10.                V oldValue = e.value;  
  11.                e.value = value;  
  12.                // 这里被重写了 A  
  13.                e.recordAccess(this);  
  14.                return oldValue;  
  15.            }  
  16.        }  
  17.   
  18.        modCount++;  
  19.        // 这里被重写了 B   
  20.        addEntry(hash, key, value, i);  
  21.        return null;  
  22.    }  

 

 

  从上面看出和hashMap 没啥区别,关键就看重写的方法啦,特别是e.recordAccess 这个方法 在hashMap     介绍中,不是提到不知道干什么的嘛,这里详细来看看。

    

Java代码 复制代码 收藏代码
  1. private static class Entry<K,V> extends HashMap.Entry<K,V> {  
  2.     /** 
  3.          * This method is invoked by the superclass whenever the value 
  4.          * of a pre-existing entry is read by Map.get or modified by Map.set. 
  5.          * If the enclosing Map is access-ordered, it moves the entry 
  6.          * to the end of the list; otherwise, it does nothing. 
  7.          */  
  8.         void recordAccess(HashMap<K,V> m) {  
  9.             LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;  
  10.             // 如果参数为true,也就是根据访问顺序  
  11.             if (lm.accessOrder) {  
  12.                 // 迭代控制的变量  
  13.                 lm.modCount++;  
  14.                 remove();  
  15.                 addBefore(lm.header);  
  16.             }  
  17.         }  
  18.       
  19.     // 这里我们发现 before和after 虽然定义了属性,是哪儿赋值的呢?为什么不为null呢?  
  20.         // 请回到 构造函数,我们发现有一个init()方法,以前是没用的,这里也进行了重写,请看         // 后面init 方法  
  21.         private void remove() {  
  22.             // 先移除当前这个空节点  
  23.             before.after = after;  
  24.             after.before = before;  
  25.         }  
  26.           
  27.     /** 
  28.          * Inserts this entry before the specified existing entry in the list. 
  29.          */  
  30.         private void addBefore(Entry<K,V> existingEntry) {  
  31.             // 然后将这个空节点 赋值  
  32.             after  = existingEntry;  
  33.             // 当前节点 ,赋值于标志位的前节点  
  34.             before = existingEntry.before;  
  35.             // 然后将复制后的节点的 两个属性,继续赋值为空,等待另一个节点的插入  
  36.             before.after = this;  
  37.             after.before = this;  
  38.         }  
  39.         // 整个  
  40.   
  41. }  

 

  init() 方法:

 

Java代码 复制代码 收藏代码
  1.   /** 
  2.    * The head of the doubly linked list. 
  3.    */  
  4. private transient Entry<K,V> header;  
  5. void init() {  
  6.       // 这里默链表 表头是header,并让hash值为-1,其他都为null,仅仅是作为一个标志位  
  7.       // 初始化这个节点  
  8.       header = new Entry<K,V>(-1nullnullnull);  
  9.       // 赋值在这里,默认这是起点,相当于还没有其他元素  
  10.       header.before = header.after = header;  
  11.   }  

 

   可能上面的文字描述很难理解,我们假设:这个双链表结构相当于 一条一节一节连起来的项链,当然每一节肯定有链接前before 和 after 这样的连接位置(属性),中间才是宝石(数据)。然后,一般项链都有一个明显的位置,方便你去下来的(header),那里其实是用来首位相连的,当我们觉得长度不够,需要添加一个宝石的时候,首先会从那里打开,也就是执行(remove)方法,然后把你需要的宝石连接在最后的位置(after  = existingEntry,before = existingEntry.before;);,从程序上来说 就是把那个header 的位置,原先连接器的先取掉掉,那个位置就从新连接新的元素。

    

继续讲解put 方法,继续往下看 还有一个addEntry(hash, key, value, i)方法,也进行了重写

 

Java代码 复制代码 收藏代码
  1.    void addEntry(int hash, K key, V value, int bucketIndex) {  
  2.        // 存放元素  
  3.        createEntry(hash, key, value, bucketIndex);  
  4.   
  5.        // Remove eldest entry if instructed, else grow capacity if appropriate  
  6.        Entry<K,V> eldest = header.after;  
  7.        // 这里始终返回的是false,也就是说 方便我们扩展,重写的- -!  
  8.        if (removeEldestEntry(eldest)) {  
  9.            removeEntryForKey(eldest.key);  
  10.        } else {  
  11.            if (size >= threshold)  
  12.                resize(2 * table.length);  
  13.        }  
  14. }  
  15. void createEntry(int hash, K key, V value, int bucketIndex) {  
  16.        // 通过key 和长度 计算出的位置,去获得那个元素,可能为空  
  17.        HashMap.Entry<K,V> old = table[bucketIndex];  
  18.        // 然后在这个位置创建一个新元素,并让next 指向old  
  19. Entry<K,V> e = new Entry<K,V>(hash, key, value, old);  
  20.        // 把这个元素放进数组的这个位置  
  21.        table[bucketIndex] = e;  
  22.        // 然后把header 的after before属性,和元素节点从新连接起来  
  23.        // 元素就在header 之前了,也就是可以保证最先访问(这里通过Set 集合遍历顺序是反的- -!)  
  24.        e.addBefore(header);  
  25.        size++;  
  26.    }  

 

   上面可以看出,linkedHashMap,元素默认是放在链表前,也就是根据存放顺序放的,而实际情况还是用的hashMap 里面的table 数组,元素位置是随机存放的,只是linkedHashMap扩展,加入了属性,对每个元素存放的位置进行了像链表结构那样的链接。那么当我们设置accessOrder=true 的时候如何才控制根据访问顺序进行排列呢?首先请看get 方法:

Java代码 复制代码 收藏代码
  1. }    // 也进行了重写  
  2. public V get(Object key) {  
  3.     // 调用父类的get方法  
  4.     Entry<K,V> e = (Entry<K,V>)getEntry(key);  
  5.     if (e == null)  
  6.         return null;  
  7.     // 这里还是调用的刚才的方法,把当前元素放在header 之前,也就完成了 根据访问顺序排序  
  8.     e.recordAccess(this);  
  9.     return e.value;  
  10. }  

 

上面对该集合实现双链表结构的原理 以及代码大概讲述了一下,可以去看看图例更加清晰,下面继续看看一些方法。

 

Java代码 复制代码 收藏代码
  1. 1.keySet(),entrySet(),values方法:  
  2.  这里还是调用的父类的,但是它重写了几个方法:  
  3.  // 这是重写的  
  4.  Iterator<K> newKeyIterator()   { return new KeyIterator();   }  
  5.  Iterator<V> newValueIterator() { return new ValueIterator(); }  
  6.  terator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); }  
  7.  // 这里返回值方式类似,都是通过 nextEntry()返回不同的内容,但是继承对象变成了LinkedHashIterator,不是原来的  // HashIterator  
  8.  private class KeyIterator extends LinkedHashIterator<K> {  
  9. public K next() { return nextEntry().getKey(); }  
  10.   }  
  11.   // 看看区别吧  
  12.   private abstract class LinkedHashIterator<T> implements Iterator<T> {  
  13.        // 后一个节点  
  14. Entry<K,V> nextEntry    = header.after;  
  15.        // 最后返回的节点  
  16. Entry<K,V> lastReturned = null;  
  17.   
  18. // 迭代的那个变量控制  
  19. int expectedModCount = modCount;  
  20.        // 如果下一个元素师头元素,说明已经到底呢,没有其他元素了  
  21.        public boolean hasNext() {  
  22.            return nextEntry != header;  
  23. }  
  24.       // 获得下一个元素  
  25.       Entry<K,V> nextEntry() {  
  26.            // 这里就不明白了,当排序参数设置为true 是,有lm.modCount ++,这里进行.next 迭代的时候,老报错  
  27.            // 不明白的意义了  
  28.     if (modCount != expectedModCount)  
  29.     throw new ConcurrentModificationException();  
  30.            // 这里元素迭代完了, 直接抛异常- -,不懂为什么这样设计  
  31.            if (nextEntry == header)  
  32.                throw new NoSuchElementException();  
  33.            // 返回下一个元素,并且将nextEntry 指向下下一个元素  
  34.            Entry<K,V> e = lastReturned = nextEntry;  
  35.            nextEntry = e.after;  
  36.            return e;  
  37. }  
  38.        // 删除  
  39.        public void remove() {  
  40.            // 这里同上  
  41.     if (lastReturned == null)  
  42.     throw new IllegalStateException();  
  43.     if (modCount != expectedModCount)  
  44.     throw new ConcurrentModificationException();  
  45.     // 这里看出lastReturned 作用就是记录遍历的当前位置,方便删除  
  46.            // 这里直接调用removeEntryForKey 方法就好了,这个- - 看着不爽,反正又不要返回值  
  47.            LinkedHashMap.this.remove(lastReturned.key);  
  48.            lastReturned = null;  
  49.            expectedModCount = modCount;  
  50. }  
  51.   }  

 

 

Java代码 复制代码 收藏代码
  1. 2.containsKey() 这里是访问hashMap 的方法,这里就不说了。  
  2.    containsValue() 进行了重写  
  3.   
  4.    public boolean containsValue(Object value) {  
  5.         // Overridden to take advantage of faster iterator  
  6.         // 这里仅仅是通过链表的两个属性进行遍历,hashMap 是通过table 数组进行遍历,效果差不        // 多  
  7.         if (value==null) {  
  8.             for (Entry e = header.after; e != header; e = e.after)  
  9.                 if (e.value==null)  
  10.                     return true;  
  11.         } else {  
  12.             for (Entry e = header.after; e != header; e = e.after)  
  13.                 if (value.equals(e.value))  
  14.                     return true;  
  15.         }  
  16.         return false;  

 

最近我不能直接在iteye 进行编辑,不知道为什么,老是卡死,都是记事本写了,复制过来,没图片和格式,望见谅!

总结:1.linkedhashMap 是继承于hashMap 也就是拥有了他一切功能

           2.他是双链表结构,好处可以存取顺序

           3.可以进行扩展,用来对那些最近访问元素的优先获得权

           4.存放效率,如果构造参数设置为true ,由于要维护链表结构,效率比hashMap 低一点,但是默认是放在最后,能直接从header 进行操作,效率其实没多大影响。

           5.get 元素类似。如果构造参数为true ,需要进行链表的操作,效率低于hashMap,否则效率一样。

           6.通过Iterator.keySet().iterator() 这样迭代,数据3000000 的情况,数据默认一个数字,linkedhashMap 慢几十毫秒,基本没影响。如果构造参数为true ,则linkedHashMap 迭代异常。

             

Java代码 复制代码 收藏代码
  1.        // 我内存不够,i大了要内存溢出  
  2.     public static  void test(){  
  3.     LinkedHashMap p = new  LinkedHashMap(3000000,0.75f,false);  
  4.     for(int i =0;i<3000000;i++){  
  5.         p.put(i, 2);  
  6.     }  
  7.     long a = System.currentTimeMillis();  
  8.     Iterator it1 = p.keySet().iterator();  
  9.     while(it1.hasNext()){  
  10.         Object o = it1.next();  
  11.         p.get(o);  
  12.     }  
  13.     long b = System.currentTimeMillis();  
  14.     System.out.println(b-a);// 250 毫秒  
  15.       
  16. }  
  17. public static void test2(){  
  18.     Map m = new HashMap(3000000);  
  19.     for(int i =0;i<3000000;i++){  
  20.         m.put(i, 2);  
  21.     }  
  22.     long a = System.currentTimeMillis();  
  23.     Iterator it1 = m.keySet().iterator();  
  24.     while(it1.hasNext()){  
  25.         Object o = it1.next();  
  26.         m.get(o);  
  27.     }  
  28.     long b = System.currentTimeMillis();  
  29.     System.out.println(b-a);// 210 毫秒  
  30. }  

0 0
原创粉丝点击