前言:已经有了HashMap,为什么还要用LinkedHashMap,本文对LinkedHashMap的实现及其与HashMap 的区别进行探究;
1 使用:
1.1 LinkedHashMap 类:
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
可以清楚地看到他是继承 了HashMap 并且实现了Map 接口,所以他的使用和HashMap 是相同的:
1.2 声明和使用:
Map<String, Object> mapData1 = new LinkedHashMap<String, Object>(16,0.75f,true);
mapData1.put("key","123");
mapData1.get("key");
mapData1.remove("key");
2 过程:
2.1 声明LinkedHashMap:
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
最终都是通过调用HashMap 的方法进行负载因子和数组扩容阈值的设置;
2.2 存入元素put:
存入元素时直接调用了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)
tab[i] = newNode(hash, key, value, null);
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);
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);
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);
return null;
}
其中数组的初始化直接用的HashMap 的resize()方法,不一样的的初始化Node 节点时会调用LinkedHashMap 的newNode方法:
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;
}
// 将节点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;
}
}
可以看到node 节点的构建时用LinkedHashMap ,在HashMap 中元素放入之后调用afterNodeInsertion 方法
LinkedHashMap 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);
}
}
在使用LinkedHashMap put 插入元素时,可以通过重写 removeEldestEntry 来实现再元素达到一定个数后移除头节点效果
private static final int MAX = 6;
LinkedHashMap<Integer, String> li_hash_map =
new LinkedHashMap<Integer, String>() {
protected boolean removeEldestEntry(Map.Entry<Integer, String> eldest)
{
return size() > MAX;
}
};
2.3 获取元素,get():
LinkedHashMap get
public V get(Object key) {
Node<K,V> e;
// 通过调用 HashMap 的getNode 获取到元素
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)// 如果初始化LinkedHashMap 设置了 accessOrder 为true
afterNodeAccess(e);
return e.value;
}
// 将e节点移动到双向链表的尾部
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;
}
}
2.4 从Map 中移除元素 remove:
调用HashMap 的remove 方法进行元素移除
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 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
元素移除后调用LinkedHashMap 中的 afterNodeRemoval 方法,从双向链表中移除删除的节点:
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;
}
2.5 遍历元素:
Iterator<Map.Entry<String, Object>> iterator = mapData1.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Object> entry = iterator.next();
System.out.println("key:" + entry.getKey() + " "
+ "Value:" + entry.getValue());
}
过程:
Iterator<Map.Entry<String, Object>> iterator = mapData1.entrySet().iterator();
// 获取迭代器
public final Iterator<Map.Entry<K,V>> iterator() {
// 构造迭代器
return new LinkedEntryIterator();
}
final class LinkedEntryIterator extends LinkedHashIterator
implements Iterator<Map.Entry<K,V>> {
// 获取下一个节点LinkedHashIterator 下nextNode调用
public final Map.Entry<K,V> next() { return nextNode(); }
}
// LinkedHashIterator 构造方法调用
LinkedHashIterator() {
next = head;// next 指向head 节点
expectedModCount = modCount;
current = null;
}
// LinkedHashIterator 下nextNode调用
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;// 返回当前节点
}
iterator.hasNext():
LinkedHashIterator 中 hasNext()
public final boolean hasNext() {
return next != null;
}
iterator.next():下一节点获取
final class LinkedEntryIterator extends LinkedHashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
3 总结:
3.1 LinkedHashMap 中继承了HashMap 类并且自己实现了Map 接口,它拥有Map 中的所有方法;
3.2 与HashMap 相同的时,其内部同样使用了数组,链表 ,和红黑树存放数据,当put 插入元素没有发生hash 冲突,会先在数组中对应的位置插入节点,当出现hash 冲突,如果发现节点是链表结构则将要插入的节点插入到链表尾部(在出现hash 冲突形成链表时,与HashMap相同,都是使用了单向链表将节点插入到链表的尾部),如果是红黑树则将节点插入到树中;
3.3 与HashMap 不同的是,LinkedHashMap 的节点构建使用了双向链表,节点之间使用before 和after 来对节点的插入顺序进行了保存,这就是的在遍历LinkedHashMap 元素时会得到与插入元素一致的效果;而在HashMap 链表结构和红黑树节点之间虽然各自使用了next 维护这种前后关系,但是table 数组中 相邻两个下标元素table[0] 和table[1]并没有维护节点之间的关系,在遍历时是通过table 数组下标位置从0开始遍历,如果遇到链表或者红黑树则通过next 指针进行遍历,当链表或者红黑树的next 为空则遍历table 数组的下一位元素,所以最终输出的结果会发生乱序的情况;
3.4 LinkedHashMap 在HashMap 基础上,在插入,访问之后进行了扩展,如插入后可以扩展是否要删除最早的元素;在访问元素之后是否要将改元素移动到双向链表最后;