Java-Collections Framework学习与总结-IdentityHashMap

这篇总结一下java.util.IdentityHashMap。从类名上可以猜到,这个类本质应该还是一个散列表,只是前面有Identity修饰,是一种特殊的HashMap。
        简单的说,IdentityHashMap和HashMap的区别在于对key的比较。
        HashMap中会调用key的hashCode方法,hashCode方法可能会根据具体情况进行重写。在比较key时会用equals方法进行比较,equals方法也可能被重写。
Java代码   收藏代码
  1. public V put(K key, V value) {  
  2.     if (key == null)  
  3.         return putForNullKey(value);  
  4.                    //调用key的hashCode方法  
  5.     int hash = hash(key.hashCode());  
  6.     int i = indexFor(hash, table.length);  
  7.     for (Entry e = table[i]; e != null; e = e.next) {  
  8.         Object k;  
  9.                                //比较:k1==k2 或者 k1.equals(k2)  
  10.         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  11.             V oldValue = e.value;  
  12.             e.value = value;  
  13.             e.recordAccess(this);  
  14.             return oldValue;  
  15.         }  
  16.     }  
  17.   
  18.     modCount++;  
  19.     addEntry(hash, key, value, i);  
  20.     return null;  
  21. }  

        IdentityHashMap中会调用System.identityHashCode(x)来获得对象的hashCode(也就是对象的hashCode方法没有被覆盖情况下的返回值),仅用“==”来进行后面key的比较。
Java代码   收藏代码
  1.   public V put(K key, V value) {  
  2.       Object k = maskNull(key);  
  3.       Object[] tab = table;  
  4.       int len = tab.length;  
  5.               //在hash方法中会调用System.identityHashCode(x)  
  6.       int i = hash(k, len);  
  7.   
  8.       Object item;  
  9.       while ( (item = tab[i]) != null) {  
  10.           //只用==进行比较  
  11.           if (item == k) {  
  12. V oldValue = (V) tab[i + 1];  
  13.               tab[i + 1] = value;  
  14.               return oldValue;  
  15.           }  
  16.           i = nextKeyIndex(i, len);  
  17.       }  
  18.   
  19.       modCount++;  
  20.       tab[i] = k;  
  21.       tab[i + 1] = value;  
  22.       if (++size >= threshold)  
  23.           resize(len); // len == 2 * current capacity.  
  24.       return null;  
  25.   }  
  26.   
  27.   private static int hash(Object x, int length) {  
  28.       int h = System.identityHashCode(x);  
  29.       // Multiply by -127, and left-shift to use least bit as part of hash  
  30.       return ((h << 1) - (h << 8)) & (length - 1);  
  31.   }  


        看个具体的例子:
Java代码   收藏代码
  1. public static void main(String[] args) {  
  2.     String k1 = new String("a");  
  3.     String v1 = new String("A");  
  4.     String k2 = new String("a");  
  5.     String v2 = new String("A");  
  6.     HashMap hashMap = new HashMap();  
  7.     hashMap.put(k1, v1);  
  8.     hashMap.put(k2, v2);  
  9.     System.out.println("hashMap:"+hashMap);  
  10.       
  11.     IdentityHashMap identityHashMap = new IdentityHashMap();  
  12.     identityHashMap.put(k1, v1);  
  13.     identityHashMap.put(k2, v2);  
  14.     System.out.println("identityHashMap:"+identityHashMap);  
  15. }  

        运行结果:
Java代码   收藏代码
  1. hashMap:{a=A}  
  2. identityHashMap:{a=A, a=A}  

        IdentityHashMap和HashMap内部都实现了散列表,但有区别,体现在对散列冲突的处理上。HashMap中以分离链表的方式来解决散列冲突,也就是将散列在同一个桶内的数据组织成一个链表结构;IdentityHashMap中则以开放寻址的方式来解决散列冲突,当发生散列冲突时,数据被放入下一个空闲地址(也叫线程探测法)。
        所以IdentityHashMap和HashMap在实现细节上区别很大,来看一下吧。
