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