Map集合之LinkedHashMap

static class Node<K,V> implements Map.Entry<K,V> {

final int hash;

final K key;

V value;

Node<K,V> next;

Node(int hash, K key, V value, Node<K,V> next) {

this.hash = hash;

this.key = key;

this.value = value;

this.next = next;

}

LinkedHashMap.Entry<K,V>是HashMap.Node<K,V>的子类

而Node作为HashMap底层哈希表的数组的元素类型。

所以这里LinkedHashMap.Entry也应该是作为LinkedHashMap底层哈希表的数组的元素类型。

Entry这里有两个重要属性:Entry类型的before和after

但是我们发现Entry的构造器只设置了next,没有设置before和after。说明before和after是在后面的插入逻辑中设置的。

说明LinkedHashMap的每个底层哈希表数组的元素都会记住它的前一个和后一个元素。有双向链表的数据结构特点。

accessOrder


/**

  • The iteration ordering method for this linked hash map: true

  • for access-order, false for insertion-order.

  • @serial

*/

final boolean accessOrder;

accessOrder含义是LinkedHashMap的链表节点顺序维护规则。

当accessOrder==false时,表示LinkedHashMap的链表节点顺序就是插入顺序,不受put修改操作和get查询操作的影响。

当accessOrder==true时,表示LinkedHashMap的链表节点顺序受到put修改操作和get查询操作的影响,当put修改或get查询每个节点后,该节点会被转移到链表的尾节点存储。

accessOrder和**afterNodeAccess**方法有关系

构造器

===

public LinkedHashMap()


/**

  • Constructs an empty insertion-ordered LinkedHashMap instance

  • with the default initial capacity (16) and load factor (0.75).

*/

public LinkedHashMap() {

super();

accessOrder = false;

}

无参构造器。底层哈希表默认是16初始容量,和0.75负载因子。

链表节点顺序就是插入顺序。

public LinkedHashMap(int initialCapacity)


/**

  • Constructs an empty insertion-ordered LinkedHashMap instance

  • with the specified initial capacity and a default load factor (0.75).

  • @param initialCapacity the initial capacity

  • @throws IllegalArgumentException if the initial capacity is negative

*/

public LinkedHashMap(int initialCapacity) {

super(initialCapacity);

accessOrder = false;

}

指定初始容量的构造器。链表节点顺序就是插入顺序。

public LinkedHashMap(int initialCapacity, float loadFactor)


/**

  • Constructs an empty insertion-ordered LinkedHashMap instance

  • with the specified initial capacity and load factor.

  • @param initialCapacity the initial capacity

  • @param loadFactor the load factor

  • @throws IllegalArgumentException if the initial capacity is negative

  •     or the load factor is nonpositive
    

*/

public LinkedHashMap(int initialCapacity, float loadFactor) {

super(initialCapacity, loadFactor);

accessOrder = false;

}

指定初始容量和负载因子的构造器。链表节点顺序就是插入顺序。

public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)


/**

  • Constructs an empty LinkedHashMap instance with the

  • specified initial capacity, load factor and ordering mode.

  • @param initialCapacity the initial capacity

  • @param loadFactor the load factor

  • @param accessOrder the ordering mode - true for

  •     access-order, <tt>false</tt> for insertion-order
    
  • @throws IllegalArgumentException if the initial capacity is negative

  •     or the load factor is nonpositive
    

*/

public LinkedHashMap(int initialCapacity,

float loadFactor,

boolean accessOrder) {

super(initialCapacity, loadFactor);

this.accessOrder = accessOrder;

}

指定初始容量和负载因子,且指定访问顺序的构造器。

public LinkedHashMap(Map<? extends K, ? extends V> m)


/**

  • Constructs an insertion-ordered LinkedHashMap instance with

  • the same mappings as the specified map. The LinkedHashMap

  • instance is created with a default load factor (0.75) and an initial

  • capacity sufficient to hold the mappings in the specified map.

  • @param m the map whose mappings are to be placed in this map

  • @throws NullPointerException if the specified map is null

*/

public LinkedHashMap(Map<? extends K, ? extends V> m) {

super();

accessOrder = false;

putMapEntries(m, false);

}

指定初始键值对的构造器。链表节点顺序就是插入顺序。

成员方法

====

public V put(K key, V value)


由于LinkedHashMap继承了HashMap,而LinkedHashMap没有重写put方法和putVal方法,所以直接继承了HashMap的put和putVal方法。但是具体实现上还是有一些差别的。

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)

tab[i] = newNode(hash, key, value, null);//这里的newNode方法,LinkedHashMap重写了

else {

Node<K,V> e; K k;

if (p.hash == hash &&

((k = p.key) == key || (key != null && key.equals(k))))

e = p;

else if (p instanceof TreeNode)

e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//putTreeVal中的插入新节点newTreeNode也被重写了

else {

for (int binCount = 0; ; ++binCount) {

if ((e = p.next) == null) {

p.next = newNode(hash, key, value, null);

if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st

treeifyBin(tab, hash);//链表树化中的replacementTreeNode也被LinkedHashMap重写了

break;

}

if (e.hash == hash &&

((k = e.key) == key || (key != null && key.equals(k))))

break;

p = e;

}

}

if (e != null) { // existing mapping for key

V oldValue = e.value;

if (!onlyIfAbsent || oldValue == null)

e.value = value;

afterNodeAccess(e);

return oldValue;

}

}

++modCount;

if (++size > threshold)

resize();

afterNodeInsertion(evict);//还有这一步,HashMap该步没有啥用处,该步是专门给LinkedHashMap使用的

return null;

}

由于LinkedHashMap的底层哈希表数组元素的类型已经不是HashMap.Node<K,V>类型了,而是LinkedHashMap.Entry<K,V>类型。

