一、最基本元素存储单元
/** * HashMap.Node subclass for normal LinkedHashMap entries. */ static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } }
还是原HashMap的,多了两个引用before,after,说明这是要用双向链表了
二、初始化 构造方法
public LinkedHashMap() { super(); accessOrder = false; } 多了 accessOrder = false; /** * The iteration ordering method for this linked hash map: <tt>true</tt> * for access-order, <tt>false</tt> for insertion-order. * * @serial */ final boolean accessOrder; accessOrder表示迭代顺序,true表示访问顺序,false表示插入顺序。
三、put方法
put方法没有重写,因此和HashMap是一样的,但也有不同,不同在于LinkedHashMap实现了afterNodeAccess,afterNodeInsertion方法
看HashMap put源码
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); //这个方法HashMap是空实现,这里是发生hash冲突后,找到有相同key对值进行处理时调用 return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); //这个方法HashMap也是空实现,这里是完成新数据put后调用 return null; }
LinkedHashMap具体实现 :
1.void afterNodeAccess(Node<K,V> e) { // move node to last 注释就说明了是把该元素移到最后 LinkedHashMap.Entry<K,V> last; if (accessOrder && (last = tail) != e) { //accessOrder用到了,默认false等于不运行,true时是按插入顺序 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; } } 以上代码看出在按插入顺序时,在有相同key时,对当前节点将其移到链表末尾 2.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); } } removeEldestEntry 方法在jdk1.8中固定返回false,因此不用关注此方法 说明一下removeElestEntry用于定义删除最老元素的规则。一旦需要删除最老节点,那么将会调用removeNode删除节点。
举个例子,如果一个链表只能维持100个元素,那么当插入了第101个元素时,以如下方式重写removeEldestEntry的话,那么将会删除最老的一个元素
四、get方法(get进行了重写) ,在插入模式中,获取值后又一次进行把当前节点移到链表尾部操作
有点类似LRU(最少最近使用)算法~
/*基于访问排序*/ LinkedHashMap<String, String> linkedHashMap_accessOrder = new LinkedHashMap<>(10,0.75f,true); linkedHashMap_accessOrder.put("name1", "josan1"); linkedHashMap_accessOrder.put("name2", "josan2"); linkedHashMap_accessOrder.put("name3", "josan3"); linkedHashMap_accessOrder.put("name4", "josan4"); linkedHashMap_accessOrder.put("name5", "josan5"); linkedHashMap_accessOrder.get("name5"); linkedHashMap_accessOrder.get("name1"); linkedHashMap_accessOrder.get("name2"); linkedHashMap_accessOrder.get("name4"); linkedHashMap_accessOrder.get("name3"); //51243顺序 for (Map.Entry<String, String> entry : linkedHashMap_accessOrder.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); System.out.println("key:" + key + ",value:" + value); }
get方法: (accessOrder 为true表示按照访问排序),
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; 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; } }
层层判断和更改引用之后,p已经独立出来了,就把p移到了map的末尾,实现了访问排序
五、总结
1. HashMap中TreeNode 即红黑树的节点类是继承LinkedHashMap.Entry
2. HashMap中没有使用双向链表,before, after没有使用,单纯的红黑树
3.LinkedHashMap是HashMap的子类,很多方法都是继承自父类,
LinkedHashMap中,存取等使用的也使用红黑树,但维护了before, after的链表属性,在存取时一样使用红黑树算法,
但keySet()、values()以及entrySet()等迭代时使用的是双向链表来进行的,这样的双向链表结构保证了插入顺序的有序。
4.总得来说,LinkedHashMap底层是数组加单项链表加双向链表。
5.数组加单向链表就是HashMap的结构,记录数据用,双向链表,存储插入顺序用。然后LInkedHashMap重写了两个方法,一个是添加entry,一个是创建entry,这两种时刻都要维护双向链表。
参考出处:https://www.cnblogs.com/zhaojj/p/7832680.html