Java容器(List、Set、Map)知識點快速複習手冊(中)

Java容器(List、Set、Map)知識點快速複習手冊(中)

前言

本文快速回顧了Java中容器的知識點,用做面試複習,事半功倍。html

上篇:主要爲容器概覽,容器中用到的設計模式,List源碼java

中篇:Map源碼git

下篇:Set源碼,容器總結github

其它知識點複習手冊

  • Java基礎知識點面試手冊(上)
  • Java基礎知識點面試手冊(下)
  • Java容器(List、Set、Map)知識點快速複習手冊(上)

HashMap

http://wiki.jikexueyuan.com/project/java-collection/hashmap.html面試

源碼分析:算法

http://www.javashuo.com/article/p-ykttmamr-gg.html編程

關鍵詞

  • 初始容量16
  • 擴容是2倍,加載因子0.75
  • 頭插法
  • 0桶存放null
  • 從 JDK 1.8 開始,一個桶存儲的鏈表長度大於 8 時會將鏈表轉換爲紅黑樹(前提:鍵值對要超過64個)
  • 自動地將傳入的容量轉換爲2的冪次方
  • 保證運算速度:確保用位運算代替模運算來計算桶下標。hash& (length-1)運算等價於對 length 取模。
  • hash均勻分佈:數據在數組上分佈就比較均勻,而且可以利用所有二進制位,也就是說碰撞的概率小,
  • table數組+Entry
    []鏈表(散列表),紅黑樹
  • 擴容操做須要把鍵值對從新插入新的 table 中,從新計算全部key有特殊機制(JDK1.8後)

存儲結構

hashMap的一個內部類Node:segmentfault

1static class Node<K,V> implements Map.Entry<K,V> {
2        final int hash;
3        final K key;
4        V value;
5        Node<K,V> next; //鏈表結構,存儲下一個元素

Java容器(List、Set、Map)知識點快速複習手冊(中)
在這裏插入圖片描述
Node內部包含了一個 Entry 類型的數組table,數組中的每一個位置被當成一個桶。設計模式

1transient Entry[] table;

Entry 存儲着鍵值對。它包含了四個字段,從 next 字段咱們能夠看出 Entry 是一個鏈表。即數組中的每一個位置被當成一個桶,一個桶存放一個鏈表。數組

HashMap 使用拉鍊法來解決衝突,同一個鏈表中存放哈希值相同的 Entry。

1static class Entry<K,V> implements Map.Entry<K,V> {
 2    final K key;
 3    V value;
 4    Entry<K,V> next;
 5    int hash;
 6
 7    Entry(int h, K k, V v, Entry<K,V> n) {
 8        value = v;
 9        next = n;
10        key = k;
11        hash = h;
12    }
13
14    public final K getKey() {
15        return key;
16    }
17
18    public final V getValue() {
19        return value;
20    }
21
22    public final V setValue(V newValue) {
23        V oldValue = value;
24        value = newValue;
25        return oldValue;
26    }
27
28    public final boolean equals(Object o) {
29        if (!(o instanceof Map.Entry))
30            return false;
31        Map.Entry e = (Map.Entry)o;
32        Object k1 = getKey();
33        Object k2 = e.getKey();
34        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
35            Object v1 = getValue();
36            Object v2 = e.getValue();
37            if (v1 == v2 || (v1 != null && v1.equals(v2)))
38                return true;
39        }
40        return false;
41    }
42
43    public final int hashCode() {
44        return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
45    }
46
47    public final String toString() {
48        return getKey() + "=" + getValue();
49    }
50}

構造器

Java容器(List、Set、Map)知識點快速複習手冊(中)
在這裏插入圖片描述

構造時就會調用tableSizeFor():返回一個大於輸入參數且最近的2的整數次冪。

1static final int tableSizeFor(int cap) {
2    int n = cap - 1;
3    n |= n >>> 1;
4    n |= n >>> 2;
5    n |= n >>> 4;
6    n |= n >>> 8;
7    n |= n >>> 16;
8    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
9}

拉鍊法

應該注意到鏈表的插入是以頭插法方式進行的

