java-EnumMap、IdentityHashMap、WeakHashMap源码分析

来源:互联网 发布:c语言中break 编辑:程序博客网 时间:2024/05/15 03:37
  • EnumMap源码分析
    1、介绍
    EnumMap是与枚举类相结合的Map类。跟hash没有多大关系,虽然本文中另外两种与HashMap有关,但是EnumMap与HashMap关系并不大。
    EnumMap就是专门与枚举类结合形成Map的key-value对结构。值的注意的是,EnumMap中虽然也存储的是key-value对的数据,但是内存实现上却采用的是数组结构。key存储一个数组结构,value也用一个对应的数组结构。
    2、构造器
public EnumMap(Class<K> keyType) {        this.keyType = keyType;        //保存枚举类的所有枚举值到数组中        keyUniverse = getKeyUniverse(keyType);        vals = new Object[keyUniverse.length];    }    public EnumMap(EnumMap<K, ? extends V> m) {        keyType = m.keyType;        keyUniverse = m.keyUniverse;        vals = m.vals.clone();        size = m.size;    }    public EnumMap(Map<K, ? extends V> m) {        //如果m类是EnumMap类,与上面的构造器一样        if (m instanceof EnumMap) {            EnumMap<K, ? extends V> em = (EnumMap<K, ? extends V>) m;            keyType = em.keyType;            keyUniverse = em.keyUniverse;            vals = em.vals.clone();            size = em.size;        } else {            //如果不是EnumMap类,则不允许vals数组长度为空            if (m.isEmpty())                throw new IllegalArgumentException("Specified map is empty");            keyType = m.keySet().iterator().next().getDeclaringClass();            keyUniverse = getKeyUniverse(keyType);            vals = new Object[keyUniverse.length];            putAll(m);        }    }

上面是EnumMap的三个构造器,有构造器知道,该类存储的key-value对,key值类型必须是枚举类型。枚举类型的值通过该方法getKeyUniverse(keyType)获取枚举类数组,keyUniverse 数组存放key集合。vals数组存放value数据。
2、put方法

public V put(K key, V value) {        typeCheck(key);        int index = key.ordinal();        Object oldValue = vals[index];        vals[index] = maskNull(value);        if (oldValue == null)            size++;        return unmaskNull(oldValue);    }    private void typeCheck(K key) {        Class keyClass = key.getClass();        if (keyClass != keyType && keyClass.getSuperclass() != keyType)            throw new ClassCastException(keyClass + " != " + keyType);    }

put方法存入key-value对。由于key值是固定的枚举类的常量值,并且在构造器初始化的过程中,已经获取到了,这里put方法中在此传入key值,是为了vals数组中保存value值。
首先检查key类型是否是构造器初始化过程中绑定的枚举类型,如果不是,抛出异常ClassCastException。
然后又key值,计算value存储在vals数组中的索引值,然后将value值存入。如果原来已有value值,则替换原来的value值。
由此可见,vals长度最多只能是枚举类常量值的个数。key值只能是枚举类常量值。
通过put方法我们已经基本了解了EnumMap类的特点了,其他方法都是按照这个思想进行的。

public void putAll(Map<? extends K, ? extends V> m) {        if (m instanceof EnumMap) {            EnumMap<? extends K, ? extends V> em =                (EnumMap<? extends K, ? extends V>)m;            if (em.keyType != keyType) {                if (em.isEmpty())                    return;                throw new ClassCastException(em.keyType + " != " + keyType);            }            for (int i = 0; i < keyUniverse.length; i++) {                Object emValue = em.vals[i];                if (emValue != null) {                    if (vals[i] == null)                        size++;                    vals[i] = emValue;                }            }        } else {            /**            * 这里的putAll(m)会调用父类的方法            * 父类该方法的实现上,均采用一一放入的put方法进行            * 数据的存入。            * 此时会调用put(e.getKey(), e.getValue());            * 子类put方法会被调用,所以回到了该类中的put方法            * put方法中会检验key值的类型,如果不匹配就不能放入,            * 抛出异常            */            super.putAll(m);        }    }

keyUniverse就是初始化时保存的枚举类常量值数组。通过上面的方法,非常简单保存集合中的数据。并且EnumMap采用数组保存key-value对,管理起来很方便,逻辑不复杂。

3、get方法

public V get(Object key) {        return (isValidKey(key) ?                unmaskNull(vals[((Enum)key).ordinal()]) : null);    }    /**     * Returns true if key is of the proper type to be a key in this     * enum map.     * 检查key值是否是有效值     * key值不能为null     * 同时key的类型必须是初始化时的枚举类类型或者     * 其子类,否则就是无效值     */    private boolean isValidKey(Object key) {        if (key == null)            return false;        // Cheaper than instanceof Enum followed by getDeclaringClass        Class keyClass = key.getClass();        return keyClass == keyType || keyClass.getSuperclass() == keyType;    }

get方法很简单,只要key传入的合理的参数,很快就能得到结果。O(1)的时间复杂度。通过上面的put方法和get方法,结合起来看,EnumMap类进行key-value保存的时候,key专门用一个数组进行保存,然后vaule值存放在数组vals的相对应的位置上。当需要获取指定key的value时,直接通过key即可得到数组vals对应位置上的值。并且不会出现冲突的问题。因为Enum类的各个枚举值不相同。

4、删除方法

public V remove(Object key) {        if (!isValidKey(key))            return null;        int index = ((Enum)key).ordinal();        Object oldValue = vals[index];        vals[index] = null;        if (oldValue != null)            size--;        return unmaskNull(oldValue);    }    private boolean removeMapping(Object key, Object value) {        if (!isValidKey(key))            return false;        int index = ((Enum)key).ordinal();        if (maskNull(value).equals(vals[index])) {            vals[index] = null;            size--;            return true;        }        return false;    }    /**     * Returns true if key is of the proper type to be a key in this     * enum map.     * 检查key值是否是有效值     * key值不能为null     * 同时key的类型必须是初始化时的枚举类类型或者     * 其子类,否则就是无效值     */    private boolean isValidKey(Object key) {        if (key == null)            return false;        // Cheaper than instanceof Enum followed by getDeclaringClass        Class keyClass = key.getClass();        return keyClass == keyType || keyClass.getSuperclass() == keyType;    }

删除方法很简单,key值是不会删除的,因为key值是枚举类常量值,只会把vals数组中对应位置上的value值删除。
remove(key)是把key值对应位置上vals数组中的值删除。
removeMapping(key,value)是把对应位置上与valus相等的vals数组中的数据删除。如果不等,不删除。

5 是否存在指定的数据方法

/**     * @param key the key whose presence in this map is to be tested     * @return <tt>true</tt> if this map contains a mapping for the specified     * 首先检验key值是否是有效值     * 然后获取对应的key值的value值是否为null     * 这里需要注意的是,put方法存放key-value对的时候,     * 如果value值为null,则会通过maskNull()方法将null值,转换成NULL对象     *所以实际存入vals数组中的value值不为null值。     */    public boolean containsKey(Object key) {        return isValidKey(key) && vals[((Enum)key).ordinal()] != null;    }    private boolean containsMapping(Object key, Object value) {        return isValidKey(key) &&            maskNull(value).equals(vals[((Enum)key).ordinal()]);    }

上面两个方法看明白的话,就很容易明白EnumMap类是如何进行key-value对的管理的。key值初始化的时候已经获取,保存在了数组中,value值是通过put或者putAll方法添加到数组中的,containsKey(Object key)方法不是通过key值数组判断的,而是通过key值对应位置上的value值是否存在进行判断的。也就是说key值对应的vals数组中是否有值,决定了EnumMap类对象是否包含key值。
EnumMap类的其他方法就没什么说的了,大家看看就可以了,只要弄懂了HashMap类,其他的类就不在话下了。

  • IdentityHashMap类源码分析
    该类特点是:只有全等的key值,该类才会认为两个key值相等。比如new String(“11”) 与new String(“11”),这两个对象就不是全等,而一般的HashMap则认为上面两个对象是相等的。
    并且该类非常有意思的是,在key-value数据的存储上,类似于HashMap,采用map数组进行存储,但是key-value不是利用链表解决冲突,而是继续计算下一个索引,把数据计算在下一个有效索引的数组中,也就是数据全部存储map数组中,并且table[i]=key 则table[i+1]=value。key-value紧挨着存储在map数组中。
    1、构造器
/**     * Constructs a new, empty identity hash map with a default expected     * maximum size (21).     * 采用默认容量的构造器     */    public IdentityHashMap() {        init(DEFAULT_CAPACITY);    }    /**     * Constructs a new, empty map with the specified expected maximum size.     * Putting more than the expected number of key-value mappings into     * the map may cause the internal data structure to grow, which may be     * somewhat time-consuming.     *     * @param expectedMaxSize the expected maximum size of the map     * @throws IllegalArgumentException if <tt>expectedMaxSize</tt> is negative     * 设定容量大小的构造器     * 参数期望的最大容量expectedMaxSize不能为负数,否则异常。     * 参数expectedMaxSize并不代表实际的容量大小。     * 通过capacity(expectedMaxSize)方法我们会发现,     * 实际的容量要大一些。     */    public IdentityHashMap(int expectedMaxSize) {        if (expectedMaxSize < 0)            throw new IllegalArgumentException("expectedMaxSize is negative: "                                               + expectedMaxSize);        init(capacity(expectedMaxSize));    }    /**     * Returns the appropriate capacity for the specified expected maximum     * size.  Returns the smallest power of two between MINIMUM_CAPACITY     * and MAXIMUM_CAPACITY, inclusive, that is greater than     * (3 * expectedMaxSize)/2, if such a number exists.  Otherwise     * returns MAXIMUM_CAPACITY.  If (3 * expectedMaxSize)/2 is negative, it     * is assumed that overflow has occurred, and MAXIMUM_CAPACITY is returned.     * 将参数期望的容量大小expectedMaxSize扩大1.5倍     * 之后按照比改制大的最小2进制数设定容量大小。     * 返回的result值是实际的容量大小。     */    private int capacity(int expectedMaxSize) {        // Compute min capacity for expectedMaxSize given a load factor of 2/3        int minCapacity = (3 * expectedMaxSize)/2;        // Compute the appropriate capacity        int result;        if (minCapacity > MAXIMUM_CAPACITY || minCapacity < 0) {            result = MAXIMUM_CAPACITY;        } else {            result = MINIMUM_CAPACITY;            while (result < minCapacity)                result <<= 1;        }        return result;    }    /**     * Initializes object to be an empty map with the specified initial     * capacity, which is assumed to be a power of two between     * MINIMUM_CAPACITY and MAXIMUM_CAPACITY inclusive.     * 极限容量是initCapacity值的三分之二     * 而数组长度确实initCapacity的两倍!     * 由此可见IdentityHashMap比HashMap耗费内存空间。     * 等于说,当用到数组长度的三分之一的时候就要进行扩容操作。     * 可见内存空间消耗有多大。     */    private void init(int initCapacity) {        // assert (initCapacity & -initCapacity) == initCapacity; // power of 2        // assert initCapacity >= MINIMUM_CAPACITY;        // assert initCapacity <= MAXIMUM_CAPACITY;        threshold = (initCapacity * 2)/3;        table = new Object[2 * initCapacity];    }    /**     * @param m the map whose mappings are to be placed into this map     * @throws NullPointerException if the specified map is null     */    public IdentityHashMap(Map<? extends K, ? extends V> m) {        // Allow for a bit of growth        this((int) ((1 + m.size()) * 1.1));        putAll(m);    }

通过构造器我们知道,IdentityHashMap在初始化的时候就已经构造比较大的map数组以解决可能的冲突问题,以便将数据都存储在数组中。同时为了提高查询效率,极限容量设置的比较小,只有数组长度的三分之一。但是问题也来了,占用了太大的内存空间。也就是有效利用的空间不足数组总长度的三分之一。

2 put方法

/**     * 该方法最为重要。通过该方法我们知道,IdentityHashMap类     * 在存放key-value对时,不采用链表解决冲突,而是通过     * nextKeyIndex(i,len)方法找到下一个存放数据的索引值,如果该索引     * 处没有值则存放数据,如果有值,继续nextKeyIndex(i,len)进行查找     * 直到在数组中找到合适的存放位置。     * 同时,如果找到全等的key值,说明已经存放过key,     * 则用新value值替换旧value值。     * put方法最后,进行resize检查。     * 如果存放下一个数据的长度>=极限容量,则进行扩容。     * 通过put方法,我们可以了解到为什么在初始化table数组的时候,     * 把数组长度定义为设计容量的两倍了。     * 在init()方法中,我们知道,极限容量设计为数组长度的三分之一     * 说明,当存放的数组达到数组长度的三分之一的时候就要进行扩容     * 可见IdentityHashMap在内存空间中的消耗有多大。     */    public V put(K key, V value) {        Object k = maskNull(key);        Object[] tab = table;        int len = tab.length;        int i = hash(k, len);        Object item;        while ( (item = tab[i]) != null) {            if (item == k) {                V oldValue = (V) tab[i + 1];                tab[i + 1] = value;                return oldValue;            }            i = nextKeyIndex(i, len);        }        modCount++;        tab[i] = k;        tab[i + 1] = value;        if (++size >= threshold)            resize(len); // len == 2 * current capacity.        return null;    }    /**     * @param newCapacity the new capacity, must be a power of two.     * 扩容方法     * 扩展为原来数组长度的两倍。     * 扩容之后进行数据的转移,拷贝到新数组当中。     * 循环内部手动进行了数据的清除,设置旧数组中的无用引用为null.     * 个人理解原因是:数组长度较大,占用内存空间比较大,     * 及时释放内存空间是王道!     * 由于扩容之后空间一定够用,所以直接将原来数组中的数据     * 存放到新数组对应的位置即可。     * 并且数组存放不存在链表,只是数组中,所以管理起来比较方便。     */    private void 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 (threshold == MAXIMUM_CAPACITY-1)                throw new IllegalStateException("Capacity exhausted.");            threshold = MAXIMUM_CAPACITY-1;  // Gigantic map!            return;        }        if (oldLength >= newLength)            return;        Object[] newTable = new Object[newLength];        threshold = newLength / 3;        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;    }    /**     * @param m mappings to be stored in this map     * @throws NullPointerException if the specified map is null     * putAll方法,使用put方法进行数据的复制。     * 没什么说的。原理同上。     */    public void putAll(Map<? extends K, ? extends V> m) {        int n = m.size();        if (n == 0)            return;        if (n > threshold) // conservatively pre-expand            resize(capacity(n));        for (Entry<? extends K, ? extends V> e : m.entrySet())            put(e.getKey(), e.getValue());    }    /**     * Circularly traverses table of size len.     * 计算下一个key值出现在数组处的索引值     */    private static int nextKeyIndex(int i, int len) {        return (i + 2 < len ? i + 2 : 0);    }

通过上面的方法我们知道,该类在完成key-value对的存放时,是挨着存放key-value对到数组中。以步进2为间隔进行数据填充。

3、查询获取数据方法

/**     * 通过key值获取value对象。     * 通过程序会发现,item==k,说明只有当全等的时候     * 才会返回对象,否则找不到value值返回null。     * 同时,item==k的情况下,tab[i+1]为value值。     * 说明数组i处存放key值,i+1处存放value值。     * 这也解释了初始化数组时候2倍长度的原因了。     * @see #put(Object, Object)     */    public V get(Object key) {        Object k = maskNull(key);        Object[] tab = table;        int len = tab.length;        int i = hash(k, len);        while (true) {            Object item = tab[i];            if (item == k)                return (V) tab[i + 1];            if (item == null)                return null;            i = nextKeyIndex(i, len);        }    }    /**     * @param   key   possible key     * @return  <code>true</code> if the specified object reference is a key     *          in this map     * @see     #containsValue(Object)     * 是否包含指定的key值。与上面的get(key)方法类似     * 原理同上。     */    public boolean containsKey(Object key) {        Object k = maskNull(key);        Object[] tab = table;        int len = tab.length;        int i = hash(k, len);        while (true) {            Object item = tab[i];            if (item == k)                return true;            if (item == null)                return false;            i = nextKeyIndex(i, len);        }    }    /**     * @param value value whose presence in this map is to be tested     * @return <tt>true</tt> if this map maps one or more keys to the     *         specified object reference     * @see     #containsKey(Object)     * 是否包含指定的value值。     * 循环中i以2位步长进行循环遍历     * 说明数组偶数处存放value值。     *      IdentityHashMap<String, String> iden =                            new IdentityHashMap<>();            iden.put(null, null);            iden.put(null, null);            System.out.println(iden);            System.out.println(iden.containsKey(null));            System.out.println(iden.containsValue(null));            上面的运行结果是:            {null=null}            true            true     * 说明存放的全等的key值会替换原来的value值。     * 存放的key==null的情况下,会通过maskKey(key)方法     * 将key=null的值,替换为NULL_KEY对象。     * 请结合put方法查看代码。     */    public boolean containsValue(Object value) {        Object[] tab = table;        for (int i = 1; i < tab.length; i += 2)            if (tab[i] == value && tab[i - 1] != null)                return true;        return false;    }    /**     * @param   key   possible key     * @param   value possible value     * @return  <code>true</code> if and only if the specified key-value     *          mapping is in the map     * 是否包含指定的key-value对。     * 原理和上面的containsKey containsValue类似     */    private boolean containsMapping(Object key, Object value) {        Object k = maskNull(key);        Object[] tab = table;        int len = tab.length;        int i = hash(k, len);        while (true) {            Object item = tab[i];            if (item == k)                return tab[i + 1] == value;            if (item == null)                return false;            i = nextKeyIndex(i, len);        }    }

上面三个是查询map数组中数据的方法,上面的方法依次通过获取key在map数组中的索引进行查询,知道查询到结果为止。

4、删除数据

/**     * 使用全等的方式比较key值。     * 通过key值,得到hash值,然后得到索引值。     * 如果存在key值,则删除对应位置上的数据。同时size-1.     * 并且使用closeDeletion(i)将数组后面的数据重新remap     * 存放在数组的相应位置当中。     */    public V remove(Object key) {        Object k = maskNull(key);        Object[] tab = table;        int len = tab.length;        int i = hash(k, len);        while (true) {            Object item = tab[i];            if (item == k) {                modCount++;                size--;                V oldValue = (V) tab[i + 1];                tab[i + 1] = null;                tab[i] = null;                closeDeletion(i);                return oldValue;            }            if (item == null)                return null;            i = nextKeyIndex(i, len);        }    }    /**     * 删除指定的key-value对。     * 原理同上面的remove类似。     * 通过key值得到hash值,然后得到key值在数组中的索引值     * 有了索引值一一比较key值,只有和目标key值==时,才会删除     * 该索引处的数据。     * 删除之后,通过closeDeletion(i)方法将后面的数据重新rehash     * 重新在数组中进行存放。     * 如果没有找到==的key值,则返回false,说明删除不成功。     */    private boolean removeMapping(Object key, Object value) {        Object k = maskNull(key);        Object[] tab = table;        int len = tab.length;        int i = hash(k, len);        while (true) {            Object item = tab[i];            if (item == k) {                if (tab[i + 1] != value)                    return false;                modCount++;                size--;                tab[i] = null;                tab[i + 1] = null;                closeDeletion(i);                return true;            }            if (item == null)                return false;            i = nextKeyIndex(i, len);        }    }    /**     * 该方法是在删除数组中的数据的时候重新rehash数组中的元素     * 重新存放在数组中。     * 关于if语句中的判断有点小麻烦。     * 这个判断需要了解hash值的计算。     * 不过不影响我们大概知道是怎么回事。     * 同时我们也可以知道的是,删除元素并不会使数组的长度减小。     * 这一点比较重要。     */    private void closeDeletion(int d) {        Object[] tab = table;        int len = tab.length;        Object item;        for (int i = nextKeyIndex(d, len); (item = tab[i]) != null;             i = nextKeyIndex(i, len) ) {            int r = hash(item, len);            if ((i < r && (r <= d || d <= i)) || (r <= d && d <= i)) {                tab[d] = item;                tab[d + 1] = tab[i + 1];                tab[i] = null;                tab[i + 1] = null;                d = i;            }        }    }

由于IdentityHashMap采用在数组中保存key-value数据,并以加长的数组来解决可能引起的冲突,所以数据删除起来比较方便,只不过只有全等的情况下,才会删除key值所对应的value。同时,由于删除一对数据之后导致后面的数据遍历不到,所以当删除一对数据之后,需要对后面的数据重写在map数组上面定位。

5 相等方法和hashcode方法

/**     * 比较两个IdentityHashMap对象是否相等     * equals方法可以比较Map类对象,只要entrySet().equals(m.entrySet())返回     * true就可以了。     * 1、如果参数o属于IdentityHashMap类对象,直接使用containsMapping方法逐一     *比较每个key-value对数据是否相等即可。     * 2、如果参数o不属于containsMapping类对象,则使用entrySet()方法得到的     * EntrySet集合进行比较。     * 通过查找该类中的EntrySet内部类,没有equals方法,说明复用父类AbstractSet     * 中的equals方法,通过查找父类的equals方法,我们发现,     * AbstractSet类又调用了AbstractCollection类的     * containsAll(Collection<?> c)方法。经过一系列的调用     * 最终通过比较的是参数o集合的每个数据的(o.equals(it.next()))方法     * 进行判断的。     * 也就是说,如果参数o不是IdentityHashMap类对象,则使用参数o的equals方法     * 进行比较,不要求==全等。     */    public boolean equals(Object o) {        if (o == this) {            return true;        } else if (o instanceof IdentityHashMap) {            IdentityHashMap m = (IdentityHashMap) o;            if (m.size() != size)                return false;            Object[] tab = m.table;            for (int i = 0; i < tab.length; i+=2) {                Object k = tab[i];                if (k != null && !containsMapping(k, tab[i + 1]))                    return false;            }            return true;        } else if (o instanceof Map) {            Map m = (Map)o;            return entrySet().equals(m.entrySet());        } else {            return false;  // o is not a Map        }    }    /**     * 计算hash值。     * 最终的hash值与key和value都有关     * 保证每个key-value对的hash值是唯一的。     */    public int hashCode() {        int result = 0;        Object[] tab = table;        for (int i = 0; i < tab.length; i +=2) {            Object key = tab[i];            if (key != null) {                Object k = unmaskNull(key);                result += System.identityHashCode(k) ^                          System.identityHashCode(tab[i + 1]);            }        }        return result;    }

这两个方法是该类的关键所在,hashcode的计算不仅仅和key值有关,而且和value值有关,这样就保证了key-value对具备唯一的hash值。同时通过重写equals方法,判定只有key值全等情况下才会判断key值相等。这就是IdentityHashMap与普通HashMap不同的关键所在。

  • WeakHashMap源码分析
    1、介绍
    WeakHashMap采用弱引用队列关联map数组中存储的数据,该类与普通HashMap类似,解决冲突一样采用链表解决。了解了HashMap再来了解WeakHashMap会很容易上手。之所以采用采用WeakHashMap该类,是因为通过该类可是实现缓存,在内存空间很紧张情况下,使用该类,避免强引用占用大量的内存,销毁掉不用或者过时的对象,较早的释放空间。
    该类主要的特点就是使用引用队列,将Entry对象与引用队列关联起来,使得每个Entry对象都是弱引用,先看内部类Entry的定义
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {        V value;        int hash;        Entry<K,V> next;        /**         * Creates new entry.         */        Entry(Object key, V value,              ReferenceQueue<Object> queue,              int hash, Entry<K,V> next) {            /**            * 这句super(key,queue);            * 就把Entry对象引用队列关联了起来。            * 此时的Entry对象是弱引用对象,弱引用WeakReference            * 的构造器new WeakReference(o,q)            * 意思就是WeakReference对象引用o对象,当弱引用被垃圾回收            * 则o对象就没有引用了,也会被垃圾回收,此时WeakReference对象            * 会被加入到引用队列queue当中去。            * 在这里,我们可以吧Entry对象看作是增强了参数的WeakReference对象。            */            super(key, queue);            this.value = value;            this.hash  = hash;            this.next  = next;        }        @SuppressWarnings("unchecked")        public K getKey() {            return (K) WeakHashMap.unmaskNull(get());        }        public V getValue() {            return value;        }        public V setValue(V newValue) {            V oldValue = value;            value = newValue;            return oldValue;        }        public boolean equals(Object o) {            if (!(o instanceof Map.Entry))                return false;            Map.Entry<?,?> e = (Map.Entry<?,?>)o;            K k1 = getKey();            Object k2 = e.getKey();            if (k1 == k2 || (k1 != null && k1.equals(k2))) {                V v1 = getValue();                Object v2 = e.getValue();                if (v1 == v2 || (v1 != null && v1.equals(v2)))                    return true;            }            return false;        }        public int hashCode() {            K k = getKey();            V v = getValue();            return ((k==null ? 0 : k.hashCode()) ^                    (v==null ? 0 : v.hashCode()));        }        public String toString() {            return getKey() + "=" + getValue();        }    }

这是WeakHashMap类中内部类Entry的定义,这也就是它与普通HashMap不同的关键所在。Entry构造器中,将每个Entry与引用队列关联,而每个Entry对象就是一个弱引用!!!这个弱引用指向key值。
2、WeakHashMap构造器

 /**     * 这里构造器的方法与HashMap类的构造器一样     */    public WeakHashMap(int initialCapacity, float loadFactor) {        if (initialCapacity < 0)            throw new IllegalArgumentException("Illegal Initial Capacity: "+                                               initialCapacity);        if (initialCapacity > MAXIMUM_CAPACITY)            initialCapacity = MAXIMUM_CAPACITY;        if (loadFactor <= 0 || Float.isNaN(loadFactor))            throw new IllegalArgumentException("Illegal Load factor: "+                                               loadFactor);        int capacity = 1;        while (capacity < initialCapacity)            capacity <<= 1;        table = newTable(capacity);        this.loadFactor = loadFactor;        threshold = (int)(capacity * loadFactor);        useAltHashing = sun.misc.VM.isBooted() &&                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);    }    public WeakHashMap(int initialCapacity) {        this(initialCapacity, DEFAULT_LOAD_FACTOR);    }    public WeakHashMap() {        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);    }    public WeakHashMap(Map<? extends K, ? extends V> m) {        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,                DEFAULT_INITIAL_CAPACITY),             DEFAULT_LOAD_FACTOR);        putAll(m);    }

通过与HashMap比较,我们发现,两者的构造器上并没有什么区别,可以说一样,这就说明WeakHashMap与普通HashMap在存储数据上是一样的。

3 put方法

public V put(K key, V value) {        Object k = maskNull(key);        int h = hash(k);        Entry<K,V>[] tab = getTable();        int i = indexFor(h, tab.length);        for (Entry<K,V> e = tab[i]; e != null; e = e.next) {            if (h == e.hash && eq(k, e.get())) {                V oldValue = e.value;                if (value != oldValue)                    e.value = value;                return oldValue;            }        }        modCount++;        Entry<K,V> e = tab[i];        tab[i] = new Entry<>(k, value, queue, h, e);        if (++size >= threshold)            resize(tab.length * 2);        return null;    }    /**     * Expunges stale entries from the table.     * 该方法是WeakHashMap类的核心方法     * 每次在进行getSize() getTable()方法是都要调用该方法     * 该方法实现的功能是:     * 通过遍历引用队列当中保存的已经回收的弱引用对象     * 将原map 数组中的引用清除,map数组中只保留还没有回收的弱引用对象。     * queue.poll()弹出的是弱引用对象,该类中的Entry集成了WeakReference类     * 方法中利用两层循环:一层循环遍历引用队列中的值,另一层循环遍历     * map数组中的值,当在map数组中发现由于引用队列中相同的引用     * 则把应用变量从map数组中删除。更新map数组长度     */    private void expungeStaleEntries() {        for (Object x; (x = queue.poll()) != null; ) {            synchronized (queue) {                @SuppressWarnings("unchecked")                    Entry<K,V> e = (Entry<K,V>) x;                int i = indexFor(e.hash, table.length);                Entry<K,V> prev = table[i];                Entry<K,V> p = prev;                while (p != null) {                    Entry<K,V> next = p.next;                    if (p == e) {                        if (prev == e)                            table[i] = next;                        else                            prev.next = next;                        // Must not null out e.next;                        // stale entries may be in use by a HashIterator                        e.value = null; // Help GC                        size--;                        break;                    }                    prev = p;                    p = next;                }            }        }    }    /**     * 获取map数组     * 利用expungeStaleEntries()方法刷新map数组     */    private Entry<K,V>[] getTable() {        expungeStaleEntries();        return table;    }

put方法存储数据,key值如果已经存在,则替换原来的value,如果不存在,则找到合适的位置填充。可以看出出现冲突的情况下,是使用链表解决的。这和一般HashMap一致。不同的地方是,在存放数据之前,使用getTable()方法首先获取一次map数组,调用了expungeStaleEntries()方法,上面的注释部分我尽量给出了自己的理解。
可以说expungeStaleEntries()方法是保证WeakHashMap正常运行的关键方法。

4、获取数据方法

/**     * @see #put(Object, Object)     * 这里为什么没有使用getEntry(Object key)方法     * 来获取value值呢??代码一样的呀     */    public V get(Object key) {        Object k = maskNull(key);        int h = hash(k);        Entry<K,V>[] tab = getTable();        int index = indexFor(h, tab.length);        Entry<K,V> e = tab[index];        while (e != null) {            if (e.hash == h && eq(k, e.get()))                return e.value;            e = e.next;        }        return null;    }    public boolean containsKey(Object key) {        return getEntry(key) != null;    }    Entry<K,V> getEntry(Object key) {        Object k = maskNull(key);        int h = hash(k);        Entry<K,V>[] tab = getTable();        int index = indexFor(h, tab.length);        Entry<K,V> e = tab[index];        while (e != null && !(e.hash == h && eq(k, e.get())))            e = e.next;        return e;    }

上面是获取数据的方法,与普通HashMap区别不大,主要在于每次进行数据获取前,先获取map数组。

5、删除数据

public V remove(Object key) {        Object k = maskNull(key);        int h = hash(k);        Entry<K,V>[] tab = getTable();        int i = indexFor(h, tab.length);        Entry<K,V> prev = tab[i];        Entry<K,V> e = prev;        while (e != null) {            Entry<K,V> next = e.next;            if (h == e.hash && eq(k, e.get())) {                modCount++;                size--;                if (prev == e)                    tab[i] = next;                else                    prev.next = next;                return e.value;            }            prev = e;            e = next;        }        return null;    }    boolean removeMapping(Object o) {        if (!(o instanceof Map.Entry))            return false;        Entry<K,V>[] tab = getTable();        Map.Entry<?,?> entry = (Map.Entry<?,?>)o;        Object k = maskNull(entry.getKey());        int h = hash(k);        int i = indexFor(h, tab.length);        Entry<K,V> prev = tab[i];        Entry<K,V> e = prev;        while (e != null) {            Entry<K,V> next = e.next;            if (h == e.hash && e.equals(entry)) {                modCount++;                size--;                if (prev == e)                    tab[i] = next;                else                    prev.next = next;                return true;            }            prev = e;            e = next;        }        return false;    }

删除也比较简单,与普通的HashMap没有什么特殊的地方.就不多说了。

总结一下:
上面的三个类中的迭代器我都没有给出,不像之前的两篇博文源码全部给出,只要你看懂HashMap类的源码部分,其他的类可以说触类旁通!不在话下!重要的区别就在于各个类的特点上面。

1、EnumMap类必须和枚举类相关联,key值只能是枚举类的常量值。value使用数组存储,长度就是枚举类的常量值的个数。
2、IdentityHashMap类使用全等判断key值,不全等的key值,就认为是两个对象,可以放入map数组中。并且IdentityHashMap不用链表解决冲突,而是使用较大的map数组存储全部的key-value对。同时key-value对挨着存储在map数组的奇数位和偶数位上。因此,IdentityHash占用较大的内存空间。真正使用的map数组的有限长度最多占总长度的三分之一。
3、WeakHashMap类使用引用队列存储回收的弱引用。主要用于内存空间紧张的情况。使用此类时需要小心应对。因为说不定什么时候,map数组中的数据已经被回收掉了。
该类的定义是:

public class WeakHashMap<K,V>    extends AbstractMap<K,V>    implements Map<K,V> 

这就说明该类不能被序列化、反序列化和克隆!!!
想来也挺好理解的,既然存储的是弱引用,如果克隆的话,必须是深拷贝才行。如果要序列化,可能存在的情况是,map数组已空,没有必要,还有就是既然用到了WeakHashMap,说明内存空间已经紧张,没必要进行克隆或者序列化反序列化。
不过,在该类的entrySet()方法中返回的Set

public Set<Map.Entry<K,V>> entrySet() {        Set<Map.Entry<K,V>> es = entrySet;        return es != null ? es : (entrySet = new EntrySet());    }    private class EntrySet extends AbstractSet<Map.Entry<K,V>> {        public Iterator<Map.Entry<K,V>> iterator() {            return new EntryIterator();        }        public boolean contains(Object o) {            if (!(o instanceof Map.Entry))                return false;            Map.Entry<?,?> e = (Map.Entry<?,?>)o;            Entry<K,V> candidate = getEntry(e.getKey());            return candidate != null && candidate.equals(e);        }        public boolean remove(Object o) {            return removeMapping(o);        }        public int size() {            return WeakHashMap.this.size();        }        public void clear() {            WeakHashMap.this.clear();        }        /**        *深拷贝        * 这里很容易理解        * 如果是浅拷贝,目标数组中还是弱引用对象那么可能会被垃圾回收        * 深拷贝才能保证拷贝到目标数组的对象是强引用        */        private List<Map.Entry<K,V>> deepCopy() {            List<Map.Entry<K,V>> list = new ArrayList<>(size());            for (Map.Entry<K,V> e : this)                list.add(new AbstractMap.SimpleEntry<>(e));            return list;        }        public Object[] toArray() {            return deepCopy().toArray();        }        public <T> T[] toArray(T[] a) {            return deepCopy().toArray(a);        }    }

这里面的toArray()方法就是进行深拷贝获取map数组中的值的。想想也是对的,如果是浅拷贝,可能已经被回收了。获取值并没有多大用处了。基本也解释了为什么不实现克隆和序列化机制了。

以上都是个人的见解。如果有什么不对或者问题,欢迎大家留言评论!多多交流~!共同进步!!^_^【握手~】

0 0
原创粉丝点击