Hashtable、HashMap、TreeMap心得java
三者均實現了Map接口,存儲的內容是基於key-value的鍵值對映射,一個映射不能有重複的鍵,一個鍵最多隻能映射一個值。編程
(1) 元素特性數組
HashTable中的key、value都不能爲null;HashMap中的key、value能夠爲null,很顯然只能有一個key爲null的鍵值對,可是容許有多個值爲null的鍵值對;TreeMap中當未實現 Comparator 接口時,key 不能夠爲null;當實現 Comparator 接口時,若未對null狀況進行判斷,則key不能夠爲null,反之亦然。安全
(2)順序特性多線程
HashTable、HashMap具備無序特性。TreeMap是利用紅黑樹來實現的(樹中的每一個節點的值,都會大於或等於它的左子樹種的全部節點的值,而且小於或等於它的右子樹中的全部節點的值),實現了SortMap接口,可以對保存的記錄根據鍵進行排序。因此通常須要排序的狀況下是選擇TreeMap來進行,默認爲升序排序方式(深度優先搜索),可自定義實現Comparator接口實現排序方式。併發
(3)初始化與增加方式框架
初始化時:HashTable在不指定容量的狀況下的默認容量爲11,且不要求底層數組的容量必定要爲2的整數次冪;HashMap默認容量爲16,且要求容量必定爲2的整數次冪。函數
擴容時:Hashtable將容量變爲原來的2倍加1;HashMap擴容將容量變爲原來的2倍。高併發
(4)線程安全性工具
HashTable其方法函數都是同步的(採用synchronized修飾),不會出現兩個線程同時對數據進行操做的狀況,所以保證了線程安全性。也正由於如此,在多線程運行環境下效率表現很是低下。由於當一個線程訪問HashTable的同步方法時,其餘線程也訪問同步方法就會進入阻塞狀態。好比當一個線程在添加數據時候,另一個線程即便執行獲取其餘數據的操做也必須被阻塞,大大下降了程序的運行效率,在新版本中已被廢棄,不推薦使用。
HashMap不支持線程的同步,即任一時刻能夠有多個線程同時寫HashMap;可能會致使數據的不一致。若是須要同步(1)能夠用 Collections的synchronizedMap方法;(2)使用ConcurrentHashMap類,相較於HashTable鎖住的是對象總體, ConcurrentHashMap基於lock實現鎖分段技術。首先將Map存放的數據分紅一段一段的存儲方式,而後給每一段數據分配一把鎖,當一個線程佔用鎖訪問其中一個段的數據時,其餘段的數據也能被其餘線程訪問。ConcurrentHashMap不只保證了多線程運行環境下的數據訪問安全性,並且性能上有長足的提高。
(5)一段話HashMap
HashMap基於哈希思想,實現對數據的讀寫。當咱們將鍵值對傳遞給put()方法時,它調用鍵對象的hashCode()方法來計算hashcode,讓後找到bucket位置來儲存值對象。當獲取對象時,經過鍵對象的equals()方法找到正確的鍵值對,而後返回值對象。HashMap使用鏈表來解決碰撞問題,當發生碰撞了,對象將會儲存在鏈表的下一個節點中。 HashMap在每一個鏈表節點中儲存鍵值對對象。當兩個不一樣的鍵對象的hashcode相同時,它們會儲存在同一個bucket位置的鏈表中,可經過鍵對象的equals()方法用來找到鍵值對。若是鏈表大小超過閾值(TREEIFY_THRESHOLD, 8),鏈表就會被改造爲樹形結構。
二、詳解
hashMap的存取就是O(1),也就是直接根據hashcode就能夠找到它,每一個bucket只存儲一個節點,鏈表指向都是null,這樣就比較開心了,不要出現一個鏈表很長的狀況。
因此咱們但願它能分佈的均勻一點,若是讓咱們設計的話,咱們確定是直接對長度取模-----hashcode % length,但HashMap的設計者卻不是這樣寫的,它寫成了2進制運算,以下:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
index = (n - 1) & hash
爲何設計成(n - 1) & hash 這樣呢?在 n 爲 2次冪的狀況下時,(n - 1) & hash ≈ hash % n ,由於2進制的運算速度遠遠高於取模,因此就使用了這種方式,因此要求爲2的冪。
咱們能夠看到它求hash的過程,將32位的hashCode值向左移動16位,高位補0,也就是隻要了高16位,這是爲何呢?由於hashcode的計算方法致使哈希值的差別主要在高位,而 (n - 1) & hash是忽略了容量以上的高位的,因此 使用h >>>16就是爲了不相似狀況的哈希衝突
1.7
put加鎖
經過分段加鎖segment,一個hashmap裏有若干個segment,每一個segment裏有若干個桶,桶裏存放K-V形式的鏈表,put數據時經過key哈希獲得該元素要添加到的segment,而後對segment進行加鎖,而後在哈希,計算獲得給元素要添加到的桶,而後遍歷桶中的鏈表,替換或新增節點到桶中
size
分段計算兩次,兩次結果相同則返回,不然對因此段加鎖從新計算
1.8
put CAS 加鎖
1.8中不依賴與segment加鎖,segment數量與桶數量一致;
首先判斷容器是否爲空,爲空則進行初始化利用volatile的sizeCtl做爲互斥手段,若是發現競爭性的初始化,就暫停在那裏,等待條件恢復,不然利用CAS設置排他標誌(U.compareAndSwapInt(this, SIZECTL, sc, -1));不然重試
對key hash計算獲得該key存放的桶位置,判斷該桶是否爲空,爲空則利用CAS設置新節點
不然使用synchronize加鎖,遍歷桶中數據,替換或新增長點到桶中
最後判斷是否須要轉爲紅黑樹,轉換以前判斷是否須要擴容
size
利用LongAdd累加計算
多線程put時可能會致使get無限循環,具體表現爲CPU使用率100%;
緣由:在向HashMap put元素時,會檢查HashMap的容量是否足夠,若是不足,則會新建一個比原來容量大兩倍的Hash表,而後把數組從老的Hash表中遷移到新的Hash表中,遷移的過程就是一個rehash()的過程,多個線程同時操做就有可能會造成循環鏈表,因此在使用get()時,就會出現Infinite Loop的狀況
當多個線程同時執行addEntry(hash,key ,value,i)時,若是產生哈希碰撞,致使兩個線程獲得一樣的bucketIndex去存儲,就可能會發生元素覆蓋丟失的狀況
public: Java語言中訪問限制最寬的修飾符,通常稱之爲「公共的」。被其修飾的類、屬性以及方法不只能夠跨類訪問,並且容許跨包(package)訪問。
private: Java語言中對訪問權限限制的最窄的修飾符,通常稱之爲「私有的」。被其修飾的類、屬性以及方法只能被該類的對象訪問,其子類不能訪問,更不能容許跨包訪問。
protect: 介於public 和 private 之間的一種訪問修飾符,通常稱之爲「保護形」。被其修飾的類、屬性以及方法只能被類自己的方法及子類訪問,即便子類在不一樣的包中也能夠訪問。
default:即不加任何訪問修飾符,一般稱爲「默認訪問模式「。該模式下,只容許在同一個包中進行訪問。
registerNatives() //私有方法
getClass() //返回此 Object 的運行類。
hashCode() //用於獲取對象的哈希值。
equals(Object obj) //用於確認兩個對象是否「相同」。
clone() //建立並返回此對象的一個副本。
toString() //返回該對象的字符串表示。
notify() //喚醒在此對象監視器上等待的單個線程。
notifyAll() //喚醒在此對象監視器上等待的全部線程。
wait(long timeout) //在其餘線程調用此對象的 notify() 方法或 notifyAll() 方法,或 者超過指定的時間量前,致使當前線程等待。
wait(long timeout, int nanos) //在其餘線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其餘某個線程中斷當前線程,或者已超過某個實際時間量前,致使當前線程等待。
wait() //用於讓當前線程失去操做權限,當前線程進入等待序列
finalize() //當垃圾回收器肯定不存在對該對象的更多引用時,由對象的垃圾回收器調用此方法。
接口和抽象類是java面向對象設計的兩個基礎機制。
擴展
java不支持多繼承的問題
接口類Marker Interface
@FunctionalInterface Annotation
面向對象的要素
須要遵照的設計原則
開關原則
里氏替換
接口分離
依賴反轉
反射與動態代理原理
1 關於反射
反射最大的做用之一就在於咱們能夠不在編譯時知道某個對象的類型,而在運行時經過提供完整的」包名+類名.class」獲得。注意:不是在編譯時,而是在運行時。
功能:
•在運行時能判斷任意一個對象所屬的類。
•在運行時能構造任意一個類的對象。
•在運行時判斷任意一個類所具備的成員變量和方法。
•在運行時調用任意一個對象的方法。
說大白話就是,利用Java反射機制咱們能夠加載一個運行時才得知名稱的class,獲悉其構造方法,並生成其對象實體,能對其fields設值並喚起其methods。
應用場景:
反射技術經常使用在各種通用框架開發中。由於爲了保證框架的通用性,須要根據配置文件加載不一樣的對象或類,並調用不一樣的方法,這個時候就會用到反射——運行時動態加載須要加載的對象。
特色:
因爲反射會額外消耗必定的系統資源,所以若是不須要動態地建立一個對象,那麼就不須要用反射。另外,反射調用方法時能夠忽略權限檢查,所以可能會破壞封裝性而致使安全問題。
2 動態代理
爲其餘對象提供一種代理以控制對這個對象的訪問。在某些狀況下,一個對象不適合或者不能直接引用另外一個對象,而代理對象能夠在二者之間起到中介的做用(可類比房屋中介,房東委託中介銷售房屋、簽定合同等)。
所謂動態代理,就是實現階段不用關心代理誰,而是在運行階段才指定代理哪一個一個對象(不肯定性)。若是是本身寫代理類的方式就是靜態代理(肯定性)。
組成要素:
(動態)代理模式主要涉及三個要素:
其一:抽象類接口
其二:被代理類(具體實現抽象接口的類)
其三:動態代理類:實際調用被代理類的方法和屬性的類
實現方式:
實現動態代理的方式不少,好比 JDK 自身提供的動態代理,就是主要利用了反射機制。還有其餘的實現方式,好比利用字節碼操做機制,相似 ASM、CGLIB(基於 ASM)、Javassist 等。
舉例,常可採用的JDK提供的動態代理接口InvocationHandler來實現動態代理類。其中invoke方法是該接口定義必須實現的,它完成對真實方法的調用。經過InvocationHandler接口,全部方法都由該Handler來進行處理,即全部被代理的方法都由InvocationHandler接管實際的處理任務。此外,咱們常能夠在invoke方法實現中增長自定義的邏輯實現,實現對被代理類的業務邏輯無侵入
經過實現Serializable接口,這種是隱式序列化(不須要手動),這種是最簡單的序列化方式,會自動序列化全部非static和 transient關鍵字修飾的成員變量。
Externalizable接口繼承自Serializable, 咱們在實現該接口時,必須實現writeExternal()和readExternal()方法,並且只能經過手動進行序列化,而且兩個方法是自動調用的,所以,這個序列化過程是可控的,能夠本身選擇哪些部分序列化
若是想將方式一和方式二的優勢都用到的話,能夠採用方式三, 先實現Serializable接口,而且添加writeObject()和readObject()方法。注意這裏是添加,不是重寫或者覆蓋。可是添加的這兩個方法必須有相應的格式。
1,方法必需要被private修飾 ----->才能被調用
2,第一行調用默認的defaultRead/WriteObject(); ----->隱式序列化非static和transient
3,調用read/writeObject()將得到的值賦給相應的值 --->顯式序列化