1HashMap<String, String> map = new HashMap<>();
2map.put("K1", "V1");
3map.put("K2", "V2");
4map.put("K3", "V3");
  • 新建一個 HashMap,默認大小爲 16;
  • 插入 <K1,V1> 鍵值對,先計算 K1 的 hashCode 爲 115,使用除留餘數法獲得所在的桶下標 115%16=3。
  • 插入 <K2,V2> 鍵值對,先計算 K2 的 hashCode 爲 118,使用除留餘數法獲得所在的桶下標 118%16=6。
  • 插入 <K3,V3> 鍵值對,先計算 K3 的 hashCode 爲 118,使用除留餘數法獲得所在的桶下標 118%16=6,插在 <K2,V2> 前面。

查找須要分紅兩步進行:

計算鍵值對所在的桶;
在鏈表上順序查找,時間複雜度顯然和鏈表的長度成正比。

put 操做

  • 當咱們 put 的時候,若是 key 存在了,那麼新的 value 會代替舊的 value
  • 若是 key 存在的狀況下,該方法返回的是舊的 value,
  • 若是 key 不存在,那麼返回 null。
1public V put(K key, V value) {
 2    if (table == EMPTY_TABLE) {
 3        inflateTable(threshold);
 4    }
 5    // 鍵爲 null 單獨處理
 6    if (key == null)
 7        return putForNullKey(value);
 8    int hash = hash(key);
 9    // 肯定桶下標
10    int i = indexFor(hash, table.length);
11    // 先找出是否已經存在鍵爲 key 的鍵值對,若是存在的話就更新這個鍵值對的值爲 value
12    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
13        Object k;
14        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
15            V oldValue = e.value;
16            e.value = value;
17            e.recordAccess(this);
18            return oldValue;
19        }
20    }
21
22    modCount++;
23    // 插入新鍵值對
24    addEntry(hash, key, value, i);
25    return null;
26}

HashMap 容許插入鍵爲 null 的鍵值對。可是由於沒法調用 null 的 hashCode() 方法,也就沒法肯定該鍵值對的桶下標,只能經過強制指定一個桶下標來存放。HashMap 使用第 0 個桶存放鍵爲 null 的鍵值對。

1private V putForNullKey(V value) {
 2    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
 3        if (e.key == null) {
 4            V oldValue = e.value;
 5            e.value = value;
 6            e.recordAccess(this);
 7            return oldValue;
 8        }
 9    }
10    modCount++;
11    addEntry(0, null, value, 0);
12    return null;
13}

使用鏈表的頭插法,也就是新的鍵值對插在鏈表的頭部,而不是鏈表的尾部。

1void addEntry(int hash, K key, V value, int bucketIndex) {
 2    if ((size >= threshold) && (null != table[bucketIndex])) {
 3        resize(2 * table.length);
 4        hash = (null != key) ? hash(key) : 0;
 5        bucketIndex = indexFor(hash, table.length);
 6    }
 7
 8    createEntry(hash, key, value, bucketIndex);
 9}
10
11void createEntry(int hash, K key, V value, int bucketIndex) {
12    Entry<K,V> e = table[bucketIndex];
13    // 頭插法,鏈表頭部指向新的鍵值對
14    table[bucketIndex] = new Entry<>(hash, key, value, e);
15    size++;
16}
1Entry(int h, K k, V v, Entry<K,V> n) {
2    value = v;
3    next = n;
4    key = k;
5    hash = h;
6}

補充:hashmap裏hash方法的高位優化:

http://www.javashuo.com/article/p-wcboirew-cq.html

https://note.youdao.com/yws/res/18743/50AADC7BB42845B29CDA293FC409250C?ynotemdtimestamp=1548155508277

設計者將key的哈希值的高位也作了運算(與高16位作異或運算,使得在作&運算時,此時的低位其實是高位與低位的結合),這就增長了隨機性,減小了碰撞衝突的可能性!

爲什麼要這麼作?

table的長度都是2的冪,所以index僅與hash值的低n位有關,hash值的高位都被與操做置爲0了。

這樣作很容易產生碰撞。設計者權衡了speed, utility, and quality,將高16位與低16位異或來減小這種影響。設計者考慮到如今的hashCode分佈的已經很不錯了,並且當發生較大碰撞時也用樹形存儲下降了衝突。僅僅異或一下,既減小了系統的開銷,也不會形成的由於高位沒有參與下標的計算(table長度比較小時),從而引發的碰撞。

肯定桶下標

不少操做都須要先肯定一個鍵值對所在的桶下標。

1int hash = hash(key);
2int i = indexFor(hash, table.length);

4.1 計算 hash 值

