键值对Map-05

举报
kwan的解忧杂货铺 发表于 2024/08/08 20:53:56 2024/08/08
【摘要】 1.LinkedHashMap 简介LinkedHashMap 是 HashMap 的一个子类。它继承了 HashMap 的所有特性,同时还具有一些额外的功能,位于 java.util 包下。与 HashMap 不同的是,LinkedHashMap 会保持元素插入的顺序,因此它是有序的。具体来说,LinkedHashMap 使用一个双向链表来维护插入顺序,而 HashMap 则不保证元素的...

1.LinkedHashMap 简介

LinkedHashMap 是 HashMap 的一个子类。它继承了 HashMap 的所有特性,同时还具有一些额外的功能,位于 java.util 包下。

与 HashMap 不同的是,LinkedHashMap 会保持元素插入的顺序,因此它是有序的。具体来说,LinkedHashMap 使用一个双向链表来维护插入顺序,而 HashMap 则不保证元素的遍历顺序。这使得 LinkedHashMap 可以按照元素插入的顺序进行迭代,并且这个迭代顺序不会随着时间的推移而改变。

2.LinkedHashMap 特点

LinkedHashMap的主要特性包括:

  1. 有序性:维护元素插入的顺序,可以按照插入顺序进行迭代。
  2. 基于 HashMap 实现:LinkedHashMap 底层使用 HashMap 来存储键值对,因此具有 HashMap 的高效查找和插入操作。
  3. 可以选择按插入顺序或访问顺序排序:在构造 LinkedHashMap 对象时,可以选择按照插入顺序或者访问顺序(最近访问的元素放在最后)来排序。
  4. 线程不安全:和 HashMap 一样,LinkedHashMap 也不是线程安全的。如果在多线程环境中使用,需要考虑同步措施。

默认情况下,LinkedHashMap 按照插入顺序进行排序。如果希望按照访问顺序进行排序,可以使用带有 accessOrder 参数的构造方法:

LinkedHashMap<K, V> linkedHashMap = new LinkedHashMap<>(initialCapacity, loadFactor, true);

在此构造方法中,accessOrder 为 true 表示按访问顺序排序,为 false 表示按插入顺序排序。

3.LinkedHashMap 双向链表?

image-20240126152502028

数据结构:数组+单向链表+双向链表

每一个节点都是双向链表的节点,维持插入顺序。head 指向第一个插入的节点,tail 指向最后一个节点。

数组+单向链表是 HashMap 的结构,用于记录数据。

双向链表保存的是插入顺序,顺序访问。

next 是用于维护数据位置的,before 和 after 是用于维护插入顺序的。

// Entry继承HashMap的Node
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);
  }
}
/**
 * The head (eldest) of the doubly linked list.
 */
// 旧数据放在head节点
transient LinkedHashMap.Entry<K,V> head;

/**
 * The tail (youngest) of the doubly linked list.
 */
// 新数据放在tail节点
transient LinkedHashMap.Entry<K,V> tail;

/**
 * The iteration ordering method for this linked hash map: <tt>true</tt>
 * for access-order, <tt>false</tt> for insertion-order.
 *
 * @serial
 */
// false-按插入顺序存储数据 true-按访问顺序存储数据
final boolean accessOrder;

4.按 put 的顺序进行遍历?

可以使用 LinkedHashMap,有 2 种功能,一个是插入顺序,一个是访问顺序

初始化时可以指定。相对于访问顺序,插入顺序使用的场景更多一些,所以默认是插入顺序进行编排。

5.LinkedHashMap2 种遍历顺序?

LinkedHashMap 有两种遍历顺序,插入顺序和访问顺序

插入方式:遍历时和插入时位置固定

访问方式:put 和 get 方法都会将当前元素移到双向链表的最后

是否使用访问顺序遍历,是通过**LinkedHashMap 的 accessOrder 参数控制的,true 为访问顺序遍历,false 为插入顺序遍历。**默认是 false,插入方式遍历。如果是 true,注意并发修改异常。因为 get 方法会修改 LinkedHashMap 的结构。

  • LinkedHashMap 的底层数据结构继承至 HashMap 的 Node,并且其内部存储了前驱和后继节点。

  • LinkedHashMap 通过 accessOrder 来控制元素的相关顺序,false-按插入顺序存储数据,true-按访问顺序存储数据,默认为 false。

