1. 程式人生 > >HashMap jdk1.8版本新特性講解

HashMap jdk1.8版本新特性講解

大家好,我是IT修真院北京分院第31期的學員,一枚正直純潔善良的JAVA程式設計師。今天給大家分享一下,HashMap jdk1.8版 特性講解.

1.背景介紹

什麼是HASHMAP?

HashMap是基於雜湊表的Map介面的實現,儲存的是鍵值對,並允許使用null鍵和null值.HashMap是非Synchronized,即是執行緒不安全的.如何要滿足執行緒安全可以使用ConcurrentHashMap.HashMap根據鍵的hashCode值儲存資料,大多數情況下可以直接定位到它的值,因而具有很快的訪問速度,但遍歷順序是不確定的.

雜湊表及MAP介面

雜湊表(也叫散列表),是根據關鍵碼值(key value)而直接進行訪問的資料結構.

也就是說,它通過把關鍵碼值(key value)對映到表中一個位置來訪問記錄,以加快查詢的速度.這個對映函式叫做雜湊函式,存放記錄的陣列叫做散列表.

Map主要用於儲存鍵值對.Map的三個特點:1,包含鍵值對;2,鍵唯一;3,鍵對應的值唯一.Map還提供了3個集合檢視,分別是一組鍵值對,一組鍵,一組值.

MAP介面主要常用的實現類

Map介面主要有四個常用的實用類,分別是HashMap,Hashtable,LinkedHashMap和TreeMap.類繼承關係如下圖所示:

HASHTABLE

Hashtable是遺留類,很多對映的常用功能與HashMap類似,不同的是它繼承自Dictionary類,並且是執行緒安全的,任一時間只有一個執行緒能寫Hashtable.Hashtable不建議在新程式碼中使用,不需要執行緒安全的場合可以用HashMap替換,需要執行緒安全的場合可以用ConcurrentHashMap替換.

雜湊表及Map介面 雜湊表(也叫散列表),是根據關鍵碼值(key value)而直接進行訪問的資料結構. 也就是說,它通過把關鍵碼值(key value)對映到表中一個位置來訪問記錄,以加快查詢的速度.這個對映函式叫做雜湊函式,存放記錄的陣列叫做散列表. Map主要用於儲存鍵值對.Map的三個特點:1,包含鍵值對;2,鍵唯一;3,鍵對應的值唯一.Map還提供了3個集合檢視,分別是一組鍵值對,一組鍵,一組值.

MAP介面主要常用的實現類

Map介面主要有四個常用的實用類,分別是HashMap,Hashtable,LinkedHashMap和TreeMap.類繼承關係如下圖所示:

HASHTABLE

Hashtable是遺留類,很多對映的常用功能與HashMap類似,不同的是它繼承自Dictionary類,並且是執行緒安全的,任一時間只有一個執行緒能寫Hashtable.Hashtable不建議在新程式碼中使用,不需要執行緒安全的場合可以用HashMap替換,需要執行緒安全的場合可以用ConcurrentHashMap替換.

LINKEDHASHMAP

LinkedHashMap是HashMap的一個子類,儲存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先得到的記錄肯定是先插入的,也可以在構造時帶引數,按照訪問次序排序.

Map介面主要常用的實現類 Map介面主要有四個常用的實用類,分別是HashMap,Hashtable,LinkedHashMap和TreeMap.類繼承關係如下圖所示:

HASHTABLE

Hashtable是遺留類,很多對映的常用功能與HashMap類似,不同的是它繼承自Dictionary類,並且是執行緒安全的,任一時間只有一個執行緒能寫Hashtable.Hashtable不建議在新程式碼中使用,不需要執行緒安全的場合可以用HashMap替換,需要執行緒安全的場合可以用ConcurrentHashMap替換.

LINKEDHASHMAP

LinkedHashMap是HashMap的一個子類,儲存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先得到的記錄肯定是先插入的,也可以在構造時帶引數,按照訪問次序排序.

TREEMAP

