Android开发经验谈随笔-生活工作点滴Android开发

WeakHashMap源码解析

2019-07-16  本文已影响11人  103style

转载请以链接形式标明出处:
本文出自:103style的博客

base on jdk_1.8.0_77

目录


WeakHashMap简介

WeakHashMap 继承于AbstractMap,实现了Map接口。

HashMap 一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null

不过WeakHashMap键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。

这个“弱键”的原理呢?大致上就是,通过WeakReference和ReferenceQueue实现的WeakHashMapkey 是“弱键”,即是 WeakReference 类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。实现步骤是:

  • 新建WeakHashMap,将“键值对”添加到WeakHashMap中。
    实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
  • 某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。
  • 当下一次我们需要操作WeakHashMap时,会先同步tablequeuetable中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对
    这就是“弱键”如何被自动从WeakHashMap中删除的步骤了。

HashMap 一样,WeakHashMap是不同步的。可以使用 Collections.synchronizedMap 方法来构造同步的 WeakHashMap


WeakHashMap的常量、成员变量介绍

HashMap 一样得常量:

WeakHashMap的全局变量:


WeakHashMap的构造函数

public WeakHashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public WeakHashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
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);
}
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);
}

通过下面的代码计算合适的table长度(大于指定容量的最小的2的指数幂)。其他和 HashMap 类似。

int capacity = 1;
while (capacity < initialCapacity)
    capacity <<= 1;

WeakHashMap相关的函数

int                    hash(Object k)//计算key的hash
int                    indexFor(int h, int length)//计算key再table上的索引
void                   expungeStaleEntries()//删除引用队列中entry
Entry<K,V>[]           getTable()//获取当前table
void                   clear()
V                      get(Object key)
V                      put(K key, V value)
void                   putAll(Map<? extends K, ? extends V> map)
V                      remove(Object key)
boolean                isEmpty()
int                    size()
void                   resize(int newCapacity)
boolean                containsKey(Object key)
boolean                containsValue(Object value)
void                   transfer(Entry<K,V>[] src, Entry<K,V>[] dest)
hash(Object k)

函数中注释的解释大致是:

此函数确保在每个位位置仅由常数倍数相差的哈希码具有有限数量的冲突

final int hash(Object k) {
    int h = k.hashCode();
    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
indexFor(int h, int length)

计算对应hashtable的索引位置。

private static int indexFor(int h, int length) {
    return h & (length-1);
}
expungeStaleEntries()

删除table中有的 queue中的entry

private void expungeStaleEntries() {
    for (Object x; (x = queue.poll()) != null; ) {
        synchronized (queue) {
            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;
                    e.value = null; // Help GC
                    size--;
                    break;
                }
                prev = p;
                p = next;
            }
        }
    }
}
getTable()

获取当前的数组table,并在返回前删除引用队列中entry

private Entry<K,V>[] getTable() {
    expungeStaleEntries();
    return table;
}
clear()

先清空引用队列queue中的元素,然后将table中的数据全部设置为null,然后再次清空引用队列queue中的元素。

public void clear() {
    // clear out ref queue. We don't need to expunge entries
    // since table is getting cleared.
    while (queue.poll() != null)
        ;

    modCount++;
    Arrays.fill(table, null);
    size = 0;

    // Allocation of array may have caused GC, which may have caused
    // additional entries to go stale.  Removing these entries from the
    // reference queue will make them eligible for reclamation.
    while (queue.poll() != null)
        ;
}
get(Object key)

通过keyhash找到索引,然后遍历链表找到对应的值。

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;
}
put(K key, V value)

get类似,通过keyhash找到索引,然后检查链表是否有对应的key,有个话更新对应的值。

没有的话就通过new Entry<>(k, value, queue, h, e)构建一个新的节点添加到之前的链表前面。然后再判断是否要扩容。

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;
}
putAll(Map<? extends K, ? extends V> map)

if (numKeysToBeAdded > threshold)判断是为了避免 m中有重复的key.

public void putAll(Map<? extends K, ? extends V> m) {
    int numKeysToBeAdded = m.size();
    if (numKeysToBeAdded == 0)
        return;
    
    if (numKeysToBeAdded > threshold) {
        int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
        if (targetCapacity > MAXIMUM_CAPACITY)
            targetCapacity = MAXIMUM_CAPACITY;
        int newCapacity = table.length;
        while (newCapacity < targetCapacity)
            newCapacity <<= 1;
        if (newCapacity > table.length)
            resize(newCapacity);
    }

    for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
        put(e.getKey(), e.getValue());
}
remove(Object key)

put类似,put是添加或者修改节点,remove则为删除节点。

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;
}
resize(int newCapacity)

扩容操作,如果忽略null元素并处理引用队列导致大量收缩,则恢复旧表。

void resize(int newCapacity) {
    Entry<K,V>[] oldTable = getTable();
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }

    Entry<K,V>[] newTable = newTable(newCapacity);
    transfer(oldTable, newTable);
    table = newTable;
    
    if (size >= threshold / 2) {
        threshold = (int)(newCapacity * loadFactor);
    } else {
        expungeStaleEntries();
        transfer(newTable, oldTable);
        table = oldTable;
    }
}
transfer(Entry<K,V>[] src, Entry<K,V>[] dest)

删除src中的null key元素,并将其他元素放到dest中的对应位置。

private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
    for (int j = 0; j < src.length; ++j) {
        Entry<K,V> e = src[j];
        src[j] = null;
        while (e != null) {
            Entry<K,V> next = e.next;
            Object key = e.get();
            if (key == null) {
                e.next = null;  // Help GC
                e.value = null; //  "   "
                size--;
            } else {
                int i = indexFor(e.hash, dest.length);
                e.next = dest[i];
                dest[i] = e;
            }
            e = next;
        }
    }
}

小结

通过下图我们发现基本所有的数据操作都调用了expungeStaleEntries()来移除被gc回收的key.

getTable
expungeStaleEntries

参考文章


以上

上一篇下一篇

猜你喜欢

热点阅读