最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java Map常用方法和實(shí)現(xiàn)類(lèi)的核心原理

 更新時(shí)間:2026年02月26日 16:01:01   作者:百錦再@新空間創(chuàng)想科技  
在Java集合框架中,Map是最核心、最常用的數(shù)據(jù)結(jié)構(gòu)之一,本文將從Map接口的設(shè)計(jì)哲學(xué)出發(fā),深入剖析HashMap、LinkedHashMap、TreeMap、Hashtable、ConcurrentHashMap等主要實(shí)現(xiàn)類(lèi)的底層原理、源碼實(shí)現(xiàn)、性能特性,感興趣的朋友跟隨小編一起看看吧

前言

在Java集合框架中,Map是最核心、最常用的數(shù)據(jù)結(jié)構(gòu)之一。與Collection體系下的List、Set不同,Map采用**鍵值對(duì)(Key-Value)**的存儲(chǔ)方式,每個(gè)鍵映射到一個(gè)值,鍵在同一個(gè)Map中不可重復(fù)。這種設(shè)計(jì)使得Map特別適合需要通過(guò)鍵快速查找值的場(chǎng)景,如緩存系統(tǒng)、配置管理、數(shù)據(jù)索引等。

本文將從Map接口的設(shè)計(jì)哲學(xué)出發(fā),深入剖析HashMap、LinkedHashMap、TreeMap、Hashtable、ConcurrentHashMap等主要實(shí)現(xiàn)類(lèi)的底層原理、源碼實(shí)現(xiàn)、性能特性,并結(jié)合Java 8+的新特性,幫助讀者全面掌握Map的使用技巧和選型策略。

第一章 Map接口概述

1.1 Map的繼承體系

Java中的Map體系是一個(gè)獨(dú)立于Collection的并行框架,其核心繼承結(jié)構(gòu)如下:

Map (interface)
├── HashMap (class)
│   └── LinkedHashMap (class)
├── TreeMap (class)
├── Hashtable (class)
│   └── Properties (class)
└── ConcurrentMap (interface)
    └── ConcurrentHashMap (class)

1.2 Map的核心特性

  • 鍵唯一性:每個(gè)鍵最多映射到一個(gè)值,鍵的不可重復(fù)性通過(guò)equals()hashCode()保證
  • 值可重復(fù):不同的鍵可以對(duì)應(yīng)相同的值
  • 元素?zé)o序:大部分Map實(shí)現(xiàn)(如HashMap)不保證元素的順序
  • 允許null:HashMap允許一個(gè)null鍵和多個(gè)null值,但Hashtable和ConcurrentHashMap不允許

1.3 存儲(chǔ)結(jié)構(gòu)的理解

從數(shù)據(jù)結(jié)構(gòu)角度看,Map的存儲(chǔ)可以分為三個(gè)層面:

  1. key視角:所有key構(gòu)成一個(gè)Set集合 → 無(wú)序、不可重復(fù),key所在的類(lèi)必須重寫(xiě)equals()hashCode()
  2. value視角:所有value構(gòu)成一個(gè)Collection集合 → 無(wú)序、可重復(fù),value所在的類(lèi)需要重寫(xiě)equals()
  3. entry視角:每個(gè)key-value對(duì)構(gòu)成一個(gè)Entry對(duì)象,所有entry構(gòu)成一個(gè)Set集合 → 無(wú)序、不可重復(fù)

這種設(shè)計(jì)體現(xiàn)了Map與Set、List的內(nèi)在聯(lián)系,也為后續(xù)的遍歷操作奠定了基礎(chǔ)。

第二章 HashMap:最常用的Map實(shí)現(xiàn)

HashMap是基于哈希表實(shí)現(xiàn)的Map,它根據(jù)鍵的hashCode值存儲(chǔ)數(shù)據(jù),具有O(1)的平均查找時(shí)間,是日常開(kāi)發(fā)中使用頻率最高的Map實(shí)現(xiàn)。

2.1 底層數(shù)據(jù)結(jié)構(gòu)演進(jìn)

HashMap的底層實(shí)現(xiàn)經(jīng)歷了從JDK 7到JDK 8的重要優(yōu)化:

版本底層結(jié)構(gòu)節(jié)點(diǎn)類(lèi)型特點(diǎn)
JDK 7數(shù)組 + 鏈表Entry頭插法,擴(kuò)容時(shí)可能產(chǎn)生循環(huán)鏈表
JDK 8+數(shù)組 + 鏈表 + 紅黑樹(shù)Node/TreeNode尾插法,鏈表長(zhǎng)度>8且數(shù)組長(zhǎng)度>64時(shí)樹(shù)化

2.2 核心源碼深度解析

2.2.1 重要成員變量

