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