1. WeakHashMap简介

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

  和HashMap一样,WeakHashMap也是一种以key-value键值对的形式进行数据的存储,底层存储结构为数组加链表,并且键值都可以为null。与HashMap不同的是,WeakHashMap实现了弱引用,当一个key不再正常使用时,会从WeakHashMap中自动移除,被GC回收并存入ReferenceQueue中。而这一特点主要是通过ReferenceQueue加WeakReference组合来实现的,ReFerenceQueue主要是配合WeakReference来记录被回收的元素,在WeakReference构造时关联一个ReferenceQueue,当该WeakReference被GC回收后便会将该对象放入ReferenceQueue中,也就是说ReferenceQueue中的key一定是被GC回收过的。所以WeakHashMap在使用中会出现size不断变小,isEmpty会从false变为true等情况。

  一般情况下,除非整个map对象被GC回收,否则里面的元素便会一致存在,不会被GC回收。而WeakHashMap中的Entry实现了WeakReference,是一种弱引用类型,弱引用类型的对象会在GC运行时被回收的,也就是说WeakHashMap的Entry中的key只要没有被其他强引用指向,便会在GC运行时被回收。key在被GC回收后会把Entry存入queue,所以这时候仅仅是key被回收了变为null,但是Entry实体还是存在WeakHashMap中的,所以在每次对WeakHashMap进行操作时,都会主动把queue中的所有Entry移除WeakHashMap,利用弱键的机制从而实现了Entry的弱引用。

  还有就是WeakHashMap中没有红黑树的优化处理,取而代之的是在hash函数里做了处理,保证了再默认加载因子0.75的情况下,链表中元素数量不会超过8个左右。

  使用场景:缓存

2. WeakHashMap实现

1. 核心参数

  和HashMap差不多,底层都是通过数组加链表的方式存储数据,默认的初始容量、加载因子和最大容量等参数都和HashMap一致,只不过增加了ReferenceQueue来存储被GC回收的弱键以及NULL_KEY,此外WeakHashMap内部Entry继承自WeakReference,实现了弱引用。在Entry被创建时,会调用父类WeakReference的构造函数并将ReferenceQueue(queue)与其相关联,当该Entry实体的key被GC回收后,便会将该实体放到queue中。

    //默认初始容量16
private static final int DEFAULT_INITIAL_CAPACITY = 16;
//最大容量2^30
private static final int MAXIMUM_CAPACITY = 1 << 30;
//默认加载因子0.75
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
//底层entry数组存储
Entry<K,V>[] table;
//WeakHashMap大小
private int size;
//扩容阈值
private int threshold;
//加载因子
private final float loadFactor;
//ReferenceQueue用来存储已被GC回收的弱键
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
//修改次数,用来实现fail-fast机制
int modCount;
//当key为null时,用NULL_KEY来代替
private static final Object NULL_KEY = new Object();
//entry实体的set集合,用于foreach遍历
private transient Set<Map.Entry<K,V>> entrySet;
//entry实体,继承自WeakReference,实现了Map.Entry接口
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
    
V value;
final int hash;
Entry<K,V> next; /**
* Creates new entry.
*/
Entry(Object key, V value, ReferenceQueue<Object> queue, int hash, Entry<K,V> next) {
        //调用父类WeakReference的构造函数,把queue作为被GC回收的队列,key没有被引用时会被GC回收,然后将Entry放入queue中
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 Objects.hashCode(k) ^ Objects.hashCode(v);
} public String toString() {
return getKey() + "=" + getValue();
}
}

2. 构造函数

  WeakHashMap的构造函数和HashMap的构造函数也有点类似,围绕着初始容量和加载因子两个核心参数进行构造。不同的是,WeakHashMap在构造时就已经把底层entry数组实例化了,而HashMap在第一次添加元素时才进行底层Entry数组的实例化。

    //构造一个指定加载因子的WeakHashMap,把初始容量设为大于等于initialCapacity的最小2次幂
