Java原始碼分析——java.util工具包解析(三)——HashMap、TreeMap、LinkedHashMap、Hashtable類解析
Map,中文名字對映,它儲存了鍵-值對的一對一的關係形式,並用雜湊值來作為存貯的索引依據,在查詢、插入以及刪除時的時間複雜度都為O(1),是一種在程式中用的最多的幾種資料結構。Java在java.util工具包中實現了Map介面,來作為各大Map實現類的規範,其中主要的Map實現類有三個,分別是:HashMap、TreeMap以及LinkedHashMap類,三者的關係如圖所示:
先從Map介面說起,討論其Java的Map規範以及實現的定義,在Map介面內,還定義了另外一個內部介面,該介面用來存貯鍵值對的,其原始碼如下:
interface Entry<K,V> {
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getKey().compareTo(c2.getKey());
}
public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {
return (Comparator< Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getValue().compareTo(c2.getValue());
}
//......
}
從該內部介面的實現來看,它在比較鍵或值時,需要的實現了Comparable與Comparator介面的類,以此來比較,也就是說,我們在用Map介面實現類存貯自定義的物件時,最好實現這兩個介面。通時,在定義增刪查改的基礎上,加了檢查的一項:
//檢查Map裡缺少值的鍵,並將新值賦給該鍵
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction)
//檢查Map裡包含值的鍵,並把新值賦給鍵
default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) ;
//這個方法是前兩者的綜合
default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) ;
HashMap
HashMap繼承AbstractMap類,實現了Map、 Cloneable,、Serializable介面:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable{
}
HashMap提供了4個構造器用於初始化:
//提供容量與負載因子的構造器
public HashMap(int initialCapacity, float loadFactor) {
//......
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
//提供容量的構造器
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//預設構造器
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
//提供自Map的gouzaoqi構造器
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
其中的loadFactor指的是負載因子,有的稱載入因子,是用來當雜湊桶容量不夠時,擴充套件threshold 大小使用的,雜湊桶是用來存放鍵值對的一個數組,其擴充套件的threshold 的大小是原來雜湊桶容量 的大小乘以負載因子的。threshold 就是個門檻,是來判定什麼時候需要擴充套件雜湊桶的大小的,當雜湊桶中存貯的元素多於threshold 值時,則需要進行桶容量的擴充套件,以此來優化hashMap的效率。其初始預設的桶容量、負載因子如下:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//開始擴充套件桶容量的判斷:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//......
if (++size > threshold)
resize();
//......
}
那麼,hashMap怎麼進行存貯鍵值對,而讓其查詢的時間複雜度為O(1)呢?是利用每個鍵的雜湊值,在進行存貯的時候,先計算鍵的雜湊值,通過雜湊值來確定索引值,這樣就可以完成時間複雜度為O(1)的操作:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
然而這樣會引發一個危險的操作,因為雜湊值是不確定的,桶的容量是有限的,怎麼防止通過雜湊值得到的索引值不越界,是通過位運算子來操作的:
iedex = (length - 1) & hash;
length代表著桶的容量,hash代表著鍵的雜湊值,從桶容量的擴充套件可以看出,桶的容量始終是2n,減去1用二進位制表示則是後n位全是1,前面都是0,這樣就可以成功的儲存後n位的雜湊值,從而讓它不產生越界的索引值。還有一個問題是,當鍵通過該方法產生的索引值一樣,怎麼解決這個鍵衝突,hashMap是採用經典的連結串列法來解決的,當產生的索引值上有資料時,便將該鍵鏈至當前索引值的鍵的next屬性。在hashMap類中的Node節點用來儲存鍵值對的資料:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
//......
}
hashMap儲存資料時,首先會將鍵值對插入桶中,然後檢查容量是否到了threshold 門檻,到了則進行擴容,擴容的時候需要注意的是插入到新的桶中的元素處理,與剛開始插入舊桶裡一樣,計算鍵的雜湊值並插入桶中:
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
//桶中有資料
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//新桶擴容後的門檻
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
//從舊桶中取出資料插入新桶中
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
對於插入到桶中的元素,其操作策略是先判斷桶的容量夠不夠,再進行插入操作:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//如果存貯的數量達到門檻
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
其中注意的是:
static final int TREEIFY_THRESHOLD = 8;
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
這句話的意思是如果某個桶中的元素超過了8個,那麼就不再用連結串列法來解決鍵衝突,而是將整個連結串列轉化成一個平衡二叉樹來提升效率。樹的節點定義如下:
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
}
HashMap裡的平衡二叉樹是由紅黑樹來實現的,限於篇幅原因就不再講解樹的知識(就是懶,23333)。
TreeMap
TreeMap類,如其名,是一個以樹結構來實現Map的一個類,其中節點的定義如下:
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;
}
TreeMap是以紅黑樹來做存貯結構的,因為TreeMap是有序的,所以它存貯的物件都是必須實現Comparable或者Comparator介面的,以便於排序,它有4個構造方法:
//預設的構造方法
public TreeMap() {
comparator = null;
}
//接受外部的比較器
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
//接受一個Map實現類
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
}
//接受一個SortedMap實現類
public TreeMap(SortedMap<K, ? extends V> m) {
comparator = m.comparator();
try {
buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
} catch (java.io.IOException cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
}
預設是不用外部的比較器的,因為傳進來的的物件是實現了Comparable或者Comparator介面。它的插入方法:
public V put(K key, V value)
{
// 先以 t 儲存連結串列的 root 節點
Entry<K,V> t = root;
// 如果 t==null,表明是一個空連結串列,即該 TreeMap 裡沒有任何 Entry
if (t == null)
{
// 將新的 key-value 建立一個 Entry,並將該 Entry 作為 root
root = new Entry<K,V>(key, value, null);
// 設定該 Map 集合的 size 為 1,代表包含一個 Entry
size = 1;
// 記錄修改次數為 1
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
Comparator<? super K> cpr = comparator;
// 如果比較器 cpr 不為 null,即表明採用定製排序
if (cpr != null)
{
do {
// 使用 parent 上次迴圈後的 t 所引用的 Entry
parent = t;
// 拿新插入 key 和 t 的 key 進行比較
cmp = cpr.compare(key, t.key);
// 如果新插入的 key 小於 t 的 key,t 等於 t 的左邊節點
if (cmp < 0)
t = t.left;
// 如果新插入的 key 大於 t 的 key,t 等於 t 的右邊節點
else if (cmp > 0)
t = t.right;
// 如果兩個 key 相等,新的 value 覆蓋原有的 value,
// 並返回原有的 value
else
return t.setValue(value);
} while (t != null);
}
else
{
if (key == null)
throw new NullPointerException();
Comparable<?
相關推薦
Java原始碼分析——java.util工具包解析(三)——HashMap、TreeMap、LinkedHashMap、Hashtable類解析
Map,中文名字對映,它儲存了鍵-值對的一對一的關係形式,並用雜湊值來作為存貯的索引依據,在查詢、插入以及刪除時的時間複雜度都為O(1),是一種在程式中用的最多的幾種資料結構。Java在java.util工具包中實現了Map介面,來作為各大
spark mllib原始碼分析之隨機森林(Random Forest)(三)
6. 隨機森林訓練
6.1. 資料結構
6.1.1. Node
樹中的每個節點是一個Node結構
class Node @Since("1.2.0") (
@Since("1.0.0") val id: Int,
@S
Java併發程式設計——執行緒池的使用(三)執行緒池執行任務、取消任務
一、執行緒池執行Runnable任務
executor.execute(runnable) executor.execute(new Runnable() {
@Override
public void run(
Java原始碼分析——java.util工具包解析(五)——UUID、Base64、內建觀察者模式Observer介面、EventListener、RandomAccess
UUID
關於UUID,我們需要知道它最重要的一點,就是它會生成全地球唯一的一個id,它可以作為資料庫的主鍵存在,標識各個元組。 UUID保證對在同一時空中的所有機器都是唯一的,利用機器的當前日期和時間、時鐘序列、全域性唯一的IEEE機
Java原始碼分析——java.util工具包解析(四)——四大引用型別以及WeakHashMap類解析
WeakHashMap是Map的一種很獨特的實現,從它的名字可以看出,它是存貯弱引用的對映的,先來複習一下Java中的四大引用型別:
強引用:我們使用的大部分引用實際上都是強引用,這是使用最普遍的引用。強引用的物件垃圾回收器絕不
Java原始碼分析——java.util工具包解析(二)——HashSet、TreeSet、LinkedHashSet類解析
Set,即集合,與數學上的定義一樣,集合具有三個特點:
無序性:一個集合中,每個元素的地位都是相同的,元素之間是無序的。
互異性:一個集合中,任何兩個元素都認為是不相同的,即每個元素只能出現一次。
確定性:給定一個集
Java原始碼分析——java.util工具包解析(一)——ArrayList、LinkedList、Vector類解析
Java中,List列表類與Set集合類的共同源頭是Collection介面,而Collection的父介面是Iterable介面,在Collection介面下又實現了三個常用的介面以及一個抽象方法,分別為Queue介面、List介面、Se
Java原始碼分析——java.lang.reflect反射包解析(三) 動態代理、Proxy類、WeakCache類
代理模式是一個經常被各種框架使用的模式,比如Spring AOP、Mybatis中就經常用到,當一個類訪問另外一個類困難時,可通過一個代理類來間接訪問,在Java中,為了保證程式的簡單性,代理類與目標類需要實現相同的介面。也就是說代理模式起
Java原始碼分析——java.lang.reflect反射包解析(二) Array類,陣列的建立
在Java中,引用型別中有那麼幾個特殊的類,Object類是所有類的起源、Class類定義所有類的抽象與行為、ClassLoader類實現了類從.class檔案中載入進jvm,而Array陣列類,則實現了陣列手動的建立。
&
Java原始碼分析——java.lang.reflect反射包解析(一) AccessibleObject、ReflectionFactory、Filed、Method、Constructor類
Java的反射機制一直是被人稱讚的,它的定義是:程式在執行中時,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性。簡單的來說就是可以通過Java的反射機制知道自己想知道的類的一切資訊。
Java原始碼分析--java.util.ArrayList
序列化問題
/** 使用transient關鍵字,即使繼承了Serializable,也不會序列化
* 一般情況下 elementData.capacity < element.size,我們並不希望將空的元素也序列化
* ps: 可以看
Java原始碼分析--java.util.Hashtable
都說Hashtable是執行緒安全的,我們看一看Hashtable與HashMap有那些不同。
定址方式
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
//
java集合原始碼解析(三)--List
今天給大家帶來有序集合的介面List,我想也應該是大家在工作中用的比較多的
先來看看介面的定義:
public interface List<E> extends Collection<E>可以看出介面List直接繼承於介面Collection,並且一樣使用了
java原始碼分析——java.lang.Object
所有的java類均繼承Object類,
package java.lang;
public class Object {
public Object() {
}
private static native void registerNative
Java併發之ThreadPoolExecutor原始碼解析(三)
Worker
先前,筆者講解到ThreadPoolExecutor.addWorker(Runnable firstTask, boolean core),在這個方法中工作執行緒可能建立成功,也可能建立失敗,具體視執行緒池的邊界條件,以及當前記憶體情況而定。
那麼,如果執行緒池當前的狀態,是允許建立Worke
死磕java concurrent包系列(三)基於ReentrantLock理解AQS的條件佇列
基於Codition分析AQS的條件佇列
前言
上一篇我們講了AQS中的同步佇列佇列,現在我們研究一下條件佇列。
在java中最常見的加鎖方式就是synchorinzed和Reentrantlock,我們都說Reentrantlock比synchorinzed更加靈活,其實就靈活在Reentrantlock中
【Java原始碼】基於陣列實現的ArrayList(上)
眾所周知,Java中ArrayList是基於陣列實現的
咱們先看其基本屬性:
private static final int DEFAULT_CAPACITY = 10;
private static final Object[
Java單元測試工具:JUnit4(三)——JUnit詳解之執行流程及常用註解
(三)執行流程及常用註解
這篇筆記記錄JUnit測試類執行時,類中方法的執行順序;以及JUnit中常用的註解。
1.JUnit的執行流程
1.1 新建測試類
Java基礎(三)HashMap原始碼剖析
關於HashMap,在網上看到了不少的好文章,萬花叢中過的過程中,我自己卻有了很大的感慨。
關於HashMap很多好的文章介紹,都是關注於HashMap的一個點,進行展開介紹。簡簡單單幾張圖,幾行文字
java執行緒深度解析(三)——併發模型(Future)
Main:啟動系統,呼叫Client發出請求;
Client:返回Data物件,理解返回FutureData,並開啟ClientThread執行緒裝配RealData;
Data:返回資料的介面;
FutureData:Future資料,構造很快,但是是一個虛擬的資料,需要裝配RealData;
RealD