成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

阿里面試官:LinkedHashMap是怎么保證元素有序的?

開發 前端
這其實跟HashMap的底層設計有關,HashMap并不是像ArrayList那樣,按照元素的插入順序存儲。而是先計算key的哈希值,再用哈希值對數組長度求余,算出數組下標,存儲到下標所在的位置,如果該位置上存在鏈表或者紅黑樹,再把這個元素插入到鏈表或者紅黑樹上面。

阿里的上下班時間是1095,這么忙也不能耽誤更新《解讀Java源碼專欄》,在這個系列中,我將手把手帶著大家剖析Java核心組件的源碼,內容包含集合、線程、線程池、并發、隊列等,深入了解其背后的設計思想和實現細節,輕松應對工作面試。

這是解讀Java源碼系列的第五篇,將跟大家一起學習Java中比較神秘的數據結構 - LinkedHashMap。

引言

新手程序員在使用HashMap的時候,會有個疑問,為什么存到HashMap中的數據不是有序的?

這其實跟HashMap的底層設計有關,HashMap并不是像ArrayList那樣,按照元素的插入順序存儲。而是先計算key的哈希值,再用哈希值對數組長度求余,算出數組下標,存儲到下標所在的位置,如果該位置上存在鏈表或者紅黑樹,再把這個元素插入到鏈表或者紅黑樹上面。

這樣設計,可以實現快速查詢,也就犧牲了存儲順序。因為不同key的哈希值差別很大,所以在數組中存儲是無序的。

然而,有時候我們在遍歷HashMap的時候,又希望按照元素插入順序迭代,有沒有什么方式能實現這個需求?

有的,就是今天的主角LinkedHashMap,不但保證了HashMap的性能,還實現了按照元素插入順序或者訪問順序進行迭代。

在這篇文章中,你將學到以下內容:

  1. LinkedHashMap與HashMap區別?
  2. LinkedHashMap特點有哪些?
  3. LinkedHashMap底層實現原理?
  4. 怎么使用``LinkedHashMap實現 LRU 緩存?

簡介

LinkedHashMap繼承自HashMap,是HashMap的子類,內部額外維護了一個雙鏈表,來保證元素的插入順序或訪問順序,用空間換時間。 與HashMap相比,LinkedHashMap有三個優點:

  1. 維護了元素插入順序,支持以元素插入順序進行迭代。
  2. 維護了元素的訪問順序,支持以元素訪問順序進行迭代。最近訪問或者更新的元素,會被移動到鏈表末尾,類似于LRU(Least Recently Used,最近最少使用)。當面試的時候,手寫LRU緩存,需要用到或者參考LinkedHashMap。
  3. 迭代效率更高,迭代LinkedHashMap的時候,不需要遍歷整個數組,只需遍歷雙鏈表即可,效率更高。

圖片圖片

類屬性

public class LinkedHashMap<K, V> extends HashMap<K, V> implements Map<K, V> {

    /**
     * 頭節點
     */
    transient Entry<K, V> head;

    /**
     * 尾節點
     */
    transient Entry<K, V> tail;

    /**
     * 迭代排序方式,true表示按照訪問順序,false表示按照插入順序
     */
    final boolean accessOrder;

    /**
     * 雙鏈表的節點類
     */
    static class Entry<K, V> extends HashMap.Node<K, V> {
        /**
         * 雙鏈表的前驅節點和后繼節點
         */
        Entry<K, V> before, after;

        /**
         * 構造雙鏈表的節點
         *
         * @param hash 哈希值
         * @param key  鍵
         * @param value 值
         * @param next 后繼節點
         */
        Entry(int hash, K key, V value, Node<K, V> next) {
            super(hash, key, value, next);
        }
    }

}

可以看出LinkedHashMap繼承自HashMap,在HashMap的單鏈表Node節點的基礎上,增加了前驅節點before、后繼節點after、頭節點head、尾節點tail,擴展成了雙鏈表節點Entry,并記錄了迭代排序方式accessOrder。

初始化

