推荐一个我自己写的程序员在线工具站:
http://cxytools.com
提供一站式在线工具平台,专为程序员设计,包括时间日期、JSON处理、SQL格式化、随机字符串生成、UUID生成、随机数生成、文本Hash等功能,提升开发效率。
以下是正文。
在 Java 编程中,集合框架为我们提供了多种实现,以满足不同的需求。LinkedHashMap
类是 HashMap
的一个子类,它不仅保持了 HashMap
的基本功能,还通过双向链表实现了键值对的插入顺序。
1. LinkedHashMap
类概述
1.1 定义
LinkedHashMap
类位于 java.util
包中,是 HashMap
的一个具体实现。与 HashMap
不同,LinkedHashMap
通过维护一个双向链表来记录键值对的插入顺序或访问顺序,从而可以保证迭代时的顺序。
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {
// 构造方法和主要方法省略
}
1.2 主要特点
- 有序性:
LinkedHashMap
保证了迭代顺序,可以是插入顺序或访问顺序。 - 性能:与
HashMap
相比,性能稍微低一些,因为要维护双向链表,但仍然提供了常数时间的性能特性。 - 允许
null
键和值:可以存储null
键和值,与HashMap
类似。
2. 常用方法
2.1 构造方法
LinkedHashMap
提供了多种构造方法,用于创建不同类型的映射。
// 创建一个空的 LinkedHashMap
LinkedHashMap<K,V> map = new LinkedHashMap<>();
// 创建一个具有指定初始容量和加载因子的 LinkedHashMap
LinkedHashMap<K,V> map = new LinkedHashMap<>(int initialCapacity, float loadFactor);
// 创建一个具有指定初始容量、加载因子和访问顺序的 LinkedHashMap
LinkedHashMap<K,V> map = new LinkedHashMap<>(int initialCapacity, float loadFactor, boolean accessOrder);
// 创建一个包含指定 Map 的 LinkedHashMap
LinkedHashMap<K,V> map = new LinkedHashMap<>(Map<? extends K, ? extends V> m);
2.2 put
方法
put
方法用于将指定的键值对放入映射中,如果映射中已存在该键,则更新其对应的值。
public V put(K key, V value)
示例
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("Alice", 30);
map.put("Bob", 25);
System.out.println(map); // 输出:{Alice=30, Bob=25}
2.3 get
方法
get
方法用于根据指定的键获取对应的值,如果映射中不包含该键,则返回 null
。
public V get(Object key)
示例
Integer age = map.get("Alice");
System.out.println("Alice's age: " + age); // 输出:Alice's age: 30
2.4 remove
方法
remove
方法用于根据指定的键从映射中移除键值对。
public V remove(Object key)
示例
map.remove("Bob");
System.out.println(map); // 输出:{Alice=30}
2.5 containsKey
方法
containsKey
方法用于判断映射中是否包含指定的键。
public boolean containsKey(Object key)
示例
boolean containsAlice = map.containsKey("Alice");
System.out.println("Contains Alice: " + containsAlice); // 输出:Contains Alice: true
2.6 containsValue
方法
containsValue
方法用于判断映射中是否包含指定的值。
public boolean containsValue(Object value)
示例
boolean containsAge30 = map.containsValue(30);
System.out.println("Contains value 30: " + containsAge30); // 输出:Contains value 30: true
2.7 size
方法
size
方法用于获取映射中的键值对数量。
public int size()
示例
int size = map.size();
System.out.println("Size: " + size); // 输出:Size: 1
2.8 clear
方法
clear
方法用于移除映射中的所有键值对。
public void clear()
示例
map.clear();
System.out.println(map); // 输出:{}
2.9 迭代器
LinkedHashMap
提供了 keySet
和 entrySet
方法,分别用于获取键的集合和键值对的集合。
public Set<K> keySet()
public Set<Map.Entry<K,V>> entrySet()
示例
for (String key : map.keySet()) {
System.out.println("Key: " + key);
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
3. 内部实现原理
LinkedHashMap
的内部实现主要基于哈希表和双向链表。它通过哈希表来存储键值对,并通过双向链表来维护键值对的顺序。
3.1 哈希表结构
LinkedHashMap
使用哈希表来存储键值对,哈希表中的每个节点除了存储键和值之外,还包含前驱和后继指针,用于维护双向链表。
3.2 双向链表
LinkedHashMap
通过双向链表来维护键值对的顺序。链表中的每个节点对应于哈希表中的一个节点,链表的头节点是第一个插入的键值对,尾节点是最后一个插入的键值对。
3.3 迭代顺序
LinkedHashMap
提供了两种迭代顺序:插入顺序和访问顺序。默认情况下,迭代顺序是插入顺序。如果在创建 LinkedHashMap
时指定了访问顺序,则每次访问键值对时,都会将该键值对移动到链表的末尾。
LinkedHashMap<K,V> map = new LinkedHashMap<>(initialCapacity, loadFactor, true); // true 表示按访问顺序
4. 实际应用
4.1 基本用法
LinkedHashMap
最常见的应用场景是需要保持键值对的插入顺序。
示例
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("Alice", 30);
map.put("Bob", 25);
map.put("Charlie", 35);
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
// 输出顺序为:
// Key: Alice, Value: 30
// Key: Bob, Value: 25
// Key: Charlie, Value: 35
4.2 实现 LRU 缓存
LinkedHashMap
可以通过指定访问顺序来实现简单的 LRU(最近最少使用)缓存。
示例
LinkedHashMap<String, Integer> lruCache = new LinkedHashMap<>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, Integer> eldest) {
return size() > 5; // 当缓存大小超过5时移除最老的键值对
}
};
lruCache.put("A", 1);
lruCache.put("B", 2);
lruCache.put("C", 3);
lruCache.put("D", 4);
lruCache.put("E", 5);
lruCache.get("A"); // 访问 A,使其成为最近使用的键
lruCache.put("F", 6); // 插入新的键值对 F
for (Map.Entry<String, Integer> entry : lruCache.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
// 输出顺序为:
// Key: B, Value: 2
// Key: C, Value: 3
// Key: D, Value: 4
// Key: E, Value: 5
// Key: A, Value: 1
// Key: F, Value: 6
4.3 迭代顺序控制
在某些情况下,我们可能需要对集合进行按访问顺序迭代,这在实现缓存或其他类似数据结构时非常有用。
示例
LinkedHashMap<String
, Integer> accessOrderMap = new LinkedHashMap<>(16, 0.75f, true);
accessOrderMap.put("A", 1);
accessOrderMap.put("B", 2);
accessOrderMap.put("C", 3);
accessOrderMap.get("A"); // 访问 A,使其成为最近使用的键
accessOrderMap.get("B"); // 访问 B,使其成为最近使用的键
for (Map.Entry<String, Integer> entry : accessOrderMap.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
// 输出顺序为:
// Key: C, Value: 3
// Key: A, Value: 1
// Key: B, Value: 2
5. 性能考虑
在使用 LinkedHashMap
时,需要注意以下几点:
- 空间开销:由于需要维护双向链表,
LinkedHashMap
比HashMap
占用更多的内存。 - 时间复杂度:插入、删除和访问操作的时间复杂度为 O(1),与
HashMap
相同,但由于链表操作,实际性能略低于HashMap
。 - 迭代性能:
LinkedHashMap
的迭代性能优于HashMap
,因为链表结构使得迭代时不需要跳跃。
6. 扩展阅读
6.1 HashMap
类
HashMap
是 LinkedHashMap
的父类,用于实现基本的键值对映射。建议阅读 HashMap 的官方文档。
6.2 TreeMap
类
TreeMap
类基于红黑树实现,提供键的自然顺序或自定义顺序的映射。建议阅读 TreeMap 的官方文档。