1final int hash(Object k) {
 2    int h = hashSeed;
 3    if (0 != h && k instanceof String) {
 4        return sun.misc.Hashing.stringHash32((String) k);
 5    }
 6
 7    h ^= k.hashCode();
 8
 9    // This function ensures that hashCodes that differ only by
10    // constant multiples at each bit position have a bounded
11    // number of collisions (approximately 8 at default load factor).
12    h ^= (h >>> 20) ^ (h >>> 12);
13    return h ^ (h >>> 7) ^ (h >>> 4);
14}
1public final int hashCode() {
2    return Objects.hashCode(key) ^ Objects.hashCode(value);
3}

4.2 取模

令 x = 1<\<\4,即 \x 爲 2 的 4 次方,它具備如下性質:

1x   : 00010000
2x-1 : 00001111

令一個數 y 與 x-1 作與運算,能夠去除 y 位級表示的第 4 位以上數:

1y       : 10110010
2x-1     : 00001111
3y&(x-1) : 00000010

這個性質和 y 對 x 取模效果是同樣的:

1y   : 10110010
2x   : 00010000
3y%x : 00000010

咱們知道,位運算的代價比求模運算小的多,所以在進行這種計算時用位運算的話能帶來更高的性能。

肯定桶下標的最後一步是將 key 的 hash 值對桶個數取模:hash%capacity,若是能保證 capacity 爲 2 的 n 次方,那麼就能夠將這個操做轉換爲位運算。

1static int indexFor(int h, int length) {
2    return h & (length-1);
3}

當 length 老是 2 的 n 次方時,h& (length-1)運算等價於對 length 取模,也就是 h%length,可是 & 比 % 具備更高的效率。這看上去很簡單,其實比較有玄機的,咱們舉個例子來講明:

<table>

  • 從上面的例子中能夠看出:當它們和 15-1(1110)「與」的時候,8 和 9產生了相同的結果,也就是說它們會定位到數組中的同一個位置上去,這就產生了碰撞,8 和 9 會被放到數組中的同一個位置上造成鏈表,那麼查詢的時候就須要遍歷這個鏈 表,獲得8或者9,這樣就下降了查詢的效率。

  • 同時,咱們也能夠發現,當數組長度爲 15 的時候,hash 值會與 15-1(1110)進行「與」,那麼最後一位永遠是 0,而 0001,0011,0101,1001,1011,0111,1101 這幾個位置永遠都不能存放元素了,空間浪費至關大,數組可使用的位置比數組長度小了不少,這意味着進一步增長了碰撞的概率。

  • 而當數組長度爲16時,即爲2的n次方時,2n-1 獲得的二進制數的每一個位上的值都爲 1,這使得在低位上&時,獲得的和原 hash 的低位相同,加之 hash(int h)方法對 key 的 hashCode 的進一步優化,加入了高位計算,就使得只有相同的 hash 值的兩個值纔會被放到數組中的同一個位置上造成鏈表。

因此說,當數組長度爲 2 的 n 次冪的時候,不一樣的 key 算得得 index 相同的概率較小,那麼數據在數組上分佈就比較均勻,也就是說碰撞的概率小

擴容-基本原理

設 HashMap 的 table 長度爲 M,須要存儲的鍵值對數量爲 N,若是哈希函數知足均勻性的要求,那麼每條鏈表的長度大約爲 N/M,所以平均查找次數的複雜度爲 O(N/M)。

爲了讓查找的成本下降,應該儘量使得 N/M 儘量小,所以須要保證 M 儘量大,也就是說 table 要儘量大。HashMap 採用動態擴容來根據當前的 N 值來調整 M 值,使得空間效率和時間效率都能獲得保證。

和擴容相關的參數主要有:capacity、size、threshold 和 load_factor。
Java容器(List、Set、Map)知識點快速複習手冊(中)

1static final int DEFAULT_INITIAL_CAPACITY = 16;
 2
 3static final int MAXIMUM_CAPACITY = 1 << 30;
 4
 5static final float DEFAULT_LOAD_FACTOR = 0.75f;
 6
 7transient Entry[] table;
 8
 9transient int size;
10
11int threshold;
12
13final float loadFactor;
14
15transient int modCount;

從下面的添加元素代碼中能夠看出,當須要擴容時,令 capacity 爲原來的兩倍。

1void addEntry(int hash, K key, V value, int bucketIndex) {
2    Entry<K,V> e = table[bucketIndex];
3    table[bucketIndex] = new Entry<>(hash, key, value, e);
4    if (size++ >= threshold)
5        resize(2 * table.length);
6}

