附圖:
集合架構(gòu):
map架構(gòu):
一:前言
HashMap想必是所有java開(kāi)發(fā)人員最常用的集合操作類之一椭坚,在日常開(kāi)發(fā)中我們最常用的雙列(key-value)集合便是hashmap,它是一個(gè)采用哈希表實(shí)現(xiàn)的鍵值對(duì)集合,是一種<key,value>的存儲(chǔ)結(jié)構(gòu),繼承自 [AbstractMap]迅细,實(shí)現(xiàn)了 [Map 接口]。它特殊的數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)致使他能在眾多的集合操作類中脫穎而出咆疗,線性鏈表結(jié)構(gòu)讓它在保證了查詢效率的同時(shí)也兼顧了增刪的效率波丰,簡(jiǎn)單方便的API操作可以完成各種復(fù)雜的數(shù)據(jù)業(yè)務(wù),成為最受歡迎的集合操作類。
二:為什么會(huì)出現(xiàn)HashMap:
我們知道莺戒,在數(shù)據(jù)結(jié)構(gòu)中伴嗡,數(shù)組結(jié)構(gòu)可以保證元素的檢索效率,但是在添加和刪除的功能上就顯得略微遲鈍了从铲,例如java中的ArrayList瘪校。在檢索元素的時(shí)候,只需要根據(jù)索引直接檢索名段,無(wú)需去遍歷整個(gè)集合阱扬,但是如果要增加或刪除元素,就必須去重新維護(hù)索引伸辟。另一種數(shù)據(jù)結(jié)構(gòu)鏈表就恰恰相反麻惶,增加或刪除元素只需要修改引用,而由于沒(méi)有索引信夫,它在檢索元素的時(shí)候就必須從頭到尾的去遍歷整個(gè)集合窃蹋,最具有代表性的就是java中的linkedList。當(dāng)然静稻,在實(shí)際開(kāi)發(fā)中完全可以根據(jù)業(yè)務(wù)需求去選擇更適合的工具類去操作數(shù)據(jù)(多檢索動(dòng)作的就選用底層是數(shù)組結(jié)構(gòu)的集合類脐彩,多增刪動(dòng)作的就選用底層是鏈表結(jié)構(gòu)的集合類)。但是如果檢索和增刪操作都很頻繁呢姊扔?這時(shí)候,HashMap就應(yīng)運(yùn)而生了梅誓,毫無(wú)疑問(wèn)它是數(shù)組和鏈表的組合體(線性鏈表結(jié)構(gòu))恰梢,集數(shù)組與鏈表的優(yōu)點(diǎn)為一身。我們先來(lái)解釋一下“線性鏈表”這個(gè)名詞吧梗掰,其本質(zhì)就是一個(gè)元素為鏈表的數(shù)組嵌言。
三:數(shù)據(jù)結(jié)構(gòu)(線性鏈表):
HashMap的存儲(chǔ)結(jié)構(gòu):哈希表有多種不同的實(shí)現(xiàn)方法,HashMap采用的是一種常用的實(shí)現(xiàn)方法——拉鏈法及穗,我們可以理解為“鏈表的數(shù)組”摧茴。
從上圖可以看出HashMap是Y軸方向是數(shù)組,X軸方向就是鏈表的存儲(chǔ)方式埂陆。大家都知道數(shù)組的存儲(chǔ)方式在內(nèi)存的地址是連續(xù)的苛白,大小固定,一旦分配不能被其他引用占用焚虱。它的特點(diǎn)是查詢快购裙,時(shí)間復(fù)雜度是O(1),插入和刪除的操作比較慢鹃栽,時(shí)間復(fù)雜度是O(n)躏率,鏈表的存儲(chǔ)方式是非連續(xù)的,大小不固定,特點(diǎn)與數(shù)組相反薇芝,插入和刪除快蓬抄,查詢速度慢。HashMap可以說(shuō)是一種折中的方案吧夯到。
數(shù)據(jù)的存儲(chǔ)結(jié)構(gòu)主要包括:順序存儲(chǔ)嚷缭,鏈?zhǔn)酱鎯?chǔ),索引存儲(chǔ)黄娘,散列存儲(chǔ)峭状。
這里注意:hashmap的鏈表中的每一個(gè)元素是一個(gè)Entry對(duì)象,包括key和value逼争,當(dāng)然還有其他的東西优床,而非只有鍵和值。
四:架構(gòu):
本圖引自武皇帝博客
五:特點(diǎn):
(1):底層實(shí)現(xiàn)是 鏈表數(shù)組誓焦,JDK 8 后又加了 紅黑樹(shù)
(2):實(shí)現(xiàn)了 Map 全部的方法
(3):key 用 Set 存放胆敞,所以想做到 key 不允許重復(fù),key 對(duì)應(yīng)的類需要重寫(xiě) hashCode 和 equals 方法
(4):允許空鍵和空值(但空鍵只有一個(gè)杂伟,且放在第一位)
(5):元素是無(wú)序的移层,而且順序會(huì)不定時(shí)改變
(6):插入、獲取的時(shí)間復(fù)雜度基本是 O(1)(前提是有適當(dāng)?shù)墓:瘮?shù)赫粥,讓元素分布在均勻的位置)(7):遍歷整個(gè) Map 需要的時(shí)間與 桶(數(shù)組) 的長(zhǎng)度成正比(因此初始化時(shí) HashMap 的容量不宜太大)
(8):兩個(gè)關(guān)鍵因子:初始容量观话、加載因子
(9):除了不允許 null 并且同步,Hashtable 幾乎和他一樣越平。
六:原理:
1频蛔、首先判斷Key是否為Null,如果為null秦叛,直接查找Enrty[0]晦溪,如果不是Null,先計(jì)算KeyHashCode挣跋,然后經(jīng)過(guò)二次Hash三圆。得到Hash值,這里的Hash特征值是一個(gè)int值避咆。
2舟肉、根據(jù)Hash值,要找到對(duì)應(yīng)的數(shù)組啊查库,所以對(duì)Entry[]的長(zhǎng)度length求余度气,得到的就是Entry數(shù)組的index。
3膨报、找到對(duì)應(yīng)的數(shù)組磷籍,就是找到了所在的鏈表适荣,然后按照鏈表的操作對(duì)Value進(jìn)行插入、刪除和查詢操作院领。
七:源碼解析:
一下源碼出自JDK1.7
7.1:重要屬性
/**
* The default initial capacity - MUST be a power of two.
* 默認(rèn)的初始化容量弛矛,必須是2的指數(shù)。
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* 最大容量 1<<30.必須是2的指數(shù)比然。
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
* 默認(rèn)的負(fù)載因子丈氓,值是0.75f(跟數(shù)據(jù)結(jié)構(gòu)中的hash有關(guān))。
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* An empty table instance to share when the table is not inflated.
* 一個(gè)空的entry數(shù)組
*/
static final Entry<?, ?>[] EMPTY_TABLE = {};
/**
* The table, resized as necessary. Length MUST Always be a power of two.
* Entry數(shù)組强法,哈希表万俗,長(zhǎng)度必須為2的冪
*/
transient Entry<K, V>[] table = (Entry<K, V>[]) EMPTY_TABLE;
/**
* The number of key-value mappings contained in this map.
* 已存元素的個(gè)數(shù)
*/
transient int size;
/**
* The next size value at which to resize (capacity * load factor).
* 下次擴(kuò)容的臨界值,size>=threshold就會(huì)擴(kuò)容
*/
int threshold;
/**
* The load factor for the hash table.
* 加載因子
*/
final float loadFactor;
/**
* HashMap被改變的次數(shù)
*/
transient int modCount;
7.2構(gòu)造方法
/**
* 無(wú)參構(gòu)造(默認(rèn)容量16饮怯,默認(rèn)加載因子0.75)
*/
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
/**
* 帶參構(gòu)造(給出初識(shí)容量闰歪,加載因子使用默認(rèn)0.75)
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* 真正的初始化
* @param initialCapacity
* @param loadFactor
*/
public HashMap(int initialCapacity, float loadFactor) {
//如果初始化容量小于0,拋出異常
if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
//如果初始化容量大于最大容量1<<30蓖墅,則令其等于1<<30
if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY;
//如果加載因子小于等于0或者不是數(shù)字库倘,則拋出異常
if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
//賦值成員變量
this.loadFactor = loadFactor;
threshold = initialCapacity;
//初始化
init();
}
7.3:靜態(tài)內(nèi)部類Entry
/**
* ClassName: Entry 靜態(tài)內(nèi)部類
*/
static class Entry<K, V> implements Map.Entry<K, V> {
final K key;//key
V value;//value
Entry<K, V> next;//下一個(gè)元素
int hash;//hash值
/**
* 創(chuàng)建一個(gè)Entry
*/
Entry(int h, K k, V v, Entry<K, V> n) {
value = v;
next = n;
key = k;
hash = h;
}
//獲取key
public final K getKey() {
return key;
}
//獲取value
public final V getValue() {
return value;
}
//設(shè)置value
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
//與另一個(gè)Entry對(duì)象比較
public final boolean equals(Object o) {
//先判斷是否是entry對(duì)象類型
if (!(o instanceof Map.Entry)) return false;
Map.Entry e = (Map.Entry) o;//獲取到需要比較的entry對(duì)象
Object k1 = getKey();//獲取當(dāng)前Ehtry的key值
Object k2 = e.getKey();//獲取要比較Entry的key值
//比較兩者的key值
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
//同樣的方法比較value值
Object v1 = getValue();
Object v2 = e.getValue();
//如果key和value都相等,那兩個(gè)Entry就相等
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
//否則论矾,不等
return false;
}
//獲取hash值
public final int hashCode() {
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
}
//tostring
public final String toString() {
return getKey() + "=" + getValue();
}
/**
* This method is invoked whenever the value in an entry is overwritten
* by an invocation of put(k,v) for a key k that's already in the
* HashMap.
*/
void recordAccess(HashMap<K, V> m) {
}
/**
* This method is invoked whenever the entry is removed from the table.
*/
void recordRemoval(HashMap<K, V> m) {
}
}
HashMap底層維護(hù)的Entry類型的數(shù)組教翩,每個(gè)元素都是Entry類型,Entry類型會(huì)維護(hù)兩個(gè)信息贪壳,一個(gè)是key的信息饱亿,一個(gè)是value的信息,而key跟value正好是我們往HashMap里面put的那個(gè)key跟value闰靴。還有一個(gè)Entry類型的引用路捧,用于指向下一個(gè)Entry類型的引用,還有一個(gè)hash哈希值,就是位置传黄。
7.4:添加元素
在看添加元素之前,我們先要了解hashmap到底是怎樣存儲(chǔ)元素的队寇。首先我們知道HashMap里面實(shí)現(xiàn)一個(gè)靜態(tài)內(nèi)部類Entry膘掰,這個(gè)上面的代碼已經(jīng)說(shuō)明了,其重要的屬性有 key , value, next佳遣,從屬性key,value我們就能很明顯的看出來(lái)Entry就是HashMap鍵值對(duì)實(shí)現(xiàn)的一個(gè)基礎(chǔ)bean识埋,我們上面說(shuō)到HashMap的基礎(chǔ)就是一個(gè)線性數(shù)組,這個(gè)數(shù)組就是Entry[]零渐,Map里面的內(nèi)容都保存在Entry[]里面窒舟。理解了這些,我們?cè)賮?lái)看它put的源碼就稍微容易點(diǎn)了诵盼,直接上代碼
/**
* 添加元素
*/
public V put(K key, V value) {
//如果這個(gè)table是一個(gè)空的entry數(shù)組惠豺,(table指成員變量transient Entry<K, V>[] table)
if (table == EMPTY_TABLE) {
inflateTable(threshold);//就以默認(rèn)加載因子創(chuàng)建一個(gè)entry
}
//如果key是null的(hashmap是允許一個(gè)null鍵的)
if (key == null)
return putForNullKey(value);
//當(dāng)key不為null
int hash = hash(key);//計(jì)算key的hash值
int i = indexFor(hash, table.length);//根據(jù)哈希值和table數(shù)組的長(zhǎng)度定位數(shù)組項(xiàng)(取模)
/**
* 對(duì)數(shù)組項(xiàng)的鏈表進(jìn)行遍歷
*/
for (Entry<K, V> e = table[i]; e != null; e = e.next) {
Object k;
//如果key的哈希值與鏈表中的某一項(xiàng)的哈希值相等且key本身引用值相等或者引用值所指向的對(duì)象相等
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
//則替換相應(yīng)項(xiàng)的value值為新的value银还,并返回老的value
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//如果沒(méi)有找到相同的key,則加入該項(xiàng)到鏈表中
modCount++;
addEntry(hash, key, value, i);//此方法直接將新的項(xiàng)加入到鏈表的頭部洁墙,新項(xiàng)的next引用指向原來(lái)的鏈表項(xiàng)
return null;
}
public V put(K key, V value)
添加方法是hashMap的精髓所在蛹疯,我們來(lái)看看他具體都做了哪些事情,
(第一步):先判斷成員變量table(即entry數(shù)組)是否為EMPTY_TABLE空數(shù)組热监,如果是捺弦,就調(diào)用 inflateTable(threshold) 方法以默認(rèn)加載因子為參數(shù)創(chuàng)建一個(gè)新的Entry數(shù)組。反之則代碼繼續(xù)運(yùn)行孝扛。
(第二步):判斷這個(gè)key是否為null(hashMap是運(yùn)行null鍵的)列吼,如果put的鍵是null,則執(zhí)行putForNullKey(value) 方法苦始。我們來(lái)看看這個(gè)方法:
private V putForNullKey(V value) {
//如果事先已經(jīng)存在key為null的映射寞钥,則替換后返回old 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;
}
}
//如果不存在盈简,則添加新的項(xiàng)到鏈表中
modCount++;
//addEntry(int hash, K key, V value, int bucketIndex)我們發(fā)現(xiàn)當(dāng)key為null時(shí)凑耻,此Entry始終放在數(shù)組的第一個(gè)索引0處
addEntry(0, null, value, 0);
return null;
}
這個(gè)方法是對(duì)key為null的情況作了一個(gè)特殊處理,當(dāng)事先已經(jīng)有key為null的映射時(shí)柠贤,用新的value代替舊的value香浩,并將舊的value返回;當(dāng)事先沒(méi)有key為null的映射時(shí)臼勉,則是直接調(diào)用 addEntry(0, null, value, 0)方法邻吭,注意這個(gè)方法的參數(shù),我們發(fā)現(xiàn)當(dāng)key為null時(shí)宴霸,此Entry始終放在數(shù)組的第一個(gè)索引0處囱晴。
(第三步):如果key不為null,則先算出key的hash值瓢谢,再根據(jù)hash值和table數(shù)組的長(zhǎng)度進(jìn)行取莫運(yùn)算畸写,找到Entry要存放的索引位置(這時(shí)也就找到對(duì)應(yīng)的鏈表了)
(第四步):遍歷這個(gè)index索引處的鏈表,尋找鏈表中所有的key氓扛,如果key已存在枯芬,則用新的value替換舊的value,并將舊的value返回采郎。我們來(lái)看看代碼:
/**
* 對(duì)數(shù)組項(xiàng)的鏈表進(jìn)行遍歷
*/
for (Entry<K, V> e = table[i]; e != null; e = e.next) {
Object k;
//如果key的哈希值與鏈表中的某一項(xiàng)的哈希值相等且key本身引用值相等或者引用值所指向的對(duì)象相等
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
//則替換相應(yīng)項(xiàng)的value值為新的value千所,并返回老的value
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
(第五步):如果第四步?jīng)]有找到相同的key,則證明這個(gè)鏈表中此key的映射還不存在蒜埋,這時(shí)候淫痰,我們便可以直接去執(zhí)行添加Entry,并將最新的Entry放在鏈表頭部整份,將其引用指向舊的Entry待错,返回null籽孙。
上面我們大致已經(jīng)了解了hashmap元素的實(shí)現(xiàn),但是這里遺漏一個(gè)問(wèn)題朗鸠,我們都知道hashmap的key是唯一的蚯撩,并且大部分同學(xué)也都知道是通過(guò)hashCode和equals方法來(lái)實(shí)現(xiàn)的,那么它到底是如何實(shí)現(xiàn)key唯一的呢烛占?我們大可先了解了解hashcode和equal胎挎。
我們首先想到的是用equals比較,沒(méi)錯(cuò)忆家,這樣可以實(shí)現(xiàn)犹菇,但隨著內(nèi)部元素的增多,put和get的效率將越來(lái)越低芽卿,這里的時(shí)間復(fù)雜度是O(n)揭芍,假如有1000個(gè)元素,put時(shí)需要比較1000次卸例。實(shí)際上称杨,HashMap很少會(huì)用到equals方法,因?yàn)槠鋬?nèi)通過(guò)一個(gè)哈希表管理所有元素筷转,當(dāng)我們調(diào)用put存值時(shí)姑原,HashMap首先會(huì)調(diào)用K的hashCode方法,獲取哈希碼呜舒,通過(guò)哈希碼快速找到某個(gè)存放位置锭汛,這個(gè)位置可以被稱之為bucketIndex,通過(guò)上面所述hashCode的協(xié)定可以知道袭蝗。
如果hashCode不同唤殴,equals一定為false,如果hashCode相同到腥,equals不一定為true朵逝。
所以理論上,hashCode可能存在沖突的情況乡范,有個(gè)專業(yè)名詞叫碰撞配名,當(dāng)碰撞發(fā)生時(shí),計(jì)算出的bucketIndex也是相同的篓足,這時(shí)會(huì)取到bucketIndex位置已存儲(chǔ)的元素,最終通過(guò)equals來(lái)比較闰蚕,equals方法就是哈希碼碰撞時(shí)才會(huì)執(zhí)行的方法栈拖,所以前面說(shuō)HashMap很少會(huì)用到equals。
現(xiàn)在我們知道没陡,執(zhí)行put方法后涩哟,最終HashMap的存儲(chǔ)結(jié)構(gòu)會(huì)有這三種情況索赏,情形3是最少發(fā)生的,哈希碼發(fā)生碰撞屬于小概率事件贴彼。到目前為止潜腻,我們了解了兩件事:
(1):HashMap通過(guò)鍵的hashCode來(lái)快速的存取元素。
(2):當(dāng)不同的對(duì)象hashCode發(fā)生碰撞時(shí)器仗,HashMap通過(guò)單鏈表來(lái)解決融涣,將新元素加入鏈表表頭,通過(guò)next指向原有的元素精钮。單鏈表在Java中的實(shí)現(xiàn)就是對(duì)象的引用(復(fù)合)威鹿。
這里添加一個(gè)小的知識(shí)點(diǎn)(時(shí)間復(fù)雜度與空間復(fù)雜度)
時(shí)間復(fù)雜度:時(shí)間頻度 一個(gè)算法執(zhí)行所耗費(fèi)的時(shí)間,其評(píng)判標(biāo)準(zhǔn)以時(shí)間長(zhǎng)短為主
空間復(fù)雜度:空間頻度 一個(gè)算法執(zhí)行所消耗的內(nèi)存空間轨香,其評(píng)判標(biāo)準(zhǔn)以內(nèi)存空間為主
相關(guān)博文:http://blog.csdn.net/booirror/article/details/7707551/
7.5元素獲取
/**
* 元素獲取
*/
public V get(Object key) {
//先判斷key是否為null
if (key == null)
return getForNullKey();
//根據(jù)key獲取對(duì)應(yīng)的entry
Entry<K, V> entry = getEntry(key);
//返回value
return null == entry ? null : entry.getValue();
}
元素的獲取看起來(lái)很簡(jiǎn)單忽你,大致分為三步
(第一步):先判斷此key是否為null,如果不為null則繼續(xù)向下執(zhí)行臂容,反之調(diào)用 getForNullKey() 方法科雳,這也是一個(gè)經(jīng)過(guò)處理的方法,來(lái)看看方法實(shí)現(xiàn)
private V getForNullKey() {
//判斷map大小
if (size == 0) {
return null;
}
//直接獲取table[0]
for (Entry<K, V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
上邊說(shuō)過(guò)了脓杉,當(dāng)put是key為null則直接存入table數(shù)據(jù)的0索引處糟秘,所以取的時(shí)候當(dāng)然也從0索引處取
(第二步):根據(jù)key獲取對(duì)應(yīng)的Entry對(duì)象
final Entry<K, V> getEntry(Object key) {
//先判斷map的大小
if (size == 0) {
return null;
}
//計(jì)算key的hash值
int hash = (key == null) ? 0 : hash(key);
//遍歷當(dāng)前hash列所對(duì)應(yīng)的鏈表,并根據(jù)key查找Entry對(duì)象并返回
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;
}
(第三步):當(dāng)找到對(duì)應(yīng)的Entry時(shí)丽已,則返回Entry中的value值蚌堵,反正返回null即沒(méi)有找到
線程安全:
測(cè)試代碼
/**
* ClassName: TestHashMap
* @author lvfang
* @Desc: TODO
* @date 2017-9-23
*/
public class TestHashMap implements Runnable {
private HashMap<Integer, Integer> map = null;
public TestHashMap(HashMap<Integer, Integer> map){
this.map = map;
}
@Override
public void run() {
for(int i=0 ; i<50 ; i++) map.put(i, i);
System.out.println(map.size());
}
public static void main(String[] args) throws InterruptedException {
HashMap<Integer, Integer> map = new HashMap<>();
map.put(1, 1);
map.get(1);
for(int i=0 ; i<5 ; i++){
new Thread(new TestHashMap(map)).start();
}
}
}
上邊的例子很明顯,開(kāi)啟5個(gè)線程沛婴,每個(gè)線程對(duì)map中都添加key為0-50的鍵和值吼畏,由于hashmap的key是唯一的,所以最終map的大小還是50嘁灯,而運(yùn)行結(jié)果則不定泻蚊。
為什么會(huì)產(chǎn)生線程安全:
HashMap在并發(fā)時(shí)可能出現(xiàn)的問(wèn)題主要是兩方面,首先如果多個(gè)線程同時(shí)使用put方法添加元素,而且假設(shè)正好存在兩個(gè)put的key發(fā)生了碰撞(hash值一樣)丑婿,那么根據(jù)HashMap的實(shí)現(xiàn)性雄,這兩個(gè)key會(huì)添加到數(shù)組的同一個(gè)位置,這樣最終就會(huì)發(fā)生其中一個(gè)線程的put的數(shù)據(jù)被覆蓋羹奉。第二就是如果多個(gè)線程同時(shí)檢測(cè)到元素個(gè)數(shù)超過(guò)數(shù)組大小*loadFactor秒旋,這樣就會(huì)發(fā)生多個(gè)線程同時(shí)對(duì)Node數(shù)組進(jìn)行擴(kuò)容,都在重新計(jì)算元素位置以及復(fù)制數(shù)據(jù)诀拭,但是最終只有一個(gè)線程擴(kuò)容后的數(shù)組會(huì)賦給table迁筛,也就是說(shuō)其他線程的都會(huì)丟失,并且各自線程put的數(shù)據(jù)也丟失耕挨。
解決方案一:方法同步必然是不可少的或者Hashtable细卧;Map<String, String> hashtable = new Hashtable<>();
解決方案二:用collections創(chuàng)建一個(gè)線程安全的map尉桩;Map<String, String> synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());
解決方案三:java并發(fā)包下的ConcurrentHashMap;Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();
面試題:
http://www.cnblogs.com/zywu/p/5753736.html
http://blog.csdn.net/song19890528/article/details/16891015
http://www.cnblogs.com/lchzls/p/6714474.html
相關(guān)博文:
http://www.importnew.com/21396.html
http://blog.csdn.net/happy_horse/article/details/52316865
http://www.cnblogs.com/wuhuangdi/p/4175991.html
http://blog.csdn.net/jerryburning/article/details/47619491
http://blog.csdn.net/ghsau/article/details/16890151