从源码看Android常用的数据结构 ( SDK23版本 ) ( 五, Map篇 )

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/j550341130/article/details/80855498

此系列文章放在了我的专栏里, 欢迎查看
https://blog.csdn.net/column/details/24187.html

相关衔接
从源码看Android常用的数据结构 ( SDK23版本 ) ( 一 , 总述 )
从源码看Android常用的数据结构 ( SDK23版本 ) ( 二, List篇 )
从源码看Android常用的数据结构 ( SDK23版本 ) ( 三 , Queue篇)
从源码看Android常用的数据结构 ( SDK23版本 ) ( 四, Set篇 )
从源码看Android常用的数据结构 ( SDK23版本 ) ( 五, Map篇 )
从源码看Android常用的数据结构 ( SDK23版本 ) ( 六, ConcurrentHashMap )

Github里有一份Android使用到的小技术, 欢迎查看:
https://github.com/YouCii/LearnApp


总览

A {@code Map} is a data structure consisting of a set of keys and values
in which each key is mapped to a single value.  The class of the objects
used as keys is declared when the {@code Map} is declared, as is the
class of the corresponding values.
A {@code Map} provides helper methods to iterate through all of the
keys contained in it, as well as various methods to access and updat
the key/value pairs.

Map是由一组键/值组成的数据结构, 其中每个键被映射到单个值. 声明Map对象时, value相对应的
Key的类型也会一同声明.
Map提供帮助方法来遍历其中包含的所有键, 以及各种方法来访问和更新键值对. 

主要实现类有: HashMap, LinkedHashMap, TreeMap, ArrayMap, WeakHashMap, Hashtable, ConcurrentHashMap.

先总体比较一下, 后面再详细分析


HashMap

HashMap是最常用的一个键值对集合, 使用Hash来比对key值, 使用内部类HashMapEntry来存储键值.
其内部的数据结构是 数组+链表 的形式, 每个hash值对应的index下是一串链表, 用于储存hash碰撞的元素.

注意, 这里只使用了23版本的sdk, 如果查看26+版本下的HashMap源码, 发现有很大不同, 关键的一点是优化了大量hash碰撞情况下的遍历效率, 即在每个链表Size大于8时使用红黑树进行处理. 具体的请查看高版本源码, AndroidSDK26+或者Java1.8均可.

HashMap is an implementation of {@link Map}. All optional operations are supported.
All elements are permitted as keys or values, including null.
Note that the iteration order for HashMap is non-deterministic. If you want
deterministic iteration, use {@link LinkedHashMap}.

HashMap是Map接口的一个实现类, 支持所有可选操作以及包括null在内的所有类型的key/value.
注意, HashMap迭代是乱序的, 如果需要确定的迭代顺序的话可以用LinkedHashMap.

Note: the implementation of {@code HashMap} is not synchronized.
If one thread of several threads accessing an instance modifies the map
structurally, access to the map needs to be synchronized. A structural
modification is an operation that adds or removes an entry. Changes in
the value of an entry are not structural changes.

注意, HashMap不是同步的. 在多线程访问的情况下, 如果某线程进行结构化修改的话, 所有对该map
的访问都需要加同步. 这里说的结构化修改指的是add/remove一对键值, 而不是修改某entry的value.

The {@code Iterator} created by calling the {@code iterator} method
may throw a {@code ConcurrentModificationException} if the map is structurally
changed while an iterator is used to iterate over the elements. Only the
{@code remove} method that is provided by the iterator allows for removal of
elements during iteration. It is not possible to guarantee that this
mechanism works in all cases of unsynchronized concurrent modification. It
should only be used for debugging purposes.

Iterator会通过iterator()方法创建, 但是如果当前已经有一个iterator在迭代过程中进行了结构修改,
那iterator()方法可能会抛出ConcurrentModificationException异常. 
在迭代过程中如果想要删除元素的话, 只能够使用迭代器提供的remove()方法. 但是也不能保证这种机制
在所有的未经同步的并发修改情况下都有效, 并发情况下使用的话应该只用于调试目的.

下面看下核心的方法

真正存储数据的数组

transient HashMapEntry<K, V>[] table; // 因为是数组, 所以会有扩容

核心读写方法

