JDK源码分析(9)之 WeakHashMap 相关

发布时间:2020-06-28 05:46:14 作者:沙漏半杯
来源:网络 阅读:181

平时我们使用最多的数据结构肯定是 HashMap,但是在使用的时候我们必须知道每个键值对的生命周期,并且手动清除它;但是如果我们不是很清楚它的生命周期,这时候就比较麻烦;通常有这样几种处理方式:

所以本文将主要介绍WeakHashMap的特性,以及补充一些关于 HashMap 实现的对比;相关 HashMap 的介绍也可以参考 HashMap 相关;

一、使用场景

上面也介绍了,WeakHashMap适用于不是非常重要的缓存类似的场景;例如:

WeakHashMap<Object, Integer> map = new WeakHashMap<>();for (int i = 0; i < 100; i++) {
  map.put(new Object(), i);
}

System.out.println(map.size());  // 1System.gc();                     // 2System.out.println(map.size());  // 3System.out.println(map.size());  // 4System.out.println(map.size());  // 5System.out.println(map);         // 6System.out.println(map.size());  // 7

// 打印:
100
100
100
46
{}
0

对于以上的结果你可能和我打印的不一样,WeakHashMap按照语义应该是,当 key 没有强引用指向的时候,会自动清除 key 和 value;我这里先解释它的释放过程,如果你觉得很清晰,那WeakHashMap你就算是掌握了;

将上面的代码改成多线程分析思路也是一样的,如果你觉得有不清楚的地方可以查看下文;

二、WeakHashMap 源码分析

1. 类定义

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

可以看到虽然WeakHashMap也是基于哈希表,但是却并非像LinkedHashMap一样是继承于HashMap,并且WeakHashMap也没有实现Cloneable, Serializable两个接口,这是因为WeakHashMap基于WeakReference实现的,弱引用并不建议实现序列化,同时弱引用一般用于不是很重要的缓存,也就没必要实现Cloneable, Serializable两个接口了;

2. 核心方法

private final ReferenceQueue<Object> queue = new ReferenceQueue<>();private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
  V value;  final int hash;
  Entry<K,V> next;

  Entry(Object key, V value, ReferenceQueue<Object> queue, int hash, Entry<K,V> next) {    super(key, queue);    this.value = value;    this.hash  = hash;    this.next  = next;
  }  public K getKey() { }  public V getValue() {  public V setValue(V newValue) {  public int hashCode() {  public String toString() {
}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;
      }
    }
  }
}

上面代码所列的ReferenceQueue,Entry,expungeStaleEntries()就是WeakHashMap实现的核心了;这里强烈建议要先看 Reference 完全解读 和 Reference 框架概览这两篇文章,里面同样的内容我也不会再赘述了;

三、性能对比

其实上面的内容就已经将WeakHashMap的主要实现讲完了,但是我之前在看HashMap源码的时候,并没有对比 JDK1.7 和 JDK1.8,但是在这里发现其实WeakHashMap的实现和 JDK1.7 差不多,所以接下来我将简单对比一下WeakHashMapHashMap

1. 容量计算

WeakHashMapHashMap中都要求容量是2的幂,因为当容量为2的幂时,使用除留余数法计算哈希桶位置时可以使用hash % length = hash & (length-1)的性质进行优化;

// WeakHashMapint capacity = 1;while (capacity < initialCapacity)
  capacity <<= 1;// HashMapstatic final int tableSizeFor(int cap) {  int n = cap - 1;
  n |= n >>> 1;
  n |= n >>> 2;
  n |= n >>> 4;
  n |= n >>> 8;
  n |= n >>> 16;  return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

简单测试可以得到:


initCap = 1050100
WeakHashMap303226
HashMap333

代码比较简单我就不贴了,从上表也可以看到了tableSizeFor不仅高效而且稳定;

2. 哈希计算

// WeakHashMapfinal int hash(Object k) {  int h = k.hashCode();
  h ^= (h >>> 20) ^ (h >>> 12);  return h ^ (h >>> 7) ^ (h >>> 4);
}// HashMapstatic final int hash(Object key) {    int h;    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

两种hash算法都是要避免极端的hashCode(),但是HashMap却更为透彻,因为影响哈希桶位置的只有 hash 的低位(容量2的n次方,n个低位),直接将高位与上低位,使高位 hash 参与位置计算,简洁且高效;

此外还有put方法,但是里面还牵涉红黑树,对于本文就扯得有点远了,所以暂不讲;

总结


推荐阅读:
  1. JDK源码分析(11)之 BlockingQueue 相关
  2. JDK源码分析(10)之 Hashtable 相关

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java jdk 源码

上一篇:主从搭建

下一篇:Lintcode16 Permutations II solution 题解

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》