1、WeakHashMap-源码分析-底层数据结构
1、WeakHashMap-底层数据结构
1-1、WeakHashMap是哈希表,与HashMap区别不大,但是它的键是"弱键"。
1-2、故WeakHashMap底层数据结构=数组+链表(未进行链表和红黑树转换,得与HashMap区分)。
2、WeakHashMap-属性字段:
// 默认的初始容量是16,必须是2的幂。
private static final int DEFAULT_INITIAL_CAPACITY = 16;
// 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换)
private static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认加载因子
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 存储数据的Entry数组,长度是2的幂。
// WeakHashMap是采用拉链法实现的,每一个Entry本质上是一个单向链表
private Entry[] table;
// WeakHashMap的大小,它是WeakHashMap保存的键值对的数量
private int size;
// WeakHashMap的阈值,用于判断是否需要调整WeakHashMap的容量(threshold = 容量*加载因子)
private int threshold;
// 加载因子实际大小
private final float loadFactor;
// queue保存的是“已被GC清除”的“弱引用的键”。
// 弱引用和ReferenceQueue 是联合使用的:如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中
private final ReferenceQueue<K> queue = new ReferenceQueue<K>();
// WeakHashMap被改变的次数
private volatile int modCount;
//创建一个大小为n的Entry数组
@SuppressWarnings("unchecked")
private Entry<K,V>[] newTable(int n) {
return (Entry<K,V>[]) new Entry<?,?>[n];
}
/**
* 表示表内的空键。
*/
private static final Object NULL_KEY = new Object();
2、WeakHashMap-源码分析-构造方法
1、WeakHashMap-源码分析-构造方法:
//默认构造函数,默认的初始容量是16,默认加载因子0.75f
public WeakHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//指定"容量大小"的构造函数,自定义初始容量,默认加载因子0.75f
public WeakHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//指定"容量大小"和"加载因子"的构造函数
public WeakHashMap(int initialCapacity, float loadFactor) {
//指定的初始容量必须大于0
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Initial Capacity: "+
initialCapacity);
// WeakHashMap的最大容量只能是MAXIMUM_CAPACITY:必须是2的幂且小于2的30次方
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;
// 创建Entry数组,用来保存数据
table = newTable(capacity);
this.loadFactor = loadFactor;
// 设置"WeakHashMap阈值",当WeakHashMap中存储数据的数量达到threshold时,就需要将WeakHashMap的容量加倍。
threshold = (int)(capacity * loadFactor);
}
// 包含"子Map"的构造函数
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、WeakHashMap-源码分析-Entry类
1、WeakHashMap中的核心角色--Entry,Entry继承了WeakReference,实现了Map.Entry类
1-1、Entry继承自WeakReference,也就是弱引用,所以就具有了弱引用的特点。
1-2、实现了Map.Entry类,即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()等这些函数。
1-3、其他WeakHashMap中的Entry最大的不同就是继承自WeakReference,并把key保存在了WeakReference中。
2、Entry类源代码:
//Entry是单向链表。
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final int hash;
Entry<K,V> next; // 指向下一个节点
/**
* 构造函数,创建一个新Entry实例
* queue是用来存放那些,被jvm清除的entry的引用,因为WeakHashMap使用的是弱引用,所以一旦gc,就会有key键被清除,所以会把entry加入到queue中。
* 就是为.expungeStaleEntries()方法所用。
*/
Entry(Object key, V value, ReferenceQueue<Object> queue, int hash, Entry<K,V> next) {
//它调用了父类的构造函数,调用的WeakReference的构造函数,将key传入Reference中,保存在referent成员变量中
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
@SuppressWarnings("unchecked")
public K getKey() {
// 这里调用了Reference的get方法,从中取出referent对象
// WeakHashMap中,key如果为null会使用NULL_KEY来替代
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();
//把key和value的hashcode做一个异或处理
return Objects.hashCode(k) ^ Objects.hashCode(v);
}
public String toString() {
return getKey() + "=" + getValue();
}
}
2、构造方法调用其他方法:
//.unmaskNull()方法
//调用这个方法,因为WeakHashMap中对key为null时的特殊处理,会将其指向一个特殊的内部变量:
//private static final Object NULL_KEY = new Object();
static Object unmaskNull(Object key) {
return (key == NULL_KEY) ? null : key;
}
//处理null值, WeakHashMap中对key为null时的特殊处理,会将其指向一个特殊的内部变量:
//private static final Object NULL_KEY = new Object();
private static Object maskNull(Object key) {
return (key == null) ? NULL_KEY : key;
}
4、WeakHashMap-源码分析-添加元素
1、添加元素:.put()方法
2、源代码:
public V put(K key, V value) {
//key为null值处理
Object k = maskNull(key);
//计算hash值
int h = hash(k);
//获取table
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;
}
//处理null值, WeakHashMap中对key为null时的特殊处理,会将其指向一个特殊的内部变量:
//private static final Object NULL_KEY = new Object();
private static Object maskNull(Object key) {
return (key == null) ? NULL_KEY : key;
}
//计算hash值
final int hash(Object k) {
int h = k.hashCode();
//做了二次散列,来扩大低位的影响。
//h >>> 20 = h*2^20、h >>> 12 = h*2^12
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
/**
* 根据hashcode值,计算下标
* 将数组长度减1后与hashcode做一个位与操作,因为length必定是2的幂,所以减1后就变成了掩码,再进行与操作就能直接得到hashcode mod length的结果。
* h & (length-1) = h/length
*/
private static int indexFor(int h, int length) {
return h & (length-1);
}
//获取table
/**
* 返回第一次清除过时项后的表。
*/
private Entry<K,V>[] getTable() {
expungeStaleEntries();
return table;
}
/**
* 从表中删除过时的条目:都会将table中key已经被回收掉的Entry移除掉(将对应的value置为null)。
*/
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
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;
// 将对应的value置为null,帮助GC回收
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}
5、WeakHashMap-源码分析-扩容
1、扩容:.resize()方法
2、源代码:
void resize(int newCapacity) {
//获取当前table
Entry<K,V>[] oldTable = getTable();
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//新建一个table
Entry<K,V>[] newTable = newTable(newCapacity);
transfer(oldTable, newTable);
table = newTable;
/*
* If ignoring null elements and processing ref queue caused massive
* shrinkage, then restore old table. This should be rare, but avoids
* unbounded expansion of garbage-filled tables.
*/
if (size >= threshold / 2) {
threshold = (int)(newCapacity * loadFactor);
} else {
//从表中删除过时的条目:都会将table中key已经被回收掉的Entry移除掉。
expungeStaleEntries();
// 将旧table中的内容复制到新table中
transfer(newTable, oldTable);
table = oldTable;
}
}
// 新建Entry数组
private Entry<K,V>[] newTable(int n) {
return (Entry<K,V>[]) new Entry<?,?>[n];
}
/**
Transfers all entries from src to dest tables
将旧table中的内容复制到新table中
*/
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;
}
}
}
6、WeakHashMap-源码分析-获取元素
1、获取元素:.get()方法
2、源代码:
public V get(Object key) {
// 对null值特殊处理
Object k = maskNull(key);
// 取key的hash值
int h = hash(k);
// 取当前table
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;
}
7、WeakHashMap-源码分析-删除元素
1、删除元素:.remove()方法
2、源代码:
public V remove(Object key) {
// 对null值特殊处理
Object k = maskNull(key);
// 取key的hash
int h = hash(k);
// 取当前table
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;
// 查找对应Entry
if (h == e.hash && eq(k, e.get())) {
modCount++;
size--;
if (prev == e)
tab[i] = next;
else
prev.next = next;
// 如果找到,返回对应Entry的value
return e.value;
}
prev = e;
e = next;
}
return null;
}
8、WeakHashMap-源码分析-举例使用-Demo
public class WeakHashMapTest {
public static void main(String[] args){
testWeakHashMap();
}
private static void testWeakHashMap() {
// 创建3个String对象用来做key
String w1 = new String("key1");
String w2 = new String("key2");
String w3 = new String("key3");
// 新建WeakHashMap
Map weakHashMap = new WeakHashMap();
// 添加键值对
weakHashMap.put(w1, "v1");
weakHashMap.put(w2, "v2");
weakHashMap.put(w3, "v3");
// 打印出weakHashMap
System.out.printf("weakHashMap:%s\n", weakHashMap); //{key1=w1, key2=w2, key3=w3}
// containsKey(Object key) :是否包含键key
System.out.printf("contains key key1 : %s\n",weakHashMap.containsKey("key1"));//true
System.out.printf("contains key key4 : %s\n",weakHashMap.containsKey("key4"));//false
// containsValue(Object value) :是否包含值value
System.out.printf("contains value v1 : %s\n",weakHashMap.containsValue("v1"));//true
System.out.printf("contains value 0 : %s\n",weakHashMap.containsValue(0));//false
// remove(Object key) : 删除键key对应的键值对
weakHashMap.remove("three");
System.out.printf("weakHashMap: %s\n", weakHashMap);{key1=w1, key2=w2, key3=w3}
// ---- 测试 WeakHashMap 的自动回收特性 ----
// 将w1设置null。
// 这意味着“弱键”w1再没有被其它对象引用,调用gc时会回收WeakHashMap中与“w1”对应的键值对
w1 = null;
// 内存回收。这里,会回收WeakHashMap中与“w1”对应的键值对
System.gc();
// 遍历WeakHashMap
Iterator iter = weakHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry en = (Map.Entry)iter.next();
/**
next : key2 - w2
next : key3 - w3
*/
System.out.printf("next : %s - %s\n",en.getKey(),en.getValue());
}
// 打印WeakHashMap的实际大小
// after gc WeakHashMap size:2
System.out.printf("after gc WeakHashMap size:%s\n", weakHashMap.size());
}
}
9、WeakHashMap-源码分析-应用场景-Demo
1、应用场景
1-1、由于WeakHashMap可以自动清除Entry,所以比较适合用于存储非必需对象,用作缓存非常合适。
1-2、配合事务进行使用,存储事务过程中的各类信息,结构:
WeakHashMap<String,Map<K,V>> transactionCache;
1-2-1、key为String类型,可以用来标志区分不同的事务,起到一个事务id的作用。value是一个map,可以是一个简单的HashMap或者LinkedHashMap,用来存放在事务中需要使用到的信息。
1-2-2、在事务开始时创建一个事务id,并用它来作为key,事务结束后,将这个强引用消除掉,这样既能保证在事务中可以获取到所需要的信息,又能自动释放掉map中的所有信息。
2、应用Demo-在Tomcat的工具类里,有这样一种实现。基于LRU策略。
package org.apache.tomcat.util.collections;
public final class ConcurrentCache<K,V> {
private final int size;
private final Map<K,V> eden;
private final Map<K,V> longterm;
public ConcurrentCache(int size) {
this.size = size;
this.eden = new ConcurrentHashMap<>(size);
this.longterm = new WeakHashMap<>(size);
}
public V get(K k) {
V v = this.eden.get(k);
if (v == null) {
synchronized (longterm) {
v = this.longterm.get(k);
}
if (v != null) {
this.eden.put(k, v);
}
}
return v;
}
public void put(K k, V v) {
if (this.eden.size() >= size) {
synchronized (longterm) {
this.longterm.putAll(this.eden);
}
this.eden.clear();
}
this.eden.put(k, v);
}
}