目錄
本章研究MAP之類
7.1.HashMap
簡單的說,HashMap內部結構使用的是數組。HashMap就是就key做Hash算法,然後將hash後的值映射到數組下表,這樣就能快速操作數組。
Hashmap必須保證幾個特點:
1).hash算法必須是高效的;
2).hash值映射數組下標算法是高效的;
3).根據數組下標可以直接獲取對應的值.
原文:https://blog.csdn.net/lan861698789/article/details/81323643
1.內部組成
public class HashMap{ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默認容量2的4次方 = 16 static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量:2的30次方 static final Entry<?,?>[] EMPTY_TABLE = {};//默認空 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;//數組 transient int size;//大小 } 原文:https://blog.csdn.net/lan861698789/article/details/81323643 static class Entry<K,V> { final K key; V value; Entry<K,V> next;//下個entry int hash;
Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } } |
最主要的結構:table鏈表數組、size大小。
這裏使用了entry來做存儲. Entry裏面結構:key,value,next,hash
默認容量16
2.HASH算法
算出key的hash值
final static int hash(Object k) { h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } |
都是高效的位運算。
3.HASH值映射數組下標算法
static int indexFor(int h, int length) { return h & (length-1);//與運算 } |
4.添加put
原文:https://blog.csdn.net/lan861698789/article/details/81323643
代碼:以test.put("01", "01-value");來舉例。
private static void test01() { HashMap<String, String> test = new HashMap<String, String>(); //indexFor(hash("01"), 16); test.put("01", "01-value"); //indexFor(hash("02"), 16); test.put("02", "02-value"); //indexFor(hash("99"), 16); test.put("99", "99-value"); test.get("02");
test.remove("02"); } |
public V put(K key, V value) { if (key == null) return putForNullKey(value);//可以放空值 int hash = hash(key);//HASH算法,求出key的hash值。這裏key:01的hash爲1645 int i = indexFor(hash, table.length);//通過hash值,計算出數組中的下標。 這裏爲13 //舊值查找替換,直接通過數組下標,找到key對應的數組table[i] for (Entry<K,V> e = table[i]; e != null; e = e.next) { //循環table[i],查找是否有next元素 Object k; //對比hash和key是否相等。 如果相等,直接用新值替換舊值。並且返回舊值。 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } }
modCount++; //新增 addEntry(hash, key, value, i); return null; } 原文:https://blog.csdn.net/lan861698789/article/details/81323643 void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) {//擴容 resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } //將新增的元素放到i位置,並將它的next指向舊的元素 Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e);//代碼1 size++; } |
原理:
1).hash算法得出key的hash值
2).通過hash值得出該key在table數組的下標
3).判斷該key在table數組裏面是否存在,存在則替換爲新值。 這裏要注意next,後面會講到hash衝突就會存到next裏面。
4).如果不存在,則新增。
5).先做擴容2倍
6).新增,取出舊元素,將新元素賦值給table[i],在將table[i].next賦值爲舊元素。
這裏的舊元素,如果第一次新增就是null。如果新增的元素hash值一樣,key不一樣,那舊元素就不爲空了。後面會講到。
截圖:
代碼1執行後圖 |
分析:
通過新增,我們可以知道,雖然hashmap是以數組來存儲的,但是每次新增數組下標卻不一定是自增,所以他是無序的。
原文:https://blog.csdn.net/lan861698789/article/details/81323643
5.獲取get
代碼:
public V get(Object key) { if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key);
int hash = (key == null) ? 0 : hash(key); for (Entry<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; } |
原理:
1).hash算法得出key的hash值
2).通過hash值得出該key在table數組的下標
3).循環next,找出hash和key都一樣的元素
6.hash值衝突解決
雖然通過計算hash來獲取數組下標很高效,但是還是會存在兩個不同的key,計算得到的hash值一樣的情況。那麼hashmap會如何處理呢?
再看下hashmap的組成結構,發現entry裏面有一個next屬性,它會指向另外一個entry。
再次回顧添加的源碼,裏面兩處使用了next。
第一次,查找舊值時候,會判斷next是否存在
第二次,沒有查到舊值,新增新值時候,每次新增的值都放在table[i],把舊值放到新值的next處,也就是table[i].next,這個值可能是null.
public V put(K key, V value) { if (key == null) return putForNullKey(value);//可以放空值 int hash = hash(key);//HASH算法,求出key的hash值。這裏key:01的hash爲1645 int i = indexFor(hash, table.length);//通過hash值,計算出數組中的下標。 這裏爲13 //舊值查找替換,直接通過數組下標,找到key對應的數組table[i] //第一次 for (Entry<K,V> e = table[i]; e != null; e = e.next) { //循環table[i],查找是否有next元素 Object k; //對比hash和key是否相等。 如果相等,直接用新值替換舊值。並且返回舊值。 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } }
modCount++; //新增 //第二次 addEntry(hash, key, value, i); return null; }
void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) {//擴容 resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } //將新增的元素放到i位置,並將它的next指向舊的元素 Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e);//代碼1 size++; } 原文:https://blog.csdn.net/lan861698789/article/details/81323643 static class Entry<K,V> { final K key; V value; Entry<K,V> next;//下個entry int hash;
Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } } |
7.hashMap輸出順序
雖然hashmap是以數組來存儲的,但是每次新增數組下標卻不一定是自增,所以他是無序的。
那麼他是怎麼排序的呢?
private static void test01() { HashMap<String, String> test = new HashMap<String, String>(); System.out.println("01->"+indexFor(hash("01"), 16)); test.put("01", "01-value"); System.out.println("02->"+indexFor(hash("02"), 16)); test.put("02", "02-value"); System.out.println("99->"+indexFor(hash("99"), 16)); test.put("99", "99-value"); test.put("99", "99-value2"); //循環輸出 Iterator<Entry<String, String>> iterator = test.entrySet().iterator(); while (iterator.hasNext()) { System.out.println(iterator.next().getKey()); } test.get("02");
test.remove("02"); } |
輸出:
每次輸出都是一樣。 |
分析:
可以看出,hashmap排序是通過通過數組排序的,也就是按照table裏面的數組順序輸出的。但是因爲每次存入的數組下標不一樣。所以看起來不是先進先出這種排序了。
7.2.LinkedHashMap有序的map
1.內部組成
public class HashMap{ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默認容量2的4次方 = 16 static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量:2的30次方 static final Entry<?,?>[] EMPTY_TABLE = {};//默認空 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;//數組 transient int size;//大小 }
static class Entry<K,V> { final K key; V value; int hash; Entry<K,V> next;//下個entry Entry<K,V> before, after;//linkedHashMap特有
Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } } |
黃色部分爲linkedHashMap特有的結構。
原文:https://blog.csdn.net/lan861698789/article/details/81323643