本篇文章主要分析一下Java集合框架中的Map部分,HashMap,該源碼分析基於JDK1.8,分析工具,AndroidStudio,文章分析不足之處,還請見諒!java
基於哈希表的一個 Map 接口實現,存儲的對象是一個鍵值對對象 (Entry<K,V>);值得注意的是 HashMap 不是線程安全的,若是想要線程安全的 HashMap,能夠經過 Collections 類的靜態方法 synchronizedMap 得到線程安全的 HashMap。數組
Map map = Collections.synchronizedMap(new HashMap());
複製代碼
Java 最基本的數據結構有數組和鏈表。數組的特色是空間連續(大小固定)、尋址迅速,可是插入和刪除時須要移動元素,因此查詢快,增長刪除慢。鏈表剛好相反,可動態增長或減小空間以適應新增和刪除元素,但查找時只能順着一個個節點查找,因此增長刪除快,查找慢。有沒有一種結構綜合了數組和鏈表的優勢呢?固然有,那就是哈希表(雖然說是綜合優勢,但實際上查找確定沒有數組快,插入刪除沒有鏈表快,一種折中的方式吧),全部的數據結構均可以用這兩個基本結構來構造的,HashMap 也不例外。HashMap 其實是一個「鏈表散列」的數據結構,即數組和鏈表的結合體。安全
HashMap 的底層主要是基於數組和鏈表來實現的,它之因此有至關快的查詢速度主要是由於它是經過計算散列碼來決定存儲的位置。HashMap 中主要是經過 key 的 hashCode 來計算 hash 值的,只要 hashCode 相同,計算出來的 hash 值就同樣。若是存儲的對象對多了,就有可能不一樣的對象所算出來的 hash 值是相同的,這就出現了所謂的 hash 衝突。學過數據結構的同窗都知道,解決hash衝突的方法有不少,HashMap 底層是經過鏈表來解決 hash 衝突的。圖中,左邊部分即表明哈希表,也稱爲哈希數組,數組的每一個元素都是一個單鏈表的頭節點,鏈表是用來解決衝突的,若是不一樣的 key 映射到了數組的同一位置處,就將其放入單鏈表中。bash
內部接口 EntryEntry 接口是 Map 定義的一個內部接口數據結構
interface Entry<K, V> {
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K, V>> comparingByKey() {
return (Comparator<Map.Entry<K, V>> & Serializable) (c1, c2) -> c1.getKey().compareTo(c2.getKey());
}
public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K, V>> comparingByValue() {
return (Comparator<Map.Entry<K, V>> & Serializable) (c1, c2) -> c1.getValue().compareTo(c2.getValue());
}
public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable) (c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
}
public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable) (c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
}
}
複製代碼
對於 HashMap,它的內部裏面實現一個靜態內部類,即爲 HashMapEntry<K,V> ,其重要的屬性有 key , value, next,從屬性 key,value 咱們就能很明顯的看出來 HashMapEntry 就是 HashMap 鍵值對實現的一個基礎 bean,咱們上面說到 HashMap 的基礎就是一個線性數組,這個數組就是 HashMapEntry[],Map 裏面的內容都保存在 HashMapEntry[] 裏面。app
transient HashMapEntry<K,V>[] table = (HashMapEntry<K,V>[]) EMPTY_TABLE;
複製代碼
咱們能夠來簡單分析一下這個靜態內部類 HashMapEntry.java框架
// HashMapEntry.java靜態內部類,實現的HashMap線性數組
static class HashMapEntry<K, V> implements Map.Entry<K, V> {
// key,value值
final K key;
V value;
// 每一個數組裏麪包含的鏈表
HashMapEntry<K, V> next;
int hash;
/** * 構造函數 * 輸入參數包括"哈希值(h)", "鍵(k)", "值(v)", "下一節點(n)" */
HashMapEntry(int h, K k, V v, HashMapEntry<K, V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
/**
* * 判斷兩個Entry是否相等 * 若兩個Entry的「key」和「value」都相等,則返回true。 * 不然,返回false
*/
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry) o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
}
public final String toString() {
return getKey() + "=" + getValue();
}
/**
* * 當向HashMap中添加元素時,會調用recordAccess()
*/
void recordAccess(HashMap<K, V> m) {
}
/**
* * 當從HashMap中刪除元素時,會調用recordRemoval()。
*/
void recordRemoval(HashMap<K, V> m) {
}
}
複製代碼
HashMap 其實就是一個 HashMapEntry 數組,HashMapEntry 對象中包含了鍵和值,其中 next 也是一個 HashMapEntry 對象,它就是用來處理 hash 衝突的,造成一個鏈表。基本結構咱們已經分析了,接下來咱們就開始正題,開始分析 HashMap 的源碼實現啦!函數
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
複製代碼
首先咱們來看看 HashMap 內部申明的一些屬性,工具
//屬性申明
//默認的初始容量,必須是2的冪次方。
static final int DEFAULT_INITIAL_CAPACITY = 4;
//最大容量,默認爲2的30次方,
static final int MAXIMUM_CAPACITY = 1 << 30;
//加載因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//
static final HashMapEntry<?,?>[] EMPTY_TABLE = {};
//數組表,大小能夠改變,且大小必須爲2的冪
transient HashMapEntry<K,V>[] table = (HashMapEntry<K,V>[]) EMPTY_TABLE;
//當前Map中key-value映射的個數
transient int size;
//當實際大小超過臨界值時,會進行擴容threshold = 加載因子*容量
int threshold;
//加載因子
final float loadFactor = DEFAULT_LOAD_FACTOR;
//Hash表結構性修改次數
transient int modCount;
複製代碼
對於加載因子 loadFactor,咱們能夠稍做解釋:源碼分析
loadFactor 加載因子是表示 Hsah 表中元素的填滿的程度.
若:加載因子越大,填滿的元素越多,好處是,空間利用率高了,但:衝突的機會加大了.鏈表長度會愈來愈長,查找效率下降。反之,加載因子越小,填滿的元素越少,好處是:衝突的機會減少了,但:空間浪費多了.表中的數據將過於稀疏(不少空間還沒用,就開始擴容了)衝突的機會越大,則查找的成本越高.所以,必須在「衝突的機會」與」空間利用率」之間尋找一種平衡與折衷. 這種平衡與折衷本質上是數據結構中有名的」時-空」矛盾的平衡與折衷.
若是機器內存足夠,而且想要提升查詢速度的話能夠將加載因子設置小一點;相反若是機器內存緊張,而且對查詢速度沒有什麼要求的話能夠將加載因子設置大一點。不過通常咱們都不用去設置它,讓它取默認值 0.75 就行了。以上一些基本屬性是在 HashMap 中申明的,若是不明白什麼意思的話,能夠等到後面再來看看。咱們接着分析下面的代碼。
接着咱們來分析分析 HashMap 的構造函數
/**
* * 設置初始容量大小以及加載因子
* *
* * @param initialCapacity the initial capacity
* * @param loadFactor the load factor
* * @throws IllegalArgumentException if the initial capacity is negative
* * or the load factor is nonpositive
* */
public HashMap(int initialCapacity, float loadFactor) {
//初始容量大小在[DEFAULT_INITIAL_CAPACITY,MAXIMUM_CAPACITY]之間
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY) {
initialCapacity = MAXIMUM_CAPACITY;
}
else if (initialCapacity < DEFAULT_INITIAL_CAPACITY) {
initialCapacity = DEFAULT_INITIAL_CAPACITY;
}
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
threshold = initialCapacity;
init();
}
/**
*
* * 設置初始容量,加載因子爲默認的0.75
* * @param initialCapacity the initial capacity.
* * @throws IllegalArgumentException if the initial capacity is negative.
* */
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* * 使用默認的容量大小和加載因子
* */
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
/**
* * 根據指定的map生成一個新的HashMap,負載因子使用默認值,
* 初始容量大小爲Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,DEFAULT_INITIAL_CAPACITY)
* *
* * @param m the map whose mappings are to be placed in this map
* * @throws NullPointerException if the specified map is null
* */
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
//初始化HashMapEntry數組
inflateTable(threshold);
putAllForCreate(m);
}
}
複製代碼
經過以上 HashMap 的四種構造函數咱們能夠發現,咱們能夠經過設置初始容量大小和加載因子或者直接經過一個 map 來構造一個 HashMap,其中初始容量大小咱們設定爲 4,它必須是 2 的冪次方,同時它的範圍在 4 和 2 的 30 次方之間。構造函數比較簡單,沒什麼能夠分析的,接下來咱們着重分析一下 HashMap 的添加和獲取方法,也就是 put 和 get 方法。
//向map存入一個鍵值對,若是key已存在,則覆蓋
public V put(K key, V value) {
//若是HashMapEntry數組爲空,則從新初始化一下
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//對key爲null的鍵值對調用putForNullKey處理
if (key == null)
return putForNullKey(value);
//生成hash值
int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
//生成hash值索引
int i = indexFor(hash, table.length);
//循環遍歷Entry數組,若「該key」對應的鍵值對已經存在,則用新的value取代舊的value。而後退出,同時返回舊的value!
for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//若是找到了相同的key,則覆蓋舊的value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
//調用HashMapEntry增長這個Entry
e.recordAccess(this);
return oldValue;
}
}
//修改次數
modCount++;
//將key-value添加到table[i]處,也就是在鏈表的頭部插入一個數據<k,v>
addEntry(hash, key, value, i);
return null;
}
}
複製代碼
以上的大概內容就是,先獲取 key 的 hash 值,而後根據 hash 值來獲取在數組中的位置,判斷在 table[i] 中是否存在這個 key,也就是先循環遍歷鏈表,若是找到這個 key 的值,那麼就替換這個 key 值,而後將 value 修改進去,若是在鏈表中找不到這個 key 的話,那麼就在鏈表的頭部插入一個數據。在 put 方法中,咱們來詳細分析其中的一些方法。開頭有一個判斷數組是否爲 null 的操做
//若是HashMapEntry數組爲空,則從新初始化一下
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
/**
*
* * 初始化這個數組
* */
private void inflateTable(int toSize) {
// 尋找大於toSize的2的冪次方的最小值
int capacity = roundUpToPowerOf2(toSize);
float thresholdFloat = capacity * loadFactor;
if (thresholdFloat > MAXIMUM_CAPACITY + 1) {
thresholdFloat = MAXIMUM_CAPACITY + 1;
}
//初始化數組
threshold = (int) thresholdFloat;
table = new HashMapEntry[capacity];
}
}
複製代碼
接下來,判斷了 key 是否爲 null,若是爲 null 的話,則進行 putForNullKey(V value) 操做
/**
* * 插入key爲null的值,在數組的第一個位置進行插入操做
* */
private V putForNullKey(V value) {
//循環遍歷數組第一個數組的鏈表,若是存在null的則進行覆蓋更新操做
for (HashMapEntry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//修改次數
modCount++;
//鏈表中不存在key爲null的值,則在鏈表的開頭插入這個key爲null的值
addEntry(0, null, value, 0);
return null;
}
}
複製代碼
若是 key 爲 null 的話,hash 值爲 0,對象存儲在數組中索引爲 0 的位置。即 table[0],接着咱們分析一下 addEntry 方法,
//經過hash值來算出在table中哪一個位置,而後進行插入操做
void addEntry(int hash, K key, V value, int bucketIndex) {
//
if ((size >= threshold) && (null != table[bucketIndex])) {
//數組擴容
resize(2 * table.length);
//若是key爲null的話,那麼hash值爲0,也就是在數組的第一個位置,即table[0]位置
hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0;
//根據hash的值來算出在數組的中位置
bucketIndex = indexFor(hash, table.length);
}
//數據插入或者更新操做
createEntry(hash, key, value, bucketIndex);
}
複製代碼
咱們經過 hash 值計算獲得了 table 下表中的值,接着繼續分析 createEntry 方法
//數據操做,在鏈表頭部插入一個數組,這就是在一個鏈表頭部插入一個節點的過程。獲取table[i]的對象e,將table[i]的對象修改成新增對象,讓新增對象的next指向e。
void createEntry(int hash, K key, V value, int bucketIndex) {
//保存table[i]的對象爲e
HashMapEntry<K,V> e = table[bucketIndex];
//而後將table[i]的值修改成新增的對象,並將新增對象的next值設置爲原先table[i]的值e,這就至關於在鏈表頭部插入一個數據了。
table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
size++;
}
}
複製代碼
分析完了 key 爲 null 的狀況,接着咱們分析一下 key 不等於 null 的狀況。接着,咱們經過 key來計算出 hash 的,而後根據 hash 的值來計算在 table 數組中的位置 int i = indexFor(hash, table.length);
// indexFor返回hash值和table數組長度減1的與運算結果。爲何使用的是length-1?應爲這樣能夠保證結果的最大值是length-1,不會產生數組越界問題。
static int indexFor(int h, int length) {
return h & (length-1);
}
複製代碼
咱們找到了在數組 table 中的位置後,咱們就開始遍歷當前位置中的鏈表了,若是存在 key 則覆蓋操做,不存在的話則在鏈表頭部插入一個數據。上面即是一個完整的增長數據的操做,在這裏咱們進行歸納一下中心思想。首先,咱們判斷咱們須要增長數據的 key 是否爲 null,若是爲 null 的話,則在數組的第一個位置即 table[0] 處進行插入或者更新操做(若是在第一個位置處的鏈表中存在 key 未 null 的數據,那麼則進行覆蓋更新操做,若是不存在的話,則在鏈表頭插入一個數據,就是常見的鏈表插入操做),接下來就走 key 不等於 null 這一步了,咱們須要根據 key 來計算出 hash 的值,其次,須要根據 hash 的值來計算在 table 中的位置,獲得了位置以後,咱們重複進行在鏈表中的操做(找到就覆蓋更新,沒找到就在鏈表頭部插入一個數據),這就是 HashMap 的 put 操做。
咱們通常對哈希表的散列很天然地會想到用 hash 值對 length 取模(即除法散列法), Hashtable 中也是這樣實現的,這種方法基本能保證元素在哈希表中散列的比較均勻,但取模會用到除法運算,效率很低,HashMap 中則經過 h&(length-1) 的方法來代替取模,一樣實現了均勻的散列,但效率要高不少,這也是 HashMap 對 Hashtable 的一個改進。 接下來,咱們分析下爲何哈希表的容量必定要是 2 的整數次冪。首先,length 爲 2 的整數次冪的話,h&(length-1) 就至關於對 length 取模,這樣便保證了散列的均勻,同時也提高了效率;其次,length 爲 2 的整數次冪的話,爲偶數,這樣 length-1 爲奇數,奇數的最後一位是 1,這樣便保證了 h&(length-1) 的最後一位可能爲 0,也可能爲 1(這取決於h的值),即與後的結果可能爲偶數,也可能爲奇數,這樣即可以保證散列的均勻性,而若是 length 爲奇數的話,很明顯 length-1 爲偶數,它的最後一位是 0,這樣 h&(length-1) 的最後一位確定爲 0,即只能爲偶數,這樣任何 hash 值都只會被散列到數組的偶數下標位置上,這便浪費了近一半的空間,所以,length 取 2 的整數次冪,是爲了使不一樣 hash 值發生碰撞的機率較小,這樣就能使元素在哈希表中均勻地散列。分析完了 put 方法,接下來咱們分析一下 putAll 方法,
/**
* * @param m mappings to be stored in this map
* * @throws NullPointerException if the specified map is null
* */
public void putAll(Map<? extends K, ? extends V> m) {
int numKeysToBeAdded = m.size();
if (numKeysToBeAdded == 0)
return;
if (table == EMPTY_TABLE) {
inflateTable((int) Math.max(numKeysToBeAdded * loadFactor, threshold));
} /* */
if (numKeysToBeAdded > threshold) {
int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
if (targetCapacity > MAXIMUM_CAPACITY)
targetCapacity = MAXIMUM_CAPACITY;
int newCapacity = table.length;
while (newCapacity < targetCapacity)
newCapacity <<= 1;
if (newCapacity > table.length)
resize(newCapacity);
}
//遍歷m中的內容,而後調用put方法將元素添加到table數組中
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
}
複製代碼
遍歷的時候涉及到了 entrySet 方法,這個方法定義在 Map 接口中,HashMap 中也有實現。咱們在 HashMap 構造函數有個方法叫 putAllForCreate 方法,分析一下內容。
private void putAllForCreate(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
putForCreate(e.getKey(), e.getValue());
}
複製代碼
看這個方法名就知道大概意思,在 HashMap 初始化的時候將一個 map 所有賦值給初始值。代碼也是,先遍歷一下 Map,而後依次添加進去,咱們進入 putForCreate 方法中查看具體源碼
private void putForCreate(K key, V value) {
//獲取key的hash值
int hash = null == key ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
//根據hash值來計算在table中的位置
int i = indexFor(hash, table.length);
//循環遍歷數組下標爲i的鏈表,若是存在則覆蓋更新,也就是上面計算出來的table中的位置
for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
e.value = value;
return;
}
}
//當前數組中的位置鏈表中不存在,則在鏈表頭部插入一條數據
createEntry(hash, key, value, i);
}
}
複製代碼
該方法先計算須要添加的元素的 hash 值和在 table 數組中的索引 i。接着遍歷 table[i] 的鏈表,如有元素的 key 值與傳入 key 值相等,則替換 value,結束方法。若不存在 key 值相同的元素,則調用 createEntry 建立並添加元素。繼續進入 createEntry 方法中查看源碼
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMapEntry<K,V> e = table[bucketIndex];
table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
size++;
}
複製代碼
該方法比較簡單,就是在鏈表頭部插入一條數據,putAllForCreate 方法相似於 put 方法。至此全部 put 相關操做都解釋完畢了。put 以外,另外一個經常使用的操做就是 get,下面就來看 get 方法。
相比較於 put 方法,get 方法仍是比較簡單的,咱們來具體看下實現。
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
複製代碼
首先判斷 key 是否等於 null,等於的話那就從 getForNullKey() 中獲取 value,若是不等於的話,經過 getEntry 獲取。咱們首先看等於 null 的狀況。
private V getForNullKey() {
if (size == 0) {
return null;
}
for (HashMapEntry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
複製代碼
若是當前 table 的大小爲 0 的話,那麼將返回 null,若是不爲空的話,那麼則在數組 table[0] 中的鏈表進行循環匹配,尋找 key 的 null 的鏈表節點,若是找到的話,則直接返回 value,不然的話返回 null,很簡單。接着咱們分析 key 不等於 null 的狀況,即進入 getEntry 方法中分析。
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
for (HashMapEntry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
複製代碼
咱們已經分析了 put 方法,因此這裏面的代碼基本相似,很簡單了,首先先獲取 hash 值,而後根據 hash 值獲取在 table 中的索引 table[i],其次根據這個數組中的位置來循環遍歷 table[i] 中的鏈表,判斷是否存在這個 entry 若是存在的話則返回 value,不存在則返回 null。以上即是HashMap 中比較重要的兩個方法,一個 put,一個 get,咱們已經所有分析完了,接着咱們分析一下其他剩下來的方法。
分析完了 put、get 方法,接着分析一下 remove 方法。
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.getValue());
}
複製代碼
在 remove 方法中,調用了一個 removeEntryForKey 方法,咱們接着看看,
final Entry<K,V> removeEntryForKey(Object key) {
//若是數組爲空,直接返回null
if (size == 0) {
return null;
}
//獲取須要移除的key的hash值
int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
//獲取在數組table[i]中的索引值i
int i = indexFor(hash, table.length);
//保存當前數組中的第一個鏈表節點
HashMapEntry<K,V> prev = table[i];
HashMapEntry<K,V> e = prev;
while (e != null) {
HashMapEntry<K,V> next = e.next;
Object k;
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
//單鏈表刪除操做
prev = e;
e = next;
}
return e;
}
}
複製代碼
上面的這個過程就是先找到 table 數組中對應的索引,接着就相似於通常的鏈表的刪除操做,並且是單向鏈表刪除節點,很簡單。在 C 語言中就是修改指針,這個例子中就是將要刪除節點的前一節點的 next 指向刪除被刪除節點的 next 便可。
以上分析了 HashMap 的大部分代碼,還有一些比較不重要的方法咱們一次性給出解釋,就不一一作分析了。
清空map
/**
* * 清空map
* */
public void clear() {
modCount++;
Arrays.fill(table, null);
size = 0;
}
複製代碼
**判斷是否存在指定的value **
/**
* * 判斷是否存在指定的value
* *
* * @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 value
* */
public boolean containsValue(Object value) {
if (value == null)
return containsNullValue();
HashMapEntry[] tab = table;
for (int i = 0; i < tab.length ; i++)
for (HashMapEntry e = tab[i] ; e != null ; e = e.next)
if (value.equals(e.value))
return true;
return false;
}
複製代碼
循環遍歷數組,判斷數組table[i]下面的鏈表是否存在value等於null的節點
/**
* * 循環遍歷數組,判斷數組table[i]下面的鏈表是否存在value等於null的節點
* */
private boolean containsNullValue() {
HashMapEntry[] tab = table;
for (int i = 0; i < tab.length ; i++)
for (HashMapEntry e = tab[i] ; e != null ; e = e.next)
if (e.value == null)
return true;
return false;
}
複製代碼
**針對HashMap的淺複製 **
/**
* * 針對HashMap的淺複製
* *
* * @return a shallow copy of this map
* */
public Object clone() {
HashMap<K,V> result = null;
try {
result = (HashMap<K,V>)super.clone();
}
catch (CloneNotSupportedException e) {
// assert false;
}
if (result.table != EMPTY_TABLE) {
result.inflateTable(Math.min( (int) Math.min( size * Math.min(1 / loadFactor, 4.0f),
// we have limits...
HashMap.MAXIMUM_CAPACITY),
table.length));
} result.entrySet = null;
result.modCount = 0;
result.size = 0;
result.init();
result.putAllForCreate(this);
return result;
}
}
}
複製代碼
以上即是 HashMap 大概的源碼,其中還涉及到 entrySet 的概念,這是其餘集合框架也涉及到的,因此打算一塊兒說,本篇暫未說明。敬請期待後面的文章。若有錯誤,懇請指正,謝謝!最後咱們來簡單寫個測試,來學習一下 HashMap 的經常使用法。
package MapDemo;
import java.util.HashMap;
import java.util.Map;
public class HashMapTest {
public static void main(String[] args) {
Map<String, String> mHashMap=new HashMap<String,String>();
mHashMap.put("A", "AAAAAAAA...");
mHashMap.put("B", "BBBBBBBB...");
mHashMap.put("C", "CCCCCCCC...");
mHashMap.put("D", "DDDDDDDD...");
mHashMap.put("E", "EEEEEEEE...");
mHashMap.put("F", "FFFFFFFF...");
mHashMap.put("G", "GGGGGGGG...");
mHashMap.put("H", "HHHHHHHH...");
mHashMap.put("I", "IIIIIIII...");
//打印HashMap
System.out.println("mHashMap: "+mHashMap);
//打印HashMap的size
System.out.println("mHashMap size is: "+mHashMap.size());
//判斷HashMap中是否存在A的key,結果爲TRUE
System.out.println("mHashMap is containsKey of A:"+ mHashMap.containsKey("A"));
//判斷HashMap中是否存在IIIIIIII的value,結果爲FALSE
System.out.println("mHashMap is containsValue of IIIIIIII:"+ mHashMap.containsValue("IIIIIIII"));
//打印HashMap中的key
System.out.print("the key of mHashMap is :");
for (String string : mHashMap.keySet()) {
System.out.print(" "+string);
}
System.out.println();
//打印HashMap中的value
System.out.print("the value of mHashMap is :");
for (String string : mHashMap.values()) {
System.out.print(" "+string);
}
System.out.println();
//打印key-value集合
System.out.println("key-value集合:");
for (Map.Entry<String, String> entry : mHashMap.entrySet()) {
System.out.println("key: "+entry.getKey()+" value: "+entry.getValue());
}
}
}
複製代碼
輸出結果
mHashMap: {A=AAAAAAAA..., B=BBBBBBBB..., C=CCCCCCCC..., D=DDDDDDDD..., E=EEEEEEEE..., F=FFFFFFFF..., G=GGGGGGGG..., H=HHHHHHHH..., I=IIIIIIII...}
mHashMap size is: 9
mHashMap is containsKey of A:true
mHashMap is containsValue of IIIIIIII:false
the key of mHashMap is : A B C D E F G H I
the value of mHashMap is : AAAAAAAA... BBBBBBBB... CCCCCCCC... DDDDDDDD... EEEEEEEE... FFFFFFFF... GGGGGGGG... HHHHHHHH... IIIIIIII...
key-value集合:
key: A value: AAAAAAAA...
key: B value: BBBBBBBB...
key: C value: CCCCCCCC...
key: D value: DDDDDDDD...
key: E value: EEEEEEEE...
key: F value: FFFFFFFF...
key: G value: GGGGGGGG...
key: H value: HHHHHHHH...
key: I value: IIIIIIII...
複製代碼
專一於 Android 開發多年,喜歡寫 blog 記錄總結學習經驗,blog 同步更新於本人的公衆號,歡迎你們關注,一塊兒交流學習~