TreeMap實現SortedMap介面,能夠把它儲存的記錄根據鍵排序,預設是按鍵值的升序排序,也可以指定排序的比較器,當用Iterator遍歷TreeMap時,得到的記錄是排過序的。如果使用排序的對映,建議使用TreeMap。在使用TreeMap時,key必須實現Comparable介面或者在構造TreeMap傳入自定義的Comparator,否則會在執行時丟擲java.lang.ClassCastException型別的異常。

Hashtable Hashtable是遺留類,很多對映的常用功能與HashMap類似,不同的是它繼承自Dictionary類,並且是執行緒安全的,任一時間只有一個執行緒能寫Hashtable.Hashtable不建議在新程式碼中使用,不需要執行緒安全的場合可以用HashMap替換,需要執行緒安全的場合可以用ConcurrentHashMap替換.

2.知識剖析

JAVA 8系列之重新認識HASHMAP

Java1.8對HashMap底層的實現進行了優化,例如紅黑樹的資料結構和擴容的優化等,接下來我們將深入探討HashMap 的結構實現和功能原理.

結構實現

從結構實現來講,HashMap是陣列+連結串列+紅黑樹(JDK1.8增加了紅黑樹部分)實現的,如下圖所示. 

這裡需要講明白兩個問題:資料底層具體儲存的是什麼?這樣的儲存方法有什麼優點?

(1)HashMap類中有一個非常重要的欄位,就是Node[]table,即雜湊桶陣列,明顯它是一個Node的陣列.Node就是HashMap的一個內部類,實現了Map.Entry介面,本質就是一個對映(鍵值對).

(2)HashMap就是使用雜湊表來儲存的.在上面已經介紹了鍵值對是通過雜湊函式(雜湊函式)對映到雜湊表的地址上的,而所有的雜湊函式都有如下一個基本特徵:根據同一雜湊函式計算出來的雜湊值不同,那麼輸入的資訊肯定也不同.但是,根據同一雜湊函式計算出的雜湊值如果相同,輸入值不一定相同.

這句話的意思就是輸入不同的兩個值可能得到一樣的雜湊值.這種現象叫做碰撞.Java中HashMap為了解決衝突採用連結串列地址法.連結串列地址法:將雜湊表的每個單元作為連結串列的頭結點,所有雜湊地址為i的元素構成一個同義詞連結串列。即發生衝突時就把該關鍵字鏈在以該單元為頭結點的連結串列的尾部。

舉個例子,例如程式執行下面的程式碼:map.put("美團","小小");系統將呼叫"美團"這個key的hashCode()方法得到其hashCode 值(該方法適用於每個Java物件),然後再通過Hash演算法的後兩步運算(高位運算和取模運算,下文有介紹)來定位該鍵值對的儲存位置,有時兩個key會定位到相同的位置,表示發生了Hash碰撞。當然Hash演算法計算結果越分散均勻,Hash碰撞的概率就越小。如果雜湊桶陣列很大,即使較差的Hash演算法也會比較分散,所以就需要在空間成本和時間成本之間權衡.那麼通過什麼方式來控制map使得Hash碰撞的概率又小,雜湊桶陣列(Node[] table)佔用空間又少呢?答案就是好的Hash演算法和擴容機制。

在理解Hash和擴容流程之前,我們得先了解下HashMap的幾個欄位。從HashMap的預設建構函式原始碼可知,建構函式就是對下面幾個欄位進行初始化,原始碼如下: int threshold; // 所能容納的key-value對極限 final float loadFactor; // 負載因子 int modCount; int size;

首先,Node[] table的初始化長度length(預設值是16),Loadfactor為負載因子(預設值是0.75),threshold是HashMap所能容納的最大資料量的Node(鍵值對)個數。threshold = length * Load factor。也就是說,在陣列定義好長度之後,負載因子越大,所能容納的鍵值對個數越多。結合負載因子的定義公式可知,threshold就是在此Load factor和length(陣列長度)對應下允許的最大元素數目,超過這個數目 就重新resize(擴容),擴容後的HashMap容量是之前容量的兩倍。

