跳到主要内容

Java ConcurrentHashMap 类

提示
  1. 线程安全的映射表ConcurrentHashMap 在 Java 集合框架中提供线程安全的映射功能,允许多线程同时访问而保持数据一致性。
  2. 灵活的创建方式:可以通过指定容量和负载因子或从其他映射表导入创建 ConcurrentHashMap,默认容量为 16,负载因子为 0.75。
  3. 丰富的操作方法ConcurrentHashMap 提供多种方法,如插入元素(putputAllputIfAbsent)、访问元素(getgetOrDefault)、移除元素、以及批量操作(如 forEachsearchreduce)。

Java 集合框架中的 ConcurrentHashMap 类提供了一个线程安全的映射表。这意味着,多个线程可以同时访问这个映射表,而不会影响映射表中条目的一致性。

它实现了 ConcurrentMap 接口

Java ConcurrentHashMap 类实现了 ConcurrentMap 接口。

创建 ConcurrentHashMap

要创建一个并发哈希映射,我们首先必须导入 java.util.concurrent.ConcurrentHashMap 包。导入包之后,下面是我们在 Java 中创建并发哈希映射的方式。

// ConcurrentHashMap,容量为 8,负载因子为 0.6
ConcurrentHashMap<Key, Value> numbers = new ConcurrentHashMap<>(8, 0.6f);

在上面的代码中,我们创建了一个名为 numbers 的并发哈希映射。

这里,

  • Key - 用于将每个元素(值)与映射表中的条目关联的唯一标识符
  • Value - 由键在映射表中关联的元素

注意 new ConcurrentHashMap<>(8, 0.6) 这部分。这里,第一个参数是 容量,第二个参数是 负载因子

  • 容量 - 此映射表的容量为 8。意味着它可以存储 8 个条目。
  • 负载因子 - 此映射表的负载因子为 0.6。这意味着,当我们的哈希表填充到 60% 时,条目会被移动到一个大小是原哈希表两倍的新哈希表中。

默认容量和负载因子

可以在不定义容量和负载因子的情况下创建并发哈希映射。例如,

// 默认容量和负载因子的 ConcurrentHashMap
ConcurrentHashMap<Key, Value> numbers1 = new ConcurrentHashMap<>();

默认情况下,

  • 映射表的容量将为 16
  • 负载因子将为 0.75

从其他映射表创建 ConcurrentHashMap

下面是我们如何从其他映射表中创建一个包含所有元素的并发哈希映射。

import java.util.concurrent.ConcurrentHashMap;
import java.util.HashMap;

class Main {
public static void main(String[] args) {

// 创建一个偶数的 hashmap
HashMap<String, Integer> evenNumbers = new HashMap<>();
evenNumbers.put("Two", 2);
evenNumbers.put("Four", 4);
System.out.println("HashMap: " + evenNumbers);

// 从其他映射表创建并发哈希映射
ConcurrentHashMap<String, Integer> numbers = new ConcurrentHashMap<>(evenNumbers);
numbers.put("Three", 3);
System.out.println("ConcurrentHashMap: " + numbers);
}
}

输出

HashMap: {Four=4, Two=2}
ConcurrentHashMap: {Four=4, Two=2, Three=3}

ConcurrentHashMap 的方法

ConcurrentHashMap 类提供了多种方法,允许我们对映射表执行各种操作。

向 ConcurrentHashMap 插入元素

  • put() - 向映射表中插入指定的键/值映射
  • putAll() - 将指定映射表中的所有条目插入到此映射表
  • putIfAbsent() - 如果指定的键在映射表中不存在,则向映射表中插入指定的键/值映射

例如,

import java.util.concurrent.ConcurrentHashMap;

class Main {


public static void main(String[] args) {
// 创建偶数的 ConcurrentHashMap
ConcurrentHashMap<String, Integer> evenNumbers = new ConcurrentHashMap<>();

// 使用 put()
evenNumbers.put("Two", 2);
evenNumbers.put("Four", 4);

// 使用 putIfAbsent()
evenNumbers.putIfAbsent("Six", 6);
System.out.println("偶数的 ConcurrentHashMap: " + evenNumbers);

// 创建数字的 ConcurrentHashMap
ConcurrentHashMap<String, Integer> numbers = new ConcurrentHashMap<>();
numbers.put("One", 1);

// 使用 putAll()
numbers.putAll(evenNumbers);
System.out.println("数字的 ConcurrentHashMap: " + numbers);
}
}