//默认插入顺序
public class Data_01_LinkedHashMapTest_02 {
  public static void main(String[] args) {
    LinkedHashMap<String, Integer> map = new LinkedHashMap<>(2, 0.75f, false);
    map.put("1", 1);
    map.put("5", 5);
    map.put("6", 6);
    map.put("2", 2);
    map.put("3", 3);
    map.get("5");
    map.get("6");
    for (Integer s : map.values()) {
      System.out.println(s);
    }
  }
}
//输出结果
1
5
6
2
3
//访问顺序
public class Data_01_LinkedHashMapTest_01 {
  public static void main(String[] args) {
    LinkedHashMap<String, Integer> map = new LinkedHashMap<>(2, 0.75f, true);
    map.put("1", 1);
    map.put("5", 5);
    map.put("6", 6);
    map.put("2", 2);
    map.put("3", 3);
    map.get("5");
    map.get("6");
    for (Integer s : map.values()) {
      System.out.println(s);
    }
  }
}
//输出结果,会把put和get操作的元素放在最后
1
2
3
5
6

6.LinkedHashMap 访问顺序的原理?

public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
  super(initialCapacity, loadFactor);
  this.accessOrder = accessOrder;
}

关键点是 accessOrder 参数,默认为 false,插入方式,true 为访问方式。

当调用 get 方法时,会判断 accessOrder 的值,如果为 true,会执行 afterNodeAccess 方法,就是放到 node 的后面。

public V get(Object key) {
  Node<K,V> e;
  // 通过getNode方法取出节点,如果为null则直接返回null
  if ((e = getNode(hash(key), key)) == null)
    return null;
  // 如果accessOrder为true,则需要把节点移动到链表末尾
  if (accessOrder)
    afterNodeAccess(e);
  return e.value;
}

7.LinkedHashMap 的 put 方法的原理?

LinkedHashMap 没有 put 方法,使用的是 HashMap 的 put 方法,并且复写了 newNode 方法和 afterNodeAccess 方法。

新增的节点放到双向链表末尾

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;
}

将新增的节点添加至链表尾部

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;
    }
}

8.LinkedHashMap 的 get 方法原理?

会判断是否是访问顺序,如果是,放到双向链表末尾。

JDK1.8 的 HashMap 的 get 方法

  • 计算数据在桶中的位置 (tab.length- 1) & hash(key)
  • 通过 hash 值和 key 值判断待查找的数据是否在对应桶的首节点, 如果在,则返回对应节点 据;否则判断桶首节点的类型。如果节点 为红黑树,从红黑树中获取对应数据;如果节点为链表节点,则遍历 链表,从中获取对应数据
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;
}

9.用 LinkedHashMap 实现 LRU 算法?

主要考察 2 个点

  • accessOrder 实现 lru 的逻辑
  • removeEldestEntry 的复写

在插入之后,会调用 LinkedHashMap 的 afterNodeInsertion 方法,需要重写 removeEldestEntry 方法

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);
    }
}
class Scratch<K, V> extends LinkedHashMap<K, V> {
  private int capacity;
  public Scratch(int capacity) {
    super(16, 0.75f, true);
    this.capacity = capacity;
  }
  @Override
  protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
    return size() > capacity;
  }
}

10.LinkedHashMap 和 HashMap 区别?

  • LinkedHashMap 继承自 HashMap,是基于 HashMap 和双向链表实现的。并实现了 HashMap 中预留的钩子函数,因此不必重写 HashMap 的很多方法,设计非常巧妙。
  • HashMap 是无序的,LinkedHashMap 是有序的,分为插入顺序和访问顺序。如果是访问顺序,使用 put 和 get 时,都会把 entry 移动到双向链表的表尾。
  • LinkedHashMap 存取数据还是和 HashMap 一样,使用 entry[]数组的形式,双向链表只是为了保证顺序。
  • LinkedHashMap 也是线程不安全的。
  • LinkedHashMap 默认容量为 16,扩容因子默认为 0.75,非同步,允许[key,value]为 null。LinkedHashMap 底层数据结构为双向链表,可以看成是 LinkedList+HashMap。如果 accessOrder 为 false,则可以按插入元素的顺序遍历元素,如果 accessOrder 为 true,则可以按访问顺序遍历元素。
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。