舉個例子,例如程式執行下面的程式碼:map.put("美團","小小");系統將呼叫"美團"這個key的hashCode()方法得到其hashCode 值(該方法適用於每個Java物件),然後再通過Hash演算法的後兩步運算(高位運算和取模運算,下文有介紹)來定位該鍵值對的儲存位置,有時兩個key會定位到相同的位置,表示發生了Hash碰撞。當然Hash演算法計算結果越分散均勻,Hash碰撞的概率就越小。如果雜湊桶陣列很大,即使較差的Hash演算法也會比較分散,所以就需要在空間成本和時間成本之間權衡.那麼通過什麼方式來控制map使得Hash碰撞的概率又小,雜湊桶陣列(Node[] table)佔用空間又少呢?答案就是好的Hash演算法和擴容機制。

在理解Hash和擴容流程之前,我們得先了解下HashMap的幾個欄位。從HashMap的預設建構函式原始碼可知,建構函式就是對下面幾個欄位進行初始化,原始碼如下: int threshold; // 所能容納的key-value對極限 final float loadFactor; // 負載因子 int modCount; int size;

首先,Node[] table的初始化長度length(預設值是16),Loadfactor為負載因子(預設值是0.75),threshold是HashMap所能容納的最大資料量的Node(鍵值對)個數。threshold = length * Load factor。也就是說,在陣列定義好長度之後,負載因子越大,所能容納的鍵值對個數越多。結合負載因子的定義公式可知,threshold就是在此Load factor和length(陣列長度)對應下允許的最大元素數目,超過這個數目 就重新resize(擴容),擴容後的HashMap容量是之前容量的兩倍。

預設的負載因子0.75是對空間和時間效率的一個平衡選擇,建議大家不要修改,除非在時間和空間比較特殊的情況下,如果記憶體空間很多而又對時間效率要求很高,可以降低負載因子Load factor的值;相反,如果記憶體空間緊張而對時間效率要求不高,可以增加負載因子loadFactor的值,這個值可以大於1。

size這個欄位其實很好理解,就是HashMap中實際存在的鍵值對數量。modCount欄位主要用來記錄HashMap內部結構發生變化的次數,例如put新鍵值對,但是某個key對應的value值被覆蓋不屬於結構變化。在HashMap中,雜湊桶陣列table的長度length大小必須為2的n次方(一定是合數),HashMap採用這種非常規設計,主要是為了在取模和擴容時做優化,同時為了減少衝突,HashMap定位雜湊桶索引位置時,也加入了高位參與運算的過程。

這裡存在一個問題,即使負載因子和Hash演算法設計的再合理,也免不了會出現拉鍊過長的情況,一旦出現拉鍊過長,則會嚴重影響HashMap的效能。於是,在JDK1.8版本中,對資料結構做了進一步的優化,引入了紅黑樹。而當連結串列長度太長(預設超過8)時,連結串列就轉換為紅黑樹,利用紅黑樹快速增刪改查的特點提高HashMap的效能.

功能實現-方法

接下來我將結合HashCode的原始碼來為大家講解以下三個具有代表性的功能點:

1, 確定雜湊桶陣列索引位置; 2, 分析HashMap的put方法; 3,擴容機制.

size這個欄位其實很好理解,就是HashMap中實際存在的鍵值對數量。modCount欄位主要用來記錄HashMap內部結構發生變化的次數,例如put新鍵值對,但是某個key對應的value值被覆蓋不屬於結構變化。在HashMap中,雜湊桶陣列table的長度length大小必須為2的n次方(一定是合數),HashMap採用這種非常規設計,主要是為了在取模和擴容時做優化,同時為了減少衝突,HashMap定位雜湊桶索引位置時,也加入了高位參與運算的過程。

這裡存在一個問題,即使負載因子和Hash演算法設計的再合理,也免不了會出現拉鍊過長的情況,一旦出現拉鍊過長,則會嚴重影響HashMap的效能。於是,在JDK1.8版本中,對資料結構做了進一步的優化,引入了紅黑樹。而當連結串列長度太長(預設超過8)時,連結串列就轉換為紅黑樹,利用紅黑樹快速增刪改查的特點提高HashMap的效能.

3.編碼實戰

4.常見問題