擴容使用 resize() 實現,須要注意的是,擴容操做一樣須要把 oldTable 的全部鍵值對從新插入 newTable 中,所以這一步是很費時的。

1void resize(int newCapacity) {
 2    Entry[] oldTable = table;
 3    int oldCapacity = oldTable.length;
 4    if (oldCapacity == MAXIMUM_CAPACITY) {
 5        threshold = Integer.MAX_VALUE;
 6        return;
 7    }
 8    Entry[] newTable = new Entry[newCapacity];
 9    transfer(newTable);
10    table = newTable;
11    threshold = (int)(newCapacity * loadFactor);
12}
13
14void transfer(Entry[] newTable) {
15    Entry[] src = table;
16    int newCapacity = newTable.length;
17    for (int j = 0; j < src.length; j++) {
18        Entry<K,V> e = src[j];
19        if (e != null) {
20            src[j] = null;
21            do {
22                Entry<K,V> next = e.next;
23                int i = indexFor(e.hash, newCapacity);
24                e.next = newTable[i];
25                newTable[i] = e;
26                e = next;
27            } while (e != null);
28        }
29    }
30}

擴容-從新計算桶下標

Rehash優化:http://www.javashuo.com/article/p-edvgtahx-gy.html

在進行擴容時,須要把鍵值對從新放到對應的桶上。HashMap 使用了一個特殊的機制,能夠下降從新計算桶下標的操做。

假設原數組長度 capacity 爲 16,擴容以後 new capacity 爲 32:

1capacity     : 00010000
2new capacity : 00100000

對於一個 Key,

  • 它的哈希值若是在第 5 位上爲 0,那麼取模獲得的結果和以前同樣;
  • 若是爲 1,那麼獲得的結果爲原來的結果 +16。

總結:

通過rehash以後,元素的位置要麼是在原位置,要麼是在原位置再移動2次冪的位置

所以,咱們在擴充HashMap的時候,不須要像JDK1.7的實現那樣從新計算hash,只須要看看原來的hash值新增的那個bit是1仍是0就行了,是0的話索引沒變,是1的話索引變成「原索引+oldCap」,能夠看看下圖爲16擴充爲32的resize示意圖:
Java容器(List、Set、Map)知識點快速複習手冊(中)
在這裏插入圖片描述

計算數組容量

HashMap 構造函數容許用戶傳入的容量不是 2 的 n 次方,由於它能夠自動地將傳入的容量轉換爲 2 的 n 次方。

先考慮如何求一個數的掩碼,對於 10010000,它的掩碼爲 11111111,可使用如下方法獲得:

1mask |= mask >> 1    11011000
2mask |= mask >> 2    11111110
3mask |= mask >> 4    11111111
4

mask+1 是大於原始數字的最小的 2 的 n 次方。

1num     10010000
2mask+1 100000000

如下是 HashMap 中計算數組容量的代碼:

1static final int tableSizeFor(int cap) {
2    int n = cap - 1;
3    n |= n >>> 1;
4    n |= n >>> 2;
5    n |= n >>> 4;
6    n |= n >>> 8;
7    n |= n >>> 16;
8    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
9}

鏈表轉紅黑樹

並非桶子上有8位元素的時候它就能變成紅黑樹,它得同時知足咱們的鍵值對大於64才行的

這是爲了不在哈希表創建初期,多個鍵值對剛好被放入了同一個鏈表中而致使沒必要要的轉化。

HashTable

關鍵詞:

  • Hashtable的迭代器不是 fail-fast,HashMap 的迭代器是 fail-fast 迭代器。
  • Hashtable 的 key 和 value 都不容許爲 null,HashMap 能夠插入鍵爲 null 的 Entry。
  • HashTable 使用 synchronized 來進行同步。
  • 基於 Dictionary 類(遺留類)
  • HashMap 不能保證隨着時間的推移 Map 中的元素次序是不變的。

HashMap 與 HashTable