LinkedHashMap常見的初始化方法有四個方法:

  1. 無參初始化
  2. 指定容量大小的初始化
  3. 指定容量大小、負載系數的初始化
  4. 指定容量大小、負載系數、迭代順序的初始化
/**
 * 無參初始化
 */
Map<Integer, Integer> map1 = new LinkedHashMap<>();

/**
 * 指定容量大小的初始化
 */
Map<Integer, Integer> map2 = new LinkedHashMap<>(16);

/**
 * 指定容量大小、負載系數的初始化
 */
Map<Integer, Integer> map3 = new LinkedHashMap<>(16, 0.75f);

/**
 * 指定容量大小、負載系數、迭代順序的初始化
 */
Map<Integer, Integer> map4 = new LinkedHashMap<>(16, 0.75f, true);

再看一下構造方法的底層實現:

/**
 * 無參初始化
 */
public LinkedHashMap() {
    super();
    accessOrder = false;
}

/**
 * 指定容量大小的初始化
 */
public LinkedHashMap(int initialCapacity) {
    super(initialCapacity);
    accessOrder = false;
}

/**
 * 指定容量大小、負載系數的初始化
 *
 * @param initialCapacity 初始容量
 * @param loadFactor      負載系數
 */
public LinkedHashMap(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor);
    accessOrder = false;
}

/**
 * 指定容量大小、負載系數、迭代順序的初始化
 *
 * @param initialCapacity 初始容量
 * @param loadFactor      負載系數
 * @param accessOrder     迭代順序,true表示按照訪問順序,false表示按照插入順序
 */
public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

LinkedHashMap的構造方法底層都是調用的HashMap的構造方法,迭代順序accessOrder默認是false,表示按照元素插入順序迭代,可以在初始化LinkedHashMap的時候指定為 true,表示按照訪問順序迭代。

put源碼

LinkedHashMap的put方法完全使用的是HashMap的put方法,并沒有重新實現。不過HashMap中定義了一些空方法,留給子類LinkedHashMap去實現。 有以下三個方法:

public class HashMap<K, V> {
    
    /**
     * 在訪問節點后執行的操作
     */
    void afterNodeAccess(Node<K, V> p) {
    }

    /**
     * 在插入節點后執行的操作
     */
    void afterNodeInsertion(boolean evict) {
    }

    /**
     * 在刪除節點后執行的操作
     */
    void afterNodeRemoval(Node<K, V> p) {
    }
    
}

在HashMap的put源碼中就調用前兩個方法:

圖片圖片

看一下afterNodeInsertion()方法的源碼,看一下再插入節點后要執行哪些操作? 在插入節點后,只執行了一個操作,就是判斷是否刪除最舊的節點。removeEldestEntry()方法默認返回false,表示不需要刪除節點。我們也可以重寫removeEldestEntry()方法,當元素數量超過閾值時,返回true,表示刪除最舊的節點。

/**
 * 在插入節點后執行的操作(刪除最舊的節點)
 */
void afterNodeInsertion(boolean evict) {
    Entry<K, V> first;
    // 判斷是否需要刪除當前節點
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        // 調用HashMap的刪除節點的方法
        removeNode(hash(key), key, null, false, true);
    }
}

/**
 * 是否刪除最舊的節點,默認是false,表示不刪除
 */
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
    return false;
}

創建節點

由于afterNodeInsertion()方法并沒有把新節點插入到雙鏈表中,所以LinkedHashMap又重寫創建節點的newNode()方法,在newNode()方法中把新節點插入到雙鏈表。

public class LinkedHashMap<K, V> extends HashMap<K, V> implements Map<K, V> {

    /**
     * 創建鏈表節點
     */
    @Override
    Node<K, V> newNode(int hash, K key, V value, Node<K, V> e) {
        // 1. 創建雙鏈表節點
        LinkedHashMap.Entry<K, V> p = new LinkedHashMap.Entry<K, V>(hash, key, value, e);
        // 2. 追加到鏈表末尾
        linkNodeLast(p);
        return p;
    }

