java集合類學習
一:Collection介面
Set介面和List介面都集成於Collection介面
1.Set
無序不可重複
(1)HashSet
HashSet其實時一個map,原始碼:
public HashSet() { map = new HashMap<E,Object>(); }
add()方法呼叫的是個map,原始碼:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
可以看出HashSet加入的物件做了map的key,因此其中的元素是不可以重複的。
2.List
有序可重複
(1)ArryList
實現是一個Object型別的陣列ArrayList的長度增長演算法為
“新長度=(舊長度*3)/2+1”,原始碼演算法如下
int
newCapacity = (oldCapacity *
3
)/
2
+
1;
ArrayList.add()最終呼叫的
是System.arraycopy(src, srcPos, dest, destPos, length);方法,並沒有使用
HashCode
ArrayList為什麼執行緒不安全,請看:
https://blog.csdn.net/u012859681/article/details/78206494
(2)Vector
執行緒安全的List,所謂執行緒安全是它只有在使用add()、remove()、set()、get()等
方法時是執行緒安全的,但是它在遍歷集合的時候並不能做到執行緒池安全,通過看
它的原始碼可以發現它在呼叫add()、remove()、set()、get()等方法時,使用了
Sychronized。
(3)CopyOnWriteArrayList和Collections.synchronizedList()
CopyOnWriteArrayList和Collections.synchronizedList是實現執行緒安全的列表的
兩種方式。兩種實現方式分別針對不同情況有不同的效能表現,其中
CopyOnWriteArrayList的多執行緒寫操作效能較差,而多執行緒的讀操作效能較好。
Collections.synchronizedList的寫操作效能比CopyOnWriteArrayList在多執行緒操
作的寫操作好很多,而讀操作因為是採用了synchronized關鍵字的方式,其讀
操作效能並不如CopyOnWriteArrayList。因此在不同的應用場景下,應該選擇不
同的多執行緒安全實現類。
CopyOnWriteArrayList是java.util.concurrent包中的一個List的實現類。
CopyOnWrite的意思是在寫時拷貝,也就是如果需要對CopyOnWriteArrayList
的內容進行改變,首先會拷貝一份新的List並且在新的List上進行修改,最後將原
List的引用指向新的List。CopyOnWriteArrayList可以執行緒安全的遍歷。
(4)Queue保持一個佇列(先進先出)的順序
二:Map介面
一組成對的"鍵值對"物件
1.HashMap
(1)HashMap概述
HashMap實現是一個連結串列的陣列,基於鏈地址法形成的雜湊,陣列是由Entry組
成,一個Entry包含一個key-value 鍵值對。
簡單來說,HashMap由陣列+連結串列組成的,陣列是HashMap的主體,連結串列則是
主要為了解決雜湊衝突而存在的,如果定位到的陣列位置不含連結串列(當前entry
的next指向null),那麼對於查詢,新增等操作很快,僅需一次定址即可;如果
定位到的陣列包含連結串列,對於新增操作,其時間複雜度為O(n),首先遍歷連結串列,
存在即覆蓋,否則新增;對於查詢操作來講,仍需遍歷連結串列,然後通過key物件
的equals方法逐一比對查詢。所以,效能考慮,HashMap中的連結串列出現越少,
效能才會越好。
可以參考文章:
https://www.cnblogs.com/holyshengjie/p/6500463.html
(2)HashMap是怎麼處理Hash衝突的
Hash衝突時在put時產生,做如下操作:
(a) 對key的hashCode()做hash,然後再計算index;
(b)如果沒碰撞直接放到bucket裡;
(c)如果碰撞了,以連結串列的形式存在buckets前面(1.8是放在buckets後面)
(d)如果碰撞導致連結串列過長(大於等於TREEIFY_THRESHOLD),就把連結串列轉換
成紅黑樹;
(e)如果節點已經存在就替換old value(保證key的唯一性)
(f)如果bucket滿了(超過load factor*current capacity),就要resize。
put時原始碼:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//如果發現key已經在連結串列中存在,則修改並返回舊的值
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//如果遍歷連結串列沒發現這個key,則會呼叫以下程式碼
modCount++;
addEntry(hash, key, value, i);
return null;
}
addEntry原始碼:
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
Entry的構造方法:
Entry( int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
get時的邏輯:
(a)bucket裡的第一個節點,直接命中;
(b)如果有衝突,則通過key.equals(k)去查詢對應的entry
(c)若為樹,則在樹中通過key.equals(k)查詢,O(logn);
(d)若為連結串列,則在連結串列中通過key.equals(k)查詢,O(n)。
Hashmap多執行緒的環境下會出現死鎖,具體原因參考文章:
https://coolshell.cn/articles/9606.html
get時原始碼:
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
//先定位到陣列元素,再遍歷該元素處的連結串列
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.equals(k)))
return e.value;
}
return null;
}
(3)HashMap為什麼是執行緒不安全的
http://www.cnblogs.com/qiumingcheng/p/5259892.html
(4)1.8對HashMap的優化
JDK 1.8對HashMap進行了比較大的優化,底層實現由之前的“陣列+連結串列”改為
“陣列+連結串列+紅黑樹”,當連結串列長度太長(預設超過8)時,連結串列就轉換為紅黑
樹,利用紅黑樹快速增刪改查的特點提高HashMap的效能,其中會用到紅黑樹
的插入、刪除、查詢等演算法。
什麼是紅黑樹以及紅黑樹的優點檢視:
https://www.sohu.com/a/201923614_466939,看後面別看前面,前面都是廢話
2.HashTable
Hashtable 繼承於Dictionary,實現了Map、Cloneable、java.io.Serializable介面。
它並不繼承自Collection,把它寫在這裡純粹是為了方便和HashMap比較。
HashTable和HashMap採用相同的儲存機制,二者的實現基本一致,不同的是:
(1)HashMap是非執行緒安全的,HashTable是執行緒安全的,內部的方法基本都是
synchronized。
(2)HashTable不允許有null值的存在。
在HashTable中呼叫put方法時,如果key為null,直接丟擲
NullPointerException
3.TreeMap以及TreeMap和HashMap的比較
(1)非執行緒安全的
(2)基於紅黑樹實現。TreeMap沒有調優選項,因為該樹總處於平衡狀態。
(3)TreeMap適用於按自然順序或自定義順序遍歷鍵(key);HashMap適用於在
Map中插入、刪除和定位元素。
4.ConcurrentHashMap和HashTable的區別
ConcurrentHashMap繼承自AbstractMap,實現了ConcurrentMap介面,使得它
具有Map的屬性,同時又有多執行緒相關的屬性,它並沒有繼承Collection,寫在
這純粹是為了比較方便
ConcurrentHashMap的效能優於HashTable,ConcurrentHashMap的鎖是基於
Lock的,而HashTable的鎖是基於synchronized的
6.Lock和sychronized:
(1)sychronized獲取鎖的執行緒如果被阻塞了,那麼其它等待鎖的執行緒需要一直等
待下去,而lock就可以使執行緒不處於一直等待的狀態,可以只等待一段時間或
響應中斷。
(2)當有多個執行緒讀寫檔案時,讀操作和寫操作會發生衝突現象,寫操作和寫操作
會發生衝突現象,但是讀操作和讀操作不會發生衝突現象。如果使用
sycnronized讀操作和讀操作會發生衝突,而使用lock讀操作和讀操作不會發生
衝突。
(3)lock可以直到執行緒獲取鎖是否成功,而sychronized卻不能。
(4)sychronized可以自動釋放鎖,lock需要手動釋放鎖(經常在finally中)。
(5)鎖型別:
sychronized:可重入 不可中斷 非公平
lock:可重入 可中斷 可公平(兩者皆可)
7.瞭解ConcurrentHashMap:
ConcurrentHashMap所使用的鎖分段技術,首先將資料分成一段一段的儲存,然
後給每一段資料配一把鎖,當一個執行緒佔用鎖訪問其中一個段資料的時候,其他
段的資料也能被其他執行緒訪問。有些方法需要跨段,比如size()和containsValue(),
它們可能需要鎖定整個表而而不僅僅是某個段,這需要按順序鎖定所有段,操作
完畢後,又按順序釋放所有段的鎖。這裡“按順序”是很重要的,否則極有可能出現
死鎖,在ConcurrentHashMap內部,段陣列是final的,並且其成員變數實際上也
是final的,但是,僅僅是將陣列宣告為final的並不保證陣列成員也是final的,這需
要實現上的保證。這可以確保不會出現死鎖,因為獲得鎖的順序是固定的。
ConcurrentHashMap是由Segment陣列結構和HashEntry陣列結構組成。Segment
是一種可重入鎖ReentrantLock,在ConcurrentHashMap裡扮演鎖的角色,
HashEntry則用於儲存鍵值對資料。一個ConcurrentHashMap裡包含一個Segment
陣列,Segment的結構和HashMap類似,是一種陣列和連結串列結構, 一個Segment
裡包含一個HashEntry陣列,每個HashEntry是一個連結串列結構的元素, 每個
Segment守護者一個HashEntry數組裡的元素,當對HashEntry陣列的資料進行修改
時,必須首先獲得它對應的Segment鎖。其結構如下圖:
參考文章:http://www.importnew.com/16142.html
關於ArrayList的5道面試題請檢視網址:http://blog.csdn.net/quentain/article/details/51365432