// 默認(rèn)初始容量16,必須是2的n次冪
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默認(rèn)負(fù)載因子0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 鏈表轉(zhuǎn)紅黑樹(shù)閾值
static final int TREEIFY_THRESHOLD = 8;
// 紅黑樹(shù)轉(zhuǎn)鏈表閾值
static final int UNTREEIFY_THRESHOLD = 6;
// 樹(shù)化最小數(shù)組容量
static final int MIN_TREEIFY_CAPACITY = 64;

2.2.2 設(shè)計(jì)哲學(xué)解讀

為什么默認(rèn)負(fù)載因子是0.75?

負(fù)載因子表示散列表的空間使用程度。0.75是時(shí)間與空間的折中選擇:

  • 過(guò)高(如1):空間利用率高,但Hash碰撞概率增加,鏈表變長(zhǎng),查詢(xún)效率下降
  • 過(guò)低(如0.5):Hash碰撞減少,查詢(xún)快,但空間浪費(fèi)嚴(yán)重

為什么容量必須是2的n次冪?

這涉及HashMap的核心優(yōu)化:

  1. 高效取模:計(jì)算數(shù)組下標(biāo)時(shí),(n - 1) & hash等價(jià)于hash % n,位運(yùn)算速度遠(yuǎn)快于取模
  2. 均勻分布:2^n-1的二進(jìn)制全是1,與運(yùn)算結(jié)果能充分利用hash值的所有位,減少碰撞
  3. 擴(kuò)容優(yōu)化:擴(kuò)容后元素的新位置要么在原位置,要么在原位置+舊容量,只需看hash值新增位是0還是1

為什么鏈表轉(zhuǎn)紅黑樹(shù)的閾值是8?

這是基于泊松分布的概率統(tǒng)計(jì)。在理想隨機(jī)hashCode下,鏈表節(jié)點(diǎn)數(shù)出現(xiàn)的概率遵循泊松分布,節(jié)點(diǎn)數(shù)為8的概率接近千萬(wàn)分之六,此時(shí)鏈表查詢(xún)性能已經(jīng)很差,轉(zhuǎn)為紅黑樹(shù)可以挽回性能。而樹(shù)節(jié)點(diǎn)占用的空間是普通節(jié)點(diǎn)的兩倍,當(dāng)節(jié)點(diǎn)數(shù)降到6時(shí)再轉(zhuǎn)回鏈表,避免頻繁轉(zhuǎn)換。

2.3 put方法執(zhí)行流程

HashMap的put方法是理解其工作原理的關(guān)鍵入口:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 1. 數(shù)組延遲初始化:首次put時(shí)創(chuàng)建數(shù)組
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 2. 計(jì)算下標(biāo),如果該位置為空直接插入
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        // 3. 處理Hash沖突
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
            e = p; // 第一個(gè)節(jié)點(diǎn)就是要找的key
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // 紅黑樹(shù)插入
        else {
            // 鏈表遍歷
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null); // 尾插法
                    if (binCount >= TREEIFY_THRESHOLD - 1)
                        treeifyBin(tab, hash); // 檢查是否需要樹(shù)化
                    break;
                }
                if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 4. 找到相同key,替換value
        if (e != null) {
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    // 5. 檢查是否需要擴(kuò)容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

執(zhí)行流程總結(jié)

  1. 計(jì)算key的hash值(擾動(dòng)函數(shù):高16位與低16位異或)
  2. 通過(guò)(n - 1) & hash計(jì)算數(shù)組下標(biāo)
  3. 如果該位置為空,直接插入
  4. 如果該位置不為空,遍歷鏈表或紅黑樹(shù)
  5. 找到相同key則替換value,否則插入新節(jié)點(diǎn)
  6. 檢查是否需要樹(shù)化或擴(kuò)容

2.4 擴(kuò)容機(jī)制(resize)

當(dāng)元素個(gè)數(shù)超過(guò)threshold = capacity * loadFactor時(shí),HashMap會(huì)進(jìn)行擴(kuò)容:

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    // 計(jì)算新容量
    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; // 閾值也翻倍
    }
    // ... 初始化邏輯
    // 創(chuàng)建新數(shù)組
    @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    // 數(shù)據(jù)遷移
    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)
                    // 單個(gè)節(jié)點(diǎn)直接重新計(jì)算下標(biāo)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    // 紅黑樹(shù)拆分
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else {
                    // 鏈表拆分:保持原順序
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        // 關(guān)鍵優(yōu)化:根據(jù)hash值新增位判斷新位置
                        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;
                        }
                        e = next;
                    } while (e != null);
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead; // 原索引位置
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead; // 原索引+舊容量
                    }
                }
            }
        }
    }
    return newTab;
}

