最近正准备回顾一下Java,所以在此做一些记录。
LinkedHashMap继承了HashMap,大多数的操作调用的是HashMap的实现,在进行操作的时候多维护了一层双向链表
LinkedHashMap的节点也继承了HashMap的节点,多维护了前置节点和后置节点两个属性
1 static class Entry<K,V> extends HashMap.Node<K,V> { 2 Entry<K,V> before, after; 3 Entry(int hash, K key, V value, Node<K,V> next) { 4 super(hash, key, value, next); 5 } 6 }
1.put(K key, V value) 存放一个键值对,其实是调用了HashMap的put方法,通过重写HashMap里的部分方法来实现链表的维护
1 HashMap的put方法会生成一个节点,调用了newNode方法,而LinkedHashMap重写了此方法 2 /** 3 * 创建一个节点 4 * @param hash hash值 5 * @param key 键 6 * @param value 值 7 * @param e 下一个节点,这个是HashMap节点的属性 8 * @return 9 */ 10 Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { 11 //调用构造方法 12 LinkedHashMap.Entry<K,V> p = 13 new LinkedHashMap.Entry<K,V>(hash, key, value, e); 14 //维护链表 15 linkNodeLast(p); 16 return p; 17 } 18 19 /** 20 * 添加一个节点到末尾 21 * @param p 节点 22 */ 23 private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { 24 //保存尾部节点 25 LinkedHashMap.Entry<K,V> last = tail; 26 //更新尾部节点 27 tail = p; 28 //判断之前的尾部节点是否为空 29 if (last == null) 30 //之前的尾部节点为空,说明还没有数据,设置一下头节点 31 head = p; 32 else { 33 //说明之前已经有数据了,将新的节点作为尾部节点连接起来 34 p.before = last; 35 last.after = p; 36 } 37 } 38 39 HashMap当put一个已经存在的key时,会触发是否更新的操作,之后会调用afterNodeAccess方法,LinkedHashMap重写了此方法 40 /** 41 * accessOrder为true时,将操作的节点移到链表尾部 42 * @param e 节点 43 */ 44 void afterNodeAccess(Node<K,V> e) { 45 LinkedHashMap.Entry<K,V> last; 46 //accessOrder 这个参数是指在进行操作的时候,是否将操作的节点移动到链表的最后,默认false 47 //也就是说accessOrder为false的时候链表就是按照插入顺序维护的 48 //true的时候,会将最近使用的节点移动到链表最后 49 if (accessOrder && (last = tail) != e) { 50 //保存当前节点和其前置节点和后置节点 51 LinkedHashMap.Entry<K,V> p = 52 (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; 53 //清空后置节点,因为当前节点要被移动到最后了 54 p.after = null; 55 //判断前置节点是否为空节点 56 if (b == null) 57 //前置节点为空,说明当前节点是头节点,将它的后置节点也就是第二个节点设置为头节点 58 head = a; 59 else 60 //存在前置节点,将前置节点的后置节点连接到当前节点的下一个节点 61 b.after = a; 62 //判断后置节点是否为空 63 if (a != null) 64 //后置节点不为空,更新后置节点的前置节点 65 a.before = b; 66 else 67 //说明该节点就是尾部节点,设置前置节点为后节点 68 //a == null 说明p就是尾部节点? 有点不清楚 69 last = b; 70 //统一更新尾部节点 71 if (last == null) 72 //说明只有这么一个节点 73 head = p; 74 else { 75 //将当前节点挂到链表末尾 76 p.before = last; 77 last.after = p; 78 } 79 //设置尾部节点 80 tail = p; 81 ++modCount; 82 } 83 } 84 85 LinkedHashMap也重写了afterNodeInsertion方法 86 void afterNodeInsertion(boolean evict) { 87 LinkedHashMap.Entry<K,V> first; 88 if (evict && (first = head) != null && removeEldestEntry(first)) { 89 K key = first.key; 90 removeNode(hash(key), key, null, false, true); 91 } 92 } 93 94 protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { 95 return false; 96 } 97 98 其实LinkedHashMap中这个方法并不生效
2.get(Object key) 根据key获取值
1 /** 2 * 获取值 3 * @param key 键 4 * @return 5 */ 6 public V get(Object key) { 7 Node<K,V> e; 8 //调用了HashMap中的getNode方法 9 if ((e = getNode(hash(key), key)) == null) 10 return null; 11 if (accessOrder) 12 //移动当前操作的节点到链表最后 13 afterNodeAccess(e); 14 return e.value; 15 }
3. containsValue(Object value) 是否存在某个值
1 public boolean containsValue(Object value) { 2 //通过遍历链表实现 3 for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) { 4 V v = e.value; 5 if (v == value || (value != null && value.equals(v))) 6 return true; 7 } 8 return false; 9 }
4.remove(Object key) 删除key
1 LinkedHashMap调用了HashMap的remove方法 2 重写了afterNodeRemoval方法 3 /** 4 * 删除链表中的节点 5 * @param e 6 */ 7 void afterNodeRemoval(Node<K,V> e) { 8 //获取当前节点的前置后置节点 9 LinkedHashMap.Entry<K,V> p = 10 (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; 11 //清空前置后置节点 12 p.before = p.after = null; 13 14 if (b == null) 15 //前置节点为空,说明为头节点,更新头节点为后置节点 16 head = a; 17 else 18 //前置节点不为空,设置前置节点的后置节点为删除节点的后置节点 19 b.after = a; 20 if (a == null) 21 //后置节点为空,说明为尾部节点,更新尾部节点为其前置节点 22 tail = b; 23 else 24 //后置节点不为空,更新后置节点的前置节点 25 a.before = b; 26 }