    /**
     * 創建紅黑樹節點
     */
    @Override
    TreeNode<K, V> newTreeNode(int hash, K key, V value, Node<K, V> next) {
        // 1. 創建紅黑樹節點
        TreeNode<K, V> p = new TreeNode<K, V>(hash, key, value, next);
        // 2. 追加到鏈表末尾
        linkNodeLast(p);
        return p;
    }

    /**
     * 追加到鏈表末尾
     */
    private void linkNodeLast(LinkedHashMap.Entry<K, V> p) {
        LinkedHashMap.Entry<K, V> last = tail;
        tail = p;
        if (last == null) {
            head = p;
        } else {
            p.before = last;
            last.after = p;
        }
    }
}

get源碼

再看一下 get 方法源碼,LinkedHashMap的 get 方法是直接調用的HashMap的get方法邏輯,在獲取到value 后,判斷 value 不為空,就執行afterNodeAccess()方法邏輯,把該節點移動到鏈表末尾,afterNodeAccess()方法邏輯在前面已經講過。

/**
 * get方法入口
 */
public V get(Object key) {
    Node<K,V> e;
    // 直接調用HashMap的get方法源碼
    if ((e = getNode(hash(key), key)) == null) {
        return null;
    }
    // 如果value不為空,并且設置了accessOrder為true(表示迭代順序為訪問順序),就執行訪問節點后的操作
    if (accessOrder) {
        afterNodeAccess(e);
    }
    return e.value;
}

看一下afterNodeAccess()方法的源碼實現,看一下在訪問節點要做哪些操作?afterNodeAccess()方法的邏輯也很簡單,核心邏輯就是把當前節點移動到鏈表末尾,分為三步:

  1. 斷開當前節點與后繼節點的連接
  2. 斷開當前節點與前驅節點的連接
  3. 把當前節點插入到鏈表末尾
/**
 * 在訪問節點后執行的操作(把節點移動到鏈表末尾)
 */