@Override public V put(K key, V value) {
    if (key == null) {
        return putValueForNullKey(value); // 单独存储key为null的数据
    }
    int hash = Collections.secondaryHash(key); // 通过key计算hash值, 这里使用的是效率更好的补充散列函数
    HashMapEntry<K, V>[] tab = table;
    // 计算出index
    int index = hash & (tab.length - 1);
    // 遍历index所在的链表, 如果已经有这个key了, 更新为新值并返回旧值
    for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
        // hash和key双重比对, 因为不同的key可能会有相同的hash
        // 把e.hash == hash放在key.equals(e.key)的前面能有更高的效率
        if (e.hash == hash && key.equals(e.key)) { 
            preModify(e);
            V oldValue = e.value;
            e.value = value;
            return oldValue;
        }
    }
    // 没有现有的key, 扩容, 然后添加新的键值
    modCount++;
    if (size++ > threshold) {
        tab = doubleCapacity(); // 必定扩容一倍
        index = hash & (tab.length - 1);
    }
    table[index] = new HashMapEntry<K, V>(key, value, hash, table[index]);
    return null;
}

/**
* 相同的算法, 首先通过key计算hash, 通过hash计算index, 然后遍历index下的链表
*/
public V get(Object key) {
    if (key == null) {
        HashMapEntry<K, V> e = entryForNullKey;
        return e == null ? null : e.value;
    }

    int hash = Collections.secondaryHash(key);
    HashMapEntry<K, V>[] tab = table;
    for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)]; e != null; e = e.next) {
        K eKey = e.key;
        if (eKey == key || (e.hash == hash && key.equals(eKey))) {
            return e.value;
        }
    }
    return null;
}

/**
* 获取到index下的链表, 移除链表中的对应key的HashMapEntry
*/
@Override public V remove(Object key) {
    if (key == null) {
        return removeNullKey();
    }
    int hash = Collections.secondaryHash(key);
    HashMapEntry<K, V>[] tab = table;
    int index = hash & (tab.length - 1);
    for (HashMapEntry<K, V> e = tab[index], prev = null;
            e != null; prev = e, e = e.next) {
        if (e.hash == hash && key.equals(e.key)) {
            if (prev == null) {
                tab[index] = e.next;
            } else {
                prev.next = e.next;
            }
            modCount++;
            size--;
            postRemove(e);
            return e.value;
        }
    }
    return null;
}

几个扩容相关的方法

   private HashMapEntry<K, V>[] doubleCapacity() {
       HashMapEntry<K, V>[] oldTable = table;
       int oldCapacity = oldTable.length;
       if (oldCapacity == MAXIMUM_CAPACITY) {
           return oldTable;
       }
       int newCapacity = oldCapacity * 2; // 直接双倍
       HashMapEntry<K, V>[] newTable = makeTable(newCapacity);
       if (size == 0) {
           return newTable;
       }
       // 依次把每个节点的链表写入新的table中, 但新table的顺序没搞清是什么样
       for (int j = 0; j < oldCapacity; j++) {
           HashMapEntry<K, V> e = oldTable[j];
           if (e == null) {
               continue;
           }
           int highBit = e.hash & oldCapacity;
           HashMapEntry<K, V> broken = null;
           newTable[j | highBit] = e;
           for (HashMapEntry<K, V> n = e.next; n != null; e = n, n = n.next) {
               int nextHighBit = n.hash & oldCapacity;
               if (nextHighBit != highBit) {
                   if (broken == null)
                       newTable[j | nextHighBit] = n;
                   else
                       broken.next = n;
                   broken = e;
                   highBit = nextHighBit;
               }
           }
           if (broken != null)
               broken.next = null;
       }
       return newTable;
   }

private HashMapEntry<K, V>[] makeTable(int newCapacity) {
    @SuppressWarnings("unchecked") 
    HashMapEntry<K, V>[] newTable = (HashMapEntry<K, V>[]) new HashMapEntry[newCapacity];
    table = newTable;
    threshold = (newCapacity >> 1) + (newCapacity >> 2); // 3/4 capacity
    return newTable;
}

封装的数据类HashMapEntry, 以单向链表的形式存储, 内部存有用于查找的hash值, key/value, 以及用于实现链表的next.