擴(kuò)容優(yōu)化點(diǎn)

  • JDK 8采用尾插法,避免JDK 7頭插法在多線(xiàn)程環(huán)境下產(chǎn)生的循環(huán)鏈表問(wèn)題
  • 元素遷移時(shí),無(wú)需重新計(jì)算hash,只需看e.hash & oldCap是否為0,為0則留在原位,否則移到原位置+oldCap
  • 鏈表保持原順序,不會(huì)倒置

2.5 線(xiàn)程安全問(wèn)題

HashMap是線(xiàn)程不安全的,多線(xiàn)程環(huán)境下可能出現(xiàn)以下問(wèn)題:

  1. 數(shù)據(jù)覆蓋:兩個(gè)線(xiàn)程同時(shí)put,計(jì)算出的下標(biāo)相同,一個(gè)線(xiàn)程插入的數(shù)據(jù)可能被另一個(gè)覆蓋
  2. size不準(zhǔn)確++size操作非原子性,多個(gè)線(xiàn)程同時(shí)put可能導(dǎo)致size偏小
  3. JDK 7擴(kuò)容死循環(huán):頭插法在并發(fā)擴(kuò)容時(shí)可能形成環(huán)形鏈表,導(dǎo)致CPU 100%

解決方案:

  • 使用Collections.synchronizedMap(new HashMap<>())
  • 使用ConcurrentHashMap推薦

第三章 LinkedHashMap:保持插入順序

LinkedHashMap繼承自HashMap,在HashMap基礎(chǔ)上通過(guò)雙向鏈表維護(hù)元素的順序。

3.1 數(shù)據(jù)結(jié)構(gòu)特點(diǎn)

static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after; // 前驅(qū)和后繼指針
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

LinkedHashMap在HashMap的Node基礎(chǔ)上增加了beforeafter指針,構(gòu)成了一個(gè)雙向鏈表,用于記錄元素的插入順序或訪(fǎng)問(wèn)順序。

3.2 兩種排序模式

LinkedHashMap支持兩種迭代順序:

  1. 插入順序(默認(rèn)):按元素首次插入Map的順序迭代
  2. 訪(fǎng)問(wèn)順序:按元素最近被訪(fǎng)問(wèn)(get/put)的時(shí)間從舊到新迭代
// 指定訪(fǎng)問(wèn)順序
Map<String, String> map = new LinkedHashMap<>(16, 0.75f, true);
map.put("a", "1");
map.put("b", "2");
map.get("a"); // 訪(fǎng)問(wèn)a,a會(huì)被移動(dòng)到鏈表尾部
// 迭代順序:b, a(最近訪(fǎng)問(wèn)的在最后)

3.3 實(shí)現(xiàn)LRU緩存

利用訪(fǎng)問(wèn)順序模式,可以輕松實(shí)現(xiàn)LRU(Least Recently Used)緩存

class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int maxCapacity;
    public LRUCache(int maxCapacity) {
        super(16, 0.75f, true); // 啟用訪(fǎng)問(wèn)順序
        this.maxCapacity = maxCapacity;
    }
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > maxCapacity; // 超過(guò)容量時(shí)移除最久未訪(fǎng)問(wèn)的元素
    }
}

3.4 性能特點(diǎn)

  • 遍歷速度:只與元素個(gè)數(shù)有關(guān),與HashMap容量無(wú)關(guān),因此當(dāng)HashMap容量大而實(shí)際元素少時(shí),LinkedHashMap遍歷更快
  • 插入性能:略低于HashMap,因?yàn)樾枰S護(hù)雙向鏈表
  • 內(nèi)存占用:比HashMap多兩個(gè)指針的開(kāi)銷(xiāo)

第四章 TreeMap:基于紅黑樹(shù)的排序Map

TreeMap實(shí)現(xiàn)了SortedMapNavigableMap接口,底層基于紅黑樹(shù)實(shí)現(xiàn),能夠?qū)︽I進(jìn)行排序。

4.1 排序機(jī)制

TreeMap要求鍵要么實(shí)現(xiàn)Comparable接口(自然排序),要么在構(gòu)造時(shí)提供Comparator(定制排序):

// 自然排序:鍵必須實(shí)現(xiàn)Comparable
TreeMap<Integer, String> naturalMap = new TreeMap<>();
// 定制排序:提供Comparator
TreeMap<String, Integer> customMap = new TreeMap<>(
    (s1, s2) -> s2.compareTo(s1) // 降序
);

4.2 核心方法

TreeMap提供了豐富的導(dǎo)航方法:

TreeMap<Integer, String> map = new TreeMap<>();
map.put(1, "one");
map.put(3, "three");
map.put(5, "five");
map.put(7, "seven");
Integer firstKey = map.firstKey();        // 1
Integer lastKey = map.lastKey();          // 7
Integer lowerKey = map.lowerKey(5);       // 3(小于5的最大鍵)
Integer floorKey = map.floorKey(4);       // 3(小于等于4的最大鍵)
Integer ceilingKey = map.ceilingKey(4);   // 5(大于等于4的最小鍵)
Integer higherKey = map.higherKey(5);     // 7(大于5的最小鍵)
// 子Map視圖
SortedMap<Integer, String> headMap = map.headMap(5);   // 鍵<5的部分
SortedMap<Integer, String> tailMap = map.tailMap(5);   // 鍵>=5的部分
SortedMap<Integer, String> subMap = map.subMap(3, 6);  // 3<=鍵<6

4.3 源碼分析:compare方法

TreeMap的核心是比較邏輯,它在put、getremove等操作中都會(huì)用到:

final int compare(Object k1, Object k2) {
    return comparator == null ? 
        ((Comparable<? super K>)k1).compareTo((K)k2) : 
        comparator.compare((K)k1, (K)k2);
}

如果既沒(méi)有提供Comparator,鍵也沒(méi)有實(shí)現(xiàn)Comparable,在插入時(shí)會(huì)拋出ClassCastException。

4.4 注意事項(xiàng)

  1. 鍵不能為null:因?yàn)闊o(wú)法比較null
  2. compareTo與equals需一致:當(dāng)兩個(gè)鍵比較結(jié)果為0時(shí),TreeMap認(rèn)為它們相等,即使equals返回false
  3. 字符串鍵的特殊性:字符串的compareTo基于Unicode值,數(shù)字字符串排序時(shí)需注意
    // 錯(cuò)誤:字符串排序按字典序,"22"會(huì)排在"5"前面
    TreeMap<String, Integer> map = new TreeMap<>();
    map.put("5", 1);
    map.put("22", 2); // 實(shí)際順序:22, 5
    // 正確:轉(zhuǎn)為整數(shù)比較
    TreeMap<String, Integer> map = new TreeMap<>(
        (a, b) -> Integer.parseInt(a) - Integer.parseInt(b)
    );

第五章 Hashtable與Properties

5.1 Hashtable:古老的線(xiàn)程安全Map

Hashtable是JDK 1.0就存在的古老實(shí)現(xiàn)類(lèi),具有以下特點(diǎn):

  • 線(xiàn)程安全:所有方法都用synchronized修飾
  • 不允許null鍵和null值:否則拋出NullPointerException
  • 初始容量11,擴(kuò)容為2*old+1
  • 性能較低:全表鎖導(dǎo)致并發(fā)性能差
Hashtable<String, Integer> table = new Hashtable<>();
table.put("key", 1);
// table.put(null, 2); // 運(yùn)行時(shí)異常

性能對(duì)比

  • 寫(xiě)入速度:Hashtable可能比HashMap快(測(cè)試數(shù)據(jù):1420ms vs 797ms)
  • 讀取速度:HashMap比Hashtable快(188ms vs 265ms)

5.2 Properties:處理配置文件

Properties繼承自Hashtable,專(zhuān)門(mén)用于處理配置文件,鍵和值都是String類(lèi)型。

Properties props = new Properties();
props.setProperty("url", "jdbc:mysql://localhost:3306/db");
props.setProperty("username", "root");
props.setProperty("password", "123456");
// 加載配置文件
try (InputStream input = new FileInputStream("config.properties")) {
    props.load(input);
    String url = props.getProperty("url");
    String username = props.getProperty("username");
}

常用方法:

  • load(InputStream) / store(OutputStream):加載/存儲(chǔ)配置文件
  • getProperty(String key, String defaultValue):獲取屬性,可指定默認(rèn)值
  • list(PrintStream):打印所有屬性

第六章 ConcurrentHashMap:并發(fā)編程的利器

ConcurrentHashMap是Java并發(fā)包(java.util.concurrent)中提供的線(xiàn)程安全且高性能的Map實(shí)現(xiàn)。

6.1 設(shè)計(jì)哲學(xué)

ConcurrentHashMap的設(shè)計(jì)目標(biāo)是:在保證線(xiàn)程安全的同時(shí),提供比Hashtable更高的并發(fā)性能。

實(shí)現(xiàn)類(lèi)鎖策略并發(fā)度性能
Hashtable全表鎖極低
Collections.synchronizedMap全表鎖極低
ConcurrentHashMap JDK 7分段鎖16
ConcurrentHashMap JDK 8+CAS + synchronized + 細(xì)粒度鎖極高非常高

6.2 JDK 7實(shí)現(xiàn):分段鎖

JDK 7的ConcurrentHashMap采用Segment分段鎖機(jī)制:

  • 將整個(gè)Map分成多個(gè)Segment(默認(rèn)16個(gè))
  • 每個(gè)Segment獨(dú)立加鎖,相當(dāng)于一個(gè)小型的HashMap
  • 不同Segment的寫(xiě)操作可以并發(fā)執(zhí)行
  • 讀操作幾乎不加鎖(volatile保證可見(jiàn)性)
