IdentityHashMap

white and white發表於2020-12-19

IdentityHashMap和HashMap長的挺類似

public class IdentityHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, java.io.Serializable, Cloneable

和HashMap一樣繼承和實現了同樣的抽象類和介面,是一種特殊的HashMap,他的巧妙之處在於HashMap是用傳入的key值物件判斷是否相等,相等就覆蓋上一個value,即“equals”,而IdentityHashMap是通過key的引用來判斷的,即使值相等,但引用地址不同的話也不會覆蓋。

示例程式碼:

        Map<Integer,String> map1 = new HashMap<Integer,String>();
        Map<Integer,String> map2 = new IdentityHashMap<Integer, String>();
        Integer a = new Integer(123);
        Integer b = new Integer(123);
        map1.put(a,"1");
        map1.put(b,"1");

        map2.put(a,"1");
        map2.put(b,"1");
        System.out.println("hashmap: "+map1);
        System.out.println("IdentityHashMap: "+map2);
    }

//輸出結果
hashmap: {123=1}
IdentityHashMap: {123=1, 123=1}

我們來看看他的實現

	//使用的是"=="
    private static Object maskNull(Object key) {
        return (key == null ? NULL_KEY : key);
    }

可以看到,IdentityHashMap底層是一個object[ ]

    private void init(int initCapacity) {
        table = new Object[2 * initCapacity];
    }

那既然沒有像hashmap使用連結串列,那他遇到了hash衝突後,且key不一樣該如何解決了

public V put(K key, V value) {
        final Object k = maskNull(key);

        retryAfterResize: for (;;) {
            final Object[] tab = table;
            final int len = tab.length;
            int i = hash(k, len);

            for (Object item; (item = tab[i]) != null;
                 i = nextKeyIndex(i, len)) {
                if (item == k) {
                    @SuppressWarnings("unchecked")
                        V oldValue = (V) tab[i + 1];
                    tab[i + 1] = value;
                    return oldValue;
                }
            }

            final int s = size + 1;
            // Use optimized form of 3 * s.
            // Next capacity is len, 2 * current capacity.
            if (s + (s << 1) > len && resize(len))
                continue retryAfterResize;

            modCount++;
            tab[i] = k;
            tab[i + 1] = value;
            size = s;
            return null;
        }
    }

put方法裡,可以看到,他會在Object[ ]
+1+1的去尋找下一個有效的索引位置,並插入,但是長度是有限的,這個我們可以在上面的init方法中看到,這就要涉及到他的擴容策略了

 private boolean resize(int newCapacity) {
        // assert (newCapacity & -newCapacity) == newCapacity; // power of 2
        int newLength = newCapacity * 2;

        Object[] oldTable = table;
        int oldLength = oldTable.length;
        if (oldLength == 2 * MAXIMUM_CAPACITY) { // can't expand any further
            if (size == MAXIMUM_CAPACITY - 1)
                throw new IllegalStateException("Capacity exhausted.");
            return false;
        }
        if (oldLength >= newLength)
            return false;

        Object[] newTable = new Object[newLength];

        for (int j = 0; j < oldLength; j += 2) {
            Object key = oldTable[j];
            if (key != null) {
                Object value = oldTable[j+1];
                oldTable[j] = null;
                oldTable[j+1] = null;
                int i = hash(key, newLength);
                while (newTable[i] != null)
                    i = nextKeyIndex(i, newLength);
                newTable[i] = key;
                newTable[i + 1] = value;
            }
        }
        table = newTable;
        return true;
    }

可以看到,他的新表長度為之前長度的2倍,然後會把舊錶的值全部重新複製到新的表裡