LinkedHashMap

LinkedHashMap

因为工作中用到了LinkedHashMap来保证服务器初始化数据的顺序,但是却不是很了解其特点和原理,今天特地翻一翻LinkedHashMap的源码(JDK8)来看一看它究竟有什么不同。

首先,来看一下JDK的对LinkedHashMap的介绍:

Hash table and linked list implementation of the Map interface,with predictable iteration order. This implementation differs from HashMap in that it maintains a doubly-linked list running through all of its entries. This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order). Note that insertion order is not affected if a key is re-inserted into the map.(A key k is reinserted into a map m if m.put(k, v) is invoked when m.containsKey(k) would return true immediately prior to the invocation.)
哈希表和链表实现了Map接口,具有可预测的迭代顺序(有序的)。这个实现与HashMap的不同之处在于,它维护一个贯穿其所有条目的双链接列表。这个链表定义了迭代顺序,通常是键插入到映射中的顺序(插入顺序)。注意,如果一个键重新插入到映射中,插入顺序不会受到影响。(map中put一个已经插入过的key值,containsKey将返回true)

This implementation spares its clients from the unspecified, generally chaotic ordering provided by {@link HashMap} (and {@link Hashtable}), without incurring the increased cost associated with {@link TreeMap}. It can be used to produce a copy of a map that has the same order as the original, regardless of the original map's implementation:
这个实现使其客户端免受{@link HashMap}(和{@link Hashtable})提供的未指定的、通常混乱的排序(无序),而不会增加与{@link TreeMap}相关的成本。无论原始Map怎么实现,它都可以用来产生一个与原始Map具有相同的顺序的Map副本:

 void foo(Map m) {
     Map copy = new LinkedHashMap(m);
     ...
 }

This technique is particularly useful if a module takes a map on input, copies it, and later returns results whose order is determined by that of the copy.(Clients generally appreciate having things returned in the same order they were presented.)
这个是一个特别有用的技术,输入一个map,复制它,返回结果,它的顺序取决于原复制对象。 (客户一般都倾向于返回顺序跟插入顺序一致)

