Java之WeakHashMap源碼淺析
定義
從名字可以得知主要和Map有關(guān),不過還有一個Weak,我們就更能自然而然的想到這里面還牽扯到一種弱引用結(jié)構(gòu),因此想要徹底搞懂,我們還需要知道四種引用。
- 強引用:
- 如果一個對象具有強引用,它就不會被垃圾回收器回收。即使當前內(nèi)存空間不足,JVM也不會回收它,而是拋出 OutOfMemoryError 錯誤,使程序異常終止。 比如String str = "hello"這時候str就是一個強引用。
- 軟引用:
- 內(nèi)存足夠的時候,軟引用對象不會被回收,只有在內(nèi)存不足時,系統(tǒng)則會回收軟引用對象,如果回收了軟引用對象之后仍然沒有足夠的內(nèi)存,才會拋出內(nèi)存溢出異常。
- 弱引用:
- 如果一個對象具有弱引用,在垃圾回收時候,一旦發(fā)現(xiàn)弱引用對象,無論當前內(nèi)存空間是否充足,都會將弱引用回收。
- 虛引用:
- 如果一個對象具有虛引用,就相當于沒有引用,在任何時候都有可能被回收。 使用虛引用的目的就是為了得知對象被GC的時機,所以可以利用虛引用來進行銷毀前的一些操作,比如說資源釋放等。
源碼解析
看下WeakHashMap 的構(gòu)造函數(shù)
public WeakHashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Initial Capacity: "+
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load factor: "+
loadFactor);
int capacity = 1;
// 保證容量是2的整數(shù)倍,有助于hash運算
while (capacity < initialCapacity)
capacity <<= 1;
// 初始化table數(shù)組
table = newTable(capacity);
this.loadFactor = loadFactor;
// 閥值
threshold = (int)(capacity * loadFactor);
}沒什么好說的 table 是一個 Entry數(shù)組 Entry<K,V>[] table; newTable會初始化一個數(shù)組數(shù)組的容量就是前面計算出來的capacity,其值為2的整數(shù)次方。
HashMap的容量為什么是2的n次方?HashMap是如何保證容量是2的n次方的? HashMap容量取2的n次方,主要與hash尋址有關(guān)。在put(key,value)時,putVal()方法中通過i = (n - 1) & hash來計算key的散列地址。其實,i = (n - 1) & hash是一個%操作。也就是說,HashMap是通過%運算來獲得key的散列地址的。但是,%運算的速度并沒有&的操作速度快。而&操作能代替%運算,必須滿足一定的條件,也就是a%b=a&(b-1)僅當b是2的n次方的時候方能成立。這也就是為什么HashMap的容量需要保持在2的n次方了。
再看下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) {
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
...
}可以看到Entry是繼承WeakReference的,我們結(jié)合WeakReference再看一下:
public class WeakReference<T> extends Reference<T> {
/**
* Creates a new weak reference that refers to the given object. The new
* reference is not registered with any queue.
* 創(chuàng)建一個新的弱應(yīng)用給傳入的對象,這個新的引用不注冊任何隊列
*
* @param referent object the new weak reference will refer to
*/
public WeakReference(T referent) {
super(referent);
}
/**
* Creates a new weak reference that refers to the given object and is
* registered with the given queue.
* 創(chuàng)建一個新的弱應(yīng)用給傳入的對象,這個新的引用注冊給一個給定的隊列
*
* @param referent object the new weak reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*/
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}我們發(fā)現(xiàn)在 weakhashmap 中把key注冊給了 WeakReference ,也就是說在 WeakHashMap 中key是一個弱引用。但這個queue是什么我們接著看,在往 WeakHashMap 中put一個元素的時候,會創(chuàng)建Entry。再看 WeakHashMap 的put操作,我們?nèi)绻煜?HashMap 其實我們不需要怎么看這部分的代碼,無非是計算hash值,散列分布到數(shù)組的各個位置,如果 hash 沖突使用拉鏈法進行解決。這里和hashmap有一點不一樣的是hashmap如果鏈長達到閥值會使用紅黑樹。
public V put(K key, V value) {
// 如果key是null則給定一個空的對象進行修飾
Object k = maskNull(key);
// 計算key的hash
int h = hash(k);
// 獲取table
Entry<K,V>[] tab = getTable();
// 根據(jù)hash找到數(shù)組下標
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;
}可以看到這個queue是一個實例化final修飾的屬性。
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
再看下getTable是什么情況,看源碼會知道所有的WeekHashMap的所有操作都要調(diào)用 getTable -> expungeStaleEntries
private Entry<K,V>[] getTable() {
expungeStaleEntries();
return table;
}我們看下expungeStaleEntries做了哪些事情?
private void expungeStaleEntries() {
// 從 ReferenceQueue中拉取元素
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
// 拿到entry的值賦值為null幫助GC
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}expungeStaleEntries 就是WeakHashMap的核心了,它承擔(dān)著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)用了清理。
WeakHashMap的一點點缺點
提到缺點我不太認為是缺點,在某種場景下缺點也有可能是優(yōu)點,而且很多缺點也是可以彌補的。
但非要說個一二三,這里列出下面兩種:
1.非線程安全
關(guān)鍵修改方法沒有提供任何同步,多線程環(huán)境下肯定會導(dǎo)致數(shù)據(jù)不一致的情況,所以使用時需要多注意。
2.單純作為Map沒有HashMap好
HashMap在Jdk8做了好多優(yōu)化,比如單鏈表在過長時會轉(zhuǎn)化為紅黑樹,降低極端情況下的操作復(fù)雜度。但WeakHashMap沒有相應(yīng)的優(yōu)化,有點像jdk8之前的HashMap版本。
WeakHashMap可以應(yīng)用的地方
1.緩存
2.診斷工具,比如atlas,將字節(jié)碼緩存放入到WeakHashMap中
到此這篇關(guān)于Java之WeakHashMap源碼淺析的文章就介紹到這了,更多相關(guān)WeakHashMap源碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
經(jīng)典的Java面試題及回答集錦(基礎(chǔ)篇)
本文給大家收藏整理了java面試題及回答,非常不錯,具有參考借鑒價值,需要的朋友參考下吧2018-03-03
java分析html算法(java網(wǎng)頁蜘蛛算法示例)
近來有些朋友在做蜘蛛算法,或者在網(wǎng)頁上面做深度的數(shù)據(jù)挖掘,下面使用示例2014-03-03
ApplicationListenerDetector監(jiān)聽器判斷demo
這篇文章主要為大家介紹了ApplicationListenerDetector監(jiān)聽器判斷demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03
MyBatis參數(shù)綁定中參數(shù)名不一致導(dǎo)致的錯誤問題解決方法
作為一名Java開發(fā)者,我在實際項目中曾多次遇到MyBatis參數(shù)綁定的問題,其中最常見的一種情況是MyBatis參數(shù)綁定中參數(shù)名不一致導(dǎo)致的錯誤,這類問題看似簡單,但若不深入理解MyBatis的參數(shù)綁定機制,極易陷入誤區(qū),本文將帶大家一起探討解決方案,需要的朋友可以參考下2025-06-06
spring boot對IP地址設(shè)置黑白名單的項目實踐
本文主要介紹了spring boot對IP地址設(shè)置黑白名單的項目實踐,通過YML配置文件定義過濾器類并注冊FilterConfig來實現(xiàn)訪問控制,具有一定的參考價值,感興趣的可以了解一下2025-07-07
springboot項目集成swagger-bootstrap-ui全過程
這篇文章主要介紹了springboot項目集成swagger-bootstrap-ui全過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05
java通過jacob實現(xiàn)office在線預(yù)覽功能
這篇文章主要為大家詳細介紹了java通過jacob實現(xiàn)office在線預(yù)覽功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-08-08

