数据结构如下所示:
image.png

LinkedHashMap - 图2

参考了https://blog.csdn.net/justloveyou_/article/details/71713781,发现和我看的源码(JDK1.8)有出入,找不到原文中的方法,发现1.6和1.8之间有做过修改。

基于1.6

  1. LinkedHashMap的Entry继承了HashMap中的Node,新增了before和after指针(实现双向链表),和Next不同,next是维护各个哈希桶中的Entry链。
  2. LinkedHashMap完全继承了HashMap的 put(Key,Value) 方法,只是对put(Key,Value)方法所调用的recordAccess方法和addEntry方法进行了重写;对于get(Key)方法而言,LinkedHashMap则直接对它进行了重写。

    1. /**
    2. * Associates the specified value with the specified key in this map.
    3. * If the map previously contained a mapping for the key, the old
    4. * value is replaced.
    5. *
    6. * @param key key with which the specified value is to be associated
    7. * @param value value to be associated with the specified key
    8. * @return the previous value associated with key, or null if there was no mapping for key.
    9. * Note that a null return can also indicate that the map previously associated null with key.
    10. */
    11. public V put(K key, V value) {
    12. //当key为null时,调用putForNullKey方法,并将该键值对保存到table的第一个位置
    13. if (key == null)
    14. return putForNullKey(value);
    15. //根据key的hashCode计算hash值
    16. int hash = hash(key.hashCode());
    17. //计算该键值对在数组中的存储位置(哪个桶)
    18. int i = indexFor(hash, table.length);
    19. //在table的第i个桶上进行迭代,寻找 key 保存的位置
    20. for (Entry<K,V> e = table[i]; e != null; e = e.next) {
    21. Object k;
    22. //判断该条链上是否存在hash值相同且key值相等的映射,若存在,则直接覆盖 value,并返回旧value
    23. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
    24. V oldValue = e.value;
    25. e.value = value;
    26. e.recordAccess(this); // LinkedHashMap重写了Entry中的recordAccess方法--- (1)
    27. return oldValue; // 返回旧值
    28. }
    29. }
    30. modCount++; //修改次数增加1,快速失败机制
    31. //原Map中无该映射,将该添加至该链的链头
    32. addEntry(hash, key, value, i); // LinkedHashMap重写了HashMap中的createEntry方法 ---- (2)
    33. return null;
    34. }
    1. /**
    2. * This override alters behavior of superclass put method. It causes newly
    3. * allocated entry to get inserted at the end of the linked list and
    4. * removes the eldest entry if appropriate.
    5. *
    6. * LinkedHashMap中的addEntry方法
    7. */
    8. void addEntry(int hash, K key, V value, int bucketIndex) {
    9. //创建新的Entry,并插入到LinkedHashMap中
    10. createEntry(hash, key, value, bucketIndex); // 重写了HashMap中的createEntry方法
    11. //双向链表的第一个有效节点(header后的那个节点)为最近最少使用的节点,这是用来支持LRU算法的
    12. Entry<K,V> eldest = header.after;
    13. //如果有必要,则删除掉该近期最少使用的节点,
    14. //这要看对removeEldestEntry的覆写,由于默认为false,因此默认是不做任何处理的。
    15. if (removeEldestEntry(eldest)) {
    16. removeEntryForKey(eldest.key);
    17. } else {
    18. //扩容到原来的2倍
    19. if (size >= threshold)
    20. resize(2 * table.length);
    21. }
    22. }
    23. -------------------------------我是分割线------------------------------------
    24. /**
    25. * Adds a new entry with the specified key, value and hash code to
    26. * the specified bucket. It is the responsibility of this
    27. * method to resize the table if appropriate.
    28. *
    29. * Subclass overrides this to alter the behavior of put method.
    30. *
    31. * HashMap中的addEntry方法
    32. */
    33. void addEntry(int hash, K key, V value, int bucketIndex) {
    34. //获取bucketIndex处的Entry
    35. Entry<K,V> e = table[bucketIndex];
    36. //将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry
    37. table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    38. //若HashMap中元素的个数超过极限了,则容量扩大两倍
    39. if (size++ >= threshold)
    40. resize(2 * table.length);
    41. }

基于1.8

1.8的LinkedHashMap的put()方法,基本继承了HashMap的,没有变动。但是LinkedHashMap重写了HashMap的三个回调方法:

  1. // Callbacks to allow LinkedHashMap post-actions
  2. void afterNodeAccess(Node<K,V> p) { } //设置按照访问顺序,accessOrder为true时
  3. void afterNodeInsertion(boolean evict) { }
  4. void afterNodeRemoval(Node<K,V> p) { }

这三个回调方法(如上)在HashMap中均没有实现,是个空方法。但这三个方法都没有找到将节点插入到双向队列中的操作。都是维护插入后的节点信息的。
LinkedHashMap的newNode方法,该方法重写了HashMap的newNode方法。

  1. Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
  2. LinkedHashMap.Entry<K,V> p =
  3. new LinkedHashMap.Entry<K,V>(hash, key, value, e);
  4. linkNodeLast(p);
  5. return p;
  6. }
  7. // link at the end of list
  8. private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { //该方法会将节点放到双向链表的尾部,也就是最近访问的一个
  9. LinkedHashMap.Entry<K,V> last = tail;
  10. tail = p;
  11. if (last == null)
  12. head = p;
  13. else {
  14. p.before = last;
  15. last.after = p;
  16. }
  17. }

这样就知道了在创建新节点的时候,把该节点放到了尾部。所以我们就清楚了当accessOrder设置为false时,会按照插入顺序进行排序,当accessOrder为true是,会按照访问顺序(afterNodeAccess()触发)(也就是插入和访问都会将当前节点放置到尾部,尾部代表的是最近访问的数据,这和JDK1.6是反过来的,jdk1.6头部是最近访问的)。

使用场景

本地缓存(LRU)

  1. class LRULinkedHashMap<K,V> extends LinkedHashMap<K,V>{
  2. //定义缓存的容量
  3. private int capacity;
  4. private static final long serialVersionUID = 1L;
  5. //带参数的构造器
  6. LRULinkedHashMap(int capacity){
  7. //调用LinkedHashMap的构造器,传入以下参数
  8. super(16,0.75f,true);
  9. //传入指定的缓存最大容量
  10. this.capacity=capacity;
  11. }
  12. //实现LRU的关键方法,如果map里面的元素个数大于了缓存最大容量,则删除链表的顶端元素
  13. @Override
  14. public boolean removeEldestEntry(Map.Entry<K, V> eldest){
  15. System.out.println(eldest.getKey() + "=" + eldest.getValue());
  16. return size()>capacity;
  17. }
  18. }