static final class Segment<K,V> extends ReentrantLock implements Serializable {
    transient volatile HashEntry<K,V>[] table;
    // ...
}

6.3 JDK 8+實(shí)現(xiàn):CAS + synchronized

JDK 8對(duì)ConcurrentHashMap進(jìn)行了重大重構(gòu):

  1. 放棄分段鎖,改用CAS + synchronized實(shí)現(xiàn)
  2. 與HashMap結(jié)構(gòu)對(duì)齊:數(shù)組+鏈表+紅黑樹(shù)
  3. 鎖粒度更細(xì):只鎖住鏈表或紅黑樹(shù)的頭節(jié)點(diǎn)
  4. 讀操作完全無(wú)鎖(volatile保證可見(jiàn)性)
// putVal核心片段
final V putVal(K key, V value, boolean onlyIfAbsent) {
    // ... 非空校驗(yàn)等
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable(); // 初始化,CAS保證線(xiàn)程安全
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            // 該位置為空,CAS嘗試插入
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
                break;
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f); // 幫助擴(kuò)容
        else {
            V oldVal = null;
            synchronized (f) { // 鎖住鏈表頭節(jié)點(diǎn)
                // 鏈表或紅黑樹(shù)操作
            }
        }
    }
}

6.4 弱一致性迭代器

ConcurrentHashMap的迭代器是弱一致性的:

  • 迭代器創(chuàng)建后,如果Map發(fā)生修改,不會(huì)拋出ConcurrentModificationException
  • 迭代器反映的是創(chuàng)建時(shí)刻或之后某個(gè)時(shí)刻的數(shù)據(jù)快照
  • 迭代過(guò)程中修改Map,迭代器可能看到,也可能看不到修改結(jié)果
  • 適用于高并發(fā)場(chǎng)景,避免了快速失敗機(jī)制帶來(lái)的問(wèn)題

6.5 批量操作

ConcurrentHashMap提供了強(qiáng)大的批量操作API:

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// forEach:遍歷每個(gè)元素
map.forEach(1, (k, v) -> System.out.println(k + ":" + v));
// search:查找第一個(gè)符合條件的元素
String result = map.search(1, (k, v) -> v > 100 ? k : null);
// reduce:累加操作
Integer sum = map.reduceValues(1, Integer::sum);
// 用作頻率統(tǒng)計(jì)(MultiSet)
ConcurrentHashMap<String, LongAdder> freqs = new ConcurrentHashMap<>();
freqs.computeIfAbsent("word", k -> new LongAdder()).increment();

parallelismThreshold參數(shù)控制并行度:小于閾值時(shí)串行執(zhí)行,大于閾值時(shí)并行執(zhí)行。

第七章 Map常用方法詳解

7.1 基礎(chǔ)操作方法

方法描述返回值說(shuō)明
put(K key, V value)添加鍵值對(duì)返回該key之前的value,如果沒(méi)有則返回null
get(Object key)根據(jù)key獲取value存在則返回value,否則返回null
remove(Object key)刪除鍵值對(duì)返回被刪除的value
clear()清空所有鍵值對(duì)void
size()返回鍵值對(duì)數(shù)量int
isEmpty()判斷是否為空boolean

7.2 查詢(xún)方法

方法描述
containsKey(Object key)判斷是否包含指定鍵
containsValue(Object value)判斷是否包含指定值(HashMap中效率較低,需遍歷)
getOrDefault(Object key, V defaultValue)獲取值,不存在則返回默認(rèn)值

7.3 遍歷方法

Map的遍歷方式多樣,可根據(jù)場(chǎng)景選擇:

7.3.1 entrySet遍歷(最常用)

for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

7.3.2 keySet + get遍歷

for (String key : map.keySet()) {
    System.out.println(key + ": " + map.get(key));
}
// 缺點(diǎn):每次get都需要二次查找,效率較低

7.3.3 values遍歷(僅需值時(shí))

for (Integer value : map.values()) {
    System.out.println(value);
}

7.3.4 Iterator遍歷(支持remove)

Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<String, Integer> entry = iterator.next();
    if (entry.getValue() < 0) {
        iterator.remove(); // 安全刪除
    }
}

7.3.5 Java 8 forEach(最簡(jiǎn)潔)

map.forEach((key, value) -> System.out.println(key + ": " + value));

7.3.6 Stream API遍歷(支持鏈?zhǔn)讲僮鳎?/h4>
map.entrySet().stream()
    .filter(entry -> entry.getValue() > 10)
    .forEach(entry -> System.out.println(entry.getKey()));

7.4 Java 8+新增的默認(rèn)方法

Java 8在Map接口中增加了多個(gè)實(shí)用默認(rèn)方法,極大地簡(jiǎn)化了代碼:

