Java集合Map总结

Java集合Map总结

  • [一、Map 核心特性](#一、Map 核心特性)
  • [二、Map 接口核心方法](#二、Map 接口核心方法)
  • [三、Map 主要实现类](#三、Map 主要实现类)
    • [1. HashMap(最常用)](#1. HashMap(最常用))
      • [* 底层结构演变](#* 底层结构演变)
      • [* 核心原理:哈希计算与桶定位](#* 核心原理:哈希计算与桶定位)
      • [* 关键参数](#* 关键参数)
      • [* 扩容机制(resize)](#* 扩容机制(resize))
      • [* 使用 HashMap 的关键注意事项](#* 使用 HashMap 的关键注意事项)
      • [* HashMap 性能对比](#* HashMap 性能对比)
      • [* 总结](#* 总结)
    • [2. LinkedHashMap](#2. LinkedHashMap)
    • [3. TreeMap](#3. TreeMap)
    • [4. Hashtable(过时,不推荐使用)](#4. Hashtable(过时,不推荐使用))
    • [5. ConcurrentHashMap](#5. ConcurrentHashMap)
  • [四、Map 遍历方式对比](#四、Map 遍历方式对比)
  • [五、使用 Map 的注意事项](#五、使用 Map 的注意事项)
  • 六、总结

Map 是 Java 中最核心的集合之一,是 Java 集合框架(java.util 包)中 键值对(Key-Value) 存储的核心接口,是独立的顶级接口。其核心特性是: 键(Key)唯一且不可重复,值(Value)可重复 ,通过键可以快速查找对应的值,是日常开发中最常用的集合之一。

一、Map 核心特性

  • 1.键值映射:每个键对应一个值,键是唯一的(重复放入相同键会覆盖原有值),值可重复。
  • 2.无序 / 有序:
    • 基础实现(如 HashMap)是无序的(不保证插入顺序和遍历顺序一致);
    • 有序实现(如 LinkedHashMap、TreeMap)可保证插入顺序或排序顺序。
  • 3.允许 null:
    • HashMap、LinkedHashMap 允许键和值为 null(但键只能有一个 null);
    • Hashtable、TreeMap 不允许键或值为 null。
  • 4.非线程安全:大部分实现(HashMap、LinkedHashMap、TreeMap)非线程安全,Hashtable 是线程安全但性能差,推荐使用 Collections.synchronizedMap() 或 ConcurrentHashMap(JUC 包)。

二、Map 接口核心方法

Map 定义了操作键值对的核心方法,所有实现类都需实现这些方法,常用方法如下:

方法签名 功能说明
V put(K key, V value) 插入键值对,若键已存在则覆盖原值,返回被覆盖的值(无则返回 null)
V get(Object key) 根据键获取值,键不存在返回 null
V remove(Object key) 删除指定键的键值对,返回被删除的值(无则返回 null)
boolean containsKey(Object key) 判断是否包含指定键
boolean containsValue(Object value) 判断是否包含指定值(效率低,需遍历所有值)
int size() 返回键值对数量
boolean isEmpty() 判断是否为空
void clear() 清空所有键值对
Set keySet() 返回所有键的 Set 集合(视图,修改会同步到原 Map)
Collection values() 返回所有值的 Collection 集合(视图)
Set<Map.Entry<K, V>> entrySet() 返回所有键值对(Entry 对象)的 Set 集合(遍历推荐用此方法)

核心内部接口:Map.Entry<K, V>
Map.Entry 是 Map 的内部接口,代表一个键值对,常用方法:

  • K getKey():获取键;
  • V getValue():获取值;
  • V setValue(V value):修改值(会同步到原 Map)。

三、Map 主要实现类

Java 提供了多种 Map 实现,适配不同场景,核心实现类如下:

1. HashMap(最常用)

核心特点

  • 底层实现 :JDK 1.8 前是「数组 + 链表」,JDK 1.8 后是「数组 + 链表 + 红黑树」(链表长度 ≥8 且数组长度 ≥64 时,链表转红黑树,提升查询效率)。
  • 无序:遍历顺序与插入顺序无关。
  • 允许 null 键 / 值:键只能有一个 null,值可多个 null。
  • 初始容量与负载因子
    • 默认初始容量 16,负载因子 0.75(当元素数量 ≥ 容量 × 负载因子时,触发扩容,扩容为原容量 2 倍);
    • 容量必须是 2 的幂(哈希桶定位优化)。
  • 线程安全 :非线程安全,多线程环境下可能出现死循环(JDK 1.7)、数据丢失等问题。

适用场景

日常开发中绝大多数场景(非线程安全、无需有序),如缓存、配置存储等。

示例代码

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class HashMapDemo {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        
        // 插入键值对
        map.put("Apple", 10);
        map.put("Banana", 20);
        map.put("Apple", 15); // 覆盖原有值
        
        // 获取值
        System.out.println(map.get("Banana")); // 20
        System.out.println(map.get("Apple"));  // 15
        
        // 遍历键值对(推荐 entrySet)
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }
        
        // 删除
        map.remove("Banana");
        System.out.println(map.size()); // 1
    }
}

* 底层结构演变

HashMap 的底层结构在 JDK 1.8 有重大优化,核心目标是解决哈希冲突导致的链表过长问题,提升查询效率。

JDK 版本 核心结构 哈希冲突解决 查询时间复杂度
1.7 及之前 数组(哈希桶)+ 单向链表 链地址法(链表存储冲突元素) O (n)(链表越长效率越低)
1.8 及之后 数组(哈希桶)+ 单向链表 + 红黑树 链地址法 + 红黑树优化(链表转树) 平均 O (1),最坏 O (log n)

核心概念说明

1.哈希桶(Bucket) :数组的每个元素称为哈希桶,存储链表 / 红黑树的头节点,数组下标通过「键的哈希值」计算得到。

2.哈希冲突 :不同键的哈希值计算出相同的数组下标,需通过链表 / 红黑树存储这些冲突元素。

3.红黑树转换条件

  • 链表长度 ≥ 8 且 数组长度 ≥ 64 时,链表转为红黑树;
  • 红黑树节点数 ≤ 6 时,转回链表(避免红黑树维护成本过高)。

* 核心原理:哈希计算与桶定位

HashMap 能高效查找的核心是「哈希值计算 + 桶定位」,步骤如下:
1.键的哈希值计算

JDK 1.8 对键的 hashCode() 做了优化,减少哈希冲突:

java 复制代码
static final int hash(Object key) {
    int h;
    // 1. 取key的hashCode()
    // 2. 高位与低位异或(扰动函数),让高位参与哈希计算,减少冲突
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • 若键为 null,哈希值固定为 0(因此 null 键永远存在下标 0 的桶中);
  • 「扰动函数」h ^ (h >>> 16):将哈希值的高 16 位与低 16 位异或,让高位特征参与桶定位,降低数组长度较小时的冲突概率。

2.桶下标计算

通过哈希值计算数组下标,确保结果在数组长度范围内:

java 复制代码
// n 是数组长度,n >= 1(必须是 2 的幂(2⁰,2¹,2²,......))
int index = (n - 1) & hash;
  • 为什么用 & 而非 %?因为当 n 是 2 的幂时,(n-1) & hash 等价于 hash % n,但位运算效率远高于取模;
  • 要求数组长度必须是 2 的幂(2⁰,2¹,2²,...),是该计算方式的前提(HashMap 初始化 / 扩容时会保证这一点)。

* 关键参数

参数名 默认值 作用
initialCapacity(初始容量) 16 哈希桶数组的初始长度,必须是 2 的幂(若手动指定非 2 的幂,会自动调整为最近的 2 的幂
loadFactor(负载因子) 0.75 扩容阈值的计算系数:threshold = capacity × loadFactor
threshold(扩容阈值) 16 × 0.75 = 12 当 HashMap 中元素数量 ≥ threshold 时,触发扩容
size 0 实际存储的键值对数量
modCount 0 结构修改次数(用于快速失败机制,遍历中修改会抛 ConcurrentModificationException)

参数设计的合理性

  • 负载因子 0.75:是「时间效率」和「空间效率」的平衡。
    • 负载因子过高(如 1.0):空间利用率高,但哈希冲突概率大幅增加,链表 / 红黑树变长,查询变慢;
    • 负载因子过低(如 0.5):哈希冲突少,但数组空桶多,空间浪费。

* 扩容机制(resize)

HashMap 的 resize()是核心底层逻辑,目的是解决哈希桶数组容量不足导致的哈希冲突加剧问题,通过扩大数组容量、重新分配元素位置,维持「平均 O (1)」的操作效率。

java 复制代码
final Node<K,V>[] resize() {
    // 1. 保存原哈希桶数组的引用
    Node<K,V>[] oldTab = table;
    // 2. 初始化 oldCap:原数组为 null(未初始化)则 oldCap=0,否则为原数组长度
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    // 3. 原扩容阈值(辅助计算 newCap)
    int oldThr = threshold;
    // 4. 声明 newCap(扩容后的容量)、newThr(扩容后的阈值)
    int newCap, newThr = 0;

    // ===== 分场景计算 newCap =====
    // 场景1:原数组已初始化(oldCap > 0,即已完成过首次扩容)
    if (oldCap > 0) {
        // 1.1 原容量达到最大值(2^30),不再扩容,直接返回原数组
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 1.2 原容量翻倍作为 newCap(oldCap << 1 等价于 oldCap × 2),且不超过最大值
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) {
            newThr = oldThr << 1; // 阈值也翻倍
        }
    }
    // 场景2:原数组未初始化,但阈值已设置(如手动指定初始容量的构造函数)
    else if (oldThr > 0) {
        newCap = oldThr; // 扩容后的容量 = 原阈值(因为构造时 threshold 已被设为调整后的 2 的幂)
    }
    // 场景3:原数组未初始化,且阈值为 0(无参构造)
    else {
        newCap = DEFAULT_INITIAL_CAPACITY; // newCap = 16(默认初始容量)
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 阈值=12
    }
    // ... 后续迁移元素到新数组(容量为 newCap)...
}

HashMap 的哈希桶数组(table)长度固定时,随着元素增加,哈希冲突会越来越严重(链表 / 红黑树变长),查询 / 插入性能下降。扩容通过「翻倍数组容量 + 重新散列元素」,降低冲突概率。

在 HashMap 的 resize() 方法中,oldCap 和 newCap 是描述哈希桶数组容量的核心变量,分别对应「扩容前的数组容量」和「扩容后的数组容量」

变量名 全称(语义) 核心含义 数据类型
oldCap old Capacity 扩容哈希桶数组(table)的长度(容量) int
newCap new Capacity 扩容哈希桶数组(table)的目标长度(容量) int
场景 oldCap(扩容前) newCap(扩容后/初始化后) 说明
无参构造首次扩容 0(数组未初始化) 16 懒加载,首次 put 触发初始化,容量为默认 16
第一次扩容(16→32) 16 32 容量翻倍(16×2)
第二次扩容(32→64) 32 64 容量继续翻倍
手动指定初始容量 0 0 1 tableSizeFor(0) 调整为 1,扩容后容量 1
手动指定初始容量 1(initialCapacity=1) 1 1 tableSizeFor(1) 直接返回 1(已是 2 的幂,无需调整),初始化后容量为 1
容量达最大值(2^30) 1073741824(2^30) 不变化 不再扩容,返回原数组

HashMap 哈希桶数组的最小合法长度是 1(2⁰),由 tableSizeFor方法兜底保障;无参构造的默认长度是 16,而长度 1 仅作为边界场景存在(手动指定容量 0/1 时触发),实际开发中应避免使用(哈希冲突极高)。

HashMap 通过 tableSizeFor(int cap) 方法强制将容量调整为「不小于输入值的最小 2 的幂」,且兜底返回 1,确保长度不会小于 1:

java 复制代码
static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    // 核心兜底:若 n < 0(如 cap=0 时,n=-1),返回 1;否则返回 n+1(保证 ≥1)
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

关键验证:

  • 当 cap=0 时:n = 0-1 = -1 → 触发 n < 0,返回 1;
  • 当 cap=1 时:n = 0 → 移位后仍为 0 → 返回 0+1=1;
  • 当 cap≥2 时:返回最小的 2 的幂(如 cap=3 → 返回 4,cap=5 → 返回 8)。

不同场景下的数组长度初始化:

场景 手动指定初始容量 数组长度调整结果 说明
场景 1 new HashMap<>(0) 1 tableSizeFor(0) 会返回 1(最小 2 的幂)
场景 2 new HashMap<>(1) 1 1 本身是 2 的幂,无需调整
场景 3 new HashMap<>(2) 2 2 是 2 的幂,直接使用
场景 4 new HashMap<>()(无参) 首次 put 后为 16 无参构造默认初始容量是 16(1 << 4)
场景 5 new HashMap<>(-1) 抛异常 容量不能为负数,校验阶段直接报错

扩容是 HashMap 性能的核心影响点,JDK 1.8 对扩容逻辑做了优化:
扩容触发条件

  • 当 size ≥ threshold 且 新元素插入的桶不为空时,触发扩容;
  • 扩容后数组长度变为原来的 2 倍(保证「数组长度是 2 的幂」(2⁰,2¹,2²,...));
  • 新阈值 threshold = 新容量 × loadFactor。

JDK 1.8 扩容核心优化
扩容时需要重新计算所有元素的桶下标,JDK 1.8 利用「数组长度是 2 的幂」的特性,简化了下标计算:

  • 原下标:index = hash & (oldCap - 1);
  • 新下标:newIndex = hash & (newCap - 1) = hash & (2×oldCap - 1);
  • 结论:新下标要么等于原下标,要么等于 原下标 + oldCap(无需重新计算哈希,只需判断哈希值的某一位)。

hash 是 HashMap 为了减少哈希冲突,对键的原始 hashCode() 做「高位扰动」后的最终哈希值,是计算桶下标的核心依据:

  • 若键为 null:hash = 0(固定值);
  • 若键非 null:hash = 键的 hashCode() ^ (键的 hashCode() >>> 16)(扰动函数)。

HashMap 源码中通过 static final int hash(Object key) 方法生成该值,核心代码:

java 复制代码
static final int hash(Object key) {
    int h;
    // 三步核心逻辑:
    // 1. 若 key 为 null → h = 0;否则取 key 的原始 hashCode()
    // 2. 将 h 的高 16 位右移 16 位(h >>> 16),与原 h 做异或(^)
    // 3. 返回最终扰动后的 hash 值
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

扩容步骤

1.创建新的**哈希桶数组**,长度为原数组的 2 倍;

2.遍历原数组的每个桶,处理桶中的元素(链表 / 红黑树):

  • 若为链表:按规则拆分到新数组的两个桶中(保持原顺序,避免死循环);
  • 若为红黑树:拆分后若节点数 ≤ 6,转回链表;

3.替换原数组为新数组,更新容量和阈值。

DK 1.7 扩容的坑(死循环)

JDK 1.7 扩容时,链表采用「头插法」,多线程环境下会导致链表成环,进而引发死循环。JDK 1.8 改为「尾插法」,解决了该问题,但 HashMap 仍非线程安全(仍可能出现数据丢失、覆盖等问题)。

* 使用 HashMap 的关键注意事项

①自定义类作为键必须重写 hashCode () 和 equals ()

  • 原因:HashMap 通过 hashCode() 定位桶,通过 equals() 判断键是否相等;
  • 若不重写:即使两个对象内容相同,也会被当作不同键,导致哈希冲突或重复插入;
  • 重写规则:
    • equals() 相等的对象,hashCode() 必须相等;
    • hashCode() 相等的对象,equals() 不一定相等(哈希冲突)。

反例(错误)

java 复制代码
class Person {
    String name;
    public Person(String name) { this.name = name; }
}

// 两个内容相同的Person会被当作不同键
Map<Person, Integer> map = new HashMap<>();
map.put(new Person("张三"), 1);
map.put(new Person("张三"), 2);
System.out.println(map.size()); // 2(错误,期望1)

正例(正确)

java 复制代码
class Person {
    String name;
    public Person(String name) { this.name = name; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

Map<Person, Integer> map = new HashMap<>();
map.put(new Person("张三"), 1);
map.put(new Person("张三"), 2);
System.out.println(map.size()); // 1(正确)

②避免频繁扩容

  • 若已知 Map 存储的元素数量,初始化时指定容量:
java 复制代码
// 预计存储1000个元素,指定容量为 1000 / 0.75 ≈ 1334,向上取2的幂为2048
Map<String, Integer> map = new HashMap<>(2048);
  • 避免初始化容量为非 2 的幂(HashMap 会自动调整,但可能不符合预期)。

③慎用 null 键 / 值

  • null 键只能有一个,null 值可多个;
  • 弊端:get(key) 返回 null 时,无法区分「键不存在」和「值为 null」,建议用 containsKey(key) 先判断。

④遍历中修改 Map 的正确方式

  • 禁止在增强 for 循环中直接 remove,会触发 ConcurrentModificationException;
  • 正确方式:
java 复制代码
// 方式1:使用Iterator删除
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<String, Integer> entry = iterator.next();
    if (entry.getValue() == 2) {
        iterator.remove(); // 安全删除
    }
}

// 方式2:Java 8+ removeIf
map.entrySet().removeIf(entry -> entry.getValue() == 2);

⑤红黑树树化的前提

链表转红黑树需同时满足两个条件:

  • 链表长度 ≥ 8;
  • 数组长度 ≥ 64。
    若数组长度 < 64,即使链表长度 ≥8,也只会触发扩容而非树化(优先通过扩容减少冲突)。

* HashMap 性能对比

操作 平均时间复杂度 最坏时间复杂度(JDK 1.7) 最坏时间复杂度(JDK 1.8)
put O(1) O (n)(链表过长) O (log n)(红黑树)
get O(1) O (n)(链表过长) O (log n)(红黑树)
remove O(1) O (n)(链表过长) O (log n)(红黑树)

* 总结

HashMap 是 Java 中最核心的集合实现,其设计体现了「哈希表 + 冲突优化」的经典思路:

1.JDK 1.8 引入红黑树,解决了链表过长导致的性能下降问题;

2.哈希计算和桶定位的优化,保证了平均 O (1) 的操作效率;

3.扩容机制的优化(尾插法、下标简化),解决了死循环并提升了扩容性能;

4.非线程安全,高并发场景需使用 ConcurrentHashMap;

5.自定义键必须重写 hashCode () 和 equals (),否则会导致功能异常。

2. LinkedHashMap

核心特点

  • 底层实现 :HashMap + 双向链表(维护插入顺序或访问顺序)。
  • 有序:默认保证「插入顺序」,也可通过构造函数指定「访问顺序」(最近访问的元素排在末尾)。
  • 性能:略低于 HashMap(额外维护链表),但遍历效率更高。
  • 其他 :允许 null 键 / 值,非线程安全。

适用场景

需要保证遍历顺序与插入顺序一致的场景,如:LRU 缓存(通过访问顺序实现)、日志记录等。

示例代码(访问顺序)

java 复制代码
import java.util.LinkedHashMap;
import java.util.Map;

public class LinkedHashMapDemo {
    public static void main(String[] args) {
        // 构造函数:初始容量16,负载因子0.75,访问顺序(true)
        Map<String, Integer> map = new LinkedHashMap<>(16, 0.75f, true);
        
        map.put("A", 1);
        map.put("B", 2);
        map.put("C", 3);
        
        map.get("B"); // 访问B,B会被移到末尾
        
        // 遍历:A -> C -> B(访问顺序)
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }
}

3. TreeMap

核心特点

  • 底层实现:红黑树(一种自平衡的二叉查找树)。
  • 有序:按键的「自然顺序」(如 Integer 升序、String 字典序)或「自定义比较器(Comparator)」排序。
  • 不允许 null 键(会抛出 NullPointerException),值可以为 null。
  • 性能:插入、查询、删除的时间复杂度为 O (log n),低于 HashMap。
  • 非线程安全

适用场景

需要对键进行排序的场景,如:排行榜、按日期排序的日志等。

示例代码(自定义比较器)

java 复制代码
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;

public class TreeMapDemo {
    public static void main(String[] args) {
        // 自定义比较器:按字符串长度降序
        Map<String, Integer> map = new TreeMap<>(new Comparator<String>() {
            @Override
            public int compare(String s1, String s2) {
                return Integer.compare(s2.length(), s1.length());
            }
        });
        
        map.put("Apple", 5);
        map.put("Banana", 6);
        map.put("Cherry", 6);
        
        // 遍历:Banana/Cherry(长度6) -> Apple(长度5)
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }
}

4. Hashtable(过时,不推荐使用)

核心特点

  • 底层实现:数组 + 链表(JDK 1.8 未优化为红黑树)。
  • 线程安全:所有方法加 synchronized 锁,性能极低。
  • 不允许 null 键 / 值:会抛出 NullPointerException。
  • 初始容量:默认 11,负载因子 0.75,扩容为 2× 原容量 + 1。

替代方案

  • 线程安全且高性能:java.util.concurrent.ConcurrentHashMap(JUC 包);
  • 简单线程安全:Collections.synchronizedMap(new HashMap<>())。

5. ConcurrentHashMap

核心特点

  • 线程安全 :JDK 1.7 用「分段锁(Segment)」,JDK 1.8 用「CAS + synchronized」(只锁当前哈希桶,并发性能大幅提升)。
  • 底层实现:同 HashMap(数组 + 链表 + 红黑树)。
  • 不允许 null 键 / 值(避免与「键不存在返回 null」混淆)。
  • 高并发:支持多线程并发读写,性能远高于 Hashtable。

适用场景

多线程环境下的高并发 Map 操作,如:分布式缓存、并发任务存储等。

四、Map 遍历方式对比

遍历方式 优点 缺点
keySet() + get() 代码简单 效率低(需两次哈希查找),遍历 + 修改可能抛异常
entrySet() 效率高(一次遍历获取键值),推荐使用 代码略复杂
forEach()(Java 8+) 简洁(Lambda 表达式) 无法在遍历中修改 Map(除了通过 Entry.setValue ())
迭代器(Iterator) 支持遍历中删除元素 代码繁琐

遍历示例(Java 8 forEach)

java 复制代码
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);

// Java 8 Lambda 遍历
map.forEach((key, value) -> System.out.println(key + ": " + value));

五、使用 Map 的注意事项

1.键的哈希与相等性:

  • 键的类必须重写 hashCode() 和 equals()(如自定义类作为键),否则会导致重复键或查找失败;
  • 示例:若 Person 类未重写 hashCode() 和 equals(),则两个内容相同的 Person 对象会被当作不同键。

2.避免频繁扩容:

  • 若已知 Map 大小,初始化时指定容量(如 new HashMap<>(100)),减少扩容次数;
  • 负载因子不宜修改(默认 0.75 是时间 / 空间的平衡)。

3.线程安全问题:

  • 非线程安全 Map(HashMap/LinkedHashMap)在多线程下修改会导致 ConcurrentModificationException;
  • 优先使用 ConcurrentHashMap 而非 Hashtable。

4.null 键 / 值处理:

  • 避免依赖 null 值(可改用 Optional 包装),防止空指针;
  • TreeMap/ConcurrentHashMap 不允许 null 键,需提前校验。

5.遍历中修改 Map:

  • 禁止在增强 for 循环中直接删除元素(抛 ConcurrentModificationException);
  • 正确方式:使用 Iterator 的 remove() 方法,或 Java 8+ 的 map.entrySet().removeIf(...)。

六、总结

实现类 有序性 线程安全 null 键 / 值 核心场景
HashMap 无序 允许 日常通用场景
LinkedHashMap 插入 / 访问顺序 允许 需有序、LRU 缓存
TreeMap 键排序 键不允许 排序场景
Hashtable 无序 是(低效) 不允许 过时,不推荐
ConcurrentHashMap 无序 是(高效) 不允许 高并发场景

HashMap 是 Java 集合框架中最常用的键值对存储实现,基于哈希表实现,兼顾查询、插入、删除的高效性。

实际开发中,优先选择 HashMap(通用)、LinkedHashMap(有序)、ConcurrentHashMap(高并发),避免使用 Hashtable。

相关推荐
古城小栈7 小时前
性能边界:何时用 Go 何时用 Java 的技术选型指南
java·后端·golang
古城小栈7 小时前
Go 异步编程:无锁数据结构实现原理
java·数据结构·golang
黄旺鑫7 小时前
系统安全设计规范 · 短信风控篇【参考】
java·经验分享·系统·验证码·设计规范·短信·风控
算法与双吉汉堡8 小时前
【短链接项目笔记】Day1 用户模块
java·spring boot·笔记·后端
一念一花一世界8 小时前
Arbess从基础到实践(23) - 集成GitLab+Hadess实现Java项目构建并上传制品
java·gitlab·cicd·arbess·制品库
啃火龙果的兔子8 小时前
Java 学习路线及学习周期
java·开发语言·学习
Selegant8 小时前
Quarkus vs Spring Boot:谁更适合云原生时代的 Java 开发?
java·spring boot·云原生
ss2738 小时前
SpringBoot+Vue宠物商城系统
java