LinkedHashMap继承自HashMap,并且实现了Map接口
LinkedHashMap在继承了HashMap的底层数据结构的同时,维护了一个双向链表,保证了数据插入或访问顺序,是有序的哈希表
特点
- 元素是有序的:维护了双向链表,默认是按照插入顺序(也可以实现访问顺序:每次访问节点就将当前节点移动到链表尾端)
- 元素的键不能重复,值可以重复
- 元素的键和值都支持null值
- hash冲突:根据key的hash值和数组长度位运算得到索引位,若此位置已经有值,则形成链表存储在此索引位(一个索引位为一个桶,桶内是链表或红黑树)
- 数组扩容:当数组元素个数大于数组容量的75%的时候,数组会进行扩容,扩容到原来的2倍
- 链表转红黑树:当链表长度大于8并且数组长度大于64时,链表转化为红黑树;当红黑树的节点个数减少到6时,会退化为链表
- 非线程安全:不支持多线程操作
底层原理
- LinkedHashMap是基于数组+链表+红黑树+双向链表的数据结构实现的
- 数组的默认初始化容量为16,比例因子为0.75,当数组元素个数大于16*0.75(临界值),数组会进行扩容,并且将旧数组中的数组拷贝到新数组中
- 当插入的元素出现hash冲突(计算出的索引位已经有值),则会在当前索引位形成链表存储数据
- 当链表长度大于8但是数组长度小于64的时候,数组会进行扩容,并将原数组中的链表拆分成2个链表存储在不同位置的桶中(索引位)
- 当链表长度大于8但是数组长度大于64的时候,链表会转化为红黑树
- 维护双向链表,确认元素插入或者访问的顺序。在解决hash冲突时调整链表或者红黑树,双向链表不需要调整
迭代器直接遍历双向链表:
LinkedHashMap
的entrySet()
、keySet()
、values()
遍历时间复杂度为O(n)
,且顺序确定
方法
构造方法
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
{
//双向链表头节点
transient LinkedHashMap.Entry<K,V> head;
//双向链表尾节点
transient LinkedHashMap.Entry<K,V> tail;
//指定链表顺序:true 按照访问节点顺序;false 按照插入顺序
final boolean accessOrder;
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
//创建一个LinkedHashMap,手动设置初始化容量,默认比例因子0.75
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
//创建一个LinkedHashMap,默认初始化容量为16,比例因子为0.75
public LinkedHashMap() {
super();
accessOrder = false;
}
//创建一个LinkedHashMap,将传入集合中的元素按照元素顺序复制到LinkedHashMap
//(添加元素时维护了双向链表)
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
//创建一个LinkedHashMap,手动设置初始化容量,比例因子,元素顺序(按照插入还是访问顺序)
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
}
添加单个元素
实际使用的是HashMap中的put方法,LinkedHashMap只是重写了创建节点的方法(此中维护双向链表)
//hashmap中的put方法
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) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
//调用LinkedHashMap中的方法创建节点
tab[i] = newNode(hash, key, value, null);
else {
......
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
//创建新节点(LinkedHashMap.Entry继承自HashMap.Node,额外维护了双向链表的前后指针)
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<>(hash, key, value, e);
//建立新节点的双向链表关系(前后指针)
linkNodeLast(p);
return p;
}
//在双向列表尾部添加节点
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
创建LinkedHashMap节点
//创建节点后维护节点在双向链表中的位置
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
TreeNode<K,V> p = new TreeNode<>(hash, key, value, next);
linkNodeLast(p);
return p;
}
//创建节点后维护节点在双向链表中的位置
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<>(hash, key, value, e);
linkNodeLast(p);
return p;
}
添加多个元素
实际调用HashMap中的putAll方法,LinkedHashMap只是重写了创建节点的方法
//实际调用hashmap中的方法
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true);
}
//hashmap中的方法
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
......
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
//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)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
......
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
删除单个元素
实际调用hashmap中的删除方法,在删除完成后,调用LinkedHashMap中的afterNodeRemoval方法解绑双向链表
//hashmap中的删除方法
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
//在removeNode方法中,删除完节点后,调用afterNodeRemoval解绑双向链表
void afterNodeRemoval(Node<K,V> e) { // unlink
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
//在双向链表中,将已删除的节点中的前后指针置为null
p.before = p.after = null;
//如果e节点的前一个节点(b)为null,那么双向链表的头节点就是a
if (b == null)
head = a;
//如果e节点的前一个节点(b)不为null,则b节点的后一个节点置为a
else
b.after = a;
//如果e的后一个节点(a)是null,那么双向链表的尾节点就是b
if (a == null)
tail = b;
//如果e的后一个节点(a)不为null,则a的前一个节点置为b
else
a.before = b;
}
获取单个元素
如果设置了集合顺序为访问顺序,则获取到元素后要重新维护元素在双向列表中的位置(移动到链表的尾端)
//调用hashmap中的getNode方法获取元素,
//如果LinkedHashMap设置的顺序是按照访问顺序,
//则调用afterNodeAccess重新维护元素在双向链表中的位置
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;
}
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
//首先将尾节点tail赋值给last,p指向e节点
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
//e节点的后一个节点置为null
p.after = null;
//如果e节点的前一个节点(b)为null,则e节点的后一个节点(a)为头节点
if (b == null)
head = a;
//如果e节点的前一个节点(b)不为null,则b的后一个节点是a
else
b.after = a;
//如果e的后一个节点(a)不为null,则a的前一个节点是b
if (a != null)
a.before = b;
//如果e的后一个节点(a)为null,则尾节点是b
else
last = b;
//最终确认:last节点(也就是尾节点)是null,那么头节点就是e
if (last == null)
head = p;
//last不为null,那么e的前一个节点是last(尾节点);last的后一个节点就是p
else {
p.before = last;
last.after = p;
}
//双向链表尾部指针指向e
tail = p;
++modCount;
}
}
迭代器
abstract class LinkedHashIterator {
LinkedHashMap.Entry<K,V> next;
LinkedHashMap.Entry<K,V> current;
int expectedModCount;
//创建迭代器时,直接将双向链表的head放入迭代器的next
LinkedHashIterator() {
next = head;
expectedModCount = modCount;
current = null;
}
public final boolean hasNext() {
return next != null;
}
//从head开始迭代遍历双向列表
final LinkedHashMap.Entry<K,V> nextNode() {
LinkedHashMap.Entry<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
current = e;
next = e.after;
return e;
}
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
removeNode(p.hash, p.key, null, false, false);
expectedModCount = modCount;
}
}