Java代码   收藏代码
  1. public class IdentityHashMap  
  2.     extends AbstractMap  
  3.     implements Map, java.io.Serializable, Cloneable  
  4. {  
  5.     /** 
  6.      * The initial capacity used by the no-args constructor. 
  7.      * MUST be a power of two.  The value 32 corresponds to the 
  8.      * (specified) expected maximum size of 21, given a load factor 
  9.      * of 2/3. 
  10.      */  
  11.     private static final int DEFAULT_CAPACITY = 32;  
  12.   
  13.     /** 
  14.      * The minimum capacity, used if a lower value is implicitly specified 
  15.      * by either of the constructors with arguments.  The value 4 corresponds 
  16.      * to an expected maximum size of 2, given a load factor of 2/3. 
  17.      * MUST be a power of two. 
  18.      */  
  19.     private static final int MINIMUM_CAPACITY = 4;  
  20.   
  21.     /** 
  22.      * The maximum capacity, used if a higher value is implicitly specified 
  23.      * by either of the constructors with arguments. 
  24.      * MUST be a power of two <= 1<<29. 
  25.      */  
  26.     private static final int MAXIMUM_CAPACITY = 1 << 29;  
  27.   
  28.     /** 
  29.      * The table, resized as necessary. Length MUST always be a power of two. 
  30.      */  
  31.     private transient Object[] table;  
  32.   
  33.     /** 
  34.      * The number of key-value mappings contained in this identity hash map. 
  35.      * 
  36.      * @serial 
  37.      */  
  38.     private int size;  
  39.   
  40.     /** 
  41.      * The number of modifications, to support fast-fail iterators 
  42.      */  
  43.     private transient volatile int modCount;  
  44.   
  45.     /** 
  46.      * The next size value at which to resize (capacity * load factor). 
  47.      */  
  48.     private transient int threshold;  
  49.   
  50.     /** 
  51.      * Value representing null keys inside tables. 
  52.      */  
  53.     private static final Object NULL_KEY = new Object();  

        几个地方要注意下,MAXIMUM_CAPACITY是2的29次方,还记得HashMap的是2的30次方,为啥会差2呗呢?这个问题先记住。还有数组的类型是Object类型,由于数组的每个位置只放一个数据(目前看起来是这样),所以下面出现NULL_KEY,表示一个为null的键,为了区别于null。那value放在哪呢?继续往下看。
Java代码   收藏代码
  1. /** 
  2.  * Constructs a new, empty identity hash map with a default expected 
  3.  * maximum size (21). 
  4.  */  
  5. public IdentityHashMap() {  
  6.     init(DEFAULT_CAPACITY);  
  7. }  
  8.   
  9. /** 
  10.  * Constructs a new, empty map with the specified expected maximum size. 
  11.  * Putting more than the expected number of key-value mappings into 
  12.  * the map may cause the internal data structure to grow, which may be 
  13.  * somewhat time-consuming. 
  14.  * 
  15.  * @param expectedMaxSize the expected maximum size of the map 
  16.  * @throws IllegalArgumentException if expectedMaxSize is negative 
  17.  */  
  18. public IdentityHashMap(int expectedMaxSize) {  
  19.     if (expectedMaxSize < 0)  
  20.         throw new IllegalArgumentException("expectedMaxSize is negative: "  
  21.                                            + expectedMaxSize);  
  22.     init(capacity(expectedMaxSize));  
  23. }  
  24.   
  25. /** 
  26.  * Returns the appropriate capacity for the specified expected maximum 
  27.  * size.  Returns the smallest power of two between MINIMUM_CAPACITY 
  28.  * and MAXIMUM_CAPACITY, inclusive, that is greater than 
  29.  * (3 * expectedMaxSize)/2, if such a number exists.  Otherwise 
  30.  * returns MAXIMUM_CAPACITY.  If (3 * expectedMaxSize)/2 is negative, it 
  31.  * is assumed that overflow has occurred, and MAXIMUM_CAPACITY is returned. 
  32.  */  
  33. private int capacity(int expectedMaxSize) {  
  34.     // Compute min capacity for expectedMaxSize given a load factor of 2/3  
  35.     int minCapacity = (3 * expectedMaxSize)/2;  
  36.   
  37.     // Compute the appropriate capacity  
  38.     int result;  
  39.     if (minCapacity > MAXIMUM_CAPACITY || minCapacity < 0) {  
  40.         result = MAXIMUM_CAPACITY;  
  41.     } else {  
  42.         result = MINIMUM_CAPACITY;  
  43.         while (result < minCapacity)  
  44.             result <<= 1;  
  45.     }  
  46.     return result;  
  47. }  
  48.   
  49. /** 
  50.  * Initializes object to be an empty map with the specified initial 
  51.  * capacity, which is assumed to be a power of two between 
  52.  * MINIMUM_CAPACITY and MAXIMUM_CAPACITY inclusive. 
  53.  */  
  54. private void init(int initCapacity) {  
  55.     // assert (initCapacity & -initCapacity) == initCapacity; // power of 2  
  56.     // assert initCapacity >= MINIMUM_CAPACITY;  
  57.     // assert initCapacity <= MAXIMUM_CAPACITY;  
  58.   
  59.     threshold = (initCapacity * 2)/3;  
  60.     table = new Object[2 * initCapacity];  
  61. }  
  62.   
  63. /** 
  64.  * Constructs a new identity hash map containing the keys-value mappings 
  65.  * in the specified map. 
  66.  * 
  67.  * @param m the map whose mappings are to be placed into this map 
  68.  * @throws NullPointerException if the specified map is null 
  69.  */  
  70. public IdentityHashMap(Mapextends K, ? extends V> m) {  
  71.     // Allow for a bit of growth  
  72.     this((int) ((1 + m.size()) * 1.1));  
  73.     putAll(m);  
  74. }  

        还记得HashMap中有个加载因子,这里没有这个属性,在代码中固定为2/3了。要注意init方法中的table = new Object[2 * initCapacity],这里怎么变成了容量的2倍了?继续看看添加方法吧。
Java代码   收藏代码
  1. /** 
  2.  * Associates the specified value with the specified key in this identity 
  3.  * hash map.  If the map previously contained a mapping for the key, the 
  4.  * old value is replaced. 
  5.  * 
  6.  * @param key the key with which the specified value is to be associated 
  7.  * @param value the value to be associated with the specified key 
  8.  * @return the previous value associated with key, or 
  9.  *         null if there was no mapping for key. 
  10.  *         (A null return can also indicate that the map 
  11.  *         previously associated null with key.) 
  12.  * @see     Object#equals(Object) 
  13.  * @see     #get(Object) 
  14.  * @see     #containsKey(Object) 
  15.  */  
  16. public V put(K key, V value) {  
  17.     Object k = maskNull(key);  
  18.     Object[] tab = table;  
  19.     int len = tab.length;  
  20.     int i = hash(k, len);  
  21.   
  22.     Object item;  
  23.     while ( (item = tab[i]) != null) {  
  24.         if (item == k) {  
  25. oldValue = (V) tab[i + 1];  
  26.             tab[i + 1] = value;  
  27.             return oldValue;  
  28.         }  
  29.         i = nextKeyIndex(i, len);  
  30.     }  
  31.   
  32.     modCount++;  
  33.     tab[i] = k;  
  34.     tab[i + 1] = value;  
  35.     if (++size >= threshold)  
  36.         resize(len); // len == 2 * current capacity.  
  37.     return null;  
  38. }  

        首先是对"Null"key的处理,将其转化成前面的NULL_KEY对象,以便计算hash值,同时能和真正为null的位置区分。
        接下来通过一个hash函数计算hash值:
Java代码   收藏代码
  1. /** 
  2.  * Returns index for Object x. 
  3.  */  
  4. private static int hash(Object x, int length) {  
  5.     int h = System.identityHashCode(x);  
  6.     // Multiply by -127, and left-shift to use least bit as part of hash  
  7.     return ((h << 1) - (h << 8)) & (length - 1);  
  8. }  

        这个hash函数中,语句((h<<1) - (h<<8)) & (length-1),后半部分之前分析过,就是取余。前面的(h<<1) - (h<<8)会保留h的后7位和一个0作为后8位,前面的由于减操作变的不确定了。这么做的意图还不是很清晰(路过高手指点下),但这样做会产生一个结果就是整合hash函数得到的结果都是偶数。为什么是偶数?继续往下看。
        接下来会用得到的hash值作为内部数据的下标来获取数组中对应的数据,如果对应位置没有数据的话(没有发生冲突),会把Key放到这个位置,然后把Value放到下一个位置(hash + 1)。也就是说,所有的Key都保存在内部数组的偶数下标位置,所有的Value都保存在所有的奇数下标位置。现在明白了为什么初始化时内部容量变成了2倍,为什么hash函数的结果是偶数了吧。所以当产生冲突的时候,会继续寻找下一个可用位置:
Java代码   收藏代码
  1. /** 
  2.  * Circularly traverses table of size len. 
  3.  */  
  4. private static int nextKeyIndex(int i, int len) {  
  5.     return (i + 2 < len ? i + 2 : 0);  
  6. }  

        有了put方法的分析,get方法也就显而易见了。
Java代码   收藏代码
  1. /** 
  2.  * Returns the value to which the specified key is mapped, 
  3.  * or {@code null} if this map contains no mapping for the key. 
  4.  * 
  5.  * 

    More formally, if this map contains a mapping from a key 

  6.  * {@code k} to a value {@code v} such that {@code (key == k)}, 
  7.  * then this method returns {@code v}; otherwise it returns 
  8.  * {@code null}.  (There can be at most one such mapping.) 
  9.  * 
  10.  * 

    A return value of {@code null} does not necessarily 

  11.  * indicate that the map contains no mapping for the key; it's also 
  12.  * possible that the map explicitly maps the key to {@code null}. 
  13.  * The {@link #containsKey containsKey} operation may be used to 
  14.  * distinguish these two cases. 
  15.  * 
  16.  * @see #put(Object, Object) 
  17.  */  
  18. public V get(Object key) {  
  19.     Object k = maskNull(key);  
  20. ect[] tab = table;  
  21.     int len = tab.length;  
  22.     int i = hash(k, len);  
  23.     while (true) {  
  24.  Object item = tab[i];  
  25.         if (item == k)  
  26.             return (V) tab[i + 1];  
  27.         if (item == null)  
  28.             return null;  
  29.         i = nextKeyIndex(i, len);  
  30.     }  
  31. }  

        添加和获取的方法实现起来比较容易,但删除一个数据就蛋疼了。需要考虑冲突的问题,如果删除的位置之前发生过n次冲突,那么删除动作不仅要删除当前位置的数据,还要把之前发生过冲突的n个元素的位置往前调整(不严格的说),否则线性探测链就会断掉,会影响其他操作(如get)的正确性。来看一下具体实现:
Java代码   收藏代码
  1. /** 
  2.  * Removes the mapping for this key from this map if present. 
  3.  * 
  4.  * @param key key whose mapping is to be removed from the map 
  5.  * @return the previous value associated with key, or 
  6.  *         null if there was no mapping for key. 
  7.  *         (A null return can also indicate that the map 
  8.  *         previously associated null with key.) 
  9.  */  
  10. public V remove(Object key) {  
  11.     Object k = maskNull(key);  
  12.     Object[] tab = table;  
  13.     int len = tab.length;  
  14.     int i = hash(k, len);  
  15.   
  16.     while (true) {  
  17.         Object item = tab[i];  
  18.         if (item == k) {  
  19.             modCount++;  
  20.             size--;  
  21.             V oldValue = (V) tab[i + 1];  
  22.             tab[i + 1] = null;  
  23.             tab[i] = null;  
  24.             closeDeletion(i);  
  25.             return oldValue;  
  26.         }  
  27.         if (item == null)  
  28.             return null;  
  29.         i = nextKeyIndex(i, len);  
  30.     }  
  31.   
  32. }  

        可以看到,前面的代码和put类似,只是删除东西完成之后还要调用一个closeDeletion方法。
Java代码   收藏代码
  1. /** 
  2.  * Rehash all possibly-colliding entries following a 
  3.  * deletion. This preserves the linear-probe 
  4.  * collision properties required by get, put, etc. 
  5.  * 
  6.  * @param d the index of a newly empty deleted slot 
  7.  */  
  8. private void closeDeletion(int d) {  
  9.     // Adapted from Knuth Section 6.4 Algorithm R  
  10.     Object[] tab = table;  
  11.     int len = tab.length;  
  12.   
  13.     // Look for items to swap into newly vacated slot  
  14.     // starting at index immediately following deletion,  
  15.     // and continuing until a null slot is seen, indicating  
  16.     // the end of a run of possibly-colliding keys.  
  17.     Object item;  
  18.     for (int i = nextKeyIndex(d, len); (item = tab[i]) != null;  
  19.          i = nextKeyIndex(i, len) ) {  
  20.         // The following test triggers if the item at slot i (which  
  21.         // hashes to be at slot r) should take the spot vacated by d.  
  22.         // If so, we swap it in, and then continue with d now at the  
  23.         // newly vacated i.  This process will terminate when we hit  
  24.         // the null slot at the end of this run.  
  25.         // The test is messy because we are using a circular table.  
  26.         int r = hash(item, len);  
  27.         if ((i < r && (r <= d || d <= i)) || (r <= d && d <= i)) {  
  28.             tab[d] = item;  
  29.             tab[d + 1] = tab[i + 1];  
  30.             tab[i] = null;  
  31.             tab[i + 1] = null;  
  32.             d = i;  
  33.         }  
  34.     }  
  35. }  

        大概过程是从删除的位置开始往下找(通过nextKeyIndex方法),当存在下一个元素的时候,通过重新计算元素hash值的方式,判断下一个元素放到本容器中时是否产生过冲突。如果产生过冲突,那么将该元素放到当前位置(d、d+1),将该元素之前位置(i、i+1)清空。然后把i赋给d,继续往下找。知道找到null位置为止。
        有了上面的分析,其他代码很容易看懂了。java.util.IdentityHashMap总结到这里。

你可能感兴趣的:(Java基础知识)