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-09Java數(shù)據(jù)結(jié)構(gòu)之隊(duì)列與OJ題
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之隊(duì)列與OJ題,本文章先是對(duì)隊(duì)列進(jìn)行介紹,后又介紹了四道OJ相關(guān)的題目,來使其深入理解,需要的朋友可以參考下2023-01-01Redis實(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