WeakHashMap是一种弱引用map,内部的key会存储为弱引用,

当jvm gc的时候,如果这些key没有强引用存在的话,会被gc回收掉,

下一次当我们操作map的时候会把对应的Entry整个删除掉,基于这种特性,WeakHashMap特别适用于缓存处理。

//初始化容量
private static final int DEFAULT_INITIAL_CAPACITY = ; //最大容量
private static final int MAXIMUM_CAPACITY = << ; //扩容因子
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
//内部类 Entry<K,V>[] table; //长度
private int size; //扩容银子
private int threshold; //装载因子
private final float loadFactor; //引用队列
private final ReferenceQueue<Object> queue = new ReferenceQueue<>(); //修改次数
int modCount;

(1)容量

容量为数组的长度,亦即桶的个数,默认为16,最大为2的30次方,当容量达到64时才可以树化。

(2)装载因子

装载因子用来计算容量达到多少时才进行扩容,默认装载因子为0.75。

(3)引用队列

当弱键失效的时候会把Entry添加到这个队列中,当下次访问map的时候会把失效的Entry清除掉。

(4 ) entry为内部类

构造方法

 public WeakHashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < )
throw new IllegalArgumentException("Illegal Initial Capacity: "+
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load factor: "+
loadFactor);
int capacity = ;
while (capacity < initialCapacity)
capacity <<= ;
table = newTable(capacity);
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
} 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) + ,
DEFAULT_INITIAL_CAPACITY),
DEFAULT_LOAD_FACTOR);
putAll(m);
}

put方法:

 public V put(K key, V value) {
    //如果为空,用空代替对象
Object k = maskNull(key);
//计算hash指
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)
如果插入元素数量达到了句扩容至hash桶的两倍大小
resize(tab.length * );
return null;
}

(1)计算hash;

这里与HashMap有所不同,HashMap中如果key为空直接返回0,这里是用空对象来计算的。

另外打散方式也不同,HashMap只用了一次异或,这里用了四次,HashMap给出的解释是一次够了,而且就算冲突了也会转换成红黑树,对效率没什么影响。

(2)计算在哪个桶中;

(3)遍历桶对应的链表;

(4)如果找到元素就用新值替换旧值,并返回旧值;

(5)如果没找到就在链表头部插入新元素;

HashMap就插入到链表尾部。

(6)如果元素数量达到了扩容门槛,就把容量扩大到2倍大小;

HashMap中是大于threshold才扩容,这里等于threshold就开始扩容了。

扩容:

 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 / ) {
threshold = (int)(newCapacity * loadFactor);
} else {
expungeStaleEntries();
transfer(newTable, oldTable);
table = oldTable;
}
}
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;
}
}
}
}
  private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
for (int j = ; 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;
}
}
}

(1)判断旧容量是否达到最大容量;

(2)新建新桶并把元素全部转移到新桶中;

(3)如果转移后元素个数不到扩容门槛的一半,则把元素再转移回旧桶,继续使用旧桶,说明不需要扩容;

(4)否则使用新桶,并计算新的扩容门槛;

(5)转移元素的过程中会把key为null的元素清除掉,所以size会变小;

expungeStaleEntries

()1)当key失效的时候gc会自动把对应的Entry添加到这个引用队列中;

(2)所有对map的操作都会直接或间接地调用到这个方法先移除失效的Entry,比如getTable()、size()、resize();

(3)这个方法的目的就是遍历引用队列,并把其中保存的Entry从map中移除掉,具体的过程请看类注释;

(4)从这里可以看到移除Entry的同时把value也一并置为null帮助gc清理元素,防御性编程。

总结:

(1)WeakHashMap使用(数组 + 链表)存储结构,并不是像hashmap一样,有红黑树的加入;

(2)WeakHashMap中的key是弱引用,gc的时候会被清除;

(3)每次对map的操作都会剔除失效key对应的Entry;

(4)使用String作为key时,一定要使用new String()这样的方式声明key,才会失效,其它的基本类型的包装类型是一样的;

