摘要
在当今的编程世界中,深入了解各类数据类型对于开发者而言至关重要。本篇聚焦于 JDK 21 版本下,Java.util 包所提供的 Map 类型。Map 作为一种关键的数据结构,能够以键值对的形式高效存储和检索数据,广泛应用于众多领域。
本文将简要概述 Map 数据类型在 JDK 21 中的具体实现细节。为了帮助开发者依据不同的应用场景精准选型,我们会分别介绍非线程安全的Map接口及其实现和线程安全的Map接口及其实现。随后本文还将概述 Map 映射类型其广泛的应用场景,助力开发者灵活运用 Map 类型解决各类编程难题,提升代码质量与程序性能。
Java-Map 类图
图1 Java-Map类型数据结构类图(基本)
图2 Java-Map类型数据结构类图(线程安全的)
Dictionary(Deprecated)
Dictionary
是Map
接口的前身,但功能相对较弱且不够灵活。与现代的Map
类似,它也是用于存储键值对,通过键来获取对应的值。不过,它的实现细节比较简单,没有像HashMap
等Map
实现类那样复杂的哈希机制或排序功能。
Map 接口
Map 接口是所有Map 映射类型实现的父类,为Java提供了一种基于key-value键值对存储的数据类型。以下是关于Map 接口的重要方法总结。
-
size:
返回此映射中键值对的数量。若映射包含的元素数量超过Integer.MAX_VALUE
,则返回Integer.MAX_VALUE
。 -
isEmpty:
若此映射不包含任何键值对,则返回true
,用于判断映射是否为空。 -
containsKey:
判断映射中是否包含指定键的映射关系。如果存在键k
使得Objects.equals(key, k)
为true
,则返回true
。 -
containsValue:
判断映射中是否有一个或多个键映射到指定的值。对于大多数Map
接口的实现,此操作可能需要线性时间复杂度(与映射大小相关)。 -
get:
返回指定键所映射的值,如果此映射不包含该键的映射关系,则返回null
。 -
put:
将指定的值与指定的键关联(可选操作)。如果映射先前包含该键的映射关系,则旧值会被指定值替换。返回与键关联的先前值,若不存在该键的映射,则返回null
(若实现支持null
值,返回null
也可能表示之前将null
与该键关联)。 -
remove:
如果存在指定键的映射关系,则从此映射中移除该映射(可选操作),并返回此映射先前与该键关联的值,若不存在该键的映射,则返回null
。 -
putAll:
将指定映射中的所有映射关系复制到此映射中(可选操作)。其效果等同于针对指定映射中的每个键值对,依次调用此映射的put(k, v)
方法。 -
clear:
从此映射中移除所有映射关系(可选操作),调用后映射将变为空。
视图相关方法
-
keySet:
返回此映射中包含的键的Set
视图。该集合由映射支持,对映射的更改会反映在集合中,反之亦然。 -
values:
返回此映射中包含的值的Collection
视图。与keySet
类似,对映射的更改会反映在该集合中,反之亦然,迭代时修改映射可能导致未定义结果,支持通过特定操作移除元素(从而移除映射中对应映射关系),但不支持add
和addAll
操作。 -
entrySet:
返回此映射中包含的映射关系(键值对)的Set
视图。同样遵循映射与视图相互影响、迭代时修改需遵循规则等原则,支持通过相关操作移除元素以移除映射中对应关系,不支持add
和addAll
操作。
Java8 引入
-
getOrDefault:
返回指定键所映射的值,如果此映射不包含该键的映射关系,则返回默认值defaultValue
。默认实现未对同步或原子性属性做保证,若实现要提供原子性保证需重写此方法并记录其并发属性。 -
forEach:
对映射中的每个条目执行给定的操作,直到所有条目都被处理或操作抛出异常。默认实现按照条目集迭代顺序(若有指定顺序)执行操作,未保证同步和原子性属性,若有相关要求需重写并记录。若指定的操作action
为null
,会抛出NullPointerException
;若在迭代期间发现条目被移除,会抛出ConcurrentModificationException
。 -
replaceAll:
对每个条目的值使用给定函数进行替换,直到所有条目都被处理或函数抛出异常。 -
putIfAbsent:
如果指定键尚未与值关联(或映射到null
),则将其与给定值关联并返回null
,否则返回当前值。 -
replace:
仅当指定键当前映射到某个值时,才替换该键对应的条目值,返回先前与指定键关联的值,若不存在该键的映射,则返回null
(若实现支持null
值,返回null
也可能表示之前将null
与该键关联)。 -
computeIfAbsent:
如果指定键尚未与值关联(或映射到null
),则尝试使用给定的映射函数计算其值并将其放入此映射中(除非计算结果为null
),然后返回与指定键关联的当前值(存在则返回现有值,不存在则可能返回计算后的值或null
)。 -
computeIfPresent:
如果指定键的值存在且非null
,则尝试根据键及其当前映射值计算新的映射。若重映射函数返回null
,则移除该映射,返回新的值(若不存在则返回null
)。 -
compute:
尝试根据指定键及其当前映射值(或null
,如果不存在当前映射)计算映射。若重映射函数返回null
,则移除映射(若原本不存在则保持不变),返回新的值(若不存在则返回null
)。 -
merge:
如果指定键尚未与值关联或关联的值为null
,则将其与给定的非null
值关联;否则,使用给定的重映射函数替换关联的值,若结果为null
则移除该映射,返回与指定键关联的新值(若不存在则返回null
)。
Java8 Map方法示例
/**
* Java 8中Map接口新增的方法示例
*/
private static void Java8MapMethodTest() {
// 创建一个HashMap实例,这里使用Map接口来引用它,方便后续切换不同的Map实现类
Map<String, Integer> map = new HashMap<>();
// 1. getOrDefault方法示例
map.put("key1", 10);
// 使用getOrDefault获取"key1"对应的值,由于存在该键的映射,返回映射的值10
Integer value1 = map.getOrDefault("key1", 5);
System.out.println("getOrDefault for key1: " + value1);
// 使用getOrDefault获取"key2"对应的值,不存在该键的映射,返回默认值5
Integer value2 = map.getOrDefault("key2", 5);
System.out.println("getOrDefault for key2: " + value2);
// 2. forEach方法示例
// 使用forEach遍历map中的每个条目,并打印键值对信息
map.forEach((key, value) -> System.out.println("Key: " + key + ", Value: " + value));
try {
// 故意在迭代过程中通过remove方法移除一个条目,会触发ConcurrentModificationException异常
map.forEach((key, value) -> {
if ("key1".equals(key)) {
map.remove(key);
}
});
} catch (ConcurrentModificationException e) {
System.out.println("Caught ConcurrentModificationException as expected: " + e.getMessage());
}
// 3. replaceAll方法示例
Map<String, Integer> mapToReplaceAll = new HashMap<>();
mapToReplaceAll.put("key3", 3);
mapToReplaceAll.put("key4", 4);
// 使用replaceAll方法将每个条目的值替换为原来值的两倍
mapToReplaceAll.replaceAll((key, oldValue) -> oldValue * 2);
System.out.println("After replaceAll: " + mapToReplaceAll);
// 4. putIfAbsent方法示例
Map<String, Integer> mapForPutIfAbsent = new HashMap<>();
mapForPutIfAbsent.put("key5", 5);
// 对于已经存在的"key5"键,putIfAbsent不会改变其值,返回已有的值5
Integer existingValue = mapForPutIfAbsent.putIfAbsent("key5", 6);
System.out.println("putIfAbsent for existing key5: " + existingValue);
// 对于不存在的"key6"键,putIfAbsent会添加新的键值对,并返回null
Integer newKeyValue = mapForPutIfAbsent.putIfAbsent("key6", 6);
System.out.println("putIfAbsent for new key6: " + newKeyValue);
System.out.println("Map after putIfAbsent operations: " + mapForPutIfAbsent);
// 5. replace方法示例
Map<String, Integer> mapForReplace = new HashMap<>();
mapForReplace.put("key7", 7);
// 对于存在的"key7"键,replace会替换其值,并返回旧值7
Integer replacedValue = mapForReplace.replace("key7", 8);
System.out.println("replace for existing key7: " + replacedValue);
// 对于不存在的"key8"键,replace返回null
Integer nonExistentReplacedValue = mapForReplace.replace("key8", 9);
System.out.println("replace for non-existent key8: " + nonExistentReplacedValue);
System.out.println("Map after replace operations: " + mapForReplace);
// 6. computeIfAbsent方法示例
Map<String, String> mapForComputeIfAbsent = new HashMap<>();
// 对于不存在的"key9"键,使用computeIfAbsent通过给定函数计算值并添加键值对,返回计算后的值"ComputedValue"
String computedValue = mapForComputeIfAbsent.computeIfAbsent("key9", k -> "ComputedValue");
System.out.println("computeIfAbsent for new key9: " + computedValue);
// 对于已经存在的"key10"键(假设已存在,这里示例代码没添加,实际可自行添加测试),computeIfAbsent不会改变其值,返回已有的值
String existingComputedValue = mapForComputeIfAbsent.computeIfAbsent("key10", k -> "AnotherComputedValue");
System.out.println("computeIfAbsent for existing key10: " + existingComputedValue);
System.out.println("Map after computeIfAbsent operations: " + mapForComputeIfAbsent);
// 7. computeIfPresent方法示例
Map<String, Integer> mapForComputeIfPresent = new HashMap<>();
mapForComputeIfPresent.put("key11", 11);
// 对于存在且值非null的"key11"键,使用computeIfPresent根据键和旧值计算新值并替换,返回新值12
Integer computedIfPresentValue = mapForComputeIfPresent.computeIfPresent("key11", (k, v) -> v + 1);
System.out.println("computeIfPresent for existing key11: " + computedIfPresentValue);
// 对于不存在的"key12"键,computeIfPresent返回null
Integer nonExistentComputeIfPresentValue = mapForComputeIfPresent.computeIfPresent("key12", (k, v) -> v + 1);
System.out.println("computeIfPresent for non-existent key12: " + nonExistentComputeIfPresentValue);
System.out.println("Map after computeIfPresent operations: " + mapForComputeIfPresent);
// 8. compute方法示例
Map<String, Integer> mapForCompute = new HashMap<>();
mapForCompute.put("key13", 13);
// 对于存在的"key13"键,使用compute根据键和旧值计算新值并替换,返回新值14
Integer computedNewValue = mapForCompute.compute("key13", (k, v) -> v + 1);
System.out.println("compute for existing key13: " + computedNewValue);
// 对于不存在的"key14"键,compute根据计算结果添加新的键值对(这里示例计算返回固定值15),返回新值15
Integer computedNewValueForAbsentKey = mapForCompute.compute("key14", (k, v) -> 15);
System.out.println("compute for absent key14: " + computedNewValueForAbsentKey);
System.out.println("Map after compute operations: " + mapForCompute);
// 9. merge方法示例
Map<String, String> mapForMerge = new HashMap<>();
mapForMerge.put("key15", "Value1");
// 对于已存在的"key15"键,使用merge根据重映射函数合并值,返回新值"Value1New"
String mergedValue = mapForMerge.merge("key15", "New", (oldValue, newValue) -> oldValue + "New");
System.out.println("merge for existing key15: " + mergedValue);
// 对于不存在的"key16"键,merge添加新的键值对,返回给定的非null值"NewValue"
String mergedValueForNewKey = mapForMerge.merge("key16", "NewValue", (oldValue, newValue) -> oldValue + newValue);
System.out.println("merge for new key16: " + mergedValueForNewKey);
System.out.println("Map after merge operations: " + mapForMerge);
}
Java9 +引入
-
of:
返回一个包含X个映射关系的不可变映射。 -
ofEntries:
返回一个不可变映射,其包含从给定条目中提取的键和值,条目本身不会存储在映射中。 -
entry:
返回一个包含给定键和值的不可变Entry
,适合用于通过Map.ofEntries
方法填充Map
实例。 -
copyOf:Java10引入:
返回一个包含给定映射中条目的不可变Map
,给定映射不能为null
,且不能包含null
键或值。若给定映射后续被修改,返回的映射不会反映这些修改。若给定映射是不可变映射,调用此方法通常不会创建副本,会根据具体情况返回原映射或基于其构建的新的不可变映射。
Java9+ Map方法示例
/**
* Java 9中Map接口新增的方法示例
*/
private static void Java9MapMethodsTest() {
// 1. Map.of方法示例
// 创建一个包含0个映射关系的不可变映射(空映射)
Map<String, Integer> emptyMap = Map.of();
System.out.println("Empty Map: " + emptyMap);
// 创建一个包含1个映射关系的不可变映射,键为"one",值为1
Map<String, Integer> singleEntryMap = Map.of("one", 1);
System.out.println("Single Entry Map: " + singleEntryMap);
// 创建一个包含2个映射关系的不可变映射,注意重复键会抛出异常,这里键分别为"two"、"three",值为2、3
try {
Map<String, Integer> twoEntriesMap = Map.of("two", 2, "two", 3);
System.out.println("Two Entries Map: " + twoEntriesMap);
} catch (IllegalArgumentException e) {
System.out.println("Caught IllegalArgumentException for duplicate key: " + e.getMessage());
}
// 创建一个包含多个(这里以3个为例)映射关系的不可变映射,键值分别为"four":4, "five":5, "six":6
Map<String, Integer> multipleEntriesMap = Map.of("four", 4, "five", 5, "six", 6);
System.out.println("Multiple Entries Map: " + multipleEntriesMap);
// 2. Map.ofEntries方法示例
// 先使用Map.entry方法创建多个Entry实例,模拟给定的条目
Map.Entry<String, Integer> entry1 = Map.entry("seven", 7);
Map.Entry<String, Integer> entry2 = Map.entry("eight", 8);
// 使用ofEntries方法基于创建的Entry实例构建不可变映射
Map<String, Integer> entriesMap = Map.ofEntries(entry1, entry2);
System.out.println("Map created by ofEntries: " + entriesMap);
// 3. Map.entry方法示例
// 创建一个不可变的Entry实例,键为"nine",值为9
Map.Entry<String, Integer> newEntry = Map.entry("nine", 9);
System.out.println("Created Entry: " + newEntry);
// 注意,尝试创建键或值为null的Entry会抛出NullPointerException,以下代码会报错,注释掉仅作示意
// Entry<String, Integer> nullEntry = Map.entry(null, 10);
// 4. Map.copyOf方法示例
// 创建一个普通的可变映射(这里用HashMap举例)
java.util.HashMap<String, Integer> mutableMap = new java.util.HashMap<>();
mutableMap.put("ten", 10);
// 使用copyOf方法将可变映射转换为不可变映射,注意原映射不能有null键或值,这里符合要求
Map<String, Integer> copiedMap = Map.copyOf(mutableMap);
System.out.println("Copied Map: " + copiedMap);
// 尝试修改原可变映射,添加新的键值对
mutableMap.put("eleven", 11);
// 再次查看不可变映射,不会反映原可变映射后续的修改
System.out.println("Copied Map after modifying original mutable map: " + copiedMap);
// 创建一个本身就是不可变映射(这里利用之前创建的entriesMap)
Map<String, Integer> immutableMap = Map.ofEntries(Map.entry("twelve", 12));
// 对不可变映射使用copyOf方法,通常不会创建副本,返回原映射
Map<String, Integer> copiedImmutableMap = Map.copyOf(immutableMap);
System.out.println("Copied Immutable Map (should be the same as original): " + copiedImmutableMap);
}
AbstractMap
AbstractMap
是一个抽象类,它实现了Map
接口。在 Java 集合框架中,它为Map
接口的实现类提供了一个基本的骨架实现,简化了创建自定义Map
实现的过程。
SequencedMap
SequencedMap
用于表示具有明确元素顺序的Map,
它在 Java 21 中被引入。这个接口扩展了Map
接口,并添加了与顺序相关的操作。
主要功能
-
获取首尾元素相关操作:它提供了像
firstEntry()
、lastEntry()
这样的方法,可以获取映射中的第一个和最后一个键值对。例如,在一个存储学生成绩排名(键为学生姓名,值为成绩)的SequencedMap
中,可以很方便地获取成绩最高和最低的学生信息。 -
元素顺序遍历操作:
SequencedMap
支持正向和反向的迭代顺序。通过reversed()
方法可以获取一个反向顺序的视图,这样就可以方便地按照相反的顺序遍历映射中的元素。例如,在一个按照插入顺序存储事件日志(键为时间戳,值为事件详情)的SequencedMap
中,如果需要按照时间倒序查看日志,就可以使用reversed()
方法。
SortedMap
SortedMap
扩展了SequencedMap
接口,用于表示按照键的自然顺序(如果键实现了Comparable
接口)或者通过指定的比较器进行排序的Map
。这种排序使得在遍历Map
或者查找元素时可以按照特定的顺序进行。
主要功能
-
范围视图获取方法:
SortedMap
提供了获取子映射的方法,如subMap(K fromKey, K toKey)
,它返回一个包含指定范围内键值对的视图。例如,在一个存储员工工资信息(键为员工姓名,值为工资)的SortedMap
中,按照员工姓名排序后,可以通过subMap
方法获取某一部门(员工姓名范围)的工资信息。 -
首尾键获取方法:
firstKey()
和lastKey()
方法可以获取排序后的第一个和最后一个键。例如,在一个按照时间顺序存储销售记录(键为销售时间,值为销售额)的SortedMap
中,通过firstKey()
和lastKey()
可以获取最早和最晚的销售时间。 -
比较器相关方法:可以通过
comparator()
方法获取用于排序的比较器。如果是按照键的自然顺序排序,返回null
;如果是通过指定的比较器排序,则返回该比较器。
NavigableMap
NavigableMap
扩展了SortedMap
接口,它提供了导航方法来遍历和操作排序后的Map
。这些导航方法允许更灵活地在排序后的键值对之间进行查找和操作,比如查找最接近给定键的键值对等。
主要方法
- 导航方法:
-
ceilingEntry(K key)
:返回大于等于给定键的最小键值对。例如,在一个存储商品价格区间(键为价格下限,值为价格上限)的NavigableMap
中,通过ceilingEntry
可以找到价格大于等于某个指定价格的最小价格区间。 -
floorEntry(K key)
:返回小于等于给定键的最大键值对。例如,在一个按照时间顺序存储任务完成时间(键为任务编号,值为完成时间)的NavigableMap
中,通过floorEntry
可以找到完成时间小于等于某个指定时间的最后一个任务。 -
higherEntry(K key)
:返回大于给定键的最小键值对。 -
lowerEntry(K key)
:返回小于给定键的最大键值对。
-
- 范围查询和操作方法:
-
subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)
:返回一个子映射,并且可以通过布尔参数指定是否包含起始和结束键。这比SortedMap
的subMap
方法更灵活。 -
headMap(K toKey, boolean inclusive)
:返回一个小于(或小于等于,取决于inclusive
参数)指定键的子映射。 -
tailMap(K fromKey, boolean inclusive)
:返回一个大于(或大于等于,取决于inclusive
参数)指定键的子映射。
-
TreeMap
TreeMap
实现了NavigableMap
接口。TreeMap
基于红黑树(Red - Black Tree)数据结构来存储键值对,这使得它可以按照键的自然顺序(如果键实现了Comparable
接口)或者根据自定义的比较器来对键进行排序。
TreeMap 实现示例
TreeMap<String, Integer> treeMap = new TreeMap<>();
treeMap.put("apple", 1);
treeMap.put("banana", 2);
treeMap.put("cherry", 3);
System.out.println("Value of 'apple': " + treeMap.get("apple"));
System.out.println("First key: " + treeMap.firstKey());
System.out.println("Last key: " + treeMap.lastKey());
treeMap.remove("banana");
System.out.println("After removal: " + treeMap);
TreeMap 自定义比较器实现示例
class CustomComparator implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
}
class TreeMapWithComparatorExample {
public static void main(String[] args) {
TreeMap<Integer, String> treeMapWithComparator = new TreeMap<>(new CustomComparator());
treeMapWithComparator.put(1, "One");
treeMapWithComparator.put(3, "Three");
treeMapWithComparator.put(2, "Two");
System.out.println("TreeMap with custom comparator: " + treeMapWithComparator);
}
}
HashMap
HashMap
它基于哈希表(hash table)数据结构来实现,通过对键进行哈希运算来确定存储位置,从而能够快速地进行插入、删除和查找操作。
-
哈希桶(Hash Buckets):
HashMap
内部维护了一个数组,这个数组的每个元素被称为哈希桶。当插入一个键值对时,首先会根据键的哈希值计算出它在数组中的索引位置,然后将键值对存储在对应的哈希桶中。例如,对于一个简单的HashMap<String, Integer>
,如果要插入键为"apple"
,值为1
的键值对,会先计算"apple"
的哈希值,再根据哈希值确定存储在数组中的哪个位置。 -
哈希冲突(Hash Collisions)解决机制:当不同的键计算出相同的哈希值(即发生哈希冲突)时,
HashMap
使用链表(在 Java 8 之后,当链表长度超过一定阈值(默认为 8)时会转换为红黑树结构)来存储这些具有相同哈希值的键值对。这样可以有效地解决哈希冲突,保证数据的存储和检索效率。
LinkedHashMap
LinkedHashMap
是HashMap
的子类,它在HashMap
的基础上,维护了一个双向链表,用于记录键值对的插入顺序或者访问顺序(可以通过构造函数指定)。
-
双向链表:除了具有
HashMap
的哈希表结构用于快速存储和检索键值对外,LinkedHashMap
内部的双向链表保证了元素的顺序性。当按照插入顺序维护时,每次插入新的键值对,都会将其添加到链表的末尾。当按照访问顺序维护时,每次访问(通过get
或put
等操作访问键)一个键值对,都会将其移动到链表的末尾。
EnumMap
EnumMap
是一个专门为枚举键设计的映射实现。它内部使用数组来实现,这使得它非常高效。
特点
- 键必须是单个枚举类型的枚举常量。
EnumMap
在内部以数组形式存储其映射,因此它比基于哈希表的实现(如HashMap
)更高效。- 它是非同步的。
- 它不允许使用
null
键。
使用场景
当映射的键是枚举类型时,使用EnumMap
可以提供更好的性能和内存效率。
IdentityHashMap
IdentityHashMap
是一个特殊的Map
实现,它比较键的方式是通过==
而不是equals
方法。
特点
- 它使用哈希表实现,但比较键的方式是通过
==
操作符,而不是equals
方法。这意味着两个具有相同值的对象(例如,两个不同的字符串实例,但具有相同的字符序列)被视为不同的键。 - 它通常用于需要身份敏感的哈希映射操作,例如,当需要区分对象实例时。
- 它是非同步的。
- 它允许使用
null
键和值。
使用场景
用于需要基于对象身份(而不是对象相等性)的映射。
WeakHashMap
WeakHashMap
是一个基于弱引用的Map
实现。
特点
- 它使用弱引用来存储键。弱引用允许垃圾回收器在没有其他强引用的情况下回收其引用的对象。
- 当键对象不再被其他地方引用时,它们可以被垃圾回收器回收,从而从
WeakHashMap
中自动删除对应的映射。 - 这使得
WeakHashMap
非常适合缓存实现,因为它可以自动清理不再使用的条目。 - 它是非同步的。
- 它允许使用
null
值,但不允许使用null
键。
使用场景
- 用于缓存,其中需要自动删除不再被外部引用的条目。
- 用于创建映射,其键的生命周期不受映射本身管理的约束。
线程安全的Map
HashTable(Deprecated)
Hashtable继承了Dictionary,
是一个线程安全的古老的(从 Java 1.0 开始)键值对存储类。他与HashMap类似,
同样基于哈希表的数据结构来存储键值对。但Hashtable
是同步的(线程安全的),这意味着在多线程环境下,多个线程访问Hashtable
时会进行同步操作,保证数据的一致性,但这也导致了性能上的损失。
ConcurrentMap 接口
ConcurrentMap
是一个接口,它位于java.util.concurrent
包中。这个接口用于定义支持并发访问的Map
操作。它是java.util.Map
接口的子接口,提供了额外的原子操作方法,这些方法在多线程环境下可以安全地执行,无需额外的外部同步。
ConcurrentHashMap
ConcurrentHashMap
实现了ConcurrentMap
接口,提供了一种高效的、支持高并发访问的哈希表数据结构。它在保证线程安全的同时,还能提供较好的性能,通过锁分段、CAS等技术来减少多线程访问时的锁竞争。
工作原理
-
分段锁(Segment Locks)技术(Java 7 及以前):在早期版本(Java 7 及以前)中,
ConcurrentHashMap
采用了分段锁的设计。它将整个哈希表分为多个段(Segment),每个段是一个独立的哈希表,并且有自己的锁。当多个线程访问不同段的键值对时,它们可以并发地进行操作,只有当多个线程访问同一个段中的键值对时,才会产生锁竞争。例如,假设有一个ConcurrentHashMap
被分为 16 个段,那么最多可以允许 16 个线程同时进行插入、删除或查找操作,只要它们操作的键值对位于不同的段中。 -
CAS(Compare - and - Swap)操作和数组扩容优化(Java 8 及以后):从 Java 8 开始,
ConcurrentHashMap
的内部结构进行了优化。它采用了数组 + 链表 / 红黑树的结构,类似于HashMap
,但在并发控制方面做了更多的改进。它使用 CAS 操作来实现无锁的插入和更新操作,减少了锁的使用。同时,在扩容时,它采用了更加高效的方式,允许多个线程同时协助进行扩容操作,提高了扩容的效率。
ConcurrentNavigableMap 接口
ConcurrentNavigableMap
是一个扩展了ConcurrentMap
和NavigableMap的
接口,它提供了导航操作,并保持并发访问的安全性。
ConcurrentSkipListMap
ConcurrentSkipListMap
实现了ConcurrentNavigableMap
接口。它基于跳表(Skip List)数据结构实现,提供了一种可以高效地进行并发访问的有序映射。跳表是一种类似于平衡树的数据结构,它通过多层索引来提高查找效率。
工作原理
-
跳表数据结构:跳表由多个层次的链表组成。最底层的链表包含了所有的键值对,每一层链表都是下一层链表的一个子集,并且通过 “跳跃” 指针来跨越一些节点,从而加快查找速度。在
ConcurrentSkipListMap
中,通过使用锁和 CAS 操作来保证在多线程环境下的并发安全。当插入一个新的键值对时,可能会涉及到对多个层次链表的更新,这些更新操作是原子性的,以确保数据的一致性。
对比总结
如果需要有序的映射,则ConcurrentSkipListMap
是一个很好的选择;如果不需要排序,则ConcurrentHashMap
通常提供更好的性能。
参考文献
豆包
相关文章推荐
Wend看源码-Java-集合学习(List)-CSDN博客
Wend看源码-Java-集合学习(Set)-CSDN博客
Wend看源码-Java-集合学习(Queue)-CSDN博客