7.4.1 computeIfAbsent / computeIfPresent

// 如果key不存在,則通過(guò)函數(shù)計(jì)算value并放入Map
map.computeIfAbsent("key", k -> new ArrayList<>()).add("value");
// 經(jīng)典用法:實(shí)現(xiàn)多值Map
Map<String, List<String>> multiMap = new HashMap<>();
multiMap.computeIfAbsent("group1", k -> new ArrayList<>()).add("item1");
// 如果key存在,則根據(jù)原值計(jì)算新值
map.computeIfPresent("key", (k, v) -> v * 2);

7.4.2 merge方法

// 合并操作:如果key不存在則放入給定值,存在則通過(guò)合并函數(shù)計(jì)算新值
map.merge("key", 1, Integer::sum); // 統(tǒng)計(jì)功能
// 經(jīng)典用法:?jiǎn)卧~計(jì)數(shù)
String text = "apple banana apple orange apple";
Map<String, Integer> wordCount = new HashMap<>();
for (String word : text.split(" ")) {
    wordCount.merge(word, 1, Integer::sum);
}
// 結(jié)果:{apple=3, banana=1, orange=1}

7.4.3 putIfAbsent

// 僅在key不存在時(shí)放入
map.putIfAbsent("key", "value");

7.4.4 replace / replaceAll

// 替換指定key的值(僅當(dāng)存在時(shí))
map.replace("key", "newValue");
// 對(duì)所有entry應(yīng)用替換函數(shù)
map.replaceAll((k, v) -> v.toUpperCase());

7.5 Java 9的Map.of工廠(chǎng)方法

Java 9提供了更簡(jiǎn)潔的Map初始化方式:

// 創(chuàng)建不可變Map(最多支持10對(duì)鍵值)
Map<String, Integer> map1 = Map.of(
    "a", 1,
    "b", 2,
    "c", 3
);
// 任意數(shù)量鍵值對(duì)
Map<String, Integer> map2 = Map.ofEntries(
    Map.entry("a", 1),
    Map.entry("b", 2),
    Map.entry("c", 3),
    Map.entry("d", 4)
);

第八章 實(shí)現(xiàn)類(lèi)對(duì)比與選型指南

8.1 核心特性對(duì)比

特性HashMapLinkedHashMapTreeMapHashtableConcurrentHashMap
順序無(wú)序插入/訪(fǎng)問(wèn)順序鍵排序無(wú)序無(wú)序
null鍵允許1個(gè)允許1個(gè)不允許不允許不允許
null值允許允許允許不允許不允許
線(xiàn)程安全是(全表鎖)是(分段/CAS)
性能最高略低于HashMap較低(log n)讀慢寫(xiě)快高并發(fā)下最優(yōu)
底層結(jié)構(gòu)數(shù)組+鏈表+紅黑樹(shù)數(shù)組+鏈表+紅黑樹(shù)+雙向鏈表紅黑樹(shù)數(shù)組+鏈表CAS+數(shù)組+鏈表+紅黑樹(shù)
適用場(chǎng)景通用緩存需保持順序需排序/范圍查詢(xún)遺留系統(tǒng)高并發(fā)共享數(shù)據(jù)

8.2 時(shí)間復(fù)雜度對(duì)比

操作HashMapLinkedHashMapTreeMapHashtableConcurrentHashMap
getO(1)O(1)O(log n)O(1)O(1)
putO(1)O(1)O(log n)O(1)O(1)
removeO(1)O(1)O(log n)O(1)O(1)
containsKeyO(1)O(1)O(log n)O(1)O(1)
containsValueO(n)O(n)O(n)O(n)O(n)

8.3 選型建議

根據(jù)不同的業(yè)務(wù)場(chǎng)景,選擇合適的Map實(shí)現(xiàn):

場(chǎng)景1:通用緩存,無(wú)特殊順序要求

  • ? 首選:HashMap(性能最高)
  • 如果線(xiàn)程安全要求:ConcurrentHashMap

場(chǎng)景2:需要保持插入順序

  • ? 首選:LinkedHashMap
  • 案例:實(shí)現(xiàn)FIFO隊(duì)列、記錄操作日志

場(chǎng)景3:需要按鍵排序或范圍查詢(xún)

  • ? 首選:TreeMap
  • 案例:排行榜、日程表、字典序輸出

場(chǎng)景4:實(shí)現(xiàn)LRU緩存

  • ? 首選:LinkedHashMap(訪(fǎng)問(wèn)順序模式)
  • 案例:內(nèi)存緩存、最近訪(fǎng)問(wèn)記錄

場(chǎng)景5:高并發(fā)共享數(shù)據(jù)

  • ? 首選:ConcurrentHashMap
  • 案例:全局配置、在線(xiàn)用戶(hù)統(tǒng)計(jì)