输出

偶数的 ConcurrentHashMap: {Six=6, Four=4, Two=2}
数字的 ConcurrentHashMap: {Six=6, One=1, Four=-4, Two=2}

访问 ConcurrentHashMap 元素

1. 使用 entrySet()、keySet() 和 values()

  • entrySet() - 返回映射表中所有键/值映射的集合
  • keySet() - 返回映射表中所有键的集合
  • values() - 返回映射表中所有值的集合

例如,

import java.util.concurrent.ConcurrentHashMap;

class Main {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> numbers = new ConcurrentHashMap<>();

numbers.put("One", 1);
numbers.put("Two", 2);
numbers.put("Three", 3);
System.out.println("ConcurrentHashMap: " + numbers);

// 使用 entrySet()
System.out.println("键/值映射: " + numbers.entrySet());

// 使用 keySet()
System.out.println("键: " + numbers.keySet());

// 使用 values()
System.out.println("值: " + numbers.values());
}
}

输出

ConcurrentHashMap: {One=1, Two=2, Three=3}
/值映射: [One=1, Two=2, Three=3]
: [One, Two, Three]
: [1, 2, 3]

2. 使用 get() 和 getOrDefault()

  • get() - 返回与指定键关联的值。如果未找到该键,则返回 null
  • getOrDefault() - 返回与指定键关联的值。如果未找到该键,则返回指定的默认值。

例如,

import java.util.concurrent.ConcurrentHashMap;

class Main {
public static void main(String[] args) {

ConcurrentHashMap<String, Integer> numbers = new ConcurrentHashMap<>();
numbers.put("One", 1);
numbers.put("Two", 2);
numbers.put("Three", 3);
System.out.println("ConcurrentHashMap: " + numbers);

// 使用 get()
int value1 = numbers.get("Three");
System.out.println("使用 get(): " + value1);

// 使用 getOrDefault()
int value2 = numbers.getOrDefault("Five", 5);
System.out.println("使用 getOrDefault(): " + value2);
}
}

输出

ConcurrentHashMap: {One=1, Two=2, Three=3}
使用 get(): 3
使用 getOrDefault(): 5

移除 ConcurrentHashMap 元素

  • remove(key) - 返回并移除与指定键关联的条目
  • remove(key, value) - 仅当指定的键映射到指定的值时,从映射表中移除该条目并返回一个布尔值

例如,

import java.util.concurrent.ConcurrentHashMap;

class Main {
public static void main(String[] args) {

ConcurrentHashMap<String, Integer> numbers = new ConcurrentHashMap<>();
numbers.put("One", 1);
numbers.put("Two", 2);
numbers.put("Three", 3);
System.out.println("ConcurrentHashMap: " + numbers);

// 使用单参数的 remove 方法
int value = numbers.remove("Two");
System.out.println("移除的值: " + value);

// 使用双参数的 remove 方法
boolean result = numbers.remove("Three", 3);
System.out.println("是否移除了条目 {Three=3}? " + result);

System.out.println("更新后的 ConcurrentHashMap: " + numbers);
}
}

输出

ConcurrentHashMap: {One=1, Two=2, Three=3}
移除的值: 2
是否移除了条目 {Three=3}True
更新后的 ConcurrentHashMap: {One=1}

批量 ConcurrentHashMap 操作

ConcurrentHashMap 类提供了可以安全应用于并发映射表的不同批量操作。

1. forEach() 方法

forEach() 方法遍历我们的条目并执行指定的函数。

它包括两个参数。

  • parallelismThreshold - 指定在映射表中多少元素后操作以并行方式执行。
  • transformer - 这将在数据传递给指定函数之前转换数据。

例如,

import java.util.concurrent.ConcurrentHashMap;

class Main {
public static void main(String[] args) {

ConcurrentHashMap<String, Integer> numbers = new ConcurrentHashMap<>();
numbers.put("One", 1);
numbers.put("Two", 2);
numbers.put("Three", 3);
System.out.println("ConcurrentHashMap: " + numbers);

// 不带转换器函数的 forEach()
numbers.forEach(4, (k, v) -> System.out.println("键: " + k + " 值: " + v));

// 带转换器函数的 forEach()
System.out.print("值为 ");
numbers.forEach(4, (k, v) -> v, (v) -> System.out.print(v + ", "));
}
}


