Java 集合
fail-fast機(jī)制
在java集合類(lèi)中,使用modCount來(lái)檢查數(shù)組的狀態(tài).
當(dāng)在迭代集合的時(shí)候,(通常會(huì)實(shí)現(xiàn) iterator()方法來(lái)獲取迭代對(duì)象,或者 foreach),
集合里面的數(shù)據(jù),在其他地方被修改了,這個(gè)時(shí)候 modCount就會(huì)被修改,與迭代過(guò)程的modCount不一致.將拋出ConcurrentModificationException異常,這個(gè)過(guò)程就叫 fail-fast
List 篇
主要介紹 ArrayList,Vector和LinkedList,CopyOnWriteArrayList
ArrayList
是一個(gè)數(shù)組長(zhǎng)度可自增長(zhǎng)的線程不安全的列表.內(nèi)部使用數(shù)組實(shí)現(xiàn). Object[] elementData.
時(shí)間復(fù)雜度 :
根據(jù)索引查詢(xún),復(fù)雜度為 O(1).
根據(jù)元素查詢(xún),復(fù)雜度為 O(N).
插入和刪除,如果在列首復(fù)雜度為 O(N);在列尾復(fù)雜度為 O(1);平均復(fù)雜為 O(N)
插入和刪除非列尾數(shù)據(jù), 將會(huì)導(dǎo)致數(shù)組移動(dòng)數(shù)據(jù).
RandomAccess 接口
RandomAccess 是一個(gè)標(biāo)記接口(沒(méi)有任何方法).
如果實(shí)現(xiàn)了 RandomAccess 接口,表示該集合可以進(jìn)行隨機(jī)快速訪問(wèn)(通常底層是 數(shù)組形式的集合),如 ArrayList.
可以快速訪問(wèn)的集合,使用
未實(shí)現(xiàn) RandomAccess接口的集合,如LinkedList,使用迭代器效率更高.
for (Iterator i=list.iterator(); i.hasNext(); )i.next();使用 instanceof RandomAccess來(lái)判斷是否 支持隨機(jī)快速訪問(wèn).
Vector
是一個(gè)數(shù)組長(zhǎng)度可自增長(zhǎng)的線程安全的列表,內(nèi)部使用數(shù)組實(shí)現(xiàn).
線程安全的vector,照樣可以觸發(fā) fail-fast.
時(shí)間復(fù)雜度與 arraylist 一樣.
在不涉及線程安全的前提下,arraylist效率更高.
fun main(args: Array<String>) {val vector = Vector(listOf(1, 2, 3, 4, 5))singleThread(vector)multiThread(vector)multiThreadSynchronized(vector) }/*** 單線程下,迭代拋出 ConcurrentModificationException*/ fun singleThread(vector: Vector<Int>) {val it = vector.iterator()while (it.hasNext()) {println(it.next())vector.add(1)} }/*** 多線程下,迭代拋出 ConcurrentModificationException*/ fun multiThread(vector: Vector<Int>) {thread {repeat(10000) {vector.add(it)}}thread {vector.forEach { println(it) }} }/*** 多線程下,修改vector是添加同步鎖,避免同時(shí)迭代同時(shí)修改*/ fun multiThreadSynchronized(vector: Vector<Int>) {thread {synchronized(vector) {repeat(10000) {vector.add(it)}}}thread {vector.forEach { println(it) }} }Stack
繼承自 vector,線程安全,棧結(jié)構(gòu),先進(jìn)后出.
LinkedList
是以雙向鏈表方式實(shí)現(xiàn)的非線程安全的集合.
時(shí)間復(fù)雜度:
刪除和插入元素,復(fù)雜度為 O(1).
查詢(xún)時(shí),由于采用雙向鏈表,判斷離哪頭近,從哪頭開(kāi)始找.
最糟糕的情況是O(N/2),所以它的復(fù)雜度為 O(N).
CopyOnWriteArrayList
CopyOnWriteArrayList是并發(fā)包中的實(shí)現(xiàn).使用 ReenterLock和System.arraycopy來(lái)實(shí)現(xiàn)數(shù)組讀寫(xiě)的線程安全.
在讀取的時(shí)不加鎖,所以可以支持多線程同時(shí)讀取數(shù)據(jù),并且同時(shí)讀取數(shù)不受限制.
在寫(xiě)入(add,set,remove等操作)的時(shí)候,使用 ReenterLock鎖住方法,然后操作數(shù)據(jù)時(shí)先將原數(shù)組拷貝一份,對(duì)拷貝數(shù)據(jù)進(jìn)行操作,操作完后將拷貝的原來(lái)的數(shù)據(jù)引用指向拷貝的數(shù)組引用,實(shí)現(xiàn)數(shù)據(jù)的更新操作,更新完后釋放鎖. 和其名字操作一致(寫(xiě)時(shí)拷貝).
沒(méi)有設(shè)置初始長(zhǎng)度的方法和擴(kuò)容的策略. 因?yàn)樵摂?shù)組在每次 添加數(shù)據(jù)是, 都會(huì)使用 System.arraycopy拷貝一份比當(dāng)前數(shù)據(jù) +1 的數(shù)組.
public boolean add(E e) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;Object[] newElements = Arrays.copyOf(elements, len + 1);newElements[len] = e;setArray(newElements);return true;} finally {lock.unlock();}}ArrayList,Vector和LinkedList的區(qū)別
ArrayList和Vector內(nèi)部都是使用數(shù)組實(shí)現(xiàn)的.它們的主要區(qū)別在于
在不考慮線程安全的情況下,優(yōu)先使用 ArrayList,如果在使用前,能夠預(yù)估大概有元素,創(chuàng)建時(shí)指定個(gè)數(shù),效果更好.
LinkedList 使用雙向鏈表實(shí)現(xiàn),也是線程不安全的.與ArrayList的區(qū)別在于
如果主要需要對(duì)列表進(jìn)行遍歷,以及增刪操作,使用LinkedList其他情況使用ArrayList.
如果需要用到線程安全的列表,請(qǐng)使用 CopyOnWriteArrayList
Map篇
Map 是以鍵值對(duì)形式存儲(chǔ)的集合.
主要介紹 HashMap,TreeMap和 Hashtable,ConcurrentHashMap
HashMap
HashMap 內(nèi)部混合使用數(shù)組,鏈表,紅黑樹(shù)的結(jié)構(gòu)來(lái)構(gòu)建的鍵值對(duì)集合.
HashMap的本質(zhì)基礎(chǔ)是基于數(shù)組的. 數(shù)組的各個(gè)元素,使用單向鏈表的結(jié)構(gòu).(Node(int hash, K key, V value, Node<K,V> next)).
HashMap通過(guò)哈希函數(shù)(hash())來(lái)完成key對(duì)index的映射.
當(dāng)出現(xiàn)hash后index沖突(index位置已經(jīng)存在不同的key的鍵值對(duì))時(shí),數(shù)據(jù)將在index位置,使用單向鏈表或紅黑樹(shù)(當(dāng)鏈表長(zhǎng)度大于等8個(gè)時(shí),鏈表中的所有元素改用紅黑樹(shù)存放)來(lái)存放元素.
當(dāng)使用掉的索引 占用了 數(shù)組長(zhǎng)度的一定比例時(shí)(填裝因子),HashMap將進(jìn)行擴(kuò)容(擴(kuò)容為原來(lái)的兩倍,key與index重新映射).
HashMap的性能瓶頸:
理想的狀態(tài)是,每一個(gè)鍵值對(duì)的存放,能夠比較均勻適當(dāng)?shù)姆植荚跀?shù)組的索引上,盡量避免過(guò)多的hash值沖突.
// java 8 中的實(shí)現(xiàn)static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);} // index = (n - 1) & hash , 2的次方來(lái)說(shuō)就是取余操作. // 由于 (n -1)的二進(jìn)制 高位都是0,地位都是1. // hashcode 與 它進(jìn)行 & 操作的話, hashcode的高位變化低位不變,則計(jì)算出來(lái)的index值講不變. // 為了消除高位變化 不影響 index 的值, 于是將 hashcode 的 高16位 移動(dòng)到低16位,再和 hashcode進(jìn)行 ^ 操作. // 這樣高位的變化,也能影響index值, 降低了沖突的概率.過(guò)低的填裝因子,如填裝因子=0.5,容量剩余一半的時(shí)候就擴(kuò)容,可能導(dǎo)致空間上的浪費(fèi).
過(guò)高的填裝因子,可能導(dǎo)致為了命中未使用的索引,而頻繁出現(xiàn) 沖突.
HashMap實(shí)現(xiàn)中,填裝因子默認(rèn)使用 0.75的值.
特點(diǎn) :
時(shí)間復(fù)雜度 :
查詢(xún),插入和刪除操作的平均時(shí)間復(fù)雜度為 O(1).
極端情況下(全部發(fā)生沖突),若長(zhǎng)度小于8,使用單項(xiàng)鏈表,復(fù)雜度為 O(n),如果長(zhǎng)度大于等于8,使用紅黑樹(shù),復(fù)雜度為O(logn)
LinkedHashMap
繼承自 HashMap, 底層使用哈希表與雙向鏈表來(lái)保存元素,與HashMap的差異就在于 訪問(wèn)順序上.
構(gòu)造中,accessOrder 默認(rèn)為 false,代表按照插入順序進(jìn)行迭代;為 true,代表以訪問(wèn)順序進(jìn)行迭代(最常訪問(wèn)的在前,最不經(jīng)常訪問(wèn)的在后LRU)。
TreeMap
TreeMap內(nèi)部使用 紅黑樹(shù)來(lái)實(shí)現(xiàn)的鍵值對(duì)集合.與HashMap相比,TreeMap是一個(gè)能比較元素大小的Map集合,會(huì)對(duì)傳入的key進(jìn)行了大小排序。其中,可以使用元素的自然順序,也可以使用集合中自定義的比較器來(lái)進(jìn)行排序.
特點(diǎn) :
時(shí)間復(fù)雜度 :
紅黑樹(shù)是自平衡的二叉搜索樹(shù), 查詢(xún),添加刪除 效率都是 O(logn).
HashTable
HashTable 內(nèi)部使用數(shù)組和單項(xiàng)鏈表實(shí)現(xiàn)的鍵值對(duì)集合.
HashTable的哈希函數(shù) (hash & 0x7FFFFFFF) % tab.length,只是對(duì)key的hashcod進(jìn)行取整和取余操作.
HashTable默認(rèn)的初始大小為11,之后每次擴(kuò)充為原來(lái)的2n+1。
也就是說(shuō),HashTable的鏈表數(shù)組的默認(rèn)大小是一個(gè)素?cái)?shù)、奇數(shù)。之后的每次擴(kuò)充結(jié)果也都是奇數(shù)。
由于HashTable會(huì)盡量使用素?cái)?shù)、奇數(shù)作為容量的大小。當(dāng)哈希表的大小為素?cái)?shù)時(shí),簡(jiǎn)單的取模哈希的結(jié)果會(huì)更加均勻。(這個(gè)是可以證明出來(lái) 的,可參考:http://zhaox.github.io/algorithm/2015/06/29/hash)
特點(diǎn) :
時(shí)間復(fù)雜度 :
和HashMap一致
ConcurrentHashMap (java.util.concurrent)
和HashMap一樣,是內(nèi)部使用數(shù)組,鏈表,紅黑樹(shù)的結(jié)構(gòu)來(lái)構(gòu)建的鍵值對(duì)集合,因?yàn)樾枰WC線程安全,所以?xún)?nèi)部實(shí)現(xiàn)更加復(fù)雜.
哈希函數(shù) (h ^ (h >>> 16)) & HASH_BITS , 和HashMap類(lèi)似,與高位進(jìn)行異或操作的方式,來(lái)消除高位數(shù)據(jù)變化導(dǎo)致索引不變的情況,多加了一步正數(shù)的操作.
當(dāng)沖突的鏈表中個(gè)數(shù)超過(guò)8個(gè), 如果數(shù)組長(zhǎng)度小于64,則擴(kuò)容,否則,將鏈表數(shù)據(jù)轉(zhuǎn)化為 紅黑樹(shù)結(jié)構(gòu)存儲(chǔ).
ConcurrentHashMap 采用 CAS(Compare and Swap)原子類(lèi)型操作,來(lái)保證線程的安全性. 較 jdk1.7 的分段鎖機(jī)制性能更好.
特點(diǎn) :
構(gòu)造函數(shù)為 : ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)
沒(méi)有默認(rèn)的裝填因子,傳入 初始容量和裝填因子后,會(huì)被算法調(diào)整為 2次方,最小容量為 16.
并發(fā)級(jí)別,表示 可以同時(shí)對(duì)數(shù)據(jù)進(jìn)行寫(xiě)操作的線程數(shù), 最大可達(dá)16. 讀取不受限制.
時(shí)間復(fù)雜度 :
和HashMap一致
HashMap和HashTable , ConcurrentHashMap對(duì)比
HashMap 線程不安全 , HashTable線程安全,但還是存在fail-fast問(wèn)題 , ConcurrentHashMap線程安全,不存在fail-fast問(wèn)題.
HashMap 和 ConcurrentHashMap 內(nèi)部都是用 數(shù)組,鏈表 加紅黑樹(shù)來(lái)構(gòu)建.(較高效) HashTable 使用數(shù)組加鏈表.
HashMap 和 ConcurrentHashMap 都是用 高位異或的哈希算法. HashTable 采用 數(shù)組長(zhǎng)度為素?cái)?shù),直接取余的哈希算法.
HashMap 允許插入 null鍵值對(duì), HashTable , ConcurrentHashMap 不允許.
結(jié)論 : 不考慮線程安全的情況下使用 HashMap, 線程安全則使用 ConcurrentHashMap.
如果知道 大概會(huì)存多少數(shù)據(jù),指定 初始容量 會(huì)更高效, 避免擴(kuò)容和重新hash映射產(chǎn)生的效率問(wèn)題.
Set篇
Set 是沒(méi)有重復(fù)元素的單元素集合.
TreeSet 內(nèi)部使用 TreeMap實(shí)現(xiàn).
HashSet 內(nèi)部使用 HashMap實(shí)現(xiàn). 當(dāng)構(gòu)造參數(shù)為三個(gè)時(shí) LinkedHashMap實(shí)現(xiàn).
LinkedHashSet 繼承自 HashSet,但是都是調(diào)用 HashSet中有三個(gè)構(gòu)造參數(shù)的LinkedHashMap來(lái)實(shí)現(xiàn).
總結(jié)
- 上一篇: 我是如何使用python控制迅雷自动下载
- 下一篇: 害怕抑郁症?该系统通过日常交流就能判断你