Java容器(List、Set、Map)知識點快速複習手冊(中)
在這裏插入圖片描述

  • HashTable 基於 Dictionary 類(遺留類),而 HashMap 是基於 AbstractMap。
  • Dictionary 是任何可將鍵映射到相應值的類的抽象父類,
  • 而AbstractMap是基於Map接口的實現,它以最大限度地減小實現此接口所需的工做。
  • HashMap 的 key 和 value 都容許爲 null,而 Hashtable 的 key 和 value 都不容許爲 null
  • HashMap 的迭代器是 fail-fast 迭代器,而 Hashtable 的 enumerator 迭代器不是 fail-fast 的。
  • 因爲 Hashtable 是線程安全的也是 synchronized,因此在單線程環境下它比 HashMap 要慢。
  • Hashtable 中的幾乎全部的 public 的方法都是synchronized的,而有些方法也是在內部經過 synchronized 代碼塊來實現。
  • 可是在 Collections 類中存在一個靜態方法:synchronizedMap(),該方法建立了一個線程安全的 Map 對象,並把它做爲一個封裝的對象來返回。
  • 也可使用 ConcurrentHashMap,它是 HashTable 的替代,並且比 HashTable 可擴展性更好

ConcurrentHashMap

談談ConcurrentHashMap1.7和1.8的不一樣實現:

http://www.importnew.com/23610.html

詳細源碼分析(還未細看):

https://blog.csdn.net/yan_wenliang/article/details/51029372

http://www.javashuo.com/article/p-zggcwnug-bu.html

主要針對jdk1.7的實現來介紹

關鍵詞

  • key和value都不容許爲null
  • Hashtable是將全部的方法進行同步,效率低下。而ConcurrentHashMap經過部分鎖定+CAS算法來進行實現線程安全的
  • get方法是非阻塞,無鎖的。重寫Node類,經過volatile修飾next來實現每次獲取都是最新設置的值
  • 在高併發環境下,統計數據(計算size…等等)實際上是無心義的,由於在下一時刻size值就變化了。
  • 實現形式不一樣:
  • 1.7:Segment + HashEntry的方式進行實現
  • 1.8:與HashMap相同(散列表(數組+鏈表)+紅黑樹)採用Node數組 + CAS + Synchronized來保證併發安全進行實現

存儲結構

jdk1.7

jdk1.7中採用Segment + HashEntry的方式進行實現
Java容器(List、Set、Map)知識點快速複習手冊(中)
在這裏插入圖片描述

Segment:其繼承於 ReentrantLock 類,從而使得 Segment 對象能夠充當鎖的角色。

Segment 中包含HashBucket的數組,其能夠守護其包含的若干個桶。

1static final class HashEntry<K,V> {
2    final int hash;
3    final K key;
4    volatile V value;
5    volatile HashEntry<K,V> next;
6}

ConcurrentHashMap採用了分段鎖,每一個分段鎖維護着幾個桶,多個線程能夠同時訪問不一樣分段鎖上的桶,從而使其併發度更高(併發度就是 Segment 的個數)。

jdk1.8
Java容器(List、Set、Map)知識點快速複習手冊(中)

在這裏插入圖片描述

  • JDK 1.7 使用分段鎖機制來實現併發更新操做,核心類爲 Segment,它繼承自重入鎖 ReentrantLock,併發程度與 Segment 數量相等。

  • JDK 1.8 使用了 CAS 操做來支持更高的併發度,在 CAS 操做失敗時使用內置鎖 synchronized。

  • 而且 JDK 1.8 的實現也在鏈表過長時會轉換爲紅黑樹。

1.8中放棄了Segment臃腫的設計,取而代之的是採用Node數組 + CAS + Synchronized來保證併發安全進行實現

添加元素:put

Java容器(List、Set、Map)知識點快速複習手冊(中)
在這裏插入圖片描述

只讓一個線程對散列表進行初始化!

獲取元素:get

從頂部註釋咱們能夠讀到,get方法是不用加鎖的,是非阻塞的。

Node節點是重寫的,設置了volatile關鍵字修飾,導致它每次獲取的都是最新設置的值

獲取大小:size

每一個 Segment 維護了一個 count 變量來統計該 Segment 中的鍵值對個數。

在執行 size 操做時,須要遍歷全部 Segment 而後把 count 累計起來。

ConcurrentHashMap 在執行 size操做時先嚐試不加鎖,若是連續兩次不加鎖操做獲得的結果一致,那麼能夠認爲這個結果是正確的。

嘗試次數使用 RETRIES_BEFORE_LOCK 定義,該值爲 2,retries 初始值爲 -1,所以嘗試次數爲 3。

若是嘗試的次數超過 3 次,就須要對每一個 Segment 加鎖。

刪除元素:remove

Java容器(List、Set、Map)知識點快速複習手冊(中)
在這裏插入圖片描述