void afterNodeAccess(Node<K, V> e) {
    Entry<K, V> last;
    // 當accessOrder為true時,表示按照訪問順序,這時候才需要更新鏈表
    // 并且判斷當前節點不是尾節點
    if (accessOrder && (last = tail) != e) {
        Entry<K, V> p = (Entry<K, V>) e, b = p.before, a = p.after;
        // 1. 斷開當前節點與后繼節點的連接
        p.after = null;
        if (b == null) {
            head = a;
        } else {
            b.after = a;
        }
        // 2. 斷開當前節點與前驅節點的連接
        if (a != null) {
            a.before = b;
        } else {
            last = b;
        }
        // 3. 把當前節點插入到鏈表末尾
        if (last == null) {
            head = p;
        } else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

remove源碼

LinkedHashMap的 remove 方法完全使用的是 HashMap 的 remove 方法,并沒有重新實現。不過 HashMap的 remove 中調用了afterNodeRemoval?(),執行刪除節點后邏輯,LinkedHashMap重寫了該方法的邏輯。

圖片圖片

/**
 * 在刪除節點后執行的操作(從雙鏈表中刪除該節點)
 */
void afterNodeRemoval(Node<K, V> e) {
    LinkedHashMap.Entry<K, V> p =
            (LinkedHashMap.Entry<K, V>) e, b = p.before, a = p.after;
    p.before = p.after = null;
    // 1. 斷開當前節點與前驅節點的連接
    if (b == null) {
        head = a;
    } else {
        b.after = a;
    }
    // 2. 斷開當前節點與后繼節點的連接
    if (a == null) {
        tail = b;
    } else {
        a.before = b;
    }
}

總結

現在可以回答文章開頭提出的問題:

  1. LinkedHashMap與HashMap區別?

答案:LinkedHashMap繼承自HashMap,是HashMap的子類。

  1. LinkedHashMap特點有哪些?

答案:除了保證了與HashMap一樣高效的查詢和插入性能外,還支持以插入順序或者訪問順序進行迭代訪問。

  1. LinkedHashMap底層實現原理?

答案:LinkedHashMap底層源碼都是使用了HashMap的邏輯實現,使用雙鏈表維護元素的順序,并重寫了以下三個方法:

  1. afterNodeAccess(),在訪問節點后執行的操作
  2. afterNodeInsertion(),在插入節點后執行的操作。
  3. afterNodeRemoval(),在刪除節點后執行的操作。
  4. 怎么使用``LinkedHashMap實現 LRU 緩存?

答案:由于LinkedHashMap內部已經實現按照訪問元素的迭代順序,所以只需復用LinkedHashMap的邏輯,繼承LinkedHashMap,重寫removeEldestEntry()方法。

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author 一燈架構
 * @apiNote 使用LinkedHashMap實現LRU緩存
 */
public class LRUCache<K, V> extends LinkedHashMap<K, V> {

    /**
     * 緩存容量大小
     */
    private final int capacity;

    /**
     * 構造方法
     *
     * @param capacity 緩存容量大小
     */
    public LRUCache(int capacity) {
        // 底層使用LinkedHashMap的構造方法
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }

    /**
     * 當緩存容量達到上限時,移除最久未使用的節點
     */
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }

    public static void main(String[] args) {
        LRUCache<Integer, String> cache = new LRUCache<>(3);
        cache.put(1, "One");
        cache.put(2, "Two");
        cache.put(3, "Three");
        System.out.println(cache); // 輸出: {1=One, 2=Two, 3=Three}

        cache.get(2);
        System.out.println(cache); // 輸出: {1=One, 3=Three, 2=Two}

        cache.put(4, "Four");
        System.out.println(cache); // 輸出: {3=Three, 2=Two, 4=Four}
    }
}

責任編輯:武曉燕 來源: 一燈架構
相關推薦

2023-11-06 17:39:35

JavaArrayList線程

2021-09-27 07:11:18

MySQLACID特性

2021-01-20 07:16:07

冪等性接口token

2022-07-15 08:22:42

對象符串鍵Symbol

2021-12-21 07:07:43

HashSet元素數量

2024-11-26 17:43:51

2025-03-10 11:48:22

項目服務設計

2021-11-05 10:07:13

Redis哈希表存儲

2019-11-21 08:40:44

面試官優化性能

2023-09-01 15:27:31

2024-02-28 10:14:47

Redis數據硬盤

2025-04-14 11:41:12

RocketMQ長輪詢配置

2023-02-08 07:04:20

死鎖面試官單元

2021-10-22 08:37:13

消息不丟失rocketmq消息隊列

2021-09-07 10:44:33

Java 注解開發

2022-11-25 17:29:27

分布式事務

2020-02-25 16:56:02

面試官有話想說

2023-11-29 08:00:53

JavaTreeMap底層

2021-02-19 10:02:57

HTTPSJava安全

2024-02-04 10:08:34

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91精品麻豆日日躁夜夜躁 | 99pao成人国产永久免费视频 | 国产精品成人国产乱一区 | 日日夜夜av | 国产免费一区二区三区 | 一区二区三区在线 | 亚洲成人在线免费 | 欧美电影大全 | 日韩在线播放网址 | 国产在线观看福利 | 欧美精品乱码久久久久久按摩 | 草草视频在线观看 | 一级在线 | 久热免费 | 亚洲欧美日本国产 | 国产精品网址 | 久久草在线视频 | 久久久久久久久久久久久九 | 亚洲一区二区在线 | 欧美视频1区 | 精品国产一区二区久久 | 欧美日韩一区在线 | 日韩色视频 | 国产成人亚洲精品 | 91精品国产日韩91久久久久久 | 香蕉视频91 | 国产精品久久久久久久久久久免费看 | 91精品国产91久久久久久吃药 | 中文字幕免费观看 | 国产精品一区二区三区在线 | 久久精品成人一区 | 日韩成人在线免费观看 | 在线免费观看成人 | 日韩国产一区二区 | 国产一区二区a | 成人精品一区二区三区四区 | 一级片aaa | 精品在线一区二区 | 亚洲精品国产成人 | 影视先锋av资源噜噜 | 久久99视频|