一.Map的常用方法
在Java中,Map
接口是一个非常重要的数据结构,用于存储键值对。Map
的每个键都是唯一的,并且每个键映射到一个特定的值。Java 提供了几种 Map
实现,包括 HashMap
、LinkedHashMap
、TreeMap
等。下面是一些在使用 Map
时常用的方法和实践。
基本操作
put(K key, V value)
Map<String, Integer> map = new HashMap<>();
map.put("apple", 3);
map.put("banana", 5);
get(Object key)
返回指定键所映射的值,如果此映射不包含该键的映射关系,则返回 null
。
int count = map.get("apple"); // 返回 3
remove(Object key)
如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。
map.remove("apple");
containsKey(Object key)
如果此映射包含指定键的映射关系,则返回 true
。
boolean hasApple = map.containsKey("apple"); // 返回 true 或 false
containsValue(Object value)
如果此映射将一个或多个键映射到指定值,则返回 true
。
boolean containsThree = map.containsValue(3);
keySet()
返回此映射中包含的键的 Set
视图。
Set<String> keys = map.keySet(); // 获取所有键
values()
返回此映射中包含的值的 Collection
视图。
Collection<Integer> values = map.values(); // 获取所有值
entrySet()
返回此映射中包含的映射关系的 Set
视图。
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " -> " + value);
}
更新和计算
putIfAbsent(K key, V value)
如果指定的键尚未关联值(或映射到 null
),将其与给定值关联并返回 null
,否则返回当前值。
map.putIfAbsent("cherry", 10);
replace(K key, V value)
只有当目标键已经存在于 Map
中时,才将目标键的值设置为提供的值。
map.replace("banana", 6); // 将 "banana" 的值更新为 6
computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)
如果指定键的值不存在或为 null
,尝试使用给定的映射函数计算其值,并将其插入到 Map
中。
map.computeIfAbsent("orange", k -> 7);
computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
如果指定键的值存在且不为 null
,则尝试重新计算其值。
map.computeIfPresent("banana", (k, v) -> v + 1);
compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
无论键是否存在,都尝试计算其值。
map.compute("banana", (k, v) -> (v == null) ? 42 : v + 1);
批量操作
forEach(BiConsumer<? super K, ? super V> action)
对 Map
中的每个键值对执行给定的操作。
map.forEach((key, value) -> System.out.println(key + " has " + value + " items"));
clear()
清除 Map
中的所有键值对。
map.clear();
二.TreeMap, HashMap, LinkedHashMap
1. HashMap
特点
- 无序:
HashMap
不保证随着时间的推移,Map中的元素顺序是恒定的。 - 键值约束:允许将
null
作为键和值。 - 性能:为基本操作如
get
和put
提供常数时间的性能(O(1)),这种性能假设哈希函数将元素适当分布在buckets中。
2. LinkedHashMap
特点
- 有序:
LinkedHashMap
维护元素插入的顺序或者最近最少使用(LRU)顺序。 - 键值约束:允许
null
键和null
值。 - 性能:略低于
HashMap
的性能,因为维护了元素的插入顺序,但对于创建有序的缓存非常有用。
独有方法
- removeEldestEntry(Map.Entry<K,V> eldest):可以被覆盖,以实现在新元素添加到map时自动移除最老的元素。通常用于基于LRU策略的缓存。
3. TreeMap
特点
- 排序:
TreeMap
按照自然顺序或者构造时指定的Comparator
对键进行排序。 - 键值约束:不允许
null
键(如果使用自然排序或者自定义的Comparator不允许null
),但允许null
值。 - 性能:提供对键的有序遍历,基本操作(如获取和放置)的性能为对数时间(O(log n))。
独有方法
- firstKey(), lastKey():获取排序后的第一个和最后一个键。
- headMap(K toKey), tailMap(K fromKey), subMap(K fromKey, K toKey):返回键的范围视图
比较三种Map
性能
HashMap
通常提供最快的查找性能,因为它的操作几乎是常数时间的。LinkedHashMap
的性能略低于HashMap
,因为它维护了元素的插入顺序。TreeMap
的性能通常是O(log n),适用于需要有序访问的场景。
用途
- HashMap是最通用的
Map
,适用于需要快速访问的情况,不关心元素的顺序。 - LinkedHashMap适用于需要保持插入顺序的场景,如缓存和记忆最近插入/访问的元素。
- TreeMap适用于需要按自然顺序或自定义顺序访问键的情况,如范围搜索和排序显示。
资源消耗
HashMap
和LinkedHashMap
的内存消耗类似,但LinkedHashMap
因为额外维护了链表结构而稍高。TreeMap
通常消耗更多的内存,因为它基于红黑树结构。
三.排序方法
1. 按键排序
TreeMap
在内部就是按照键的自然顺序(或者根据构造函数中提供的比较器)进行排序的。
Map<String, Integer> unsortedMap = new HashMap<>();
unsortedMap.put("one", 1);
unsortedMap.put("three", 3);
unsortedMap.put("two", 2);
SortedMap<String, Integer> sortedMap = new TreeMap<>(unsortedMap);
2. 按值排序
如果要按照值对 Map
进行排序,你需要额外的步骤,因为 Map
的值没有内建的排序机制。一种常见的做法是使用辅助的列表来排序,然后再将结果放入 LinkedHashMap
(这会保留元素的插入顺序)。
Map<String, Integer> unsortedMap = new HashMap<>();
unsortedMap.put("one", 1);
unsortedMap.put("three", 3);
unsortedMap.put("two", 2);
// 使用流对值进行排序
LinkedHashMap<String, Integer> sortedByValue = unsortedMap.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1,
LinkedHashMap::new
));
3. 自定义排序
比如根据多个字段或条件排序,可以自定义比较器(Comparator)
Map<String, Integer> unsortedMap = new HashMap<>();
unsortedMap.put("one", 1);
unsortedMap.put("three", 3);
unsortedMap.put("two", 2);
// 自定义比较器来进行排序
LinkedHashMap<String, Integer> customSortedMap = unsortedMap.entrySet()
.stream()
.sorted((entry1, entry2) -> {
// 根据值降序排列,如果值相等,则按键升序排列
int valueComparison = entry2.getValue().compareTo(entry1.getValue());
return (valueComparison != 0) ? valueComparison : entry1.getKey().compareTo(entry2.getKey());
})
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1,
LinkedHashMap::new
));