public WeakHashMap(int initialCapacity, float loadFactor) {
//初始容量不能小于0,否则抛出异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Initial Capacity: "+ initialCapacity);
//初始容量不能大于2^30
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//加载因子必须大于0,小于等于1
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load factor: "+ loadFactor);
//把初始容量设为大于等于initialCapacity的最小2次幂
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
//实例化底层Entry数组
table = newTable(capacity);
//设置加载因子
this.loadFactor = loadFactor;
//扩容阈值设为 容量*加载因子
threshold = (int)(capacity * loadFactor);
} //构造一个默认加载因子0.75的WeakHashMap,把初始容量设为大于等于initialCapacity的最小2次幂
public WeakHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
} //构造一个默认初始容量16、默认加载因子0.75的WeakHashMap
public WeakHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
} //构造一个默认加载因子0.75的WeakHashMap,初始容量取能够容纳m中元素的最小2次幂(最小为16),并将m中所有元素放入WeakHashMap
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);
}

3.  核心方法

    //当key为null时,返回一个NULL_KEY对象
private static Object maskNull(Object key) {
return (key == null) ? NULL_KEY : key;
} //当对象时NULL_KEY时,返回null
static Object unmaskNull(Object key) {
return (key == NULL_KEY) ? null : key;
} //equals比较
private static boolean eq(Object x, Object y) {
return x == y || x.equals(y);
} //hash函数
final int hash(Object k) {
int h = k.hashCode(); // 这个函数确保在每个位位置上只相差常数倍的哈希码具有有限的冲突数(在默认负载因子下约为8)
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
} //根据hashCode和length-1进行与运算得到下标索引
private static int indexFor(int h, int length) {
return h & (length-1);
} //删除所有被GC回收的Entry
private void expungeStaleEntries() {
//将queue中的对象依次取出
for (Object x; (x = queue.poll()) != null; ) {
//锁住queue,保证对queue的操作是线程安全的
synchronized (queue) {
@SuppressWarnings("unchecked")
//将取出的对象转为Entry
Entry<K,V> e = (Entry<K,V>) x;
//获取该Entry的索引i
int i = indexFor(e.hash, table.length);
//取索引i处的Entry prev,用于遍历链表时存放上一节点
Entry<K,V> prev = table[i];
//声明Entry p,用于遍历链表时存放当前节点
Entry<K,V> p = prev;
//遍历链表
while (p != null) {
//取当前节点的下一节点next
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;
}
}
}
} //获取Entry数组,获取之前先删除所有需要被移除的Entry
private Entry<K,V>[] getTable() {
expungeStaleEntries();
return table;
} //返回WeakHashMap的大小,获取之前先删除所有需要被移除的Entry
public int size() {
if (size == 0)
return 0;
expungeStaleEntries();
return size;
} //判断WeakHashMap是否为空,获取之前先删除所有需要被移除的Entry
public boolean isEmpty() {
return size() == 0;
} //根据key获取value
public V get(Object key) {
//检查key为null则返回NULL_KEY对象
Object k = maskNull(key);
//取key的hashCode
int h = hash(k);
//通过getTable获取数组,去除所有需要被回收的Entry
Entry<K,V>[] tab = getTable();
//计算出该key的下标索引
int index = indexFor(h, tab.length);
//获取该索引位置的链表头
Entry<K,V> e = tab[index];
//遍历链表,根据hashCode值与equals比较双重判断获取value
while (e != null) {
if (e.hash == h && eq(k, e.get()))
return e.value;
e = e.next;
}
return null;
} //判断WeakHashMap中是否包含key
public boolean containsKey(Object key) {
return getEntry(key) != null;
} //根据key获取Entry实体
Entry<K,V> getEntry(Object key) {
//检查key为null则返回NULL_KEY对象
Object k = maskNull(key);
//取key的hashCode
int h = hash(k);
//通过getTable获取数组,去除所有需要被回收的Entry
Entry<K,V>[] tab = getTable();
//计算出该key的下标索引
int index = indexFor(h, tab.length);
//获取该索引位置的链表头
Entry<K,V> e = tab[index];
//遍历链表,根据hashCode值与equals比较双重判断获取Entry
while (e != null && !(e.hash == h && eq(k, e.get())))
e = e.next;
return e;
} //将key-value存入WeakHashMap,如果key已存在则将value替换并返回旧
public V put(K key, V value) {
//检查key为null则返回NULL_KEY对象
Object k = maskNull(key);
//取key的hashCode
int h = hash(k);
//通过getTable获取数组,去除所有需要被回收的Entry
Entry<K,V>[] tab = getTable();
//计算出该key的下标索引
int i = indexFor(h, tab.length);
//遍历链表,根据hashCode值与equals比较双重判断key是否存在,存在则将value替换,并直接返回旧值
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;
}
}
//若key不存在,则将该节点插入链表头,这里和HashMap不太一样,HashMap是尾部追加
modCount++;
Entry<K,V> e = tab[i];
tab[i] = new Entry<>(k, value, queue, h, e);
//添加元素后的WeakHashMap大小如果大于等于扩容阈值,则扩容1倍
if (++size >= threshold)
resize(tab.length * 2);
return null;
} //核心扩容算法
void resize(int newCapacity) {
//获取原Entry数组,获取之前先删除所有需要被移除的Entry
Entry<K,V>[] oldTable = getTable();
//获取原Entry数组的容量
int oldCapacity = oldTable.length;
//如果原数组的容量已经达到最大值2^30,则停止扩容并防止再次扩容
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//实例化新Entry数组
Entry<K,V>[] newTable = newTable(newCapacity);
//将原数组中的元素全部放入新数组
transfer(oldTable, newTable);
//将底层数组替换为新数组
table = newTable; //如果忽略空元素并处理ref队列导致大量收缩,则恢复旧表。这应该是很少见的,但是避免了垃圾填充表的无限制扩展。
if (size >= threshold / 2) {
threshold = (int)(newCapacity * loadFactor);
} else {
expungeStaleEntries();
transfer(newTable, oldTable);
table = oldTable;
}
} //将src数组中的元素全部放入dest数组
private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
//遍历src数组
for (int j = 0; j < src.length; ++j) {
//依次将src数组中的元素取出
Entry<K,V> e = src[j];
//取出后将src中去除引用
src[j] = null;
//遍历链表
while (e != null) {
//取当前节点的下一节点next
Entry<K,V> next = e.next;
//获取弱键
Object key = e.get();
//弱键为空则说明已被GC回收,将该节点清空,方便GC回收该Entry
if (key == null) {
e.next = null; // Help GC
e.value = null; // " "
size--;
} else {
//不为空说明没有被回收,重新计算在dest数组中的索引
int i = indexFor(e.hash, dest.length);
//将该节点插入dest索引i处链表头
e.next = dest[i];
dest[i] = e;
}
e = next;
}
}
} //将一个map中的所有元素全部放入WeakHashMap
public void putAll(Map<? extends K, ? extends V> m) {
//取传入map的大小
int numKeysToBeAdded = m.size();
//若传入的是一个空map,直接return
if (numKeysToBeAdded == 0)
return; //如果要添加映射的数量大于或等于阈值,这是保守的;明显的条件是(m.size() + size) >= 阈值,
//但是这个条件可能导致map的容量是适当容量的两倍,如果要添加的键与此映射中已经存在的键重叠。
//通过使用保守的计算,我们最多可额外调整一次大小。
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);
} //遍历map,依次将元素存入WeakHashMap中
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
} //根据key将Entry移除WeakHashMap
public V remove(Object key) {
//检查key为null则返回NULL_KEY对象
Object k = maskNull(key);
//取key的hashCode
int h = hash(k);
//获取Entry数组,获取之前先删除所有需要被移除的Entry
Entry<K,V>[] tab = getTable();
//计算出该key的下标索引
int i = indexFor(h, tab.length);
//取索引i处的Entry,用来遍历链表时存放上个节点
Entry<K,V> prev = tab[i];
//用来遍历链表时存放当前节点
Entry<K,V> e = prev; //遍历链表
while (e != null) {
//取当前节点的下一节点next
Entry<K,V> next = e.next;
//根据hashCode值与equals比较双重判断key是否相同,存在则移除该节点并返回该节点value
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;
} //移除WeakHashMap中指定Entry
boolean removeMapping(Object o) {
//判断对象o是否为Entry实例,不是则直接return
if (!(o instanceof Map.Entry))
return false;
//获取Entry数组,获取之前先删除所有需要被移除的Entry
Entry<K,V>[] tab = getTable();
//将对象o强转为Entry实体
Map.Entry<?,?> entry = (Map.Entry<?,?>)o;
//检查key为null则返回NULL_KEY对象
Object k = maskNull(entry.getKey());
//取key的hashCode
int h = hash(k);
//计算出该key的下标索引
int i = indexFor(h, tab.length);
//取索引i处的Entry,用来遍历链表时存放上个节点
Entry<K,V> prev = tab[i];
//用来遍历链表时存放当前节点
Entry<K,V> e = prev; //遍历链表
while (e != null) {
//取当前节点的下一节点next
Entry<K,V> next = e.next;
//根据hashCode值与equals比较双重判断Entry是否为要移除的对象,若是则移除并返回true
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;
} //清空WeakHashMap
public void clear() {
//清空queue,将queue中的元素一个个取出。这是神马骚操作,长见识了!!!
while (queue.poll() != null)
; modCount++;
//通过Arrays.fill把底层数组所有元素全部清空
Arrays.fill(table, null);
size = 0; //将数组清空后可能引发了GC导致queue中又添加了元素,再次清空
while (queue.poll() != null)
;
} //判断WeakHashMap中是否包含value
public boolean containsValue(Object value) {
//如果value为空,调用是否包含空value逻辑
if (value==null)
return containsNullValue();
//获取Entry数组,获取之前先删除所有需要被移除的Entry
Entry<K,V>[] tab = getTable();
//遍历数组
for (int i = tab.length; i-- > 0;)
//遍历链表
for (Entry<K,V> e = tab[i]; e != null; e = e.next)
//equals比较value值,相同则返回true
if (value.equals(e.value))
return true;
return false;
} //判断WeakHashMap是否包含空value
private boolean containsNullValue() {
Entry<K,V>[] tab = getTable();
for (int i = tab.length; i-- > 0;)
for (Entry<K,V> e = tab[i]; e != null; e = e.next)
if (e.value==null)
return true;
return false;
} //返回WeakHashMap中的所有key的set集合
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new KeySet();
keySet = ks;
}
return ks;
} //返回WeakHashMap中的所有value的集合
public Collection<V> values() {
Collection<V> vs = values;
if (vs == null) {
vs = new Values();
values = vs;
}
return vs;
} //返回WeakHashMap中的所有Entry的set集合
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es = entrySet;
return es != null ? es : (entrySet = new EntrySet());
}

