- EnumMap源碼分析
1、介紹
EnumMap是與枚舉類相結合的Map類。跟hash沒有多大關係,雖然本文中另外兩種與HashMap有關,但是EnumMap與HashMap關係並不大。
EnumMap就是專門與枚舉類結合形成Map的key-value對結構。值的注意的是,EnumMap中雖然也存儲的是key-value對的數據,但是內存實現上卻採用的是數組結構。key存儲一個數組結構,value也用一個對應的數組結構。
2、構造器
public EnumMap(Class<K> keyType) {
this.keyType = keyType;
//保存枚舉類的所有枚舉值到數組中
keyUniverse = getKeyUniverse(keyType);
vals = new Object[keyUniverse.length];
}
public EnumMap(EnumMap<K, ? extends V> m) {
keyType = m.keyType;
keyUniverse = m.keyUniverse;
vals = m.vals.clone();
size = m.size;
}
public EnumMap(Map<K, ? extends V> m) {
//如果m類是EnumMap類,與上面的構造器一樣
if (m instanceof EnumMap) {
EnumMap<K, ? extends V> em = (EnumMap<K, ? extends V>) m;
keyType = em.keyType;
keyUniverse = em.keyUniverse;
vals = em.vals.clone();
size = em.size;
} else {
//如果不是EnumMap類,則不允許vals數組長度爲空
if (m.isEmpty())
throw new IllegalArgumentException("Specified map is empty");
keyType = m.keySet().iterator().next().getDeclaringClass();
keyUniverse = getKeyUniverse(keyType);
vals = new Object[keyUniverse.length];
putAll(m);
}
}
上面是EnumMap的三個構造器,有構造器知道,該類存儲的key-value對,key值類型必須是枚舉類型。枚舉類型的值通過該方法getKeyUniverse(keyType)獲取枚舉類數組,keyUniverse 數組存放key集合。vals數組存放value數據。
2、put方法
public V put(K key, V value) {
typeCheck(key);
int index = key.ordinal();
Object oldValue = vals[index];
vals[index] = maskNull(value);
if (oldValue == null)
size++;
return unmaskNull(oldValue);
}
private void typeCheck(K key) {
Class keyClass = key.getClass();
if (keyClass != keyType && keyClass.getSuperclass() != keyType)
throw new ClassCastException(keyClass + " != " + keyType);
}
put方法存入key-value對。由於key值是固定的枚舉類的常量值,並且在構造器初始化的過程中,已經獲取到了,這裏put方法中在此傳入key值,是爲了vals數組中保存value值。
首先檢查key類型是否是構造器初始化過程中綁定的枚舉類型,如果不是,拋出異常ClassCastException。
然後又key值,計算value存儲在vals數組中的索引值,然後將value值存入。如果原來已有value值,則替換原來的value值。
由此可見,vals長度最多隻能是枚舉類常量值的個數。key值只能是枚舉類常量值。
通過put方法我們已經基本瞭解了EnumMap類的特點了,其他方法都是按照這個思想進行的。
public void putAll(Map<? extends K, ? extends V> m) {
if (m instanceof EnumMap) {
EnumMap<? extends K, ? extends V> em =
(EnumMap<? extends K, ? extends V>)m;
if (em.keyType != keyType) {
if (em.isEmpty())
return;
throw new ClassCastException(em.keyType + " != " + keyType);
}
for (int i = 0; i < keyUniverse.length; i++) {
Object emValue = em.vals[i];
if (emValue != null) {
if (vals[i] == null)
size++;
vals[i] = emValue;
}
}
} else {
/**
* 這裏的putAll(m)會調用父類的方法
* 父類該方法的實現上,均採用一一放入的put方法進行
* 數據的存入。
* 此時會調用put(e.getKey(), e.getValue());
* 子類put方法會被調用,所以回到了該類中的put方法
* put方法中會檢驗key值的類型,如果不匹配就不能放入,
* 拋出異常
*/
super.putAll(m);
}
}
keyUniverse就是初始化時保存的枚舉類常量值數組。通過上面的方法,非常簡單保存集合中的數據。並且EnumMap採用數組保存key-value對,管理起來很方便,邏輯不復雜。
3、get方法
public V get(Object key) {
return (isValidKey(key) ?
unmaskNull(vals[((Enum)key).ordinal()]) : null);
}
/**
* Returns true if key is of the proper type to be a key in this
* enum map.
* 檢查key值是否是有效值
* key值不能爲null
* 同時key的類型必須是初始化時的枚舉類類型或者
* 其子類,否則就是無效值
*/
private boolean isValidKey(Object key) {
if (key == null)
return false;
// Cheaper than instanceof Enum followed by getDeclaringClass
Class keyClass = key.getClass();
return keyClass == keyType || keyClass.getSuperclass() == keyType;
}
get方法很簡單,只要key傳入的合理的參數,很快就能得到結果。O(1)的時間複雜度。通過上面的put方法和get方法,結合起來看,EnumMap類進行key-value保存的時候,key專門用一個數組進行保存,然後vaule值存放在數組vals的相對應的位置上。當需要獲取指定key的value時,直接通過key即可得到數組vals對應位置上的值。並且不會出現衝突的問題。因爲Enum類的各個枚舉值不相同。
4、刪除方法
public V remove(Object key) {
if (!isValidKey(key))
return null;
int index = ((Enum)key).ordinal();
Object oldValue = vals[index];
vals[index] = null;
if (oldValue != null)
size--;
return unmaskNull(oldValue);
}
private boolean removeMapping(Object key, Object value) {
if (!isValidKey(key))
return false;
int index = ((Enum)key).ordinal();
if (maskNull(value).equals(vals[index])) {
vals[index] = null;
size--;
return true;
}
return false;
}
/**
* Returns true if key is of the proper type to be a key in this
* enum map.
* 檢查key值是否是有效值
* key值不能爲null
* 同時key的類型必須是初始化時的枚舉類類型或者
* 其子類,否則就是無效值
*/
private boolean isValidKey(Object key) {
if (key == null)
return false;
// Cheaper than instanceof Enum followed by getDeclaringClass
Class keyClass = key.getClass();
return keyClass == keyType || keyClass.getSuperclass() == keyType;
}
刪除方法很簡單,key值是不會刪除的,因爲key值是枚舉類常量值,只會把vals數組中對應位置上的value值刪除。
remove(key)是把key值對應位置上vals數組中的值刪除。
removeMapping(key,value)是把對應位置上與valus相等的vals數組中的數據刪除。如果不等,不刪除。
5 是否存在指定的數據方法
/**
* @param key the key whose presence in this map is to be tested
* @return <tt>true</tt> if this map contains a mapping for the specified
* 首先檢驗key值是否是有效值
* 然後獲取對應的key值的value值是否爲null
* 這裏需要注意的是,put方法存放key-value對的時候,
* 如果value值爲null,則會通過maskNull()方法將null值,轉換成NULL對象
*所以實際存入vals數組中的value值不爲null值。
*/
public boolean containsKey(Object key) {
return isValidKey(key) && vals[((Enum)key).ordinal()] != null;
}
private boolean containsMapping(Object key, Object value) {
return isValidKey(key) &&
maskNull(value).equals(vals[((Enum)key).ordinal()]);
}
上面兩個方法看明白的話,就很容易明白EnumMap類是如何進行key-value對的管理的。key值初始化的時候已經獲取,保存在了數組中,value值是通過put或者putAll方法添加到數組中的,containsKey(Object key)方法不是通過key值數組判斷的,而是通過key值對應位置上的value值是否存在進行判斷的。也就是說key值對應的vals數組中是否有值,決定了EnumMap類對象是否包含key值。
EnumMap類的其他方法就沒什麼說的了,大家看看就可以了,只要弄懂了HashMap類,其他的類就不在話下了。
- IdentityHashMap類源碼分析
該類特點是:只有全等的key值,該類纔會認爲兩個key值相等。比如new String(“11”) 與new String(“11”),這兩個對象就不是全等,而一般的HashMap則認爲上面兩個對象是相等的。
並且該類非常有意思的是,在key-value數據的存儲上,類似於HashMap,採用map數組進行存儲,但是key-value不是利用鏈表解決衝突,而是繼續計算下一個索引,把數據計算在下一個有效索引的數組中,也就是數據全部存儲map數組中,並且table[i]=key 則table[i+1]=value。key-value緊挨着存儲在map數組中。
1、構造器
/**
* Constructs a new, empty identity hash map with a default expected
* maximum size (21).
* 採用默認容量的構造器
*/
public IdentityHashMap() {
init(DEFAULT_CAPACITY);
}
/**
* Constructs a new, empty map with the specified expected maximum size.
* Putting more than the expected number of key-value mappings into
* the map may cause the internal data structure to grow, which may be
* somewhat time-consuming.
*
* @param expectedMaxSize the expected maximum size of the map
* @throws IllegalArgumentException if <tt>expectedMaxSize</tt> is negative
* 設定容量大小的構造器
* 參數期望的最大容量expectedMaxSize不能爲負數,否則異常。
* 參數expectedMaxSize並不代表實際的容量大小。
* 通過capacity(expectedMaxSize)方法我們會發現,
* 實際的容量要大一些。
*/
public IdentityHashMap(int expectedMaxSize) {
if (expectedMaxSize < 0)
throw new IllegalArgumentException("expectedMaxSize is negative: "
+ expectedMaxSize);
init(capacity(expectedMaxSize));
}
/**
* Returns the appropriate capacity for the specified expected maximum
* size. Returns the smallest power of two between MINIMUM_CAPACITY
* and MAXIMUM_CAPACITY, inclusive, that is greater than
* (3 * expectedMaxSize)/2, if such a number exists. Otherwise
* returns MAXIMUM_CAPACITY. If (3 * expectedMaxSize)/2 is negative, it
* is assumed that overflow has occurred, and MAXIMUM_CAPACITY is returned.
* 將參數期望的容量大小expectedMaxSize擴大1.5倍
* 之後按照比改制大的最小2進制數設定容量大小。
* 返回的result值是實際的容量大小。
*/
private int capacity(int expectedMaxSize) {
// Compute min capacity for expectedMaxSize given a load factor of 2/3
int minCapacity = (3 * expectedMaxSize)/2;
// Compute the appropriate capacity
int result;
if (minCapacity > MAXIMUM_CAPACITY || minCapacity < 0) {
result = MAXIMUM_CAPACITY;
} else {
result = MINIMUM_CAPACITY;
while (result < minCapacity)
result <<= 1;
}
return result;
}
/**
* Initializes object to be an empty map with the specified initial
* capacity, which is assumed to be a power of two between
* MINIMUM_CAPACITY and MAXIMUM_CAPACITY inclusive.
* 極限容量是initCapacity值的三分之二
* 而數組長度確實initCapacity的兩倍!
* 由此可見IdentityHashMap比HashMap耗費內存空間。
* 等於說,當用到數組長度的三分之一的時候就要進行擴容操作。
* 可見內存空間消耗有多大。
*/
private void init(int initCapacity) {
// assert (initCapacity & -initCapacity) == initCapacity; // power of 2
// assert initCapacity >= MINIMUM_CAPACITY;
// assert initCapacity <= MAXIMUM_CAPACITY;
threshold = (initCapacity * 2)/3;
table = new Object[2 * initCapacity];
}
/**
* @param m the map whose mappings are to be placed into this map
* @throws NullPointerException if the specified map is null
*/
public IdentityHashMap(Map<? extends K, ? extends V> m) {
// Allow for a bit of growth
this((int) ((1 + m.size()) * 1.1));
putAll(m);
}
通過構造器我們知道,IdentityHashMap在初始化的時候就已經構造比較大的map數組以解決可能的衝突問題,以便將數據都存儲在數組中。同時爲了提高查詢效率,極限容量設置的比較小,只有數組長度的三分之一。但是問題也來了,佔用了太大的內存空間。也就是有效利用的空間不足數組總長度的三分之一。
2 put方法
/**
* 該方法最爲重要。通過該方法我們知道,IdentityHashMap類
* 在存放key-value對時,不採用鏈表解決衝突,而是通過
* nextKeyIndex(i,len)方法找到下一個存放數據的索引值,如果該索引
* 處沒有值則存放數據,如果有值,繼續nextKeyIndex(i,len)進行查找
* 直到在數組中找到合適的存放位置。
* 同時,如果找到全等的key值,說明已經存放過key,
* 則用新value值替換舊value值。
* put方法最後,進行resize檢查。
* 如果存放下一個數據的長度>=極限容量,則進行擴容。
* 通過put方法,我們可以瞭解到爲什麼在初始化table數組的時候,
* 把數組長度定義爲設計容量的兩倍了。
* 在init()方法中,我們知道,極限容量設計爲數組長度的三分之一
* 說明,當存放的數組達到數組長度的三分之一的時候就要進行擴容
* 可見IdentityHashMap在內存空間中的消耗有多大。
*/
public V put(K key, V value) {
Object k = maskNull(key);
Object[] tab = table;
int len = tab.length;
int i = hash(k, len);
Object item;
while ( (item = tab[i]) != null) {
if (item == k) {
V oldValue = (V) tab[i + 1];
tab[i + 1] = value;
return oldValue;
}
i = nextKeyIndex(i, len);
}
modCount++;
tab[i] = k;
tab[i + 1] = value;
if (++size >= threshold)
resize(len); // len == 2 * current capacity.
return null;
}
/**
* @param newCapacity the new capacity, must be a power of two.
* 擴容方法
* 擴展爲原來數組長度的兩倍。
* 擴容之後進行數據的轉移,拷貝到新數組當中。
* 循環內部手動進行了數據的清除,設置舊數組中的無用引用爲null.
* 個人理解原因是:數組長度較大,佔用內存空間比較大,
* 及時釋放內存空間是王道!
* 由於擴容之後空間一定夠用,所以直接將原來數組中的數據
* 存放到新數組對應的位置即可。
* 並且數組存放不存在鏈表,只是數組中,所以管理起來比較方便。
*/
private void resize(int newCapacity) {
// assert (newCapacity & -newCapacity) == newCapacity; // power of 2
int newLength = newCapacity * 2;
Object[] oldTable = table;
int oldLength = oldTable.length;
if (oldLength == 2*MAXIMUM_CAPACITY) { // can't expand any further
if (threshold == MAXIMUM_CAPACITY-1)
throw new IllegalStateException("Capacity exhausted.");
threshold = MAXIMUM_CAPACITY-1; // Gigantic map!
return;
}
if (oldLength >= newLength)
return;
Object[] newTable = new Object[newLength];
threshold = newLength / 3;
for (int j = 0; j < oldLength; j += 2) {
Object key = oldTable[j];
if (key != null) {
Object value = oldTable[j+1];
oldTable[j] = null;
oldTable[j+1] = null;
int i = hash(key, newLength);
while (newTable[i] != null)
i = nextKeyIndex(i, newLength);
newTable[i] = key;
newTable[i + 1] = value;
}
}
table = newTable;
}
/**
* @param m mappings to be stored in this map
* @throws NullPointerException if the specified map is null
* putAll方法,使用put方法進行數據的複製。
* 沒什麼說的。原理同上。
*/
public void putAll(Map<? extends K, ? extends V> m) {
int n = m.size();
if (n == 0)
return;
if (n > threshold) // conservatively pre-expand
resize(capacity(n));
for (Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
/**
* Circularly traverses table of size len.
* 計算下一個key值出現在數組處的索引值
*/
private static int nextKeyIndex(int i, int len) {
return (i + 2 < len ? i + 2 : 0);
}
通過上面的方法我們知道,該類在完成key-value對的存放時,是挨着存放key-value對到數組中。以步進2爲間隔進行數據填充。
3、查詢獲取數據方法
/**
* 通過key值獲取value對象。
* 通過程序會發現,item==k,說明只有當全等的時候
* 纔會返回對象,否則找不到value值返回null。
* 同時,item==k的情況下,tab[i+1]爲value值。
* 說明數組i處存放key值,i+1處存放value值。
* 這也解釋了初始化數組時候2倍長度的原因了。
* @see #put(Object, Object)
*/
public V get(Object key) {
Object k = maskNull(key);
Object[] tab = table;
int len = tab.length;
int i = hash(k, len);
while (true) {
Object item = tab[i];
if (item == k)
return (V) tab[i + 1];
if (item == null)
return null;
i = nextKeyIndex(i, len);
}
}
/**
* @param key possible key
* @return <code>true</code> if the specified object reference is a key
* in this map
* @see #containsValue(Object)
* 是否包含指定的key值。與上面的get(key)方法類似
* 原理同上。
*/
public boolean containsKey(Object key) {
Object k = maskNull(key);
Object[] tab = table;
int len = tab.length;
int i = hash(k, len);
while (true) {
Object item = tab[i];
if (item == k)
return true;
if (item == null)
return false;
i = nextKeyIndex(i, len);
}
}
/**
* @param value value whose presence in this map is to be tested
* @return <tt>true</tt> if this map maps one or more keys to the
* specified object reference
* @see #containsKey(Object)
* 是否包含指定的value值。
* 循環中i以2位步長進行循環遍歷
* 說明數組偶數處存放value值。
* IdentityHashMap<String, String> iden =
new IdentityHashMap<>();
iden.put(null, null);
iden.put(null, null);
System.out.println(iden);
System.out.println(iden.containsKey(null));
System.out.println(iden.containsValue(null));
上面的運行結果是:
{null=null}
true
true
* 說明存放的全等的key值會替換原來的value值。
* 存放的key==null的情況下,會通過maskKey(key)方法
* 將key=null的值,替換爲NULL_KEY對象。
* 請結合put方法查看代碼。
*/
public boolean containsValue(Object value) {
Object[] tab = table;
for (int i = 1; i < tab.length; i += 2)
if (tab[i] == value && tab[i - 1] != null)
return true;
return false;
}
/**
* @param key possible key
* @param value possible value
* @return <code>true</code> if and only if the specified key-value
* mapping is in the map
* 是否包含指定的key-value對。
* 原理和上面的containsKey containsValue類似
*/
private boolean containsMapping(Object key, Object value) {
Object k = maskNull(key);
Object[] tab = table;
int len = tab.length;
int i = hash(k, len);
while (true) {
Object item = tab[i];
if (item == k)
return tab[i + 1] == value;
if (item == null)
return false;
i = nextKeyIndex(i, len);
}
}
上面三個是查詢map數組中數據的方法,上面的方法依次通過獲取key在map數組中的索引進行查詢,知道查詢到結果爲止。
4、刪除數據
/**
* 使用全等的方式比較key值。
* 通過key值,得到hash值,然後得到索引值。
* 如果存在key值,則刪除對應位置上的數據。同時size-1.
* 並且使用closeDeletion(i)將數組後面的數據重新remap
* 存放在數組的相應位置當中。
*/
public V remove(Object key) {
Object k = maskNull(key);
Object[] tab = table;
int len = tab.length;
int i = hash(k, len);
while (true) {
Object item = tab[i];
if (item == k) {
modCount++;
size--;
V oldValue = (V) tab[i + 1];
tab[i + 1] = null;
tab[i] = null;
closeDeletion(i);
return oldValue;
}
if (item == null)
return null;
i = nextKeyIndex(i, len);
}
}
/**
* 刪除指定的key-value對。
* 原理同上面的remove類似。
* 通過key值得到hash值,然後得到key值在數組中的索引值
* 有了索引值一一比較key值,只有和目標key值==時,纔會刪除
* 該索引處的數據。
* 刪除之後,通過closeDeletion(i)方法將後面的數據重新rehash
* 重新在數組中進行存放。
* 如果沒有找到==的key值,則返回false,說明刪除不成功。
*/
private boolean removeMapping(Object key, Object value) {
Object k = maskNull(key);
Object[] tab = table;
int len = tab.length;
int i = hash(k, len);
while (true) {
Object item = tab[i];
if (item == k) {
if (tab[i + 1] != value)
return false;
modCount++;
size--;
tab[i] = null;
tab[i + 1] = null;
closeDeletion(i);
return true;
}
if (item == null)
return false;
i = nextKeyIndex(i, len);
}
}
/**
* 該方法是在刪除數組中的數據的時候重新rehash數組中的元素
* 重新存放在數組中。
* 關於if語句中的判斷有點小麻煩。
* 這個判斷需要了解hash值的計算。
* 不過不影響我們大概知道是怎麼回事。
* 同時我們也可以知道的是,刪除元素並不會使數組的長度減小。
* 這一點比較重要。
*/
private void closeDeletion(int d) {
Object[] tab = table;
int len = tab.length;
Object item;
for (int i = nextKeyIndex(d, len); (item = tab[i]) != null;
i = nextKeyIndex(i, len) ) {
int r = hash(item, len);
if ((i < r && (r <= d || d <= i)) || (r <= d && d <= i)) {
tab[d] = item;
tab[d + 1] = tab[i + 1];
tab[i] = null;
tab[i + 1] = null;
d = i;
}
}
}
由於IdentityHashMap採用在數組中保存key-value數據,並以加長的數組來解決可能引起的衝突,所以數據刪除起來比較方便,只不過只有全等的情況下,纔會刪除key值所對應的value。同時,由於刪除一對數據之後導致後面的數據遍歷不到,所以當刪除一對數據之後,需要對後面的數據重寫在map數組上面定位。
5 相等方法和hashcode方法
/**
* 比較兩個IdentityHashMap對象是否相等
* equals方法可以比較Map類對象,只要entrySet().equals(m.entrySet())返回
* true就可以了。
* 1、如果參數o屬於IdentityHashMap類對象,直接使用containsMapping方法逐一
*比較每個key-value對數據是否相等即可。
* 2、如果參數o不屬於containsMapping類對象,則使用entrySet()方法得到的
* EntrySet集合進行比較。
* 通過查找該類中的EntrySet內部類,沒有equals方法,說明覆用父類AbstractSet
* 中的equals方法,通過查找父類的equals方法,我們發現,
* AbstractSet類又調用了AbstractCollection類的
* containsAll(Collection<?> c)方法。經過一系列的調用
* 最終通過比較的是參數o集合的每個數據的(o.equals(it.next()))方法
* 進行判斷的。
* 也就是說,如果參數o不是IdentityHashMap類對象,則使用參數o的equals方法
* 進行比較,不要求==全等。
*/
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (o instanceof IdentityHashMap) {
IdentityHashMap m = (IdentityHashMap) o;
if (m.size() != size)
return false;
Object[] tab = m.table;
for (int i = 0; i < tab.length; i+=2) {
Object k = tab[i];
if (k != null && !containsMapping(k, tab[i + 1]))
return false;
}
return true;
} else if (o instanceof Map) {
Map m = (Map)o;
return entrySet().equals(m.entrySet());
} else {
return false; // o is not a Map
}
}
/**
* 計算hash值。
* 最終的hash值與key和value都有關
* 保證每個key-value對的hash值是唯一的。
*/
public int hashCode() {
int result = 0;
Object[] tab = table;
for (int i = 0; i < tab.length; i +=2) {
Object key = tab[i];
if (key != null) {
Object k = unmaskNull(key);
result += System.identityHashCode(k) ^
System.identityHashCode(tab[i + 1]);
}
}
return result;
}
這兩個方法是該類的關鍵所在,hashcode的計算不僅僅和key值有關,而且和value值有關,這樣就保證了key-value對具備唯一的hash值。同時通過重寫equals方法,判定只有key值全等情況下才會判斷key值相等。這就是IdentityHashMap與普通HashMap不同的關鍵所在。
- WeakHashMap源碼分析
1、介紹
WeakHashMap採用弱引用隊列關聯map數組中存儲的數據,該類與普通HashMap類似,解決衝突一樣採用鏈表解決。瞭解了HashMap再來了解WeakHashMap會很容易上手。之所以採用採用WeakHashMap該類,是因爲通過該類可是實現緩存,在內存空間很緊張情況下,使用該類,避免強引用佔用大量的內存,銷燬掉不用或者過時的對象,較早的釋放空間。
該類主要的特點就是使用引用隊列,將Entry對象與引用隊列關聯起來,使得每個Entry對象都是弱引用,先看內部類Entry的定義
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
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);
* 就把Entry對象引用隊列關聯了起來。
* 此時的Entry對象是弱引用對象,弱引用WeakReference
* 的構造器new WeakReference(o,q)
* 意思就是WeakReference對象引用o對象,當弱引用被垃圾回收
* 則o對象就沒有引用了,也會被垃圾回收,此時WeakReference對象
* 會被加入到引用隊列queue當中去。
* 在這裏,我們可以吧Entry對象看作是增強了參數的WeakReference對象。
*/
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
@SuppressWarnings("unchecked")
public K getKey() {
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();
return ((k==null ? 0 : k.hashCode()) ^
(v==null ? 0 : v.hashCode()));
}
public String toString() {
return getKey() + "=" + getValue();
}
}
這是WeakHashMap類中內部類Entry的定義,這也就是它與普通HashMap不同的關鍵所在。Entry構造器中,將每個Entry與引用隊列關聯,而每個Entry對象就是一個弱引用!!!這個弱引用指向key值。
2、WeakHashMap構造器
/**
* 這裏構造器的方法與HashMap類的構造器一樣
*/
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;
while (capacity < initialCapacity)
capacity <<= 1;
table = newTable(capacity);
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
}
public WeakHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public WeakHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
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);
}
通過與HashMap比較,我們發現,兩者的構造器上並沒有什麼區別,可以說一樣,這就說明WeakHashMap與普通HashMap在存儲數據上是一樣的。
3 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;
}
/**
* Expunges stale entries from the table.
* 該方法是WeakHashMap類的核心方法
* 每次在進行getSize() getTable()方法是都要調用該方法
* 該方法實現的功能是:
* 通過遍歷引用隊列當中保存的已經回收的弱引用對象
* 將原map 數組中的引用清除,map數組中只保留還沒有回收的弱引用對象。
* queue.poll()彈出的是弱引用對象,該類中的Entry集成了WeakReference類
* 方法中利用兩層循環:一層循環遍歷引用隊列中的值,另一層循環遍歷
* map數組中的值,當在map數組中發現由於引用隊列中相同的引用
* 則把應用變量從map數組中刪除。更新map數組長度
*/
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;
}
}
}
}
/**
* 獲取map數組
* 利用expungeStaleEntries()方法刷新map數組
*/
private Entry<K,V>[] getTable() {
expungeStaleEntries();
return table;
}
put方法存儲數據,key值如果已經存在,則替換原來的value,如果不存在,則找到合適的位置填充。可以看出出現衝突的情況下,是使用鏈表解決的。這和一般HashMap一致。不同的地方是,在存放數據之前,使用getTable()方法首先獲取一次map數組,調用了expungeStaleEntries()方法,上面的註釋部分我儘量給出了自己的理解。
可以說expungeStaleEntries()方法是保證WeakHashMap正常運行的關鍵方法。
4、獲取數據方法
/**
* @see #put(Object, Object)
* 這裏爲什麼沒有使用getEntry(Object key)方法
* 來獲取value值呢??代碼一樣的呀
*/
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;
}
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
Entry<K,V> getEntry(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 && !(e.hash == h && eq(k, e.get())))
e = e.next;
return e;
}
上面是獲取數據的方法,與普通HashMap區別不大,主要在於每次進行數據獲取前,先獲取map數組。
5、刪除數據
public V remove(Object key) {
Object k = maskNull(key);
int h = hash(k);
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;
if (h == e.hash && eq(k, e.get())) {
modCount++;
size--;
if (prev == e)
tab[i] = next;
else
prev.next = next;
return e.value;
}
prev = e;
e = next;
}
return null;
}
boolean removeMapping(Object o) {
if (!(o instanceof Map.Entry))
return false;
Entry<K,V>[] tab = getTable();
Map.Entry<?,?> entry = (Map.Entry<?,?>)o;
Object k = maskNull(entry.getKey());
int h = hash(k);
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;
if (h == e.hash && e.equals(entry)) {
modCount++;
size--;
if (prev == e)
tab[i] = next;
else
prev.next = next;
return true;
}
prev = e;
e = next;
}
return false;
}
刪除也比較簡單,與普通的HashMap沒有什麼特殊的地方.就不多說了。
總結一下:
上面的三個類中的迭代器我都沒有給出,不像之前的兩篇博文源碼全部給出,只要你看懂HashMap類的源碼部分,其他的類可以說觸類旁通!不在話下!重要的區別就在於各個類的特點上面。
1、EnumMap類必須和枚舉類相關聯,key值只能是枚舉類的常量值。value使用數組存儲,長度就是枚舉類的常量值的個數。
2、IdentityHashMap類使用全等判斷key值,不全等的key值,就認爲是兩個對象,可以放入map數組中。並且IdentityHashMap不用鏈表解決衝突,而是使用較大的map數組存儲全部的key-value對。同時key-value對挨着存儲在map數組的奇數位和偶數位上。因此,IdentityHash佔用較大的內存空間。真正使用的map數組的有限長度最多佔總長度的三分之一。
3、WeakHashMap類使用引用隊列存儲回收的弱引用。主要用於內存空間緊張的情況。使用此類時需要小心應對。因爲說不定什麼時候,map數組中的數據已經被回收掉了。
該類的定義是:
public class WeakHashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>
這就說明該類不能被序列化、反序列化和克隆!!!
想來也挺好理解的,既然存儲的是弱引用,如果克隆的話,必須是深拷貝纔行。如果要序列化,可能存在的情況是,map數組已空,沒有必要,還有就是既然用到了WeakHashMap,說明內存空間已經緊張,沒必要進行克隆或者序列化反序列化。
不過,在該類的entrySet()方法中返回的Set
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es = entrySet;
return es != null ? es : (entrySet = new EntrySet());
}
private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
Entry<K,V> candidate = getEntry(e.getKey());
return candidate != null && candidate.equals(e);
}
public boolean remove(Object o) {
return removeMapping(o);
}
public int size() {
return WeakHashMap.this.size();
}
public void clear() {
WeakHashMap.this.clear();
}
/**
*深拷貝
* 這裏很容易理解
* 如果是淺拷貝,目標數組中還是弱引用對象那麼可能會被垃圾回收
* 深拷貝才能保證拷貝到目標數組的對象是強引用
*/
private List<Map.Entry<K,V>> deepCopy() {
List<Map.Entry<K,V>> list = new ArrayList<>(size());
for (Map.Entry<K,V> e : this)
list.add(new AbstractMap.SimpleEntry<>(e));
return list;
}
public Object[] toArray() {
return deepCopy().toArray();
}
public <T> T[] toArray(T[] a) {
return deepCopy().toArray(a);
}
}
這裏面的toArray()方法就是進行深拷貝獲取map數組中的值的。想想也是對的,如果是淺拷貝,可能已經被回收了。獲取值並沒有多大用處了。基本也解釋了爲什麼不實現克隆和序列化機制了。
以上都是個人的見解。如果有什麼不對或者問題,歡迎大家留言評論!多多交流~!共同進步!!^_^【握手~】