所以LinkedHashMap继承自HashMap的putVal方法内newNode方法被重写了。

newNode方法

//HashMap的newNode方法

Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {

return new Node<>(hash, key, value, next);

}

//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§;

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的newNode方法:

1.创建一个新的LinkedHashMap.Entry<K,V>节点对象

2.将新节点当成链表的尾节点,如果链表此时头节点为null的话,则说明加入前为空链表,则新节点还是头节点。

另外需要注意:

当新节点是链表第一个节点时,新节点的before和after还是null。

当新节点不是第一个节点时,只设置了新节点的before属性,没有设置after属性,另外老尾节点的after属性被更新为新节点。

newTreeNode方法

//HashMap的newTreeNode方法

TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {

return new TreeNode<>(hash, key, value, next);

}

//LinkedHashMap重写的newTreeNode方法

TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {

TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);

linkNodeLast§;

return p;

}

LinkedHashMap的newTreeNode的方法的重写点和上面newNode相同

replacementTreeNode方法

//HashMap的replaceTreeNode方法

TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {

return new TreeNode<>(p.hash, p.key, p.value, next);

}

//LinkedHashMap重写的replaceTreeNode方法

TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {

LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;

TreeNode<K,V> t = new TreeNode<K,V>(q.hash, q.key, q.value, next);

transferLinks(q, t);

return t;

}

// apply src’s links to dst

private void transferLinks(LinkedHashMap.Entry<K,V> src,

LinkedHashMap.Entry<K,V> dst) {

LinkedHashMap.Entry<K,V> b = dst.before = src.before;

LinkedHashMap.Entry<K,V> a = dst.after = src.after;

if (b == null)

head = dst;

else

b.after = dst;

if (a == null)

tail = dst;

else

a.before = dst;

}

//上面的TreeNode类型继承自LinkedHashMap.Enry,而LinkedHashMap.Entry继承自HashMap.Node

//因为Node,Entry都可能转化为TreeNode。所以直接让TreeNode继承自Entry,这样就可以满足三者转化关系了

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {

TreeNode<K,V> parent; // red-black tree links

TreeNode<K,V> left;

TreeNode<K,V> right;

TreeNode<K,V> prev; // needed to unlink next upon deletion

boolean red;

TreeNode(int hash, K key, V val, Node<K,V> next) {

super(hash, key, val, next);

}

将链表节点转化为红黑树节点的过程中,需要将链表节点的before和after属性传递给转化后的红黑树节点的before和after。

afterNodeAccess方法

void afterNodeAccess(Node<K,V> e) { // move node to last

LinkedHashMap.Entry<K,V> last;

if (accessOrder && (last = tail) != e) {

LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e,

b = p.before,

a = p.after;

p.after = null;

if (b == null)

head = a;

else

b.after = a;

if (a != null)

a.before = b;

else

last = b;

if (last == null)

head = p;

else {

p.before = last;

last.after = p;

}

tail = p;

++modCount;

}

}

afterNodeAccess方法的形参e来自于putVal方法,e节点key和插入键值对的key相同。

当accessOrder=true时,就把e节点放到链表尾部。且modCount++。

所以LinkedHashMap不想HashMap一样,HashMap在覆盖重复key的value后,modCount不会改变。而accessOrder==true的LinkedHashMap,在覆盖重复key的value后会将该节点作为链表尾节点,导致链表结构变更,导致modCount++。

所以最好不要使用迭代器遍历访问顺序的LinkedHashMap集合,可能会抛出ConcurrentModificationException。

afterNodeInsertion方法

void afterNodeInsertion(boolean evict) { // possibly remove eldest

LinkedHashMap.Entry<K,V> first;

if (evict && (first = head) != null && removeEldestEntry(first)) {

K key = first.key;

removeNode(hash(key), key, null, false, true);

}

}

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {

return false;

}

由于removeEldestEntry方法总是返回false,所以afterNodeInsertion方法内部不会执行

public V get(Object key)


LinkedHashMap自己重写了get方法

/**

  • Returns the value to which the specified key is mapped,

  • or {@code null} if this map contains no mapping for the key.

  • More formally, if this map contains a mapping from a key

  • {@code k} to a value {@code v} such that {@code (keynull ? knull :

  • key.equals(k))}, then this method returns {@code v}; otherwise

  • it returns {@code null}. (There can be at most one such mapping.)

  • A return value of {@code null} does not necessarily

  • indicate that the map contains no mapping for the key; it’s also

  • possible that the map explicitly maps the key to {@code null}.

  • The {@link #containsKey containsKey} operation may be used to

  • distinguish these two cases.

*/

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方法一致,但是多了一步afterNodeAccess操作。

即当accessOrder==true时,就将取出的元素重新排到链表最后。

注意:由于当accessOrder==true时,即LinkedHashMap采用访问顺序时,不能在迭代器遍历过程中使用LinkedHashMap的get操作,否则会因为modCount++导致报错。

public boolean remove(Object key, Object value)


LinkedHashMap没有重写remove方法,但是重写了remove方法内部引用的一些其他方法

public boolean remove(Object key, Object value) {

return removeNode(hash(key), key, value, true, true) != null;

}

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 &&

总结

为了帮助大家更好温习重点知识、更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

内容包括html,css,JavaScript,ES6,计算机网络,浏览器,工程化,模块化,Node.js,框架,数据结构,性能优化,项目等等。

包含了腾讯、字节跳动、小米、阿里、滴滴、美团、58、拼多多、360、新浪、搜狐等一线互联网公司面试被问到的题目,涵盖了初中级前端技术点。

前端面试题汇总

JavaScript

性能

linux

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值