一,為什麼STRING, INTERGER這樣的WRAPPER類適合作為鍵?

String, Interger這樣的wrapper類作為HashMap的鍵是再適合不過了,而且String最為常用。因為String是不可變的,也是final的,而且已經重寫了equals()和hashCode()方法了。其他的wrapper類也有這個特點。不可變性是必要的,因為為了要計算hashCode(),就要防止鍵值改變,如果鍵值在放入時和獲取時返回不同的hashcode的話,那麼就不能從HashMap中找到你想要的物件。因為獲取物件的時候要用到equals()和hashCode()方法,那麼鍵物件正確的重寫這兩個方法是非常重要的。如果兩個不相等的物件返回不同的hashcode的話,那麼碰撞的機率就會小些,這樣就能提高HashMap的效能。

二,我們可以使用自定義的物件作為鍵嗎?

這是前一個問題的延伸。當然你可能使用任何物件作為鍵,只要它遵守了equals()和hashCode()方法的定義規則,並且當物件插入到Map中之後將不會再改變了。如果這個自定義物件時不可變的,那麼它已經滿足了作為鍵的條件,因為當它建立之後就已經不能改變了。

三,我們可以使用COCURRENTHASHMAP來代替HASHTABLE嗎?

這是另外一個很熱門的面試題,因為ConcurrentHashMap越來越多人用了。我們知道Hashtable是synchronized的,但是ConcurrentHashMap同步效能更好,因為它僅僅根據同步級別對map的一部分進行上鎖。ConcurrentHashMap當然可以代替HashTable,但是HashTable提供更強的執行緒安全性。

5.參考文獻

https://blog.csdn.net/abcdad/article/details/64123291

https://zhuanlan.zhihu.com/p/21673805

https://blog.csdn.net/v_july_v/article/details/6105630

6.更多討論

相關推薦

HashMap jdk1.8版本特性講解

大家好,我是IT修真院北京分院第31期的學員,一枚正直純潔善良的JAVA程式設計師。今天給大家分享一下,HashMap jdk1.8版 特性講解.1.背景介紹什麼是HASHMAP?HashMap是基於雜湊表的Map介面的實現,儲存的是鍵值對,並允許使用null鍵和null值.

010-jdk1.8版本特性二-Optional類,Stream流

字段 combine 特征 love 1.8 filter etc 靜態 語句 1.5、Optional類 1、定義 Optional 類是一個可以為null的容器對象。如果值存在則isPresent()方法會返回true,調用get()方法會返回該對象。 Optio

jdk1.8排序特性

pareto public args java 7 AD baidu sort Go add import java.util.Collections;import java.util.List;import java.util.ArrayList;import java.

JAVA 8 主要特性 ----------------(二)版本中資料結構的修改淺析

一、版本中資料結構的修改淺析1、HashMap、HashSet、ConcurrentHashMap的資料結構發生變化   (1)HashMap簡介(結構:雜湊表+連結串列)          HashMap儲存的資料是無序的,結構雜湊表加連結串列

JAVA 8 主要特性 ----------------(二)JDK1.8優點概括

一、JDK1.8優點概括     1、速度更快            由於底層結構和JVM的改變,使得JDK1.8的速度提高。      2、程式碼更少(增加了新的語法 Lambda 表示式)&

java1.8特性降級為jdk1.7

/** * map的key表示式 */ private Function<? super T, ? extends K> keyMapper; /** * map的value表示式 */ private Function<? super T, ? extends V

JDK1.5以後各版本特性

JDK各個版本的新特性   對於很多剛接觸java語言的初學者來說,要了解一門語言,最好的方式就是要能從基礎的版本進行了解,升級的過程,以及升級的新特性,這樣才能循序漸進的學好一門語言。今天先為大家介紹一下JDK1.5版本到JDK1.7版本的特性。希望能給予幫助。 JDK1.5新特性: 1.自

python基礎8之類的特性講解

attr blog 成員 私有方法 -s error 對象 構造 size 一、概述 在上篇博客中我們已經講了一些關於類的知識,我們來回顧以下: 定義類(class dog(object))-> 實例化(d = dog()) -> 實例化對象(d) __ini