学习JDK1.8集合源码之--WeakHashMap的更多相关文章

  1. 学习JDK1.8集合源码之--LinkedHashSet

    1. LinkedHashSet简介 LinkedHashSet继承自HashSet,故拥有HashSet的全部API,LinkedHashSet内部实现简单,核心参数和方法都继承自HashSet,只 ...

  2. 学习JDK1.8集合源码之--ArrayList

    参考文档: https://cloud.tencent.com/developer/article/1145014 https://segmentfault.com/a/119000001857894 ...

  3. 学习JDK1.8集合源码之--HashMap

    1. HashMap简介 HashMap是一种key-value结构存储数据的集合,是map集合的经典哈希实现. HashMap允许存储null键和null值,但null键最多只能有一个(HashSe ...

  4. 学习JDK1.8集合源码之--ArrayDeque

    1. ArrayDeque简介 ArrayDeque是基于数组实现的一种双端队列,既可以当成普通的队列用(先进先出),也可以当成栈来用(后进先出),故ArrayDeque完全可以代替Stack,Arr ...

  5. 学习JDK1.8集合源码之--HashSet

    1. HashSet简介 HashSet是一个不可重复的无序集合,底层由HashMap实现存储,故HashSet是非线程安全的,由于HashSet使用HashMap的Key来存储元素,而HashMap ...

  6. 学习JDK1.8集合源码之--Vector

    1. Vector简介 Vector是JDK1.0版本就推出的一个类,和ArrayList一样,继承自AbstractList,实现了List.RandomAccess.Cloneable.java. ...

  7. 学习JDK1.8集合源码之--TreeMap

    1. TreeMap简介 TreeMap继承自AbstractMap,实现了NavigableMap.Cloneable.java.io.Serializable接口.所以TreeMap也是一个key ...

  8. 学习JDK1.8集合源码之--LinkedHashMap

    1. LinkedHashMap简介 LinkedHashMap继承自HashMap,实现了Map接口. LinkedHashMap是HashMap的一种有序实现(多态,HashMap的有序态),可以 ...

  9. 学习JDK1.8集合源码之--PriorityQueue

    1. PriorityQueue简介 PriorityQueue是一种优先队列,不同于普通队列的先进先出原则,优先队列是按照元素的优先级出列,每次出列都是优先级最高的元素.优先队列的应用很多,最典型的 ...

随机推荐

  1. 数论GCD——cf1055C

    被一道数论题卡了半天 网上的题解说只要匹配l或者r就行,想了下还真是.. 能让r1和r2对其就让他们对其,不能对其就讨论一下两种情况就可以了 #include <bits/stdc++.h> ...

  2. 莫烦PyTorch学习笔记(五)——模型的存取

    import torch from torch.autograd import Variable import matplotlib.pyplot as plt torch.manual_seed() ...

  3. MYSQL批量创建表的存储过程

    因为业务需要,创建了100个表,但是这些表的结构都是一样的,作为程序员,就是要解决这种重复劳动.然而这种事情还要单独写个php脚本的话太麻烦了吧,所以就干脆学了一下直接用Mysql存储过程怎么实现: ...

  4. mybatis第二篇—参数绑定

    不管我们在做数据库作业或者任务还是当时的仅靠jdbc来写一个管理系统的时候,sql语句需要一些参数,从而来实现模糊查询,精确查询,插入数据,更新数据和删除数据.这些参数,在mybatis里面,又该如何 ...

  5. wpf之渐变色LinearGradientBrush

    xmal代码: <Grid Name="grid1"> <Grid.Background> <LinearGradientBrush> < ...

  6. js中的对象、原型链机制、构造函数

    一.在js中创建对象的方式 //一.字面量或直接量创建对象 var obj1 = { name:"zs", age:12 }; //二.通过new来创建对象 var obj2 = ...

  7. Django项目:CMDB(服务器硬件资产自动采集系统)--04--04CMDB本地(Agent)模式客户端唯一标识(ID)

    # client.py # ————————01CMDB获取服务器基本信息———————— from src import plugins #__init__.py from lib.serializ ...

  8. xcode下的DerivedData

    在模拟器运行的情况下经常会出现以下的错误: error: remove /Users/mac/Library/Developer/Xcode/DerivedData/YuQing-amkrrucjrn ...

  9. HDU4004 二分答案

    第一道二分答案...今天看了大牛的博客,突然发现有个叫“二分枚举答案”的方法好像很牛,于是便搜了些资料..发现并不是很难,可能是我了解的只是冰山一脚罢了...加油ACMer!!!! #include& ...

  10. jeecms框架单点登录功能的实现

    单点登录的功能实现主要原理: 1: 在点击登录按钮的时候使用reponse.addCookie()方法向浏览器发送cookie: 2: 在前段拦截器中的request.getCookie()在接收到设 ...