集合工具類使用線程
1. hashmap源碼解析(Java7)
01.重要變量
static final int DEFAULT_INITIAL_CAPACITY = 16; //默認初始容量,必須是2的n次方 static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量,當通過構造方法傳入的容量比它還大時,就用這個最大容量,必須是2的n次方 static final float DEFAULT_LOAD_FACTOR = 0.75f; //默認負載因子 transient Entry<K,V>[] table; //用來存儲鍵值對,可以看到鍵值對都是存儲在Entry中的 transient int size; //存放元素的個數 int threshold; //臨界值 當實際大小超過臨界值時,會進行擴容threshold = 加載因子*容量 final float loadFactor; //加載因子 transient int modCount; //被修改的次數
02.四個構造函數
public HashMap(int initialCapacity, float loadFactor) { //1. 對傳入的 容量 和 加載因子進行判斷處理 //2. 設置HashMap的容量極限 //3. 計算出大於初始容量的最小 2的n次方作爲哈希表table的長度,然後用該長度創建Entry數組(table),這個是最核心的 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); // Find a power of 2 >= initialCapacity int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); table = new Entry[capacity]; init(); } public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); table = new Entry[DEFAULT_INITIAL_CAPACITY]; init(); } public HashMap(Map<? extends K, ? extends V> m) { this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); putAllForCreate(m); }
03.數據結構
Entry是一個鏈表結構,不僅包含key和value,還有可以指向下一個的next
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash; /** * Creates new entry. */ Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } }
04.put方法
public V put(K key, V value) { if (key == null) return putForNullKey(value);//儲存空鍵 int hash = hash(key);//計算hash值 int i = indexFor(hash, table.length);//計算存儲位置 for (Entry<K,V> e = table[i]; e != null; e = e.next) {//遍歷hashmap的內部數據,看是否與插入的數據是否已經存在相同鍵值,如果存在則直接替換 Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } //這個for循環,當發生併發,兩個線程衝突的時候,這個鏈表的結構會發生變化:可能兩個key互爲對方的next元素。此時通過next遍歷,會形成死循環。在jdb8中已經不存在了。最好的解決辦法是使用concurrenthashmap modCount++; addEntry(hash, key, value, i); return null; } //當需要插入的key爲null時,調用putForNullKey方法處理: //putForNullKey方法只從table[0]這個位置開始遍歷,因爲key爲null只放在table中的第一個位置,下標爲0,在遍歷中如果發現已經有key爲null了,則替換新value,返回舊value,結束;如果還沒有key爲null,調用addEntry方法增加一個Entry: private V putForNullKey(V value) { for (Entry<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++; addEntry(0, null, value, 0); return null; } //首先通過hash方法對hashcode進行處理: //可以看到只是在key的hashcode值上做了一些處理,通過hash計算出來的值將會使用indexFor方法找到它應該所在的table下標: final int hash(Object k) { int h = 0; h ^= k.hashCode(); h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } //這個方法其實相當於對table.length取模。 static int indexFor(int h, int length) { return h & (length-1); } //添加元素,得到所在下標的第一個元素,也就是當前下標位置的鏈表的頭節點,然後插入數據就是新建一個鏈表,然後新鏈表指向原來的鏈表 void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); if (size++ >= threshold) resize(2 * table.length); } Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } //只有當 size>=threshold並且 table中的那個槽中已經有Entry時,纔會發生resize。即有可能雖然size>=threshold,但是必須等到每個槽都至少有一個Entry時,纔會擴容。還有注意每次resize都會擴大一倍容量 void resize(int newCapacity) { //如果已經是最大容量不進行擴容 Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor); } /** * Transfers all entries from current table to newTable. */ void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } } //最後看createEntry,它先保存這個桶中的第一個Entry,創建新的Entry放入第一個位置,將原來的Entry接在後面。這裏採用的是頭插法插入元素。 void createEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); size++; }
A、等冪性。不管執行多少次獲取Hash值的操作,只要對象不變,那麼Hash值是固定的。如果第一次取跟第N次取不一樣,那就用起來很麻煩.
B、對等性。若兩個對象equal方法返回爲true,則其hash值也應該是一樣的。舉例說明:若你將objA作爲key存入HashMap中,然後new了一個objB。在你看來objB和objA是一個東西(因爲他們equal),但是使用objB到hashMap中卻取不出來東西。
C、互異性。若兩個對象equal方法返回爲false,hash值有可能相同,但最好是不同的,這個不是必須的,只是這樣做會提高hash類操作的性能(碰撞機率低)。
解決hash碰撞的方法:開放地址法、鏈地址法
hashmap採用的就是鏈地址法,這種方法好處是無堆積現象,但是next指針會佔用額外空間
擴展:爲何數組的長度是 2 的 n 次方呢?
1.這個方法非常巧妙,它通過 h & (table.length -1) 來得到該對象的保存位,而HashMap 底層數組的長度總是 2 的 n 次方,2n-1 得到的二進制數的每個位上的值都爲 1,那麼與全部爲 1 的一個數進行與操作,速度會大大提升。
2.當 length 總是 2 的 n 次方時,h& (length-1)運算等價於對 length 取模,也就是h%length,但是&比%具有更高的效率。
3.當數組長度爲 2 的 n 次冪的時候,不同的 key 算得的 index 相同的機率較小,那麼數據在數組上分佈就比較均勻,也就是說碰撞的機率小,相對的,查詢的時候就不用遍歷某個位置上的鏈表,這樣查詢效率也就較高了。
HashMap 的擴容機制:
當 HashMap 中的結點個數超過數組大小*loadFactor(加載因子)時,就會進行數組擴容,loadFactor 的默認值爲 0.75。也就是說,默認情況下,數組大小爲 16,那麼當 HashMap中結點個數超過 16*0.75=12 的時候,就把數組的大小擴展爲 2*16=32,即擴大一倍,然後重新計算每個元素在數組中的位置,並放進去,而這是一個非常消耗性能的操作。
05.get方法
//其實get方法和put方法如出一轍,怎麼放的怎麼拿 public V get(Object key) { if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); } //key爲null時,還是去table[0]去取: private V getForNullKey() { for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null; } //否則調用getEntry方法: final Entry<K,V> getEntry(Object 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; } //這個方法也是通過key的hashcode計算出它應該所在的下標,再遍歷這個下標的Entry鏈,如果key的內存地址相等(即同一個引用)或者equals相等,則說明找到了
2. hashmap源碼解析(Java8)
在jdk8中,仍然會根據key.hashCode()計算出hash值,再通過這個hash值去定位這個key,但是不同的是,當發生衝突時,會採用鏈表和紅黑樹兩種方法去處理,當結點個數較少時用鏈表(用Node存儲),個數較多時用紅黑樹(用TreeNode存儲),同時結點也不叫Entry了,而是分成了Node和TreeNode。再最壞的情況下,鏈表查找的時間複雜度爲O(n),而紅黑樹一直是O(logn),這樣會提高HashMap的效率。
jdk8中的HashMap中定義了一個變量TREEIFY_THRESHOLD,當節點個數>= TREEIFY_THRESHOLD - 1時,HashMap將採用紅黑樹存儲
基於Map接口實現、允許null鍵/值、非同步、不保證有序(比如插入的順序)、也不保證順序不隨時間變化
01.數據結構
數組 + 鏈表 + 紅黑樹
Node
transient Node<K,V>[] table; static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }
TreeNode
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { TreeNode<K,V> parent; // red-black tree links TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; // needed to unlink next upon deletion boolean red; TreeNode(int hash, K key, V val, Node<K,V> next) { super(hash, key, val, next); } /** * Returns root of tree containing this node. */ final TreeNode<K,V> root() { for (TreeNode<K,V> r = this, p;;) { if ((p = r.parent) == null) return r; r = p; } } 。。。。。。 }
02.常用屬性
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { // 序列號 private static final long serialVersionUID = 362498820763181265L; // 默認的初始容量是16 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 最大容量 static final int MAXIMUM_CAPACITY = 1 << 30; // 默認的填充因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; // 當桶(bucket)上的結點數大於這個值時會轉成紅黑樹 static final int TREEIFY_THRESHOLD = 8; // 當桶(bucket)上的結點數小於這個值時樹轉鏈表 static final int UNTREEIFY_THRESHOLD = 6; // 桶中結構轉化爲紅黑樹對應的數組的最小大小,如果當前容量小於它,就不會將鏈表轉化爲紅黑樹,而是用resize()代替 static final int MIN_TREEIFY_CAPACITY = 64; // 存儲元素的數組,總是2的冪 transient Node<k,v>[] table; // 存放具體元素的集 transient Set<map.entry<k,v>> entrySet; // 存放元素的個數,注意這個不等於數組的長度。 transient int size; // 每次擴容和更改map結構的計數器 transient int modCount; // 臨界值 當實際節點個數超過臨界值(容量*填充因子)時,會進行擴容 int threshold; // 填充因子 final float loadFactor; }
03.類的構造函數
//制定初始容量和填充因子 public HashMap(int initialCapacity, float loadFactor) { // 初始容量不能小於0,否則報錯 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); // 初始容量不能大於最大值,否則爲最大值 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; // 填充因子不能小於或等於0,不能爲非數字 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // 初始化填充因子 this.loadFactor = loadFactor; // 通過tableSizeFor(cap)計算出不小於initialCapacity的最近的2的冪作爲初始容量,將其先保存在threshold裏,當put時判斷數組爲空會調用resize分配內存,並重新計算正確的threshold this.threshold = tableSizeFor(initialCapacity); } //指定初始容量 public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } //默認構造函數 public HashMap() { // 初始化填充因子 this.loadFactor = DEFAULT_LOAD_FACTOR; } //HashMap(Map<? extends K>)型構造函數 public HashMap(Map<? extends K, ? extends V> m) { // 初始化填充因子 this.loadFactor = DEFAULT_LOAD_FACTOR; // 將m中的所有元素添加至HashMap中 putMapEntries(m, false); }
04.hash函數
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
05.resize函數
final Node<K,V>[] resize() { // 當前table保存 Node<K,V>[] oldTab = table; // 保存table大小 int oldCap = (oldTab == null) ? 0 : oldTab.length; // 保存當前閾值 int oldThr = threshold; int newCap, newThr = 0; // 之前table大小大於0,即已初始化 if (oldCap > 0) { // 超過最大值就不再擴充了,只設置閾值 if (oldCap >= MAXIMUM_CAPACITY) { // 閾值爲最大整形 threshold = Integer.MAX_VALUE; return oldTab; } // 容量翻倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) // 閾值翻倍 newThr = oldThr << 1; // double threshold } // 初始容量已存在threshold中 else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; // 使用缺省值(使用默認構造函數初始化) else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } // 計算新閾值 if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) // 初始化table Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; // 之前的table已經初始化過 if (oldTab != null) { // 複製元素,重新進行hash for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) //桶中只有一個結點 newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) //紅黑樹 //根據(e.hash & oldCap)分爲兩個,如果哪個數目不大於UNTREEIFY_THRESHOLD,就轉爲鏈表 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; // 將同一桶中的元素根據(e.hash & oldCap)是否爲0進行分割成兩個不同的鏈表,完成rehash do { next = e.next;//保存下一個節點 if ((e.hash & oldCap) == 0) { //保留在低部分即原索引 if (loTail == null)//第一個結點讓loTail和loHead都指向它 loHead = e; else loTail.next = e; loTail = e; } else { //hash到高部分即原索引+oldCap if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
06.put函數
put設計思路:
對key的hashCode()做hash,然後再計算桶的index;
如果沒碰撞直接放到桶bucket裏;
如果碰撞了,以鏈表的形式存在buckets後;
如果碰撞導致鏈表過長(大於等於TREEIFY_THRESHOLD),就把鏈表轉換成紅黑樹(若數組容量小於MIN_TREEIFY_CAPACITY,不進行轉換而是進行resize操作)
如果節點已經存在就替換old value(保證key的唯一性)
如果表中實際元素個數超過閾值(超過load factor*current capacity),就要resize
public V put(K key, V value) { // 對key的hashCode()做hash return putVal(hash(key), key, value, false, true); } /** * 用於實現put()方法和其他相關的方法 * * @param hash hash for key * @param key the key * @param value the value to put * @param onlyIfAbsent if true, don't change existing value * @param evict if false, the table is in creation mode. * @return previous value, or null if none */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; // table未初始化或者長度爲0,進行擴容,n爲桶的個數 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // (n - 1) & hash 確定元素存放在哪個桶中,桶爲空,新生成結點放入桶中(此時,這個結點是放在數組中) if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); // 桶中已經存在元素 else { Node<K,V> e; K k; // 比較桶中第一個元素的hash值相等,key相等 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) // 將第一個元素賦值給e,用e來記錄 e = p; // hash值不相等或key不相等 else if (p instanceof TreeNode) //紅黑樹 // 放入樹中 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // 爲鏈表結點 else { for (int binCount = 0; ; ++binCount) { // 到達鏈表的尾部 if ((e = p.next) == null) { // 在尾部插入新結點 p.next = newNode(hash, key, value, null); // 結點數量達到閾值,調用treeifyBin()做進一步判斷是否轉爲紅黑樹 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); // 跳出循環 break; } // 判斷鏈表中結點的key值與插入的元素的key值是否相等 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) // 相等,跳出循環 break; // 用於遍歷桶中的鏈表,與前面的e = p.next組合,可以遍歷鏈表 p = e; } } // 表示在桶中找到key值、hash值與插入元素相等的結點 if (e != null) { // 記錄e的value V oldValue = e.value; // onlyIfAbsent爲false或者舊值爲null if (!onlyIfAbsent || oldValue == null) //用新值替換舊值 e.value = value; // 訪問後回調 afterNodeAccess(e); // 返回舊值 return oldValue; } } // 結構性修改 ++modCount; // 實際大小大於閾值則擴容 if (++size > threshold) resize(); // 插入後回調 afterNodeInsertion(evict); return null; } //將指定映射的所有映射關係複製到此映射中 public void putAll(Map<? extends K, ? extends V> m) { putMapEntries(m, true); } //將m的所有元素存入本HashMap實例中,evict爲false時表示構造初始HashMap final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { int s = m.size(); if (s > 0) { // table未初始化 if (table == null) { // pre-size //計算初始容量 float ft = ((float)s / loadFactor) + 1.0F; int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); if (t > threshold) threshold = tableSizeFor(t);//同樣先保存容量到threshold } // 已初始化,並且m元素個數大於閾值,進行擴容處理 else if (s > threshold) resize(); // 將m中的所有元素添加至HashMap中 for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); } } } //將鏈表轉換爲紅黑樹 final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; //若數組容量小於MIN_TREEIFY_CAPACITY,不進行轉換而是進行resize操作 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1) & hash]) != null) { TreeNode<K,V> hd = null, tl = null; do { TreeNode<K,V> p = replacementTreeNode(e, null);//將Node轉換爲TreeNode if (tl == null) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) hd.treeify(tab);//重新排序形成紅黑樹 } } final void treeify(Node<K,V>[] tab) { TreeNode<K,V> root = null; for (TreeNode<K,V> x = this, next; x != null; x = next) { next = (TreeNode<K,V>)x.next; x.left = x.right = null; if (root == null) { x.parent = null; x.red = false; root = x; } else { K k = x.key; int h = x.hash; Class<?> kc = null; for (TreeNode<K,V> p = root;;) { int dir, ph; K pk = p.key; if ((ph = p.hash) > h) dir = -1; else if (ph < h) dir = 1; else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) dir = tieBreakOrder(k, pk); TreeNode<K,V> xp = p; if ((p = (dir <= 0) ? p.left : p.right) == null) { x.parent = xp; if (dir <= 0) xp.left = x; else xp.right = x; root = balanceInsertion(root, x); break; } } } } moveRootToFront(tab, root); }
07.get函數
get函數大致思路如下:
1. bucket裏的第一個節點,直接命中;
2. 如果有衝突,則通過key.equals(k)去查找對應的entry,若爲樹,複雜度O(logn), 若爲鏈表,O(n)
public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; // table已經初始化,長度大於0,且根據hash尋找table中的項也不爲空 if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { // 比較桶中第一個節點 if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; // 桶中不止一個結點 if ((e = first.next) != null) { // 爲紅黑樹結點 if (first instanceof TreeNode) // 在紅黑樹中查找 return ((TreeNode<K,V>)first).getTreeNode(hash, key); // 否則,在鏈表中查找 do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; } public boolean containsKey(Object key) { return getNode(hash(key), key) != null; } public boolean containsValue(Object value) { Node<K,V>[] tab; V v; if ((tab = table) != null && size > 0) { //外層循環搜索數組 for (int i = 0; i < tab.length; ++i) { //內層循環搜索鏈表 for (Node<K,V> e = tab[i]; e != null; e = e.next) { if ((v = e.value) == value || (value != null && value.equals(v))) return true; } } } return false; }
Map集合常用方法
Map集合:該集合存儲鍵值對。一對一對往裏存。而且要保證鍵的唯一性。
1,添加。
put(K key, V value)
putAll(Map<? extends K,? extends V> m)
2,刪除。
clear()
remove(Object key)
3,判斷。
containsValue(Object value)
containsKey(Object key)
isEmpty()
4,獲取。
get(Object key)
size()
values()
entrySet()
keySet()
Map實現類
|--Hashtable:底層是哈希表數據結構,不可以存入null鍵null值。該集合是線程同步的。jdk1.0.效率低。
|--HashMap:底層是哈希表數據結構,允許使用 null 值和 null 鍵,該集合是不同步的。將hashtable替代,jdk1.2.效率高。
|--TreeMap:底層是二叉樹數據結構。線程不同步。可以用於給map集合中的鍵進行排序。
和Set很像。
其實大家,Set底層就是使用了Map集合。
Hashmap和HashTable的異同:
01.兩者的默認容量與負載因子有變化
02.hashtable的 容量可以是任意值,而hashmap必須是2的次冪
03.hashtable中在put方法裏面不允許值與鍵爲空
04.計算索引的方式不同(indexof函數不同)
05.hashtable大部分方法都加上了sychronied關鍵字
06.hashtable每次擴容,容量爲原來的兩倍加2.
線程非安全:個人覺得HashMap在併發時可能出現的問題主要是兩方面,首先如果多個線程同時使用put方法添加元素,而且假設正好存在兩個put的key發生了碰撞(hash值一樣),那麼根據HashMap的實現,這兩個key會添加到數組的同一個位置,這樣最終就會發生其中一個線程的put的數據被覆蓋。第二就是如果多個線程同時檢測到元素個數超過數組大小*loadFactor,這樣就會發生多個線程同時對Node數組進行擴容,都在重新計算元素位置以及複製數據,但是最終只有一個線程擴容後的數組會賦給table,也就是說其他線程的都會丟失,並且各自線程put的數據也丟失。
map集合的兩種取出方式:
1,Set<k> keySet:將map中所有的鍵存入到Set集合。因爲set具備迭代器。
所有可以迭代方式取出所有的鍵,在根據get方法。獲取每一個鍵對應的值。
Map集合的取出原理:將map集合轉成set集合。在通過迭代器取出。
2,Set<Map.Entry<k,v>> entrySet:將map集合中的映射關係存入到了set集合中,
而這個關係的數據類型就是:Map.Entry
Entry其實就是Map中的一個static內部接口。
爲什麼要定義在內部呢?
因爲只有有了Map集合,有了鍵值對,纔會有鍵值的映射關係。
關係屬於Map集合中的一個內部事物。
而且該事物在直接訪問Map集合中的元素。
import java.util.*; class MapDemo2 { public static void main(String[] args) { Map<String,String> map = new HashMap<String,String>(); map.put("02","zhangsan2"); map.put("03","zhangsan3"); map.put("01","zhangsan1"); map.put("04","zhangsan4"); //將Map集合中的映射關係取出。存入到Set集合中。 Set<Map.Entry<String,String>> entrySet = map.entrySet(); Iterator<Map.Entry<String,String>> it = entrySet.iterator(); while(it.hasNext()) { Map.Entry<String,String> me = it.next(); String key = me.getKey(); String value = me.getValue(); System.out.println(key+":"+value); } /* //先獲取map集合的所有鍵的Set集合,keySet(); Set<String> keySet = map.keySet(); //有了Set集合。就可以獲取其迭代器。 Iterator<String> it = keySet.iterator(); while(it.hasNext()) { String key = it.next(); //有了鍵可以通過map集合的get方法獲取其對應的值。 String value = map.get(key); System.out.println("key:"+key+",value:"+value); } */ } }