場(chǎng)景6:處理配置文件

  • ? 首選:Properties
  • 案例:讀取application.properties

8.4 性能測(cè)試數(shù)據(jù)參考

根據(jù)實(shí)際測(cè)試(百萬(wàn)級(jí)數(shù)據(jù)):

操作HashMapLinkedHashMapTreeMapHashtable
插入100萬(wàn)條1420ms1512ms3845ms797ms
讀取1000萬(wàn)條188ms201ms892ms265ms

注:Hashtable插入快可能是由于其初始容量較小,擴(kuò)容頻率高導(dǎo)致的測(cè)試偏差,實(shí)際應(yīng)用中HashMap綜合性能最優(yōu)。

第九章 常見(jiàn)陷阱與最佳實(shí)踐

9.1 陷阱一:可變對(duì)象作為鍵

// 錯(cuò)誤示例
Map<List<String>, String> map = new HashMap<>();
List<String> key = new ArrayList<>();
key.add("a");
map.put(key, "value1");
key.add("b"); // 鍵被修改,hashCode改變
map.get(key); // 返回null,再也找不到
map.containsKey(key); // false

解決方案:使用不可變對(duì)象作為鍵,如String、Integer,或自定義不可變類(lèi)。

9.2 陷阱二:自定義類(lèi)未重寫(xiě)hashCode和equals

class User {
    String name;
    // 沒(méi)有重寫(xiě)hashCode和equals
}
Map<User, Integer> map = new HashMap<>();
User u1 = new User("Alice");
User u2 = new User("Alice");
map.put(u1, 100);
map.get(u2); // 返回null,雖然內(nèi)容相同

解決方案:作為鍵的類(lèi)必須正確重寫(xiě)hashCode()equals()

9.3 陷阱三:并發(fā)修改導(dǎo)致ConcurrentModificationException

Map<String, Integer> map = new HashMap<>();
// ... 填充數(shù)據(jù)
for (String key : map.keySet()) {
    if (key.startsWith("temp")) {
        map.remove(key); // 拋出ConcurrentModificationException
    }
}

解決方案

// 方式1:使用Iterator的remove
Iterator<String> it = map.keySet().iterator();
while (it.hasNext()) {
    String key = it.next();
    if (key.startsWith("temp")) {
        it.remove();
    }
}
// 方式2:使用removeIf(Java 8+)
map.keySet().removeIf(key -> key.startsWith("temp"));
// 方式3:使用ConcurrentHashMap(允許并發(fā)修改)

9.4 最佳實(shí)踐總結(jié)

  • 預(yù)估初始容量:如果能預(yù)知數(shù)據(jù)規(guī)模,指定初始容量避免頻繁擴(kuò)容
Map<String, Integer> map = new HashMap<>(expectedSize * 4 / 3 + 1);
  • 使用泛型:指定鍵值類(lèi)型,避免運(yùn)行時(shí)類(lèi)型轉(zhuǎn)換異常
  • 優(yōu)先使用Java 8+默認(rèn)方法:讓代碼更簡(jiǎn)潔
// 老式
if (!map.containsKey(key)) {
    map.put(key, new ArrayList<>());
}
map.get(key).add(value);
// 新式
map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
  • 選擇合適的實(shí)現(xiàn):根據(jù)業(yè)務(wù)需求而非習(xí)慣選擇
  • 注意線(xiàn)程安全:多線(xiàn)程環(huán)境優(yōu)先使用ConcurrentHashMap
  • 避免使用Hashtable:除非維護(hù)遺留代碼

結(jié)語(yǔ)

Java Map體系經(jīng)過(guò)多年的演進(jìn),從最早的Hashtable,到JDK 1.2引入的HashMap,再到JDK 1.5的ConcurrentHashMap,以及后續(xù)的各種優(yōu)化,已經(jīng)形成了一套功能完備、性能卓越的數(shù)據(jù)結(jié)構(gòu)家族。

理解Map的核心原理,不僅有助于寫(xiě)出更高效的代碼,還能在遇到復(fù)雜業(yè)務(wù)場(chǎng)景時(shí)做出正確的技術(shù)選型。本文從源碼層面剖析了各個(gè)Map實(shí)現(xiàn)類(lèi)的底層機(jī)制,并結(jié)合實(shí)際場(chǎng)景給出了使用建議。在實(shí)際開(kāi)發(fā)中,建議遵循"面向接口編程"的原則,根據(jù)具體需求選擇最合適的Map實(shí)現(xiàn),同時(shí)注意線(xiàn)程安全和鍵的不可變性等關(guān)鍵問(wèn)題。

Map的學(xué)習(xí)是一個(gè)循序漸進(jìn)的過(guò)程,掌握基礎(chǔ)用法后,深入理解其設(shè)計(jì)思想和源碼實(shí)現(xiàn),才能真正做到"知其然,知其所以然"。

