这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战
一、前言
如果了解 HashMap,那么 LinkedHashMap 就很容易上手,LinkedHashMap 是 HashMap 的一个子类。
HashMap 插入 key-value 数据,但遍历时候并不是按照插入的顺序来,如下 demo:
public class Test {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(2, "牛yyds");
map.put(1, "哈xswl");
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println(entry);
}
}
}
输出如下:
1=哈xswl
2=牛yyds
而 LinkedHashMap 会记录插入 key-value 的顺序,遍历时候会按照顺序输出,demo 如下:
public class Test {
public static void main(String[] args) {
Map<Integer, String> map = new LinkedHashMap<>();
map.put(2, "牛yyds");
map.put(1, "哈xswl");
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println(entry);
}
}
}
输出如下:
2=牛yyds
1=哈xswl
LinkedHashMap 区别于 HashMap,他会记录一下 key-value 顺序,用一个链表来记录。
举个栗子:在调用 LinkedHashMap 的 put() 方法时,实际上调用的是 HashMap 的 put():
- 先调用
HashMap的put()方法 - 再调用
newNode()方法
// 源码如下:HashMap
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// 一大堆操作
if ((p = tab[i = (n - 1) & hash]) == null)
// 调用子类方法
tab[i] = newNode(hash, key, value, null);
// ... ...
afterNodeInsertion(evict);
return null;
}
// LinkedHashMap 的 方法:newNode()
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
// 在双向链表的表尾增加一个
linkNodeLast(p);
return p;
}
LinkedHashMap 一些特性:
| 问题 | 结论 |
|---|---|
| 是否允许为空 | Key 和 Value 都允许空 |
| 是否允许重复数据 | Key 重复会覆盖、Value 允许重复 |
| 是否有序 | 有序 |
| 是否线程安全 | 非线程安全 |
二、其他操作
LinkedHashMap 最主要的是维护了一个双向链表。
主要操作有:
- 设置
value:put() - 移除
value:remove()
1. put() 操作
LinkedHashMap 调用 put() 是调用 父类 HashMap 的方法。
默认情况下,多次做 key 值覆盖,是不会改变顺序的。
因为,
LinkedHashMap有一个参数:accessOrder,默认为false。
- 如果
accessOrder = false:get()、put()操作,对应key都不会改变其在链表里的顺序- 如果
accessOrder = true:get()、put()操作,都会导致其在链表里的改变,会被挪到链表的尾部
public class Test {
public static void main(String[] args) {
Map<Integer, String> map = new LinkedHashMap<>(16, 0.75f, true);
map.put(2, "牛yyds");
map.put(1, "哈xswl");
map.put(3, "牛哇牛哇");
map.put(2, "牛yyds-1");
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println(entry);
}
}
}
输出结果如下:
1=哈xswl
3=牛哇牛哇
2=牛yyds-1
那这个参数 accessOrder 是如何影响的呢?
关键字:afterNodeAccess()
// LinkedHashMap 源码如下:
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
// 添加至末尾
afterNodeAccess(e);
return e.value;
}
// HashMap 源码如下:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
// ... ...
else {
// ... ...
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// 添加至末尾
afterNodeAccess(e);
return oldValue;
}
}
// ... ...
}
2. remove() 操作
删除某个元素时候,当然需要将次元素从链表中删除。
过程当然类似喽:
- 先调用父类
Hashmap的remove()方法 - 调用子类
afterNodeRemoval()方法
源码解析如下:
// HashMap 源码如下:
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
// ... ...
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
// ... ..
// 调用子类方法
afterNodeRemoval(node);
return node;
}
}
return null;
}
// LinkedHashMap 源码如下:
void afterNodeRemoval(Node<K,V> e) { // unlink
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.before = p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a == null)
tail = b;
else
a.before = b;
}