java集合
Collection和Map都實現了Iterator接口
1:HashMap和Hashtable 、ConcurrentHashMap
HashMap、Hashtable
a. HashMap不是線程安全的;Hashtable線程安全,但效率低,由於是Hashtable是使用synchronized的,全部線程競爭同一把鎖; html
b. HashMap的鍵和值均可覺得null,HashTable不能夠。java
c. 多線程環境下,一般也不是用HashTable,由於效率低。算法
ConcurrentHashMap
經過分析Hashtable就知道,synchronized是針對整張Hash表的,即每次鎖住整張表讓線程獨佔,ConcurrentHashMap容許多個修改操做併發進行,其關鍵在於使用了鎖分離技術。它使用了多個鎖來控制對hash表的不一樣部分進行的修改。 數組
ConcurrentHashMap內部使用segment數組來分別表示這些不一樣的部分,每個segment都包含了一個Entry數組的hashtable,每一個段其實就是一個小的hashtable,它們有本身的鎖。只要多個修改操做發生在不一樣的段上,它們就能夠併發進行。安全
有些方法須要跨段,好比size()和containsValue(),它們可能須要鎖定整個表而而不只僅是某個段,這須要按順序鎖定全部段,操做完畢後,又按順序釋放全部段的鎖。這裏「按順序」是很重要的,不然極有可能出現死鎖數據結構
Hash表的一個很重要方面就是如何解決hash衝突,ConcurrentHashMap 和HashMap使用相同的方式,都是將hash值相同的節點放在一個hash鏈中。 多線程
問:咱們可使用CocurrentHashMap來代替HashTable嗎?
能夠併發
問:如何線程安全使用hashmap
HashMap配合Collections工具類使用實現線程安全Collections.synchronizedMap()。app
同時還有ConcurrentHashMap能夠選擇,該類的線程安全是經過Lock的方式實現的,因此效率高於Hashtable。jvm
問:爲何HashMap是線程不安全的?
一:若是多個線程同時使用put方法添加元素,並且假設正好存在兩個put的key(調用hashcode())發生了碰撞(hash值同樣),那麼根據HashMap的實現,這兩個key會添加到數組的同一個位置,這樣最終就會發生其中一個線程的put的數據被覆蓋。
二:多個線程同時檢測到元素個數超過數組大小*loadFactor(負載因子),這樣就會發生多個線程同時對數組進行擴容,都在從新計算元素位置以及複製數據,可是最終只有一個線程擴容後的數組會賦給table,也就是說其餘線程的都會丟失
HashMap原理:
哈希表 : 數組連續內存空間,查找速度快,增刪慢;鏈表充分利用了內存,存儲空間是不連續的,首尾存儲上下一個節點的信息,因此尋址麻煩,查找速度慢,可是增刪快;哈希表呢,綜合了它們兩個的有點,一個哈希表,由數組和鏈表組成。
HashMap的內部存儲結構實際上是數組和鏈表的結合。當實例化一個HashMap時,系統會建立一個長度爲Capacity的Entry數組,這個長度在哈希表中被稱爲容量(Capacity),在這個數組中能夠存放元素的位置咱們稱之爲「桶」(bucket),每一個bucket都有本身的索引,系統能夠根據索引快速的查找bucket中的元素。 每一個bucket中存儲一個元素,即一個Entry對象,但每個Entry對象能夠帶一個引用變量,用於指向下一個元素,所以,在一個桶中,就有可能生成一個Entry鏈。
HashMap有四種構造方法:
HashMap():初始容量16,默認加載因子0.75
HashMap(int initialCapacity):自定義初始容量
HashMap(int initialCapacity,float loadFactor)
HashMap(Map<? extends K,? extends V> m)
這四個構造方法其實都受兩個參數的影響:容量和加載因子。容量是哈希表中桶的數量,初始容量爲16。加載因子是對哈希表的容量在自動增長resize()以前所達到尺度的描述。當哈希表中的條目數超過threshold(=Capacity*loadFactor) 的值時,要對哈希表進行rehash操做。
默認加載因子 (.75) 在時間和空間成本上尋求一種折衷。加載因子太高雖然減小了空間開銷,但同時也增長了查詢成本(在大多數HashMap 類的操做中,包括 get 和 put 操做,都反映了這一點)。在設置初始容量時應該考慮到映射中所需的條目數及其加載因子,以便最大限度地減小 rehash 操做次數。
HashMap遍歷的兩種方式
第一種:迭代器
Map map = new HashMap();
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object val = entry.getValue();
}(效率高)
Map map = new HashMap();
Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {
Object key = iter.next();
Object val = map.get(key);
}(效率低)
第二種:for each遍歷
for(Entry<Integer ,String>entry :hs.entrySet()){
System.out.println("key:"+entry.getKey()+" value:"+entry.getValue()); }
HashMap插入值
put(key, value)存儲對象到HashMap中,使用get(key)從HashMap中獲取對象。當咱們給put()方法傳遞鍵和值時,咱們先對鍵調用hashCode()方法,返回的hashCode用於找到bucket位置來儲存Entry對象。
hashcode相同,因此它們的bucket位置相同,‘碰撞’會發生,存入對應鏈表
「若是HashMap的大小超過了負載因子(load factor)定義的容量,怎麼辦?」除非你真正知道HashMap的工做原理,不然你將回答不出這道題。默認的負載因子大小爲0.75,也就是說,當一個map填滿了75%的bucket時候,和其它集合類(如ArrayList等)同樣,將會建立原來HashMap大小的兩倍的bucket數組,來從新調整map的大小,並將原來的對象放入新的bucket數組中。這個過程叫做rehashing,由於它調用hash方法找到新的bucket位置。
解決哈希表的衝突-開放地址法和鏈地址法
1 開放地址法
這個方法的基本思想是:當發生地址衝突時,按照某種方法繼續探測哈希表中的其餘存儲單元,直到找到空位置爲止。這個過程可用下式描述:
H i ( key ) = ( H ( key )+ d i ) mod m ( i = 1,2,…… , k ( k ≤ m – 1))
增量 d 能夠有不一樣的取法,並根據其取法有不一樣的稱呼:
( 1 ) d i = 1 , 2 , 3 , …… 線性探測再散列;
( 2 ) d i = 1^2 ,- 1^2 , 2^2 ,- 2^2 , k^2, -k^2…… 二次探測再散列;
( 3 ) d i = 僞隨機序列 僞隨機再散列;
二、鏈地址法
將全部哈希地址相同的記錄都連接在同一鏈表中圖形相似於圖2。也就是說,當HashMap中的每個bucket裏只有一個Entry,不發生衝突時,Hashmap是一個數組,根據索引能夠迅速找到Entry,可是,當發生衝突時,單個的bucket裏存儲的是一個Entry鏈,系統必須按順序遍歷每一個Entry,直到找到爲止。
爲了減小數據的遍歷,衝突的元素都是直接插入到第一個Entry後面的,因此,最先放入bucket中的Entry,位於Entry鏈中的最末端。這從put(K key,V value)中也能夠看出,在同一個bucket存儲Entry鏈的狀況下,新放入的Entry老是位於bucket中。
2 : ArrayList , LinkedList , Vector, Stack
ArrayList 是一個可改變大小的數組.當更多的元素加入到ArrayList中時,其大小將會動態地增加.內部的元素能夠直接經過get與set方法進行訪問,由於ArrayList本質上就是一個數組.
LinkedList 是一個雙鏈表,在添加和刪除元素時具備比ArrayList更好的性能.但在get與set方面弱於ArrayList. 固然,這些對比都是指數據量很大或者操做很頻繁的狀況下的對比,若是數據和運算量很小,那麼對比將失去意義.
Vector 和ArrayList相似,但他是線程安全的。若是你的程序自己是線程安全的(thread-safe,沒有在多個線程之間共享同一個集合/對象),那麼使用ArrayList是更好的選擇。
Vector和ArrayList在更多元素添加進來時會請求更大的空間。Vector每次請求其大小的雙倍空間,而ArrayList每次對size增加50%(初始容量10).
Stack是繼承自Vector也是線程安全的, 因爲Vector是經過數組實現的,這就意味着,Stack也是經過數組實現的,而非鏈表。固然,咱們也能夠將LinkedList看成棧來使用!
1. public push (item ) 把項 壓入棧頂。其做用與 addElement (item ) 相同。
參數 item 壓入棧頂的項 。 返回: item 參數 ;add返回boolean
2. public pop () 移除棧頂對象,並做爲函數的值 返回該對象。
返回:棧頂對象(Vector 對象的中的最後一項)。
拋出異常 : EmptyStackException 若是堆棧式空的 。。。
3. public peek() 查看棧頂對象而不移除它。。
返回:棧頂對象(Vector 對象的中的最後一項)。
拋出異常 : EmptyStackException 若是堆棧式空的 。。。
4. public boolean empty (測試堆棧是否爲空。)
問:爲何String, Interger這樣的wrapper類適合做爲鍵?
String, Interger這樣的wrapper類做爲HashMap的鍵是再適合不過了,並且String最爲經常使用。由於String是不可變的,也是final的,並且已經重寫了equals()和hashCode()方法了。(包裝類都是不可變的)
問:咱們可使用自定義的對象做爲鍵嗎?
可能使用任何對象做爲鍵,只要它遵照了equals()和hashCode()方法的定義規則,而且當對象插入到Map中以後將不會再改變了。
Iterator迭代器:
next() hasNext()
remove()將會刪除上次調用next方法時返回的元素,若是想要刪除指定位置上的元素,須要越過這個元素,若是調用remove以前沒有調用next將是不合法的,若是這樣作,將會拋出一個IllegalStateException異常。刪除集合中Iterator指向位置後面的元素
ListIterator迭代器
add(E e): 將指定的元素插入列表,插入位置爲迭代器當前位置以前
hasPrevious()
previous()
TreeSet和TreeMap的關係
其中 TreeMap 是 Map 接口的經常使用實現類,而 TreeSet 是 Set 接口的經常使用實現類。雖然 TreeMap 和 TreeSet 實現的接口規範不一樣,但TreeSet 底層是經過 TreeMap 來實現的(如同HashSet底層是是經過HashMap來實現的同樣),所以兩者的實現方式徹底同樣。而 TreeMap的實現就是紅黑樹算法。底層數據結構都是紅黑樹
相同點:
1. TreeMap和TreeSet都是有序的集合,也就是說他們存儲的值都是排好序的。
TreeMap和TreeSet都是非同步集合,所以他們不能在多線程之間共享,不過可使用方法Collections.synchroinzedMap()來實現同步
運行速度都要比Hash集合慢,他們內部對元素的操做時間複雜度爲O(logN)
不一樣點:
1. 最主要的區別就是TreeSet和TreeMap非別實現Set和Map接口
2. TreeSet只存儲一個對象,而TreeMap存儲兩個對象Key和Value(僅僅key對象有序)
3. TreeSet中不能有重複對象,而TreeMap中能夠存在
HashMap和TreeMap的區別?
HashMap-- 底層是哈希表數據結構,能夠存入null鍵null值,線程不一樣步的
TreeMap -- 底層是二叉樹數據結構(紅黑樹),線程不一樣步,能夠給map集合中的鍵進行排序
HashMap經過hashcode對其內容進行快速查找,
而TreeMap中全部的元素都保持着某種固定的順序,若是你須要獲得一個有序的結果你就應該使用TreeMap
紅黑樹有如下限制:
1. 節點必須是紅色或者是黑色
2. 根節點是黑色的
3. 全部的葉子節點是黑色的。
4. 每一個紅色節點的兩個子節點是黑色的,也就是不能存在父子兩個節點全是紅色
5. 從任意每一個節點到其每一個葉子節點的全部簡單路徑上黑色節點的數量是相同的。
問:咱們能本身寫一個容器類,而後使用 for-each 循環碼?
能夠,你能夠寫一個本身的容器類。若是你想使用 Java 中加強的循環來遍歷,你只須要實現 Iterable 接口。若是你實現 Collection 接口,默認就具備該屬性。
問:ArrayList 和 HashMap 的默認大小是多少?
在 Java 7 中,ArrayList 的默認大小是 10 個元素(擴充百分之五十),HashMap 的默認大小是16個元素(兩倍)。Vector(兩倍)
問:爲何在重寫 equals 方法的時候須要重寫 hashCode 方法?
若是你重載了equals,好比說是基於對象的內容實現的,而保留hashCode的實現不變,那麼極可能某兩個對象明明是「相等」,而hashCode卻不同。
附:synchronized(Java語言的關鍵字)
1、當兩個併發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程獲得執行。另外一個線程必須等待當前線程執行完這個代碼塊之後才能執行該代碼塊。
2、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另外一個線程仍然能夠訪問該object中的非synchronized(this)同步代碼塊。
3、尤爲關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其餘線程對object中全部其它synchronized(this)同步代碼塊的訪問將被阻塞。
4、第三個例子一樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就得到了這個object的對象鎖。結果,其它線程對該object對象全部同步代碼部分的訪問都被暫時阻塞。
synchronized和lock、volatile
lock:
加鎖和解鎖處須要經過lock()和unlock()顯示指出。會在finally塊中寫unlock()以防死鎖。
是一個類
線程須要的鎖被佔用,就是能夠嘗試得到鎖,線程能夠不用一直等待
大量同步的狀況使用明顯提升性能
Synchronized:
synchronized能夠加在方法上,也能夠加在特定代碼塊中
Java的關鍵字,在jvm層面上
線程須要的鎖被佔用,會一直等待
少許同步的狀況使用
volatile
用volatile修飾的變量,線程在每次使用變量的時候,都會讀取變量最新值。volatile很容易被誤用,用來進行原子性操做。