MySQL 8.0特性

MySQL8.0新特性一、MySQL 8.0中添加的功能1、新的系統字典表 整合了存儲有關數據庫對象信息的事務數據字典,所有的元數據都用InnoDB引擎進行存儲2、支持DDL 原子操作 InnoDB表的DDL支持事務完整性,要麽成功要麽回滾,將DDL操作回滾日誌寫入到data dictionary

Kotlin的特點及各版本特性

文章地址:sguotao.top/Kotlin-2018… Kotlin語言的特點 Kotlin語義是一座小島,是一種在Java虛擬機器上執行的靜態型別程式語言,Kotlin的目的就是要兼具現代程式語言的所有優點,同時還要具有Java語言的跨平臺性,並且要做到簡潔。它也可以被編譯成為JavaScript原

【java】java各版本特性總結

Java5: 1、泛型 Generics:         引用泛型之後,允許指定集合裡元素的型別,免去了強制型別轉換,並且能在編譯時刻進行型別檢查的好處。     &nb

在伺服器中安裝jdk1.8版本的安裝,原來這麼簡單(詳解)

因為在烏班圖的系統中由於只能註冊普通的使用者,不能註冊root使用者。 所以需要先把jdk-8u11-linux-x64.tar.gz的安裝包拷貝到普通使用者的許可權中去。 我們可以使用WinSCP視覺化工具直接對壓縮包進行拖拽到指定的目錄下,也可以使用Xshell工具使用命令列對檔

[翻譯] C# 8.0 特性

原文: [翻譯] C# 8.0 新特性 原文: Building C# 8.0 [譯註:原文主標題如此,但內容大部分為新特性介紹,所以意譯標題為 "C# 8.0 新特性"] C# 的下一個主要版本是 8.0。我們已經為它工作了很長一段時間,即使我們構建併發布了次要版本 C# 7.1, 7.2 和 7.3,

Java1.8特性詳解

前言: Java 8 已經發布很久了,很多報道表明Java 8 是一次重大的版本升級。在Java Code Geeks上已經有很多介紹Java 8新特性的文章,例如Playing with Java 8 – Lambdas and Concurrency、Java 8 Date Time

普元DevOps5.2版本特性發布

轉載本文需註明出處:EAWorld,違者必究。 作者自白: 伴隨新版本的釋出,我們團隊也對這次迭代做了些回顧,有值得分享的新特性與設計,也有一些需加強的能力,藉此與大家分享。 主題大綱: 一、新特性部分 1、安全提升,更細粒度的流程與許可權控制 2、企業級中介軟體支援,更匹配普元現有客戶

Java 11的8特性

Java 11又出新版本了,我還在Java8上停著。不過這也擋不住我對他的熱愛,忍不住查看了一下他的新效能,由於自己知識有限,只總結了以下八個特性; 1、本地變數型別推斷 什麼是區域性變數型別推斷?     var javastack = "javastack";  &nb

JAVA 8 主要特性 ----------------(六)集合Stream API

一、簡介Stream         Java8中有兩大最為重要的改變。第一個是 Lambda 表示式;另外一 個則是 Stream API(java.util.stream.*)。Stream 是 Java8 中處理集合的關鍵抽象概念,它可以指定你希望對集

MySQL 8.0特性--CTE(二)

上一篇介紹了CTE的基本用法,參考MySQL 8.0新特性--CTE(一),本篇再來介紹一下CTE Recurive遞迴。 1、什麼是CTE Recurive? A recursive common table expression is one having a subquery that refe

JAVA 8 主要特性 ----------------(七)時間日期 API -----LocalDate

一、改版原因        1、老闆的Date和Calander存在問題,日期操作名稱混亂,有的在text下,有的在util下,包名混亂         2、Simple包混亂,致命錯誤執行緒不安全。  &n

MySQL 8.0特性之Windows Function

MySQL8.0開始像Oracle一樣支援視窗函數了,先來跑個SQL,感受一下視窗函式的魅力。 mysql> create table numbers(val int); mysql> insert into numb