A special {@link #LinkedHashMap(int,float,boolean) constructor} is provided to create a linked hash map whose order of iteration is the order in which its entries were last accessed, from least-recently accessed to most-recently (access-order). This kind of map is well-suited to building LRU caches. Invoking the {@code put}, {@code putIfAbsent}, {@code get}, {@code getOrDefault}, {@code compute}, {@code computeIfAbsent}, {@code computeIfPresent}, or {@code merge} methods results in an access to the corresponding entry (assuming it exists after the invocation completes). The {@code replace} methods only result in an access of the entry if the value is replaced. The {@code putAll} method generates one entry access for each mapping in the specified map, in the order that key-value mappings are provided by the specified map's entry set iterator. No other methods generate entry accesses. In particular, operations on collection-views do not affect the order of iteration of the backing map.
提供了一个特殊的{@link LinkedHashMap(int,float,boolean)构造函数}来创建LinkedHashMap,其迭代顺序是上次访问其条目的顺序,从最少访问到最远访问(存取顺序的)。这种map非常适合构建LRU缓存。调用{@code put},{@ code putIfAbsent},{@code get},{@ code getOrDefault},{@ code compute},{@ code computeIfAbsent},{@ code computeIfPresent}或{@code merge}方法会导致访问相应的条目(假设它存在于调用完成)。如果替换了值,{@code replace}方法只会导致访问该替换条目。 {@code putAll}方法为指定映射中的每个映射生成一个条目访问,按照指定映射的条目集迭代器提供键 - 值映射的顺序。 没有其他方法可以生成条目访问。特别是,对集合视图的操作 不 会影响后备映射的迭代顺序。

The {@link #removeEldestEntry(Map.Entry)} method may be overridden to impose a policy for removing stale mappings automatically when new mappings are added to the map.
可以重写{@link removeEldestEntry(Map.Entry)}方法,以强制在将新映射添加到map时自动删除过时映射的策略。

This class provides all of the optional Map operations, and permits null elements. Like HashMap, it provides constant-time performance for the basic operations (add, contains and remove), assuming the hash function disperses elements properly among the buckets. Performance is likely to be just slightly below that of HashMap, due to the added expense of maintaining the linked list, with one exception: Iteration over the collection-views of a LinkedHashMap requires time proportional to the size of the map, regardless of its capacity. Iteration over a HashMap is likely to be more expensive, requiring time proportional to its capacity.
这个类提供了所有可选的映射操作,并允许空元素。与HashMap一样,它为基本操作(添加, 包含和除去)提供恒定时间的性能,假设哈希函数将元素正确地分散到bucket中。性能可能仅略低于HashMap,因为其需要维护链表的额外费用,但有一个例外: 对LinkedHashMap的集合视图的迭代需要的时间与Map的大小成比例,而与它的容量无关。在HashMap上的迭代可能更昂贵,需要的时间与其容量成比例。

A linked hash map has two parameters that affect its performance: initial capacity and load factor. They are defined precisely as for HashMap. Note, however, that the penalty for choosing an excessively high value for initial capacity is less severe for this class than for HashMap, as iteration times for this class are unaffected by capacity.

一个链接哈希映射有两个参数影响其性能:初始容量负载因数。它们的定义与HashMap一样精确。但是请注意,为初始容量选择过高的值对该类的影响要小于对HashMap的影响,因为该类的迭代时间不受容量的影响。

Note that this implementation is not synchronized.If multiple threads access a linked hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally. This is typically accomplished by synchronizing on some object that naturally encapsulates the map.

注意,这个实现没有同步。如果多个线程并发地访问一个链接的哈希映射,并且至少有一个线程修改了映射的结构,那么它 必须在外部同步。这通常是通过在自然封装映射的某个对象上同步来实现的。

A structural modification is any operation that adds or deletes one or more mappings or, in the case of access-ordered linked hash maps, affects iteration order. In insertion-ordered linked hash maps, merely changing the value associated with a key that is already contained in the map is not a structural modification. In access-ordered linked hash maps, merely querying the map with get is a structural modification.)

结构修改是添加或删除一个或多个映射的操作,或者在访问顺序链接哈希映射的情况下影响迭代顺序的操作。在插入顺序链接哈希映射中,仅仅改变与映射中已经包含的键相关联的值不是结构修改。在访问顺序链接哈希映射中,仅仅使用get查询映射是一种结构修改。

The iterators returned by the iterator method of the collections returned by all of this class's collection view methods are fail-fast: if the map is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove method, the iterator will throw a {@link ConcurrentModificationException}. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.
这个类的所有集合视图方法返回的迭代器都是快速失败的: 如果Map是创建迭代器后修改了结构,任何时候以任何方式删除结点(除非通过迭代器的删除方法),迭代器都会抛出一个{@link ConcurrentModificationException}。因此,在面对并发修改时,迭代器会快速而干净地失败,而不是在未来某个不确定的时间冒险做出任意的、不确定的行为。(不是线程安全的)

Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.
注意,迭代器的快速失败行为不能得到保证,因为一般来说,在存在非同步并发修改时不可能做出任何硬性保证。快速失败迭代器尽最大努力抛出ConcurrentModificationException。因此,如果要编写一个依赖于这个异常来保证其正确性的程序,那就错了:迭代器的快速失败行为应该仅用于检测错误。

然后看一下LinkedHashMap类的代码:

public class LinkedHashMap
    extends HashMap
    implements Map

可以看出,LinkedHashMap是继承HashMap的,然后观察它里面的结构会发现它有以下几个变量:

/**
* HashMap.Node subclass for normal LinkedHashMap entries.
* 比HashMap多了before, after两个结点,表示该结点的前一个结点与后一个结点。
*/
static class Entry extends HashMap.Node {
    Entry before, after;
    Entry(int hash, K key, V value, Node next) {
        super(hash, key, value, next);
    }
}
/**
* The head (eldest) of the doubly linked list.
* 双链表的头(最大的),双向链表的表头
*/
transient LinkedHashMap.Entry head;

/**
* The tail (youngest) of the doubly linked list.
* 双链表的尾部(最年轻的),双向链表的表尾
*/
transient LinkedHashMap.Entry tail;

/**
* The iteration ordering method for this linked hash map: true
* for access-order, false for insertion-order.
* 该LinkedHashMap的迭代排序方法:true为访问顺序,为false为插入顺序。
* 默认为false,即插入顺序。
*
* @serial
*/
final boolean accessOrder;

然后来看LinkedHashMap的构造方法函数:

// 无参构造函数,默认调用HashMap的构造函数,将accessOrder默认为false
public LinkedHashMap() {
    super();
    accessOrder = false;
}
/**
指定初始容量的构造函数,accessOrder默认为false
*/
public LinkedHashMap(int initialCapacity) {
    super(initialCapacity);
    accessOrder = false;
}
/**
指定初始容量和加载因为的构造函数,将accessOrder默认为false
*/
public LinkedHashMap(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor);
    accessOrder = false;
}

