Java:HashMap的使用
目录
一、概念
二、常用操作
2.1 初始化
2.2 CRUD
2.3 遍历
2.4 其他常用方法
三、与其他Map的比较
一、概念
HashMap
位于 java.util
包下。它实现了 Map
接口,提供了基于键值对(Key-Value)的数据存储方式,并允许使用 null
作为键和值。
核心思想:使用“键(Key)”的哈希值来存储和检索“值(Value)”。
核心特点:
-
基于哈希表:它使用哈希算法来存储和检索数据,这使得在理想情况下,
get
和put
操作的时间复杂度可以达到 O(1)。 -
无序:
HashMap
不保证其中元素的顺序(即插入顺序和访问顺序),并且顺序也可能会随时间(如扩容时)而变化。如果需要有序,可以使用LinkedHashMap
。 -
非线程安全:多个线程同时操作一个
HashMap
可能会导致数据不一致。如果多个线程同时访问一个HashMap
并至少有一个线程修改了它,则必须在外部进行同步。或者使用Collections.synchronizedMap()
进行包装,或者更推荐使用ConcurrentHashMap
。
二、常用操作
2.1 初始化
// 1. 最常见的无参构造,默认初始容量16,负载因子0.75
HashMap<String, Integer> map = new HashMap<>();// 2. 指定初始容量(减少扩容次数,优化性能)
HashMap<String, Integer> mapWithCapacity = new HashMap<>(32);// 3. 指定初始容量和负载因子(高级用法,通常不需要)
HashMap<String, Integer> mapWithFactor = new HashMap<>(32, 0.8f);// 4. 通过另一个Map来创建
HashMap<String, Integer> anotherMap = new HashMap<>(map);
扩容机制:扩充为原数组容量的2倍
当 HashMap 中的元素数量(
size
)超过当前阈值(threshold) 时,就会触发扩容。
阈值 (threshold) = 容量 (capacity) * 负载因子 (load factor)
默认示例:默认容量为 16,默认负载因子为 0.75。
那么阈值就是
16 * 0.75 = 12
。当执行
put()
操作后,size
变得大于 12(即 13)时,就会触发扩容。
2.2 CRUD
HashMap<String, String> capitalCities = new HashMap<>();// 添加与更新
// put(K key, V value) - 添加键值对,如果key已存在,则更新其value
capitalCities.put("USA", "Washington D.C.");
capitalCities.put("Germany", "Berlin");
capitalCities.put("Germany", "Berlin"); // 重复放入,不会改变
capitalCities.put("Germany", "New Berlin"); // Key已存在,Value会被更新为 "New Berlin"// putIfAbsent(K key, V value) - (Java 8+) 只有在key不存在或对应的value为null时,才放入
capitalCities.putIfAbsent("France", "Paris"); // 会放入,因为France不存在
capitalCities.putIfAbsent("Germany", "Paris"); // 不会放入,因为Germany已存在,Value仍然是"New Berlin"// 获取元素
// get(Object key) - 根据key获取value,如果key不存在,返回null
String capitalOfGermany = capitalCities.get("Germany"); // "New Berlin"
String capitalOfJapan = capitalCities.get("Japan"); // null// getOrDefault(Object key, V defaultValue) - (Java 8+) key不存在时返回一个默认值
String capitalOfJapanSafe = capitalCities.getOrDefault("Japan", "Not Found"); // "Not Found"// 检查元素是否存在
// containsKey(Object key) - 检查某个key是否存在
boolean hasGermany = capitalCities.containsKey("Germany"); // true
boolean hasJapan = capitalCities.containsKey("Japan"); // false// containsValue(Object value) - 检查某个value是否存在(效率较低,需要遍历)
boolean hasParis = capitalCities.containsValue("Paris"); // true// 删除元素
// remove(Object key) - 根据key删除键值对,返回被删除的value
String removedValue = capitalCities.remove("Germany"); // removedValue = "New Berlin"// remove(Object key, Object value) - (Java 8+) 只有当key和value都匹配时才删除
boolean isRemoved = capitalCities.remove("USA", "LA"); // false, 因为Value不匹配"Washington D.C.",删除失败
boolean isRemoved2 = capitalCities.remove("USA", "Washington D.C."); // true, 删除成功
2.3 遍历
// 遍历所有键:keySet()
for (String country : capitalCities.keySet()) {System.out.println("Country: " + country);// 可以通过key再get value,但效率较低(不推荐在循环内这样用)// System.out.println("Capital: " + capitalCities.get(country));
}// 遍历所有值:values()
for (String capital : capitalCities.values()) {System.out.println("Capital: " + capital);
}// 遍历所有键值对:entrySet() (最推荐、最高效的方式)
for (Map.Entry<String, String> entry : capitalCities.entrySet()) {String country = entry.getKey();String capital = entry.getValue();System.out.println(country + " -> " + capital);
}// 使用 Java 8 forEach + Lambda 表达式 (最简洁)
capitalCities.forEach((country, capital) -> {System.out.println(country + " -> " + capital);
});
2.4 其他常用方法
// size() - 返回键值对的数量
int size = capitalCities.size();// isEmpty() - 判断是否为空
boolean isEmpty = capitalCities.isEmpty();// clear() - 清空所有映射
capitalCities.clear();// replace(K key, V oldValue, V newValue) - (Java 8+) 替换操作
capitalCities.replace("France", "Paris", "Lyon"); // 只有当旧值匹配时才替换
三、与其他Map的比较
特性 | HashMap | LinkedHashMap | TreeMap | Hashtable | ConcurrentHashMap |
---|---|---|---|---|---|
排序保证 | 无顺序 | 插入顺序 或 访问顺序 (LRU) | 键的自然顺序 或 自定义比较器顺序 | 无顺序 | 无顺序 |
是否允许 null | 允许 一个 null key 和多个 null value | 允许 一个 null key 和多个 null value | 不允许 null key (取决于Comparator) | 不允许 null key 或 null value | 不允许 null key 或 null value |
线程安全 | 否 (非同步) | 否 (非同步) | 否 (非同步) | 是 (同步,每个方法都用 synchronized 修饰) | 是 (采用分段锁/CAS等更高效的并发控制) |
性能特点 | O(1) 时间复杂度的 get/put (平均情况) | 比 HashMap 稍慢,因为要维护链表 | O(log n) 时间复杂度的 get/put | 类似 HashMap,但同步开销大,性能差 | 高并发性能极佳,读操作通常无需锁 |
底层实现 | 数组 + 链表/红黑树 (哈希表) | HashMap + 双向链表 (维护顺序) | 红黑树 | 数组 + 链表 (哈希表) | 数组 + 链表/红黑树 + CAS + 分段锁 (JDK 7/8 不同) |
迭代顺序一致性 | 不保证,甚至可能随时间变化 | 保证,与插入顺序或访问顺序一致 | 保证,根据键的顺序排序 | 不保证 | 不保证 |
引入版本 | JDK 1.2 | JDK 1.4 | JDK 1.2 | JDK 1.0 (是古老遗留类) | JDK 1.5 (java.util.concurrent 包) |