Map(也可以成为字典,映射表)是一种数据结构,用于存储键值对(key-value pairs)。它是一种抽象的数据类型并且允许通过键来快速查找和访问与之相关联的值。在Java和Go中,Map提供了一种非常方便的方式来管理和组织数据。只是在Java中Map仅是一个接口,今天我们来介绍HashMap。
Java的HashMap
Java的HashMap是Java集合框架中的一种实现,用于存储键值对(key-value pairs)。它提供了快速的插入、删除和查找操作,是基于哈希表实现的。
特点:
键唯一性:HashMap中的键是唯一的,每个键只能对应一个值。如果插入已经存在的键,则会替换原有的值。
无序性:HashMap中的键值对是无序存储的,不保证存储和遍历的顺序。
允许null键和null值:HashMap中允许存储null键和null值,但只能有一个null键。
基本操作:
-
put(key, value):将指定的键值对存储到HashMap中。如果键已经存在,则更新对应的值。
-
get(key):根据键获取对应的值。
-
remove(key):根据键删除对应的键值对。
-
containsKey(key):判断HashMap中是否包含指定的键。
-
containsValue(value):判断HashMap中是否包含指定的值。
-
size():返回HashMap中键值对的数量。
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;public class Main {
public static void main(String[] args) {
// 创建一个HashMap
HashMap<String, Integer> map = new HashMap<>();// 添加键值对 map.put("apple", 1); map.put("banana", 2); map.put("orange", 3); // 获取值 int value = map.get("apple"); System.out.println("Value of apple: " + value); // 删除键值对 map.remove("orange"); System.out.println("Map after removing orange: " + map); // 判断是否包含键 boolean containsKey = map.containsKey("banana"); System.out.println("Contains key banana: " + containsKey); // 判断是否包含值 boolean containsValue = map.containsValue(2); System.out.println("Contains value 2: " + containsValue); // 获取大小 int size = map.size(); System.out.println("Size of map: " + size);
}
内部实现:
HashMap基于数组和链表(或红黑树)实现。当插入元素时,首先根据键的哈希值计算存储位置(索引),然后将键值对存储在数组的相应位置。如果多个键的哈希值相同,它们会存储在同一个位置,并通过链表(或红黑树)连接起来。
遍历HashMap:
HashMap可以通过迭代器或者forEach循环进行遍历。但是需要注意,由于HashMap是无序的,遍历时得到的元素顺序并不是插入顺序。
HashMap<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("orange", 3);
// 使用迭代器遍历
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
System.out.println(entry.getKey() + " : " + entry.getValue());
}
// 使用forEach循环遍历
map.forEach((key, value) -> System.out.println(key + " : " + value));
注意事项:
- 线程不安全:
HashMap不是线程安全的,如果多个线程同时修改HashMap,可能会导致数据不一致或者并发修改异常。 - 扩容:
当HashMap中的键值对数量超过负载因子(load factor)和初始容量的乘积时,HashMap会自动进行扩容,重新分配存储空间,这可能会导致性能下降。
要注意在并发环境中,可以使用Collections.synchronizedMap(Map<K,V> m)方法创建线程安全的HashMap。另外,Java 8引入了ConcurrentHashMap,它是一种并发安全的HashMap实现,适用于高并发环境。
其他的小技巧
这里介绍两个方法,我之前刷LC经常用,写来比较简洁
1.computeIfPresent
computeIfPresent() 是 Java 8 引入的HashMap方法之一,它允许我们根据指定的键和函数对HashMap中已存在的键(key)的值进行修改或计算。如果指定的键不存在,则不执行任何操作。该方法的签名如下:
V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)
- key:要修改值的键。
- remappingFunction:一个函数接口,用于根据给定的键和值计算新值的函数。
该方法的工作原理是:
如果给定的键在HashMap中存在,并且对应的值不为null,则会将该键与对应的值作为参数传递给 remappingFunction 函数,然后将函数的返回值作为新的值放回HashMap中。
如果给定的键不存在或者对应的值为null,则不执行任何操作。
下面是一个简单的示例,演示了如何使用 computeIfPresent() 方法:
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
// 创建一个HashMap并添加一些键值对
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
// 修改已存在的键值对
map.computeIfPresent("apple", (key, value) -> value + 1);
System.out.println("After computeIfPresent: " + map); // 输出: After computeIfPresent: {apple=2, banana=2}
// 尝试修改不存在的键值对
map.computeIfPresent("orange", (key, value) -> value + 1);
System.out.println("After computeIfPresent: " + map); // 输出: After computeIfPresent: {apple=2, banana=2}
}
}
在这个示例中,我们首先创建了一个包含一些键值对的HashMap。然后使用 computeIfPresent() 方法修改了键 "apple" 对应的值。由于 "apple" 键存在,所以对应的值会被加1。接着,我们尝试修改一个不存在的键 "orange",但由于该键不存在,所以不会有任何操作发生。
1.computeIfAbsent
同上,不同的是仅当指定的键在HashMap中不存在或其对应的值为null时。如果指定的键已经存在且对应的值不为null,则不执行任何操作。该方法的签名如下:
V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)
- key:要检查并计算值的键。
- mappingFunction:一个函数接口,用于根据给定的键计算新值的函数。
该方法的工作原理是:
如果给定的键在HashMap中不存在或者对应的值为null,则会将该键与 mappingFunction 计算得到的新值放入HashMap中。
如果给定的键在HashMap中已经存在且对应的值不为null,则不执行任何操作。
下面是一个示例,演示了如何使用 computeIfAbsent() 方法:
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
// 创建一个HashMap并添加一些键值对
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", null); // 添加一个值为null的键值对
// 使用computeIfAbsent计算值并放入HashMap
map.computeIfAbsent("apple", key -> 5); // apple键已存在,不会执行
map.computeIfAbsent("orange", key -> 3); // orange键不存在,会执行
System.out.println(map); // 输出: {apple=1, banana=null, orange=3}
}
}
在这个示例中,我们首先创建了一个包含一些键值对的HashMap。然后使用 computeIfAbsent() 方法计算了键 "apple" 和 "orange" 对应的值。由于 "apple" 键已经存在,所以对应的值不会被修改。而 "orange" 键不存在,所以会根据给定的函数计算新值,并放入HashMap中。
Go的Map
在Go语言中,Map是一种内置的数据结构,用于存储键值对。它是一种无序的集合,类似于其他编程语言中的字典(dictionary)或哈希表(hash table)。下面是关于Go语言中Map的详细介绍:
特点:
- 键和值类型:在Go语言中,Map的值可以是任意类型,但是所有值的类型也必须相同。但是Map的键(key)必须是支持相等操作符(==)的类型。具体来说,可以用作Map键的类型必须满足以下要求:
- 不能是函数类型、映射类型或切片类型。
- 不能是包含任何不可比较类型的结构体类型。
- 不能是任何不可比较类型的接口类型。
换句话说,Go语言的Map键必须是可比较的类型,以便在内部进行散列计算和比较操作。常见的可比较类型包括整数、浮点数、字符串、指针等
- 无序性:Map是无序的,不保证存储和遍历的顺序。
- 动态增长:Map可以动态地增长,可以根据需要插入或删除键值对。
声明和初始化:
在Go语言中,声明和初始化Map的语法如下:
// 声明一个空的Map
var m map[keyType]valueType
// 初始化Map并添加元素
m := make(map[keyType]valueType)
// 直接初始化并添加元素
m := map[keyType]valueType{
key1: value1,
key2: value2,
// more key-value pairs
}
基本操作:
-
添加和修改键值对:
m[key] = value
-
获取值:
value := m[key]
-
删除键值对:
delete(m, key)
-
检查键是否存在:
value, ok := m[key]
if ok {
// key存在
} else {
// key不存在
}
这里多嘴一句来解释上述格式:
"ok-idiom" 模式是 Go 语言中一种常见的惯用语法,用于处理函数返回的多个值中的第二个值,通常用来判断操作是否成功或者目标是否存在。在这种模式中,通常会使用一个布尔值来表示操作是否成功或者目标是否存在。第一个变量用于接收函数返回的值,第二个变量用于接收一个布尔值,表示操作是否成功。在 Go 语言中,通常将这个布尔值命名为 "ok"。
-
获取Map的长度:
length := len(m)
示例:
下面是一个简单的示例,展示了如何声明、初始化、添加、获取、删除Map中的键值对:
package main
import "fmt"
func main() {
// 声明和初始化Map
m := make(map[string]int)
// 添加键值对
m["apple"] = 1
m["banana"] = 2
m["orange"] = 3
// 获取值
fmt.Println("Value of apple:", m["apple"])
// 删除键值对
delete(m, "orange")
// 检查键是否存在
value, ok := m["banana"]
if ok {
fmt.Println("Value of banana:", value)
}
// 获取Map的长度
fmt.Println("Length of map:", len(m))
// 遍历Map
for key, value := range m {
fmt.Println(key, ":", value)
}
}
总结
本文简单介绍Java和Go用于存储键值对的数据结构-Map,但Java的Map是一个接口,有多种实现类,而Go的Map是一种内置的数据类型,用于在内存中快速存储和检索键值对;它们都具有动态增长和无序性。不过使用时注意是否线程安全。