摘要:源碼分析簡(jiǎn)介的和操作的時(shí)間復(fù)雜度是常量??梢源骀I值為,是線程不安全的。數(shù)組鏈表散列的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)桶,鏈表的實(shí)現(xiàn)桶的實(shí)現(xiàn)鏈表的實(shí)現(xiàn)值節(jié)點(diǎn)的鍵節(jié)點(diǎn)的值下一個(gè)節(jié)點(diǎn)鏈表構(gòu)造方法方法是線程不安全的判斷兩個(gè)元素是否相等重要屬性默認(rèn)的桶初始容量。
hashmap源碼分析 簡(jiǎn)介
hashmap的get和put操作的時(shí)間復(fù)雜度是常量。通過(guò)調(diào)用哈希函數(shù)將元素正確的分布到桶中。初始容量(capacity)的值不能設(shè)置太高,加載因子(loadfactor)不能設(shè)置的太低,否則會(huì)影響迭代的性能。
一個(gè)hashmap的實(shí)例有兩個(gè)參數(shù)將影響它的性能。初始容量、加載因子。初始容量是hashmap在創(chuàng)建時(shí)候桶的大小。加載因子用來(lái)確定何時(shí)進(jìn)行擴(kuò)容(size > 容量*加載因子)。擴(kuò)容的時(shí)候也會(huì)進(jìn)行對(duì)內(nèi)部的數(shù)據(jù)結(jié)構(gòu)進(jìn)行重新構(gòu)建,使桶的大小增加兩倍。
默認(rèn)的加載因子(0.75)在時(shí)間和空間復(fù)雜度上提供了很好的權(quán)衡。大一點(diǎn)的話會(huì)減少空間但是會(huì)增加get和put的時(shí)間。
hashmap可以存鍵值為null,是線程不安全的。如果想線程安全可以使用Collections.synchronizedMap()包裝.
或者使用ConcurrentMap,這個(gè)map是線程安全的。
hashmap是一個(gè)散列表,存儲(chǔ)的內(nèi)容是key-value。就像我們用的字典一樣,用過(guò)字母(key)查找單詞(value)。hashmap的時(shí)間復(fù)雜度是O(longN)。
在java8之前hashmap采用的是桶+鏈表的數(shù)據(jù)結(jié)構(gòu)。但是如果數(shù)據(jù)很大,鏈表的查找時(shí)間復(fù)雜度是O(n),顯然者違背了hashmap的初衷,所以在鏈表的元素大于8的時(shí)候,java8會(huì)把鏈表旋轉(zhuǎn)為紅黑樹(shù)。
[數(shù)組 鏈表 散列(hash)
](https://blog.csdn.net/u013565...
桶的實(shí)現(xiàn):
transient Node[] table;
鏈表的實(shí)現(xiàn):
static class Node重要屬性implements Map.Entry { final int hash;//hash值 final K key;//節(jié)點(diǎn)的鍵 V value;//節(jié)點(diǎn)的值 Node next;//下一個(gè)節(jié)點(diǎn)(鏈表) Node(int hash, K key, V value, Node next) {//構(gòu)造方法 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) {//判斷兩個(gè)元素是否相等 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; } }
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默認(rèn)的桶初始容量(2^4=16)。 static final int MAXIMUM_CAPACITY = 1 << 30;//最大的桶的容量 static final float DEFAULT_LOAD_FACTOR = 0.75f;//默認(rèn)的加載因子 static final int TREEIFY_THRESHOLD = 8;//當(dāng)鏈表大于這個(gè)閾值會(huì)被旋轉(zhuǎn)為紅黑樹(shù) static final int UNTREEIFY_THRESHOLD = 6;//當(dāng)做resize操作的時(shí)候,如果桶中某個(gè)節(jié)點(diǎn)的數(shù)量小于這個(gè)閾值,則把樹(shù)旋轉(zhuǎn)為鏈表 static final int MIN_TREEIFY_CAPACITY = 64;//當(dāng)桶中的數(shù)量大于64是,才會(huì)判斷是否轉(zhuǎn)換成樹(shù) transient Node構(gòu)造方法[] table;//桶 transient int size;//hashmap的存儲(chǔ)的元素大小 transient int modCount;//hashmap結(jié)構(gòu)被修改的次數(shù) int threshold;//擴(kuò)容閾值 final float loadFactor;//加載因子
構(gòu)造方法會(huì)創(chuàng)建一個(gè)空的桶,計(jì)算擴(kuò)容閾值和加載因子
HashMap(int,float)public HashMap(int initialCapacity, float loadFactor) {//桶初始化容量,加載因子 if (initialCapacity < 0)//桶初始容量不能小于0 throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY)//如果桶初始化容量大于hashmap最大的容量,則初始化容量等于最大的容量 initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor))// throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity);//計(jì)算擴(kuò)容閾值 }HashMap(int)
public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR);//加載因子為默認(rèn)的0.75 }HashMap()
public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; //桶初始容量為0,加載因?yàn)?.75 }HashMap(Map)
public HashMap(Map extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR;//加載因子為默認(rèn)的0.75 putMapEntries(m, false);//map放入桶中 } final void putMapEntries(Map extends K, ? extends V> m, boolean evict) { int s = m.size();//插入元素大小 if (s > 0) {//如果大于0 ,則繼續(xù)進(jìn)行插入操作 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); } else if (s > threshold)//如果插入元素?cái)?shù)量大于擴(kuò)容閾值,則桶的大小擴(kuò)容兩倍 resize(); 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);//插入元素 } } }主要的幾個(gè)方法分析 get(Obejct)
public V get(Object key) { Nodeput(K,V)e; return (e = getNode(hash(key), key)) == null ? null : e.value; } //計(jì)算hash static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } //根據(jù)key獲取value final Node getNode(int hash, Object key) { Node [] tab; Node first, e; int n; K k;//tab:桶 first:桶中節(jié)點(diǎn)的第一個(gè)元素 n:桶的長(zhǎng)度 k:第一個(gè)節(jié)點(diǎn)的key if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {//如果桶不為空,并且key所在的節(jié)點(diǎn)的第一個(gè)元素不為空 if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k))))//如果key是節(jié)點(diǎn)的第一元素則返回節(jié)點(diǎn)的第一個(gè)元素 return first; if ((e = first.next) != null) {//遍歷鏈表/平衡樹(shù) 查找元素 if (first instanceof TreeNode) return ((TreeNode )first).getTreeNode(hash, key);//在樹(shù)中查找 do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Nodehash()[] tab; Node p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length;//如果桶為空,擴(kuò)容兩倍 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null);//如果key所在的桶第一個(gè)元素為null則直接插入桶中的第一個(gè)節(jié)點(diǎn) else {//否則插入鏈表/樹(shù) Node e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p;//如果插入的元素等于桶中的第一個(gè)一個(gè)元素,直接返回桶中的第一個(gè)元素 else if (p instanceof TreeNode) e = ((TreeNode )p).putTreeVal(this, tab, hash, key, value);//如果是樹(shù)節(jié)點(diǎn),插入樹(shù)中 else {//插入鏈表中 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold)//如果hashmap中的元素等于擴(kuò)容閾值,則重新構(gòu)造數(shù)據(jù)結(jié)構(gòu) resize(); afterNodeInsertion(evict); return null; }
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
h是原始的hash返回的值是int類型,int取值范圍:-2147483648到2147483648,前后加起來(lái)大概四十億的映射空間。只要hash函數(shù)映射的比較松散,一般是很難出現(xiàn)碰撞的。
但是考慮到實(shí)際的內(nèi)存的大小,很難放下這么大的數(shù)組。
所以為了空間上的考慮上述中的擾動(dòng)函數(shù),對(duì)原始計(jì)算出來(lái)的hash值(int 四個(gè)字節(jié)32位),右移16位,自己的高半?yún)^(qū)和低半?yún)^(qū)做異或,就是為了混合原始hash值的高位和地位,以此來(lái)加大低位的隨機(jī)性。而且混合后的地位參雜了高位的部分特征,這樣高位的信息也被變相的保留下來(lái)了。
線程安全性hashmap線程不安全的,如果要使用安全的hashmap建議使用ConcurrentHashMap。
參考:
hash()原理: https://www.zhihu.com/questio...
關(guān)注我的公眾號(hào)第一時(shí)間閱讀有趣的技術(shù)故事
掃碼關(guān)注:
也可以在微信搜索公眾號(hào)即可關(guān)注我:codexiulian
渴望與你一起成長(zhǎng)進(jìn)步!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76918.html
摘要:當(dāng)沖突的個(gè)數(shù)比較少時(shí),使用鏈表,否則使用紅黑樹(shù)。這樣做的好處是,最壞的情況下即所有的都沖突,采用鏈表的話查找時(shí)間為而采用紅黑樹(shù)為,這也是中性能提升的奧秘,詳細(xì)的測(cè)試可以看這篇博文。 HashMap是我們最常用的集合之一,同時(shí)Java8也提升了HashMap的性能。本著學(xué)習(xí)的原則,在這探討一下HashMap。 原理 簡(jiǎn)單講解下HashMap的原理:HashMap基于Hash算法,我們...
摘要:與中的類似,也是一個(gè)數(shù)組加鏈表,不過(guò)這個(gè)線程安全。線程安全,但是它的線程安全是依賴將所有修改的代碼塊都用修飾。這是中實(shí)現(xiàn)線程安全的思路,由個(gè)組成,每個(gè)就相當(dāng)于一個(gè)數(shù)組鏈表。線程安全,但性能差,不推薦使用。 問(wèn)題描述 翻翻別人的面試經(jīng)歷 這里在知乎上看到的,分享出了自己面試阿里Java崗的面試題。 showImg(https://segmentfault.com/img/bVbfSZ5?...
摘要:哪吒社區(qū)技能樹(shù)打卡打卡貼函數(shù)式接口簡(jiǎn)介領(lǐng)域優(yōu)質(zhì)創(chuàng)作者哪吒公眾號(hào)作者架構(gòu)師奮斗者掃描主頁(yè)左側(cè)二維碼,加入群聊,一起學(xué)習(xí)一起進(jìn)步歡迎點(diǎn)贊收藏留言前情提要無(wú)意間聽(tīng)到領(lǐng)導(dǎo)們的談話,現(xiàn)在公司的現(xiàn)狀是碼農(nóng)太多,但能獨(dú)立帶隊(duì)的人太少,簡(jiǎn)而言之,不缺干 ? 哪吒社區(qū)Java技能樹(shù)打卡?【打卡貼 day2...
摘要:本文是作者自己對(duì)中線程的狀態(tài)線程間協(xié)作相關(guān)使用的理解與總結(jié),不對(duì)之處,望指出,共勉。當(dāng)中的的數(shù)目而不是已占用的位置數(shù)大于集合番一文通版集合番一文通版垃圾回收機(jī)制講得很透徹,深入淺出。 一小時(shí)搞明白自定義注解 Annotation(注解)就是 Java 提供了一種元程序中的元素關(guān)聯(lián)任何信息和著任何元數(shù)據(jù)(metadata)的途徑和方法。Annotion(注解) 是一個(gè)接口,程序可以通過(guò)...
摘要:哈希碰撞的概率取決于計(jì)算方式和空間容量的大小。超過(guò)后執(zhí)行擴(kuò)容操作。當(dāng)一個(gè)哈希桶存儲(chǔ)的鏈表長(zhǎng)度大于會(huì)將鏈表轉(zhuǎn)換成紅黑樹(shù),小于時(shí)則從紅黑樹(shù)轉(zhuǎn)換成鏈表。換句話來(lái)說(shuō),就是為了減少哈希碰撞。紅黑樹(shù)相關(guān)的操作雖然代碼不同,但是實(shí)際上要干的事情是一樣的。 前言 學(xué)習(xí)情況記錄 學(xué)習(xí)情況記錄 時(shí)間:week 3 SMART子目標(biāo) :Java 容器 記錄在學(xué)習(xí)Java容器 知識(shí)點(diǎn)中,關(guān)于HashM...
閱讀 1101·2021-11-15 18:00
閱讀 2815·2021-09-22 15:18
閱讀 1977·2021-09-04 16:45
閱讀 758·2019-08-30 15:55
閱讀 3870·2019-08-30 13:10
閱讀 1345·2019-08-30 11:06
閱讀 1994·2019-08-29 12:51
閱讀 2302·2019-08-26 13:55