static class HashMapEntry<K, V> implements Entry<K, V> {
    final K key;
    V value;
    final int hash;
    HashMapEntry<K, V> next;

    HashMapEntry(K key, V value, int hash, HashMapEntry<K, V> next) {
        this.key = key;
        this.value = value;
        this.hash = hash;
        this.next = next;
    }
    ...
}

总结一下, HashMap的读写效率还是非常高的, 通过key计算出index, 大多数情况下table[index]就是存储查找值的HashMapEntry. 在极端的碰撞情况下, 新版的HashMap也做了优化, 在单线程环境下HashMap是最先考虑的.


LinkedHashMap

LinkedHashMap 是 HashMap 的子类, 不同点是 LinkedHashMap 有固定的顺序(先入的写顺序或者读顺序), 这是因为 LinkedHashMap 额外维护了一个 transient LinkedEntry<K, V> header, 并重写了 HashMap 的 addNewEntry 方法, 令其每次添加新元素时均写入到以 header 为头的链表中.

LinkedHashMap is an implementation of {@link Map} that guarantees iteration order.
All optional operations are supported.
All elements are permitted as keys or values, including null.

LinkedHashMap是可以保证迭代顺序的一种HashMap子类, 支持所以操作, key/value支持包括null在内所有类型.

<p>Entries are kept in a doubly-linked list. The iteration order is, by default, the
order in which keys were inserted. Reinserting an already-present key doesn't change the
order. If the three argument constructor is used, and {@code accessOrder} is specified as
{@code true}, the iteration will be in the order that entries were accessed.
The access order is affected by {@code put}, {@code get}, and {@code putAll} operations,
but not by operations on the collection views.

Entries存储在一个双向链表中, 这个链表的默认顺序是key插入的顺序, 重新插入一个已存在的key不会改变其顺序.
如果使用三参数的构造方法明确指定specified为true的话, 迭代器会按照访问的顺序来排序. 而访问顺序会受put,
get,putAll方法的影响, 但是与Collection内的方法无关.

<p>Note: the implementation of {@code LinkedHashMap} is not synchronized.
If one thread of several threads accessing an instance modifies the map
structurally, access to the map needs to be synchronized. For
insertion-ordered instances a structural modification is an operation that
removes or adds an entry. Access-ordered instances also are structurally
modified by {@code put}, {@code get}, and {@code putAll} since these methods
change the order of the entries. Changes in the value of an entry are not structural changes.

注意, HashMap不是同步的. 在多线程访问的情况下, 如果某线程进行结构化修改的话, 所有对该map的访问都需
要加同步. 对于插入排序的实例, 结构化修改指的是add/remove一对键值, 访问排序实例的put/get/putAll改变
了entries的排序, 所以也属于结构化修改. 修改某entry的value并不算结构化修改.

<p>The {@code Iterator} created by calling the {@code iterator} method
may throw a {@code ConcurrentModificationException} if the map is structurally
changed while an iterator is used to iterate over the elements. Only the
{@code remove} method that is provided by the iterator allows for removal of
elements during iteration. It is not possible to guarantee that this
mechanism works in all cases of unsynchronized concurrent modification. It
should only be used for debugging purposes.
上同HashMap.

重写的addNewEntry方法, 每次添加均写入到以 header 为头的链表中

@Override void addNewEntry(K key, V value, int hash, int index) {
   LinkedEntry<K, V> header = this.header;

   // Remove eldest entry if instructed to do so.
   LinkedEntry<K, V> eldest = header.nxt;
   if (eldest != header && removeEldestEntry(eldest)) {
       remove(eldest.key);
   }

   LinkedEntry<K, V> oldTail = header.prv;
   LinkedEntry<K, V> newTail = new LinkedEntry<K,V>(key, value, hash, table[index], header, oldTail);
   table[index] = oldTail.nxt = header.prv = newTail;
}

另外, LinkedHashMap 重写了 get 方法, 唯一的不同点在于 LinkedHashMap 会执行 makeTail() 方法.

