ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

深入理解Java LinkedHashMap

2021-09-18 12:01:17  阅读:147  来源: 互联网

标签:map Java HashMap keys 深入 put null LinkedHashMap


本文我们深入Java Map接口的一个实现类LinkedHashMap的内部。它是HashMap的子类,继承了父类的核心代码。因此读者应该先了解HashMap的工作原理。

LinkedHashMap 与 HashMap

*LinkedHashMap *在大多数方面 与 HashMap 类似,但LinkedHashMap 是基于hash 表与链表结构用于增强hashMap。其底层除了有缺省为16大小的数组外,还维护了一个双向链表连结所有项。

为了维护元素顺序,LinkedHashMap 修改了 HashMap类的Map.Entry类,在其中增加了before 和 after 指针对象,源码如下:

    /**
     * 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);
        }
    }

我们看到Entry类简单增加两个指针用于连接链表,另外使用Entry类实现HashMap。

我们在看构造函数,默认定义了迭代顺序,缺省按照插入顺序:

    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

插入顺序(Insertion-Order)

下面通过实例来了解插入顺序:

@Test
public void givenLinkedHashMap_whenGetsOrderedKeyset_thenCorrect() {
    LinkedHashMap<Integer, String> map = new LinkedHashMap<>();
    map.put(1, null);
    map.put(2, null);
    map.put(3, null);
    map.put(4, null);
    map.put(5, null);

    Set<Integer> keys = map.keySet();
    Integer[] arr = keys.toArray(new Integer[0]);

    for (int i = 0; i < arr.length; i++) {
        assertEquals(new Integer(i + 1), arr[i]);
    }
}

上面代码插入5个实体,然后获取其键集合,最后迭代验证键顺序。LinkHashMap可以保证键的顺序按照插入的顺序,HashMap不能保证键的顺序。

整个特性在一些场景非常有用。如对API的调用需要按照客户端来的先后次序进行返回,那么LinkHashMap则可以轻松满足。

注:如果将键重新插入映射中,则插入顺序不受影响。

访问顺序(Access-Order)

前节我们看到构造函数包括三个参数,初始容量、载入因子以及排序策略,默认为插入顺序,还可以设为访问顺序,该机制保证迭代元素的顺序按照元素最后被访问的顺序,从最近最少访问到最近最多访问顺序:

@Test
public void givenLinkedHashMap_whenAccessOrderWorks_thenCorrect() {
    LinkedHashMap<Integer, String> map = new LinkedHashMap<>(16, .75f, true);
    map.put(1, null);
    map.put(2, null);
    map.put(3, null);
    map.put(4, null);
    map.put(5, null);

    Set<Integer> keys = map.keySet();
    assertEquals("[1, 2, 3, 4, 5]", keys.toString());
 
    map.get(4);
    assertEquals("[1, 2, 3, 5, 4]", keys.toString());
 
    map.get(1);
    assertEquals("[2, 3, 5, 4, 1]", keys.toString());
 
    map.get(3);
    assertEquals("[2, 5, 4, 1, 3]", keys.toString());
}

这种策略很容易实现Least Recently Used (LRU)缓存算法。对map特定键的访问操作,将导致改键在迭代时出现在最后。通过上面示例,显然迭代LinkHashMap不会影响顺序,仅仅显示的访问操作会影响。

LinkHashMap还提供了维护map条目为固定大小的机制,对已经条目数量为最大时,当插入新的元素则最老的元素会被删除。

我们可以重写removeEldestEntry方法,以强制该策略自动删除过时的条目。下面示例定义MyLinkedHashMap类,重写

removeEldestEntry方法:

public class MyLinkedHashMap<K, V> extends LinkedHashMap<K, V> {

    private static final int MAX_ENTRIES = 5;

    public MyLinkedHashMap(
      int initialCapacity, float loadFactor, boolean accessOrder) {
        super(initialCapacity, loadFactor, accessOrder);
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_ENTRIES;
    }

}

当条目超过最大值时,新条目被插入,代价是丢弃最旧的条目,即最近访问的条目优先于其他条目:

@Test
public void givenLinkedHashMap_whenRemovesEldestEntry_thenCorrect() {
    LinkedHashMap<Integer, String> map
      = new MyLinkedHashMap<>(16, .75f, true);
    map.put(1, null);
    map.put(2, null);
    map.put(3, null);
    map.put(4, null);
    map.put(5, null);
    Set<Integer> keys = map.keySet();
    assertEquals("[1, 2, 3, 4, 5]", keys.toString());
 
    map.put(6, null);
    assertEquals("[2, 3, 4, 5, 6]", keys.toString());
 
    map.put(7, null);
    assertEquals("[3, 4, 5, 6, 7]", keys.toString());
 
    map.put(8, null);
    assertEquals("[4, 5, 6, 7, 8]", keys.toString());
}

我们看到当增加新条目时,在键set开头的最老的条目丢失了。

其他方面

  • 并发

和HashMap一样,LinkHashMap 实现没有同步功能,所以如果多个线程同时访问时,需要在外部增加同步控制。可以使用这种方式创建:

Map m = Collections.synchronizedMap(new LinkedHashMap());

与HashMap不同的是什么操作会有结构性修改,顺序访问LInkHashMap,仅调用get 操作就会有结构性修改,另外put和remove也如此。

  • 性能说明

与HashMap一样,LInkHashMap执行基本的Map操作,如add , remove以及 contain 操作时间都是常量,只要hash函数有足够的维度,也支持存储null 键和值。

但LInkHashMap的常量时间要比HashMap要稍大,因为需要额外维护双向链表。迭代LInkHashMap集合与HashMap一样都是线性O(n),LInkHashMap性能要优于HashMap。这是因为LInkHashMap的元素数量与容量无关,HashMap的n 是容量和size的和,即O(size+capacity)。

载入因子和初始化容量与HashMap一样,但对于LinkHashMap的影响要小,因为迭代并不受容量影响。

总结

本文我们学习了LinkHashMap,它是Map接口最重要的实现。通过探索其特性、与HashMap的差异、内部工作原理及应用场景。

标签:map,Java,HashMap,keys,深入,put,null,LinkedHashMap
来源: https://blog.csdn.net/neweastsun/article/details/120364350

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有