Java中WeakHashMap的使用詳解
WeakHashMap
作為一個(gè)java開發(fā)者肯定都知道且使用HashMap,但估計(jì)大部分人都不太知道WeakHashMap。
從類定義上來看,它和普通的HashMap一樣,繼承了AbstractMap類和實(shí)現(xiàn)了Map接口,也就是說它有著與HashMap差不多的功能。
那么既然jdk已經(jīng)提供了HashMap,為什么還要再提供一個(gè)WeakHashMap呢? 黑格爾曾經(jīng)說過,存在必合理,接下來我們來看下為什么有WeakHashMap。
先來想象一下你因?yàn)槟撤N需求需要一個(gè)Cache,你肯定會(huì)面臨一個(gè)問題,就是所有數(shù)據(jù)不可能都放到Cache里,或者放到Cache里性價(jià)比太低了。
這個(gè)時(shí)候你可能很快就想到了各種Cache數(shù)據(jù)過期策略,目前也有一些優(yōu)秀的包提供了功能豐富的Cache,比如Google的Guava Cache,它支持?jǐn)?shù)據(jù)定期過期、LRU、LFU等策略,但它任然有可能會(huì)導(dǎo)致有用的數(shù)據(jù)被淘汰,沒用的數(shù)據(jù)遲遲不淘汰(如果策略使用得當(dāng)?shù)那闆r下這都是小概率事件)。
如果我現(xiàn)在說有種機(jī)制,可以讓你Cache里不用的key數(shù)據(jù)自動(dòng)清理掉,用的還留著,沒有誤殺也沒有漏殺你信不信!沒錯(cuò)WeakHashMap就是能實(shí)現(xiàn)這種功能的東西,這也是它和普通的HashMap不同的地方——它有自清理的機(jī)制。
如果讓你實(shí)現(xiàn)一種自清理的HashMap,你怎么做? 我的做法肯定是想辦法先知道某個(gè)Key肯定沒有在用了,然后清理到HashMap中對(duì)應(yīng)的K-V。
在JVM里一個(gè)對(duì)象沒用了是指沒有任何其他有用對(duì)象直接或者間接執(zhí)行它,具體點(diǎn)就是在GC過程中它是GCRoots不可達(dá)的。 Jvm提供了一種機(jī)制能讓我們感知到一個(gè)對(duì)象是否已經(jīng)變成了垃圾對(duì)象,這就是WeakReference,不了解WeakReference的可以看下我上一篇介紹博客Java弱引用(WeakReferences)。
某個(gè)WeakReference對(duì)象所指向的對(duì)象如果被判定為垃圾對(duì)象,Jvm會(huì)將該WeakReference對(duì)象放到一個(gè)ReferenceQueue里,我們只要看下這個(gè)Queue里的內(nèi)容就知道某個(gè)對(duì)象還有沒有用了。 WeakHashMap就是這么做的,所以這里的Weak是指WeakReference。接下來讓我們看下它的代碼,看它具體是怎么實(shí)現(xiàn)的。
源碼分析
private static final int DEFAULT_INITIAL_CAPACITY = 16;
private static final int MAXIMUM_CAPACITY = 1 << 30;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;和普通HashMap一樣,WeakHashMap也有一些默認(rèn)值,比如默認(rèn)容量是16,最大容量2^30,使用超過75%它就會(huì)自動(dòng)擴(kuò)容。
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(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;
}
/*
* 其他代碼
*/
}它的Entry和普通HashMap的Entry最大的不同是它繼承了WeakReference,然后把Key做成了弱引用(注意只有Key沒有Value),然后傳入了一個(gè)ReferenceQueue,這就讓它能在某個(gè)key失去所有強(qiáng)引用的時(shí)候感知到。
put
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;
}put方法也很簡(jiǎn)單,用key的hashcode在tab中定位,然后判斷是否是已經(jīng)存在的key,已經(jīng)存在就替換舊值,否則就新建Entry,遇到多個(gè)不同的key有同樣的hashCode就采用開鏈的方式解決hash沖突。
注意這里和HashMap不太一樣的地方,HashMap會(huì)在鏈表太長(zhǎng)的時(shí)候?qū)︽湵碜鰳浠?,把單鏈表轉(zhuǎn)換為紅黑樹,防止極端情況下hashcode沖突導(dǎo)致的性能問題,但在WeakHashMap中沒有樹化。
同樣,在size大于閾值的時(shí)候,WeakHashMap也對(duì)做resize的操作,也就是把tab擴(kuò)大一倍。WeakHashMap中的resize比HashMap中的resize要簡(jiǎn)單好懂些,但沒HashMap中的resize優(yōu)雅。
WeakHashMap中resize有另外一個(gè)額外的操作,就是expungeStaleEntries(),就是對(duì)tab中的死對(duì)象做清理,稍后會(huì)詳細(xì)介紹。
get
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;
}get方法就沒什么特別的了,因?yàn)镋ntry里存了hash值和key的值,所以只要用indexFor定位到tab中的位置,然后遍歷一下單鏈表就知道了。
expungeStaleEntries
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;
}
}
}
}expungeStaleEntries就是WeakHashMap的核心了,它承擔(dān)著Map中死對(duì)象的清理工作。原理就是依賴WeakReference和ReferenceQueue的特性。
在每個(gè)WeakHashMap都有個(gè)ReferenceQueue queue,在Entry初始化的時(shí)候也會(huì)將queue傳給WeakReference,這樣當(dāng)某個(gè)可以key失去所有強(qiáng)應(yīng)用之后,其key對(duì)應(yīng)的WeakReference對(duì)象會(huì)被放到queue里,有了queue就知道需要清理哪些Entry里。這里也是整個(gè)WeakHashMap里唯一加了同步的地方。
除了上文說的到resize中調(diào)用了expungeStaleEntries(),size()中也調(diào)用了這個(gè)清理方法。另外 getTable()也調(diào)了,這就意味著幾乎所有其他方法都間接調(diào)用了清理。
其他
除了上述幾個(gè)和HashMap不太一樣的地方外,WeakHashMap也提供了其他HashMap所有的方法,比如像remove、clean、putAll、entrySet…… 功能上幾乎可以完全替代HashMap,但WeakHashMap也有一些自己的缺陷。
缺陷
1.非線程安全
關(guān)鍵修改方法沒有提供任何同步,多線程環(huán)境下肯定會(huì)導(dǎo)致數(shù)據(jù)不一致的情況,所以使用時(shí)需要多注意。
2.單純作為Map沒有HashMap好
HashMap在Jdk8做了好多優(yōu)化,比如單鏈表在過長(zhǎng)時(shí)會(huì)轉(zhuǎn)化為紅黑樹,降低極端情況下的操作復(fù)雜度。
但WeakHashMap沒有相應(yīng)的優(yōu)化,有點(diǎn)像jdk8之前的HashMap版本。
3.不能自定義ReferenceQueue
WeakHashMap構(gòu)造方法中沒法指定自定的ReferenceQueue,如果用戶想用ReferenceQueue做一些額外的清理工作的話就行不通了。
如果即想用WeakHashMap的功能,也想用ReferenceQueue,貌似得自己實(shí)現(xiàn)一套新的WeakHashMap了。
用途
這里列舉幾個(gè)我所知道的WeakHashMap的使用場(chǎng)景。
1.阿里Arthas
在阿里開源的Java診斷工具中使用了WeakHashMap做類-字節(jié)碼的緩存。
// 類-字節(jié)碼緩存
private final static Map<Class<?>/*Class*/, byte[]/*bytes of Class*/> classBytesCache
= new WeakHashMap<Class<?>, byte[]>();2.Cache
WeakHashMap這種自清理的機(jī)制,非常適合做緩存了。
3.ThreadLocalMap
ThreadLocal中用ThreadLocalMap存儲(chǔ)Thread對(duì)象,雖然ThreadLocalMap和WeakHashMap不是一個(gè)東西,但ThreadLocalMap也利用到了WeakReference的特性,功能用途很類似,所以我很好奇為什么ThreadLocalMap不直接用WeakHashMap呢!
到此這篇關(guān)于Java中WeakHashMap的使用詳解的文章就介紹到這了,更多相關(guān)WeakHashMap的使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot websocket簡(jiǎn)單入門示例
這篇文章主要介紹了springboot websocket簡(jiǎn)單入門示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08
從?PageHelper?到?MyBatis?Plugin執(zhí)行概要及實(shí)現(xiàn)原理
這篇文章主要為大家介紹了從?PageHelper?到?MyBatis?Plugin執(zhí)行概要及實(shí)現(xiàn)原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
Java數(shù)據(jù)結(jié)構(gòu)之隊(duì)列與OJ題
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之隊(duì)列與OJ題,本文章先是對(duì)隊(duì)列進(jìn)行介紹,后又介紹了四道OJ相關(guān)的題目,來使其深入理解,需要的朋友可以參考下2023-01-01
Redis實(shí)現(xiàn)延遲隊(duì)列的全流程詳解
Redisson是Redis服務(wù)器上的分布式可伸縮Java數(shù)據(jù)結(jié)構(gòu),這篇文中主要為大家介紹了Redisson實(shí)現(xiàn)的優(yōu)雅的延遲隊(duì)列的方法,需要的可以參考一下2023-03-03
使用Java編寫導(dǎo)出不確定行數(shù)列數(shù)數(shù)據(jù)的工具類
這篇文章主要為大家詳細(xì)介紹了如何使用Java編寫導(dǎo)出不確定行數(shù)列數(shù)數(shù)據(jù)的工具類,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03