@Override public V get(Object key) {
    if (key == null) {
        HashMapEntry<K, V> e = entryForNullKey;
        if (e == null)
            return null;
        // 注意此处, 如果是按访问顺序进行排序的话, 把查到的这个HashMapEntry放到尾部
        if (accessOrder)
            makeTail((LinkedEntry<K, V>) e);
        return e.value;
    }
    int hash = Collections.secondaryHash(key);
    HashMapEntry<K, V>[] tab = table;
    for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)]; e != null; e = e.next) {
        K eKey = e.key;
        if (eKey == key || (e.hash == hash && key.equals(eKey))) {
            if (accessOrder) makeTail((LinkedEntry<K, V>) e);
            return e.value;
        }
    }
    return null;
}

/**
* 把某个Entry放到尾部
*/ 
private void makeTail(LinkedEntry<K, V> e) {
    // Unlink e
    e.prv.nxt = e.nxt;
    e.nxt.prv = e.prv;
    // Relink e as tail
    LinkedEntry<K, V> header = this.header;
    LinkedEntry<K, V> oldTail = header.prv;
    e.nxt = header;
    e.prv = oldTail;
    oldTail.nxt = header.prv = e;
    modCount++;
}

LinkedHashMap 的存储内部类使用了双向链表的形式, 而HashMap则是单向链表

static class LinkedEntry<K, V> extends HashMapEntry<K, V> {
    LinkedEntry<K, V> nxt;
    LinkedEntry<K, V> prv;

    /** Create the header entry */
    LinkedEntry() {
        super(null, null, 0, null);
        nxt = prv = this;
    }

    /** Create a normal entry */
    LinkedEntry(K key, V value, int hash, HashMapEntry<K, V> next,
                LinkedEntry<K, V> nxt, LinkedEntry<K, V> prv) {
        super(key, value, hash, next);
        this.nxt = nxt;
        this.prv = prv;
    }
}

TreeMap

简单讲, TreeMap是一个Entry按Key排序(自然排序或自定义的排序器)的Map, 其内部是使用二叉树结构实现存储. 注意, 这里说的是23版本sdk, 26+版本下的TreeMap使用的是红黑树
二叉树/红黑树综合了数组/链表的优点, 读要比链表快/写要比数组快, 效率较为均衡, 在需要排序的情景下较为适合.