爲何用這麼方式刪除呢,細心的同窗會發現上面定義的HashEntry的key和next都是final類型的,因此不能改變next的指向,因此又複製了一份指向刪除的結點的next。

Collections.synchronizedMap()與ConcurrentHashMap的區別

參考:https://blog.csdn.net/lanxiangru/article/details/53495854

  • Collections.synchronizedMap()和Hashtable同樣,實現上在調用map全部方法時,都對整個map進行同步,而ConcurrentHashMap的實現卻更加精細,它對map中的全部桶加了鎖,同步操做精確控制到桶,因此,即便在遍歷map時,其餘線程試圖對map進行數據修改,也不會拋出ConcurrentModificationException。
  • ConcurrentHashMap從類的命名就能看出,它是個HashMap。而Collections.synchronizedMap()能夠接收任意Map實例,實現Map的同步。好比TreeMap。

總結

ConcurrentHashMap 的高併發性主要來自於三個方面:

  • 用分離鎖實現多個線程間的更深層次的共享訪問。
  • 用 HashEntery對象的不變性來下降執行讀操做的線程在遍歷鏈表期間對加鎖的需求。
  • 經過對同一個 Volatile 變量的寫 / 讀訪問,協調不一樣線程間讀 / 寫操做的內存可見性。

LinkedHashMap

http://wiki.jikexueyuan.com/project/java-collection/linkedhashmap.html

http://www.javashuo.com/article/p-fscfnemy-ct.html

關鍵詞

  • 容許使用 null 值和 null 鍵
  • 此實現不是同步的(linkedlist,lilnkedhashset也不是同步的)
  • 維護着一個運行於全部條目的雙向鏈表。定義了迭代順序,該迭代順序能夠是插入順序或者是訪問順序。
  • 初始容量對遍歷沒有影響:遍歷的雙向鏈表,而不是散列表
  • 在訪問順序的狀況下,使用get方法也是結構性的修改(會致使Fail-Fast)

概論

Java容器(List、Set、Map)知識點快速複習手冊(中)
在這裏插入圖片描述
Java容器(List、Set、Map)知識點快速複習手冊(中)
在這裏插入圖片描述

成員變量

該 Entry 除了保存當前對象的引用外,還保存了其上一個元素 before 和下一個元素 after的引用,從而在哈希表的基礎上又構成了雙向連接列表。

1/**
 2* LinkedHashMap的Entry元素。
 3* 繼承HashMap的Entry元素,又保存了其上一個元素before和下一個元素after的引用。
 4 */
 5static class Entry<K,V> extends HashMap.Node<K,V> {
 6        Entry<K,V> before, after;
 7        Entry(int hash, K key, V value, Node<K,V> next) {
 8            super(hash, key, value, next);
 9        }
10    }

構造器

Java容器(List、Set、Map)知識點快速複習手冊(中)
在這裏插入圖片描述

  • 經過源代碼能夠看出,在 LinkedHashMap 的構造方法中,實際調用了父類 HashMap 的相關構造方法來構造一個底層存放的 table 數組,但額外能夠增長 accessOrder 這個參數,若是不設置

  • 默認爲 false,表明按照插入順序進行迭代;
  • 固然能夠顯式設置爲 true,表明以訪問順序進行迭代。
  • 在構建新節點時,構建的是LinkedHashMap.Entry 再也不是Node.

獲取元素:get

LinkedHashMap 重寫了父類 HashMap 的 get 方法,實際在調用父類 getEntry() 方法取得查找的元素後,再判斷當排序模式 accessOrder 爲 true 時,記錄訪問順序,將最新訪問的元素添加到雙向鏈表的表頭,並從原來的位置刪除。

因爲的鏈表的增長、刪除操做是常量級的,故並不會帶來性能的損失。

遍歷元素

爲啥註釋說:初始容量對遍歷沒有影響?

由於它遍歷的是LinkedHashMap內部維護的一個雙向鏈表,而不是散列表(固然了,鏈表雙向鏈表的元素都來源於散列表)

LinkedHashMap應用

http://wiki.jikexueyuan.com/project/java-collection/linkedhashmap-lrucache.html

LRU最近最少使用(訪問順序)

用這個類有兩大好處:

  • 它自己已經實現了按照訪問順序或插入順序的存儲
  • LinkedHashMap 自己有removeEldestEntry方法用於判斷是否須要移除最不常讀取的數,可是,原始方法默認不須要移除,咱們須要override這樣一個方法。