/**
指定初始容量、加载因子和accessOrder的构造函数
*/
public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

/**
Constructs an insertion-ordered LinkedHashMap instance with the same mappings as the specified map.  The LinkedHashMap instance is created with a default load factor (0.75) and an initial capacity sufficient to hold the mappings in the specified map.
使用与指定Map相同的Map构造一个插入有序的LinkedHashMap实例。创建LinkedHashMap实例时使用的是默认加载因子(0.75)和足以容纳指定映射中的映射的初始容量。
*/
public LinkedHashMap(Map m) {
    super();
    accessOrder = false;
    putMapEntries(m, false);
}


接下来,直接找到LinkedHashMap的get()方法:

public V get(Object key) {
    Node e;
    // 调用父类HashMap的getNode()方法得到key对应的value
    if ((e = getNode(hash(key), key)) == null)
        return null;
    // 判断LinkedHashMap的排序方式,true为访问顺序,为false为插入顺序。
    if (accessOrder)
        // 按访问顺序排序
        afterNodeAccess(e);
    // 按插入顺序排序
    return e.value;
}

而访问顺序又是什么呢,我们继续看afterNodeAccess()的实现:

// move node to last, 将结点 e 移到LinkedHashMap的最后
void afterNodeAccess(Node e) {
    LinkedHashMap.Entry last;
    if (accessOrder && (last = tail) != e) {
        // LinkedHashMap.Entry p = (LinkedHashMap.Entry)e;
        // LinkedHashMap.Entry b = p.before;
        // LinkedHashMap.Entry a = p.after;
        LinkedHashMap.Entry p =
            (LinkedHashMap.Entry)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;
    }
}

可以看到afterNodeAccess()就是将新插入的结点放在LinkedHashMap的最后,也就是说accessOrder为true时(访问顺序排序),会按照这样来排序:最近被访问的结点会被移动到LinkedHashMap的最后一个结点。

讲到这里发现还是没有看到LinkedHashMap在调用put()的时候怎么实现有序的,因为它并没有重写put()方法,但是通过上面观察LinkedHashMap的变量时发现它的Entry是继承HashMap.Node的,而在HashMap的put()方法中可以找到一个重要的方法:

// Create a regular (non-tree) node 创建一个常规的结点
Node newNode(int hash, K key, V value, Node next) {
    return new Node<>(hash, key, value, next);
}

而在LinkedHashMap中也有这个方法,证明LinkedHashMap重写了HashMap的newNode()方法:

Node newNode(int hash, K key, V value, Node e) {
    LinkedHashMap.Entry p =
        new LinkedHashMap.Entry(hash, key, value, e);
    linkNodeLast(p);
    return p;
}

因此,可以得知LinkedHashMap是利用它重写的Entry和newNode()方法来保证有序的,而其中重点保证有序的排序的方法就是linkNodeLast()

// link at the end of list 将结点链接在LinkedHashMap的末尾
private void linkNodeLast(LinkedHashMap.Entry p) {
    LinkedHashMap.Entry last = tail;
    tail = p;
    if (last == null)
        head = p;
    else {
        // p的前一个结点设置为last,last的下一个结点设置为p 
        p.before = last;
        last.after = p;
    }
}

总结:

  1. LinkedHashMap是有序的,维护了一个双向链表来保证迭代的顺序,通常是插入顺序。
  2. LinkedHashMap可以通过重写removeEldestEntry(Map.Entry)方法来规定新结点插入到Map时自动删除过时结点的策略,也就是说可以通过该方法实现LRU算法来插入新结点。
  3. LinkedHashMap与HashMap一样允许空元素。
  4. 一般情况下,LinkedHashMap的性能可能低于HashMap,因为LinkedHashMap需要额外维护一个双向链表,但是在对LinkedHashMap进行迭代所需要的时间与LinkedHashMap的大小成正比而与LinkedHashMap的容量无关时,LinkedHashMap的性能可能会优于HashMap,HashMap进行迭代时,需要的时间与其容量成正比。
  5. LinkedHashMap并没有实现同步,所以是非线程安全的,如果多个线程并发访问一个LinkedHashMap时,且至少有一个线程修改了其结构,那么必须对LinkedHashMap进行同步操作。
  6. LinkedHashMap实现有序的关键在于它重写了HashMap的Entry结构(增加了header、tail结点,以及其他结点的before、after的转换)、newNode()方法以及重要的linkNodeLast()方法,使得在调用LinkedHashMap的put()方法时,默认会按照插入顺序进行结点的添加。

你可能感兴趣的:(LinkedHashMap)