到此這篇關(guān)于Java Map常用方法和實(shí)現(xiàn)類(lèi)深度詳解的文章就介紹到這了,更多相關(guān)java map常用方法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring全局異常捕獲不生效問(wèn)題的解決辦法

    Spring全局異常捕獲不生效問(wèn)題的解決辦法

    Spring項(xiàng)目全局異常處理不生效,登錄接口報(bào)錯(cuò)異常信息被直接返回到接口響應(yīng)中,本文給大家介紹了Spring全局異常捕獲不生效問(wèn)題的解決辦法,文中有詳細(xì)的圖文介紹,需要的朋友可以參考下
    2024-04-04
  • 使用Spring Security控制會(huì)話(huà)的方法

    使用Spring Security控制會(huì)話(huà)的方法

    在本文中,我們將說(shuō)明Spring Security如何允許我們控制HTTP會(huì)話(huà)。這篇文章主要介紹了使用Spring Security控制會(huì)話(huà) ,需要的朋友可以參考下
    2019-05-05
  • SpringBoot入門(mén)實(shí)現(xiàn)第一個(gè)SpringBoot項(xiàng)目

    SpringBoot入門(mén)實(shí)現(xiàn)第一個(gè)SpringBoot項(xiàng)目

    今天我們一起來(lái)完成一個(gè)簡(jiǎn)單的SpringBoot(Hello World)。就把他作為你的第一個(gè)SpringBoot項(xiàng)目。具有一定的參考價(jià)值,感興趣的可以了解一下
    2021-09-09
  • Spring MultipartFile實(shí)現(xiàn)多文件上傳攻略

    Spring MultipartFile實(shí)現(xiàn)多文件上傳攻略

    這篇文章主要介紹了Spring MultipartFile實(shí)現(xiàn)多文件上傳,MultipartFile是Spring框架中用于處理文件上傳的核心接口,MultipartFile的使用需注意文件驗(yàn)證和錯(cuò)誤處理,以保證系統(tǒng)的穩(wěn)定性和安全性,需要的朋友可以參考下
    2025-10-10
  • Maven項(xiàng)目配置Tomcat的兩種方式

    Maven項(xiàng)目配置Tomcat的兩種方式

    本文主要介紹了Maven項(xiàng)目配置Tomcat的兩種方式,一種是用idea開(kāi)發(fā),另一種是eclipse開(kāi)發(fā),具有一定的參考價(jià)值,感興趣的可以了解一下
    2022-05-05
  • Spring如何實(shí)現(xiàn)管理事務(wù)

    Spring如何實(shí)現(xiàn)管理事務(wù)

    Spring通過(guò)編程式事務(wù)和聲明式事務(wù)管理來(lái)控制事務(wù)的邊界和行為,聲明式事務(wù)管理通過(guò)@Transactional注解實(shí)現(xiàn),提供了豐富的配置選項(xiàng)來(lái)控制事務(wù)的行為,如傳播行為、隔離級(jí)別、超時(shí)時(shí)間和回滾規(guī)則
    2024-11-11
  • idea 開(kāi)發(fā)神器之idea插件匯總

    idea 開(kāi)發(fā)神器之idea插件匯總

    這篇文章主要介紹了idea 開(kāi)發(fā)神器之idea插件匯總,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-12-12
  • Java 遍歷 String 字符串所有字符的操作

    Java 遍歷 String 字符串所有字符的操作

    這篇文章主要介紹了Java 遍歷 String 字符串所有字符的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-10-10
  • Spring+MyBatis實(shí)現(xiàn)數(shù)據(jù)讀寫(xiě)分離的實(shí)例代碼

    Spring+MyBatis實(shí)現(xiàn)數(shù)據(jù)讀寫(xiě)分離的實(shí)例代碼

    本篇文章主要介紹了Spring+MyBatis實(shí)現(xiàn)數(shù)據(jù)讀寫(xiě)分離的實(shí)例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • 詳解JAVA中priorityqueue的具體使用

    詳解JAVA中priorityqueue的具體使用

    這篇文章主要介紹了詳解JAVA中priorityqueue的具體使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01

最新評(píng)論

垦利县| 辽宁省| 宁河县| 常熟市| 鄢陵县| 武邑县| 常山县| 安乡县| 西宁市| 陕西省| 平陆县| 新营市| 青河县| 金川县| 汉源县| 武鸣县| 滕州市| 虹口区| 开原市| 奉节县| 武平县| 万载县| 鄯善县| 伊川县| 泾阳县| 宜章县| 和政县| 信丰县| 丰台区| 柳江县| 伊宁市| 喀喇沁旗| 宿松县| 哈密市| 宜丰县| 乐都县| 鄱阳县| 醴陵市| 泸溪县| 景洪市| 镶黄旗|