Java裏面實現LRU緩存一般有兩種選擇:

  • 使用LinkedHashMap
  • 本身設計數據結構,使用鏈表+HashMap

如下是使用 LinkedHashMap 實現的一個 LRU 緩存:

  • 設定最大緩存空間 MAX_ENTRIES 爲 3;
  • 使用 LinkedHashMap 的構造函數將 accessOrder 設置爲 true,開啓 LRU 順序;
  • 覆蓋 removeEldestEntry() 方法實現,在節點多於 MAX_ENTRIES 就會將最近最久未使用的數據移除。
1class LRUCache<K, V> extends LinkedHashMap<K, V> {
 2    private static final int MAX_ENTRIES = 3;
 3
 4    protected boolean removeEldestEntry(Map.Entry eldest) {
 5        return size() > MAX_ENTRIES;
 6    }
 7
 8    LRUCache() {
 9        super(MAX_ENTRIES, 0.75f, true);
10    }
11}
1public static void main(String[] args) {
2    LRUCache<Integer, String> cache = new LRUCache<>();
3    cache.put(1, "a");
4    cache.put(2, "b");
5    cache.put(3, "c");
6    cache.get(1);
7    cache.put(4, "d");
8    System.out.println(cache.keySet());
9}
1[3, 1, 4]

實現詳細代碼請參考文章:補充知識點-緩存

FIFO(插入順序)

還能夠在插入順序的LinkedHashMap直接重寫下removeEldestEntry方法便可輕鬆實現一個FIFO緩存

TreeMap

關鍵詞

  • 紅黑樹
  • 非同步
  • key不能爲null
  • 實現了NavigableMap接口,而NavigableMap接口繼承着SortedMap接口,是有序的(HahMap是Key無序的)
  • TreeMap的基本操做 containsKey、get、put 和 remove 的時間複雜度是 log(n) 。
  • 適用於查找性能要求不那麼高,反而對有序性要求比較高的應用場景
  • 使用Comparator或者Comparable來比較key是否相等與排序的問題

概覽

Java容器(List、Set、Map)知識點快速複習手冊(中)
在這裏插入圖片描述

獲取元素:get

詳細看:

http://www.javashuo.com/article/p-wumkjbiq-ng.html

總結:

  • 若是在構造方法中傳遞了Comparator對象,那麼就會以Comparator對象的方法進行比較。不然,則使用Comparable的compareTo(T o)方法來比較。
  • 值得說明的是:若是使用的是compareTo(T o)方法來比較,key必定是不能爲null,而且得實現了Comparable接口的。
  • 即便是傳入了Comparator對象,不用compareTo(T o)方法來比較,key也是不能爲null的

刪除元素:remove

  • 刪除節點而且平衡紅黑樹

參考

  • https://github.com/CyC2018/CS-Notes/blob/master/docs/notes/Java%20%E5%AE%B9%E5%99%A8.md
  • 公衆號:Java3y
  • Eckel B. Java 編程思想 [M]. 機械工業出版社, 2002.
  • Java Collection Framework
  • Iterator 模式
  • Java 8 系列之從新認識 HashMap
  • What is difference between HashMap and Hashtable in Java?
  • Java 集合之 HashMap
  • The principle of ConcurrentHashMap analysis
  • 探索 ConcurrentHashMap 高併發性的實現機制
  • HashMap 相關面試題及其解答
  • Java 集合細節(二):asList 的缺陷
  • Java Collection Framework – The LinkedList Class

關注我

本人目前爲後臺開發工程師,主要關注Python爬蟲,後臺開發等相關技術。

原創博客主要內容

  • 筆試面試複習知識點手冊
  • Leetcode算法題解析(前150題)
  • 劍指offer算法題解析
  • Python爬蟲相關實戰
  • 後臺開發相關實戰

同步更新如下博客

Csdn

http://blog.csdn.net/qqxx6661

擁有專欄:Leetcode題解(Java/Python)、Python爬蟲開發

知乎

https://www.zhihu.com/people/yang-zhen-dong-1/

擁有專欄:碼農面試助攻手冊

掘金

https://juejin.im/user/5b48015ce51d45191462ba55

簡書

https://www.jianshu.com/u/b5f225ca2376

我的公衆號:Rude3Knife

Java容器(List、Set、Map)知識點快速複習手冊(中)

相關文章
相關標籤/搜索