输出

ConcurrentHashMap: {One = 1, Two = 2, Three = 3}
: One: 1
: Two: 2
: Three: 3
值为 1, 2, 3,

在上述程序中,我们使用了并行阈值 4。这意味着如果映射表包含 4 个条目,操作将以并行方式执行。

forEach() 方法的变体

  • forEachEntry() - 为每个条目执行指定的函数
  • forEachKey() - 为每个键执行指定的函数
  • forEachValue() - 为每个值执行指定的函数

2. search() 方法

search() 方法根据指定的函数搜索映射表,并返回匹配的条目。

这里,指定的函数决定要搜索哪个条目。

它还包括一个可选参数 parallelThreshold。并行阈值指定在映射表中多少元素后操作以并行方式执行。

例如,

import java.util.concurrent.ConcurrentHashMap;

class Main {
public static void main(String[] args) {

ConcurrentHashMap<String, Integer> numbers = new ConcurrentHashMap<>();
numbers.put("One", 1);
numbers.put("Two", 2);
numbers.put("Three", 3);
System.out.println("ConcurrentHashMap: " + numbers);

// 使用 search()
String key = numbers.search(4, (k, v) -> {return v == 3 ? k: null;});
System.out.println("搜索到的值: " + key);

}
}

输出

ConcurrentHashMap: {One=1, Two=2, Three=3}
搜索到的值: Three

search() 方法的变体

  • searchEntries() - 搜索函数应用于键/值映射
  • searchKeys() - 搜索函数仅应用于键
  • searchValues() - 搜索函数仅应用于值

3. reduce() 方法

reduce() 方法累积(汇集)映射中的每个条目。当我们需要所有条目执行一个共同任务时,比如把一个映射的所有值加起来,就可以使用这个方法。

它包括两个参数。

  • parallelismThreshold - 它指定了在多少元素之后,映射中的操作将并行执行。
  • transformer - 这将在数据传递给指定函数之前转换数据。

例如,

import java.util.concurrent.ConcurrentHashMap;

class Main {
public static void main(String[] args) {

ConcurrentHashMap<String, Integer> numbers = new ConcurrentHashMap<>();
numbers.put("One", 1);
numbers.put("Two", 2);
numbers.put("Three", 3);
System.out.println("ConcurrentHashMap: " + numbers);

// 使用 search()
int sum = numbers.reduce(4, (k, v) -> v, (v1, v2) -> v1 + v2);
System.out.println("所有值的总和: " + sum);

}
}

输出

ConcurrentHashMap: {One=1, Two=2, Three=3}
所有值的总和: 6

在上面的程序中,注意这个语句

numbers.reduce(4, (k, v) -> v, (v1, v2) -> v1+v2);

这里,

  • 4 是并行阈值
  • (k, v) -> v 是一个转换函数。它将键/值映射转换为仅值。
  • (v1, v2) -> v1+v2 是一个归约函数。它汇集所有的值,并将所有值相加。

reduce() 方法的变体

  • reduceEntries() - 返回使用指定的归约函数汇集所有条目的结果
  • reduceKeys() - 返回使用指定的归约函数汇集所有键的结果
  • reduceValues() - 返回使用指定的归约函数汇集所有值的结果

ConcurrentHashMap 与 HashMap 的区别

这里是 ConcurrentHashMapHashMap 之间的一些区别,

  • ConcurrentHashMap 是一个 线程安全 的集合。也就是说,多个线程可以同时访问和修改它。
  • ConcurrentHashMap 提供了大量操作的方法,如 forEach()search()reduce()

为什么使用 ConcurrentHashMap?

  • ConcurrentHashMap 类允许多个线程同时访问其条目。
  • 默认情况下,concurrent hashmap 被分为 16 个段。这就是为什么允许 16 个线程同时修改映射的原因。然而,任何数量的线程都可以同时访问映射。
  • 如果指定的键已经存在,putIfAbsent() 方法不会覆盖映射中的条目。
  • 它提供了自己的同步机制。