點贊在看,養成習慣。web
點贊收藏,人生輝煌。編程
點擊關注【微信搜索公衆號:編程背鍋俠】,防止迷路。數組
第一篇 HashMap源碼中的成員變量你還不懂? 來來來!!!整理好的成員變量源碼解析
微信
第二篇 擼啊擼,再次擼HashMap源碼,踩坑源碼中構造方法!!!每次都有收穫編輯器
HashMap
,默認初始容量(16)和默認負載因⼦(0.75)// 構造一個無參數的構造方法
public HashMap() { // 將默認的加載因子0.75賦值給loadFactor,並無建立數組 this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } 複製代碼
@Test
public void test_hash_map_con_no(){ HashMap<Integer, String> map = new HashMap<>(); } 複製代碼
HashMap
// 構造一個指定容量⼤⼩的構造函數
public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } 複製代碼
@Test
public void test_hash_map_con_in(){ HashMap<Integer, String> map = new HashMap<>(16); } 複製代碼
HashMap
// 指定容量⼤小和加載因⼦的構造函數 initialCapacity: 指定的容量 loadFactor:指定的加載因⼦
public HashMap(int initialCapacity, float loadFactor) { // 判斷初始化容量initialCapacity是否小於0 if (initialCapacity < 0) // 若是⼩於0,則拋出非法的參數異常IllegalArgumentException throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); // 判斷初始化容量initialCapacity是否⼤於集合的最大容量MAXIMUM_CAPACITY->2的30次冪 if (initialCapacity > MAXIMUM_CAPACITY) // 若是超過MAXIMUM_CAPACITY,會將MAXIMUM_CAPACITY賦值給initialCapacity initialCapacity = MAXIMUM_CAPACITY; // 判斷負載因⼦loadFactor是否小於等於0或者是不是⼀個⾮數值 if (loadFactor <= 0 || Float.isNaN(loadFactor)) // 若是知足上述其中之一,則拋出非法的參數異常IllegalArgumentException throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // 將指定的加載因⼦賦值給HashMap成員變量的負載因子loadFactor this.loadFactor = loadFactor; // tableSizeFor(initialCapacity) 判斷指定的初始化容量是不是2的n次冪,若是不是那麼會變爲⽐指定初始化容量大的最小的2的n次冪。 this.threshold = tableSizeFor(initialCapacity); } 複製代碼
@Test
public void test_hash_map_con_in_lo(){ // 自定義初始化容量和加載因子 HashMap<Integer, String> map = new HashMap<>(16, 0.75f); } 複製代碼
this.threshold = tableSizeFor(initialCapacity);
以上代碼是源碼中的最後一句代碼,爲啥不是this.threshold = tableSizeFor(initialCapacity) * this.loadFactor ? 這樣才符合threshold的意思(當HashMap的size到達threshold這個閾值時會擴容)。 可是,請注意,在jdk8之後的構造方法中,並無對table這個成員變量進行初始化,table的初始化被推遲到了put方法中,在put⽅法中會對threshold從新計算,put⽅法的【resize()方法】具體實現我會在這個系列其餘文章會進行講解。 複製代碼
若是這個構造函數的initialCapacity小於0,將會拋出非法異常IllegalArgumentException。
若是loadFactor的值是isNaN,則會拋出非法異常IllegalArgumentException。 複製代碼
Map
的構造函數和默認負載因子(0.75)public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR; // 負載因子loadFactor變爲默認的負載因子0.75 putMapEntries(m, false); } 複製代碼
@Test
public void test_hash_map_con_map(){ HashMap<Integer, String> map = new HashMap<>(); map.put(1, "aaa"); map.put(2, "bbb"); map.put(3, "ccc"); // 使用構造方法 HashMap<Integer, String> all = new HashMap<>(map); // 遍歷查看 all.forEach((k, v) -> System.out.println("k: " + k + " v: " + v)); } 複製代碼
static final int tableSizeFor(int cap) {
int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } 複製代碼
// 搞個測試類【模擬上面的源碼】 n >>> 1右移1位 |=按位運算
@Test public void test_wei(){ int n = 14 - 1; // 13 n |= n >>> 1; // 15 n |= n >>> 2; // 15 n |= n >>> 4; // 15 n |= n >>> 8; // 15 n |= n >>> 16;// 15 int i = (n < 0) ? 1 : (n >= 128) ? 128 : n + 1; // 16 System.out.println(i);// 16 } 複製代碼
>>>
是右移符號。好比給定的值爲5【0101】,右移一位爲2【0010】。函數
|
符號爲或運算。好比給定的值11 | 15
,11對應的二進制【1011】,15對應的二進制【1111】,11 | 15
結果爲【1111】15。post
當在實例化
HashMap
實例時,若是給定了initialCapacity
(假設是5),因爲HashMap的 capacity
必須都是2的冪,所以這個方法用於找到大於等於initialCapacity
(假設是5)的最小的2的冪。性能
initialCapacity
若是就是2的冪,則返回的仍是這個數)。測試
int n = cap - 1
】?這是爲了防⽌,若是cap已是2的冪, ⼜沒有執行這個減1操做,則執行完後面的幾條無符號右移操做以後,返回的capacity將是這個cap的2倍。假如cap的值爲8,通過上面的計算獲得的仍是8。this
以方法tableSizeFor(int cap)舉例測試的數 cap = 65
int n = cap - 1; ===>>>> n = 65 - 1 = 64 64 對應二進制 0100 0000 n >>> 1 右移1位 0100 0000 ===>>>> 0010 0000 n |= n >>> 1 對應於 0100 0000 | 0010 0000 = 0110 0000 【96】 n >>> 2 右移2位 0110 0000 ===>>>> 0001 1000 n |= n >>> 2 對應於 0110 0000 | 0001 1000 = 0111 1000 【120】 n >>> 4 右移4位 0111 1000 ===>>>> 0000 0111 n |= n >>> 4 對應於 0111 1000 | 0000 0111 = 0111 1111 【127】 n >>> 8 右移8位 0111 1111 ===>>>> 0000 0000 n |= n >>> 8 對應於 0111 1111 | 0000 0000 = 0111 1111 【127】 n >>> 16 右移16位 0111 1111 ===>>>> 0000 0000 n |= n >>> 16 對應於 0111 1111 | 0000 0000 = 0111 1111 【127】 最後執行 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; 返回128【128爲2的7次冪,加一的緣由是湊成整數次冪】 複製代碼
// m:給定的集合。evict:最初構造此映射時爲false。若是給定的集合爲null,將會拋出空指針異常NullPointerException
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { // 獲取給定集合的長度 int s = m.size(); // 判斷給定的集合長度是否大於0 if (s > 0) { // 判斷table是否已經初始化 if (table == null) { // pre-size // 未初始化,s爲m的實際元素個數。預先計算一個容量ft。這裏爲何加1呢?有啥特殊的含義嗎? float ft = ((float)s / loadFactor) + 1.0F; // 上面計算的容量不小於最大值將這個值賦值給t,不然賦值給最大值 int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); // 判斷這個容量是否大於0,大於就對這個容量進行格式化,格式爲2的冪 if (t > threshold) threshold = tableSizeFor(t); } // 以前的數組中有元素,判斷參數中的數組長度是否大於數組容量 else if (s > threshold) // 擴容 resize(); // 遍歷給定的集合 for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { // 獲取給定集合每一個鍵值對的k和v K key = e.getKey(); V value = e.getValue(); // 將每個entry的鍵值對放到數組中 putVal(hash(key), key, value, false, evict); } } } 複製代碼
float ft = ((float)s / loadFactor) + 1.0F
這一行代碼中爲何要加1.0F?問題出現的源碼
float ft = ((float)s / loadFactor) + 1.0F; int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); 複製代碼
s/loadFactor
的結果是⼩數,加1.0F
與(int)ft
至關因而對小數作一個向上取整以儘量的保證更大容量,更大的容量可以減小resize的調用次數。因此+ 1.0F
是爲了獲取更大的容量。例如:原來集合的元素個數是6個,那麼6/0.75是8,是2的n次冪,那麼新的數組⼤小就是8了。
而後原來數組的數據就會存儲到長度是8的新的數組中了,這樣會致使在存儲元素的時候,容量不夠,還得繼續擴容,那麼性能就會下降了,而若是+1呢,數組長度直接變爲16了,這樣能夠減小數組的擴容次數,從而提升效率。
創做不易, 很是歡迎你們的點贊、評論和關注(^_−)☆ 你的點贊、評論以及關注是對我最大的支持和鼓勵,而你的支持和鼓勵 我繼續創做高質量博客的動力 !!!
本文使用 mdnice 排版