A map whose entries are sorted by their keys. All optional operations such as
{@link #put} and {@link #remove} are supported.
This map sorts keys using either a user-supplied comparator or the key's
natural order:

一种通过key排序entries的Map. 支持包括put/remove在内的所有方法. 这个Map可以通过用户指定
的comparator或者自然顺序进行排序:

<ul>
  <li>User supplied comparators must be able to compare any pair of keys in
      this map. If a user-supplied comparator is in use, it will be returned
      by {@link #comparator}.
  <li>If no user-supplied comparator is supplied, keys will be sorted by
      their natural order. Keys must be <i>mutually comparable</i>: they must
      implement {@link Comparable} and {@link Comparable#compareTo
      compareTo()} must be able to compare each key with any other key in
      this map. In this case {@link #comparator} will return null.
</ul>

1. 用户提供的comparator必须可以比较map内的所有key. 如果使用了用户提供的comparator, 
   comparator方法会返回此对象;
2. 如果没有用户提供的comparator, key会通过其自然顺序进行排序, key对象必须手动排序: 即
   必须实现comaparable接口并重写compareTo方法令其能够比较map内的所有key. 在此类实例中,
   comparator方法会返回null.

With either a comparator or a natural ordering, comparisons should be
<i>consistent with equals</i>. An ordering is consistent with equals if for
every pair of keys {@code a} and {@code b}, {@code a.equals(b)} if and only
if {@code compare(a, b) == 0}.

无论是comparator还是自然排序, 大小比较的结果应该与equals方法的表现相一致, 即对于排序与
大小比较的形式相一致的Map, a.equals(b)当且仅当compare(a, b) == 0成立时才成立.

When the ordering is not consistent with equals the behavior of this
class is well defined but does not honor the contract specified by {@link
Map}. Consider a tree map of case-insensitive strings, an ordering that is
not consistent with equals: 

当排序与大小比较的表现不一致时, 此TreeMap也可以被很好的定义, 只是不太符合Map的规则. 可以
考虑Key忽略大小写导致两个方法表现不一致的TreeMap实例: 

{@code
  TreeMap<String, String> map = new TreeMap<String, String>(
  String.CASE_INSENSITIVE_ORDER);
  map.put("a", "android");
  System.out.println(map.get("A")); // Map规则要求返回null,但是这里返回android
}

TreeMap内部是二叉树, 其结点类如下

static class Node<K, V> implements Map.Entry<K, V> {
    Node<K, V> parent; // 父结点
    Node<K, V> left; // 左子结点
    Node<K, V> right; // 右子结点
    final K key;
    V value;
    int height;
    ...
}

看下get/put方法, 发现23SDK版本的TreeMap的核心只在一个遍历方法, 通过Relation判断进行不同的逻辑:

    @Override public V get(Object key) {
        Entry<K, V> entry = find((K) key, EQUAL); // Relation为EQUAL
        return entry != null ? entry.getValue() : null;
    }

    @Override public V put(K key, V value) {
        Node<K, V> created = find(key, Relation.CREATE); // Relation为CREATE
        V result = created.value;
        created.value = value;
        return result;
    }
/**
 * Returns the node at or adjacent to the given key, creating it if requested.
 *
 * @throws ClassCastException if {@code key} and the tree's keys aren't mutually comparable.
 */
Node<K, V> find(K key, Relation relation) {
    if (root == null) { // 如果根节点为null, 即没有数据
        if (comparator == NATURAL_ORDER && !(key instanceof Comparable)) {
            // NullPointer
            throw new ClassCastException(key.getClass().getName() + " is not Comparable"); 
        }
        if (relation == Relation.CREATE) {
            root = new Node<K, V>(null, key);
            size = 1;
            modCount++;
            return root;
        } else {
            return null;
        }
    }
    // 如果没有comparator排序器, 强转至Comparable(比直接使用key快10%)
    // 注意: 如果没有comparator, key必须实现Comparable, 否则强转崩溃
    Comparable<Object> comparableKey = (comparator == NATURAL_ORDER)
            ? (Comparable<Object>) key
            : null;
    // 最近的节点, 遍历起点为root根节点
    Node<K, V> nearest = root;
    while (true) {
        // 检查是否相等
        int comparison = (comparableKey != null)
                ? comparableKey.compareTo(nearest.key)
                : comparator.compare(key, nearest.key);
        // 相等, 找到了
        if (comparison == 0) {
            switch (relation) {
                case LOWER:
                    return nearest.prev();
                case FLOOR:
                case EQUAL:
                case CREATE:
                case CEILING:
                    return nearest; // 例如put方法会走CREATE逻辑, 这里返回已存在的值, 用于替换/返回
                case HIGHER:
                    return nearest.next();
            }
        }
        // 二叉树左边是较小一位, 右边是较大一位
        Node<K, V> child = (comparison < 0) ? nearest.left : nearest.right;  
        if (child != null) {
            nearest = child;
            continue;
        }
        // nearest.key 比较大的话
        if (comparison < 0) {
            switch (relation) {
                case LOWER:
                case FLOOR:
                    return nearest.prev();
                case CEILING:
                case HIGHER:
                    return nearest;
                case EQUAL:
                    return null;
                case CREATE: // 把新建项作为左子项
                    Node<K, V> created = new Node<K, V>(nearest, key);
                    nearest.left = created;
                    size++;
                    modCount++;
                    rebalance(nearest, true);
                    return created;
            }
        } else {  // nearest.key 比较小
            switch (relation) {
                case LOWER:
                case FLOOR:
                    return nearest;
                case CEILING:
                case HIGHER:
                    return nearest.next();
                case EQUAL:
                    return null;
                case CREATE:// 把新建项作为右子项
                    Node<K, V> created = new Node<K, V>(nearest, key);
                    nearest.right = created;
                    size++;
                    modCount++;
                    rebalance(nearest, true);
                    return created;
            }
        }
    }
}

注意这里还有一个private void rebalance(Node<K, V> unbalanced, boolean insert)方法, 这个方法是树结构在添加删除元素后通过左旋右旋进行重排序的, 具体实现不再展开叙述.


ArrayMap

ArrayMap 与 ArraySet 相似度 80%, 不同点是ArrayMap存储的是键值对, 其 key 和 value 均存储一个数组 Object[] mArray, mArray内部结构是: key, value, key, value, key, value, key, value… 所以mArray的size >= 真实size的两倍.
ArrayMap 的作用也与 ArraySet 一致(其实应该说 ArraySet 与 ArrayMap 一致), 都是以略微牺牲读写效率的代价节省HashMap/HashSet的内存占用.

请参考 从源码看Android常用的数据结构 ( 四, Set篇 )

ArrayMap is a generic key->value mapping data structure that is
designed to be more memory efficient than a traditional {@link java.util.HashMap}.
It keeps its mappings in an array data structure -- an integer array of hash
codes for each item, and an Object array of the key/value pairs.  This allows it to
avoid having to create an extra object for every entry put in to the map, and it
also tries to control the growth of the size of these arrays more aggressively
(since growing them only requires copying the entries in the array, not rebuilding
a hash map).

ArrayMap 是一种通用的键值映射数据结构, 它比传统 HashMap 有更好的内存优化特性.
它将其映射保持在一个数组中: 存有每项hashCode的整数数组, 以及键值对的对象数组. 这使得它避免了为
每个条目创建额外的对象, 并且它也试图更积极地控制这些数组的大小的增长(因为增长它们只需要复制数组
中的条目, 而不重建哈希映射). 

<p>Note that this implementation is not intended to be appropriate for data structures
that may contain large numbers of items.  It is generally slower than a traditional
HashMap, since lookups require a binary search and adds and removes require inserting
and deleting entries in the array.  For containers holding up to hundreds of items,
the performance difference is not significant, less than 50%.</p>
<p>Because this container is intended to better balance memory use, unlike most other
standard Java containers it will shrink its array as items are removed from it.  Currently
you have no control over this shrinking -- if you set a capacity and then remove an
item, it may reduce the capacity to better match the current size.  In the future an
explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
同ArraySet.

这里只看put/get方法, 其余方法请参考Set篇.

public V put(K key, V value) {
    final int hash;
    int index;
    // 先查找key的位置, 判断集合中是否已存在
    if (key == null) {
        hash = 0;
        index = indexOfNull();
    } else {
        hash = key.hashCode();
        index = indexOf(key, hash);
    }
    if (index >= 0) {
        index = (index<<1) + 1;
        // 如果已经有这个元素了, 替换后返回old
        final V old = (V)mArray[index];
        mArray[index] = value;
        return old;
    }

    index = ~index; // indexOf()未能查询到时会返回一个负数来指示下一个要存到哪个位置, 规定是数组的末尾.
    if (mSize >= mHashes.length) {
        // 扩容后的size: 扩容到1.5倍, 或者8, 或者4
        final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1)) : 
        (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
        // 暂存旧数据
        final int[] ohashes = mHashes;
        final Object[] oarray = mArray;
         // 读取缓存
        allocArrays(n);
        // 把旧数据复制到新数据中
        if (mHashes.length > 0) {
            System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
            System.arraycopy(oarray, 0, mArray, 0, oarray.length);
        }
        // 再重新写入缓存
        freeArrays(ohashes, oarray, mSize);
    }
    // 如果空间尚够, 向后平移一位, 为新数据腾出位置
    if (index < mSize) {
        System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
        System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
    }
    // 上面都是维护数组的开销, 下面才是真正的更新数据, 可见其写效率非常差.
    mHashes[index] = hash;
    mArray[index<<1] = key;
    mArray[(index<<1)+1] = value;
    mSize++;
    return null;
}

@Override
public V get(Object key) {
    final int index = indexOfKey(key);
    // index<<1是key所在地, index<<1 + 1 是value所在地
    return index >= 0 ? (V)mArray[(index<<1)+1] : null;
}

WeakHashMap

WeakHashMap 是 Key 使用弱引用的Map.
它在HashMap的基础上再多维护了一个ReferenceQueue, 这个ReferenceQueue会在创建Entry实例时传入, 当此Entry实例被回收后, 就会记录在ReferenceQueue中.
在执行WeakHashMap的读写操作时会首先遍历 ReferenceQueue, 在elementData中移除Key在ReferenceQueue之中的所有entry.

WeakHashMap is an implementation of Map with keys which are WeakReferences. A
key/value mapping is removed when the key is no longer referenced. All
optional operations (adding and removing) are supported. Keys and values can
be any objects. Note that the garbage collector acts similar to a second
thread on this collection, possibly removing keys.

WeakHashMap 是一种Key使用弱引用的Map. 当一个键值对的键不再被引用时就会被remove掉. 支持所
有的可选操作(add/remove), key和value可以是任意对象. 注意, gc机制类似于操作这个集合的另外
一个线程, 可能会在任意时刻移除keys.

elementData 是类似于HashMap的entry数组, referenceQueue是记录被回收的key的队列.

    private final ReferenceQueue<K> referenceQueue;
    Entry<K, V>[] elementData;

键值对对象Entry的构造函数(省略了部分无关特性的代码)

private static final class Entry<K, V> extends WeakReference<K> implements Map.Entry<K, V> {
        Entry(K key, V object, ReferenceQueue<K> queue) {
            super(key, queue); // WeakReference包含ReferenceQueue参数的构造函数
            isNull = key == null;
            hash = isNull ? 0 : Collections.secondaryHash(key);
            value = object;
        }
}

get/put方法比HashMap的方法多了一部, 即事先执行poll()方法

    public V get(Object key) {
        poll(); // 区别
        if (key != null) {
            // 计算hash值, 与HashMap的算法不同
            int index = (Collections.secondaryHash(key) & 0x7FFFFFFF) % elementData.length;
            Entry<K, V> entry = elementData[index];
            while (entry != null) {
                if (key.equals(entry.get())) {
                    return entry.value;
                }
                entry = entry.next;
            }
            return null;
        }
        Entry<K, V> entry = elementData[0];
        while (entry != null) {
            if (entry.isNull) {
                return entry.value;
            }
            entry = entry.next;
        }
        return null;
    }

    public V put(K key, V value) {
        poll(); // 区别
        int index = 0;
        Entry<K, V> entry;
        if (key != null) {
            index = (Collections.secondaryHash(key) & 0x7FFFFFFF) % elementData.length;
            entry = elementData[index];
            while (entry != null && !key.equals(entry.get())) {
                entry = entry.next;
            }
        } else {
            entry = elementData[0];
            while (entry != null && !entry.isNull) {
                entry = entry.next;
            }
        }
        if (entry == null) {
            modCount++;
            if (++elementCount > threshold) {
                rehash();
                index = key == null ? 0 : (Collections.secondaryHash(key) & 0x7FFFFFFF)
                        % elementData.length;
            }
            entry = new Entry<K, V>(key, value, referenceQueue);
            entry.next = elementData[index];
            elementData[index] = entry;
            return null;
        }
        V result = entry.value;
        entry.value = value;
        return result;
    }

体现特性的poll()方法

    void poll() {
        Entry<K, V> toRemove;
        // 遍历referenceQueue, 移除elementData中key被回收的entry
        while ((toRemove = (Entry<K, V>) referenceQueue.poll()) != null) {
            removeEntry(toRemove);
        }
    }

    void removeEntry(Entry<K, V> toRemove) {
        Entry<K, V> entry, last = null;
        int index = (toRemove.hash & 0x7FFFFFFF) % elementData.length;
        entry = elementData[index];
        // 忽略找不到的entry, 有可能user已经移除了此entry
        while (entry != null) {
            if (toRemove == entry) {
                modCount++;
                if (last == null) {
                    elementData[index] = entry.next;
                } else {
                    last.next = entry.next;
                }
                elementCount--;
                break;
            }
            last = entry;
            entry = entry.next;
        }
    }

Hashtable

Hashtable 可以理解为加锁的 HashMap, 不支持null元素.

Hashtable 锁粒度较粗, 采用直接在方法上加锁的方式, 所以不建议使用, 推荐 ConcurrentHashMap.

    public synchronized V get(Object key)
    public synchronized V put(K key, V value)
    public synchronized boolean containsKey(Object key)

ConcurrentHashMap

ConcurrentHashMap 是线程安全的Map, 源码较复杂, SDK23版本有3400行, 26版本更是飙到了6400.
此类要看的太多, 放在单独的一篇中吧.

猜你喜欢

转载自blog.csdn.net/j550341130/article/details/80855498