WeakHashMap源码分析的更多相关文章

  1. 死磕 java集合之WeakHashMap源码分析

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 WeakHashMap是一种弱引用map,内部的key会存储为弱引用,当jvm gc的时 ...

  2. WeakHashMap 源码分析

    WeakHashMap WeakHashMap 能解决什么问题?什么时候使用 WeakHashMap? 1)WeakHashMap 是基于弱引用键实现 Map 接口的哈希表.当内存紧张,并且键只被 W ...

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

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

  4. Java集合源码分析(八)——WeakHashMap

    简介 WeakHashMap 继承于AbstractMap,实现了Map接口. 和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和 ...

  5. MyBatis源码分析(3)—— Cache接口以及实现

    @(MyBatis)[Cache] MyBatis源码分析--Cache接口以及实现 Cache接口 MyBatis中的Cache以SPI实现,给需要集成其它Cache或者自定义Cache提供了接口. ...

  6. spring源码分析之spring-core总结篇

    1.spring-core概览 spring-core是spring框架的基石,它为spring框架提供了基础的支持. spring-core从源码上看,分为6个package,分别是asm,cgli ...

  7. cglib源码分析(一): 缓存和KEY

    cglib是一个java 字节码的生成工具,它是对asm的进一步封装,提供了一系列class generator.研究cglib主要是因为它也提供了动态代理功能,这点和jdk的动态代理类似. 一. C ...

  8. Java Reference 源码分析

    @(Java)[Reference] Java Reference 源码分析 Reference对象封装了其它对象的引用,可以和普通的对象一样操作,在一定的限制条件下,支持和垃圾收集器的交互.即可以使 ...

  9. Hessian源码分析--HessianProxy

    在上一篇博客 Hessian源码分析--HessianProxyFactory 中我们了解到,客户端获得的对象其实是HessianProxy生成的目标对象,当调用目标对象的方法时,会调用Hessian ...

随机推荐

  1. 在OpenSSL中添加自定义加密算法

    一.简介 本文以添加自定义算法EVP_ssf33为例,介绍在OpenSSL中添加自定义加密算法的方法 二.步骤 1.修改crypto/object/objects.txt,注册算法OID,如下: rs ...

  2. dedecms开启报错调试

    位置:/include/common.inc.php //error_reporting(E_ALL); error_reporting(E_ALL || ~E_NOTICE); 替换成 error_ ...

  3. Part3_lesson1---ARM汇编编程概述

    bootloader以及内核需要使用汇编语言,特别是在初始化的时候!以及在效率要求很高的地方会使用. 汇编程序框架: 其入口在_start处,这个入口需要用一个关键字为.global来声明它是一个全局 ...

  4. 两款JSON类库Jackson与JSON-lib的性能对比(新增第三款测试)

    本篇文章主要介绍了"两款JSON类库Jackson与JSON-lib的性能对比(新增第三款测试)",主要涉及到两款JSON类库Jackson与JSON-lib的性能对比(新增第三款 ...

  5. 编写高质量代码改善C#程序的157个建议——建议117:使用SSL确保通信中的数据安全

    建议117:使用SSL确保通信中的数据安全 SSL(Secure Socket Layer)最初是由NetScape公司设计的,用于Web安全的网络协议.目前它已经广泛应用到各类网络传输通信中了.SS ...

  6. mybatis Mapper.xml和Mapper.java

    mybatis Mapper.xml和Mapper.java 通过Mapper.xml和Mapper.java来实现mybatis.环境和入门的一样的.关键:Mapper.xml + Mapper.j ...

  7. 查看Linux系统是32位的还是64位的

    方法1: getconf LONG_BIT 上面显示我先用的是32位的系统. 方法2: .uanme -a .uname -m 显示i686或x86就是32位的系统,显示x86_64就是64位系统. ...

  8. 洛谷P2147[SDOI2008]洞穴勘测(lct)

    题目描述 辉辉热衷于洞穴勘测. 某天,他按照地图来到了一片被标记为JSZX的洞穴群地区.经过初步勘测,辉辉发现这片区域由n个洞穴(分别编号为1到n)以及若干通道组成,并且每条通道连接了恰好两个洞穴.假 ...

  9. c3p0-数据库连接池原理

    一直用c3p0很久了,但也没时间或没主动去研究过,直到最近频频在出现一些莫名其妙的问题,觉得还是有必要了解和研究一下. c3p0是什么 c3p0的出现,是为了大大提高应用程序和数据库之间访问效率的. ...

  10. 【转】Android - 线程同步

    什么是线程同步? 当使用多个线程来访问同一个数据时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数据不一致),所以我们用同步机制来解决这些问题. 实现同步机制有两个方法: 1.同步代码块 ...