14 java.util.LinkedHashMap

来源:互联网 发布:mastercam编程步骤 编辑:程序博客网 时间:2024/05/02 21:31

LinkedHashMap

                                                            2015.01.28                                                            By 970655147

备注 : “[” 和”]”之间的内容是 我添加的


Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。注意,如果在映射中重新插入 键,则插入顺序不受影响。(如果在调用 m.put(k, v) 前 m.containsKey(k) 返回了 true,则调用时会将键 k 重新插入到映射 m 中。)

此实现可以让客户避免未指定的、由 HashMap(及 Hashtable)所提供的通常为杂乱无章的排序工作,同时无需增加与 TreeMap 相关的成本。使用它可以生成一个与原来顺序相同的映射副本,而与原映射的实现无关:

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

如果模块通过输入得到一个映射,复制这个映射,然后返回由此副本确定其顺序的结果,这种情况下这项技术特别有用。(客户通常期望返回的内容与其出现的顺序相同。)
提供特殊的构造方法[可配置accessOrder属性的构造方法]来创建链接哈希映射,该哈希映射的迭代顺序就是最后访问其条目的顺序,从近期访问最少到近期访问最多的顺序(访问顺序)。这种映射很适合构建 LRU 缓存。调用 put 或 get 方法将会访问相应的条目(假定调用完成后它还存在)。putAll 方法以指定映射的条目集迭代器提供的键-值映射关系的顺序,为指定映射的每个映射关系生成一个条目访问。任何其他方法均不生成条目访问。[这里 我的理解 : 所谓的指定映射是指原集合中存在的映射]特别是,collection 视图上的操作不 影响底层映射的迭代顺序。

可以重写 removeEldestEntry(Map.Entry) 方法来实施策略,以便在将新映射关系添加到映射时自动移除旧的映射关系。

此类提供所有可选的 Map 操作,并且允许 null 元素。与 HashMap 一样,它可以为基本操作(add、contains 和 remove)提供稳定的性能,假定哈希函数将元素正确分布到桶中。由于增加了维护链接列表的开支,其性能很可能比 HashMap 稍逊一筹,不过这一点例外:LinkedHashMap 的 collection 视图迭代所需时间与映射的大小 成比例。HashMap 迭代时间很可能开支较大,因为它所需要的时间与其容量 成比例。[最后一点, 详见HashMap, 以及此处的迭代方式]

链接的哈希映射具有两个影响其性能的参数:初始容量和加载因子。它们的定义与 HashMap 极其相似。要注意,为初始容量选择非常高的值对此类的影响比对 HashMap 要小,因为此类的迭代时间不受容量的影响。

注意,此实现不是同步的。如果多个线程同时访问链接的哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射的意外的非同步访问:

Map m = Collections.synchronizedMap(new LinkedHashMap(...));**结构修改是指添加或删除一个或多个映射关系,或者在按访问顺序链接的哈希映射中影响迭代顺序的任何操作。在按插入顺序链接的哈希映射中,仅更改与映射中已包含键关联的值不是结构修改。在按访问顺序链接的哈希映射中,仅利用 get 查询映射不是结构修改。[这里 详见Entry.recordAccess])**

Collection(由此类的所有 collection 视图方法所返回)的 iterator 方法返回的迭代器都是快速失败 的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器自身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来不确定的时间任意发生不确定行为的风险。

注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。

此类是 Java Collections Framework 的成员。


现在 我们来思考一下, 假如给你一个 :
public class MyLinkedHashMap extends HashMap
那么 你应该怎么来实现保存插入顺序呢 ?
我说一下 我的思路 :
1. 使用一个数据结构保存插入顺序 到对应的条目的映射
2. 从每一个结点下手, 每一个结点保存一些额外的信息[保存插入顺序的下一个结点的引用, 用于迭代, 相当于多维护了一个链表]

对于思路一 : 其实从效率上来说就像一个ArrayList, 当在插入顺序中间加上一个 或者删去一个结点, 都需要将该索引以及之后的数据向后 或者向前移动, 所以 对于上面的情况来说, 效率是比较地低下的, 不过好在能够随机存取数据

对于思路二 : 其实和思路一相比, 就像ArrayList 和LinkedList的区别, 优势在于在插入顺序中间插入 或者删除一个数据效率比较高, 不过不能够随机存取数据

jdk中LinkedHashMap的实现是继承自HashMap 并在结点中加入一个before, after 来保存当前结点的前一个结点和下一个结点的引用, 并且在添加数据, 删除数据的时候额外的初更新这两个数据, 来维护链表的性质
还有一个优化就是迭代数据的时候[迭代, 查找是否存在指定的Value], 使用插入顺序来迭代, 这样的话, 迭代的效率就只与集合中元素的个数有关, 而与集合的capacity没有关系

start ->

声明

/** * <p>Hash table and linked list implementation of the <tt>Map</tt> interface, * with predictable iteration order.  This implementation differs from * <tt>HashMap</tt> 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 * (<i>insertion-order</i>).  Note that insertion order is not affected * if a key is <i>re-inserted</i> into the map.  (A key <tt>k</tt> is * reinserted into a map <tt>m</tt> if <tt>m.put(k, v)</tt> is invoked when * <tt>m.containsKey(k)</tt> would return <tt>true</tt> immediately prior to * the invocation.) * * <p>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: * <pre> *     void foo(Map m) { *         Map copy = new LinkedHashMap(m); *         ... *     } * </pre> * 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.) * * <p>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 (<i>access-order</i>).  This kind of map is well-suited to * building LRU caches.  Invoking the <tt>put</tt> or <tt>get</tt> method * results in an access to the corresponding entry (assuming it exists after * the invocation completes).  The <tt>putAll</tt> 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.  <i>No * other methods generate entry accesses.</i> In particular, operations on * collection-views do <i>not</i> affect the order of iteration of the backing * map. * * <p>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. * * <p>This class provides all of the optional <tt>Map</tt> operations, and * permits null elements.  Like <tt>HashMap</tt>, it provides constant-time * performance for the basic operations (<tt>add</tt>, <tt>contains</tt> and * <tt>remove</tt>), assuming the hash function disperses elements * properly among the buckets.  Performance is likely to be just slightly * below that of <tt>HashMap</tt>, due to the added expense of maintaining the * linked list, with one exception: Iteration over the collection-views * of a <tt>LinkedHashMap</tt> requires time proportional to the <i>size</i> * of the map, regardless of its capacity.  Iteration over a <tt>HashMap</tt> * is likely to be more expensive, requiring time proportional to its * <i>capacity</i>. * * <p>A linked hash map has two parameters that affect its performance: * <i>initial capacity</i> and <i>load factor</i>.  They are defined precisely * as for <tt>HashMap</tt>.  Note, however, that the penalty for choosing an * excessively high value for initial capacity is less severe for this class * than for <tt>HashMap</tt>, as iteration times for this class are unaffected * by capacity. * * <p><strong>Note that this implementation is not synchronized.</strong> * If multiple threads access a linked hash map concurrently, and at least * one of the threads modifies the map structurally, it <em>must</em> be * synchronized externally.  This is typically accomplished by * synchronizing on some object that naturally encapsulates the map. * * If no such object exists, the map should be "wrapped" using the * {@link Collections#synchronizedMap Collections.synchronizedMap} * method.  This is best done at creation time, to prevent accidental * unsynchronized access to the map:<pre> *   Map m = Collections.synchronizedMap(new LinkedHashMap(...));</pre> * * 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.  <strong>In access-ordered linked hash maps, * merely querying the map with <tt>get</tt> is a structural * modification.</strong>) * * <p>The iterators returned by the <tt>iterator</tt> method of the collections * returned by all of this class's collection view methods are * <em>fail-fast</em>: if the map is structurally modified at any time after * the iterator is created, in any way except through the iterator's own * <tt>remove</tt> 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. * * <p>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 <tt>ConcurrentModificationException</tt> on a best-effort basis. * Therefore, it would be wrong to write a program that depended on this * exception for its correctness:   <i>the fail-fast behavior of iterators * should be used only to detect bugs.</i> * * <p>This class is a member of the * <a href="{@docRoot}/../technotes/guides/collections/index.html"> * Java Collections Framework</a>. * * @param <K> the type of keys maintained by this map * @param <V> the type of mapped values * * @author  Josh Bloch * @see     Object#hashCode() * @see     Collection * @see     Map * @see     HashMap * @see     TreeMap * @see     Hashtable * @since   1.4 */public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>

这里 我们只说明一些相比于HashMap此类的不同, 比如 增加了那些属性, 重写了什么方法, 增加了什么方法 等等

LinkedHashMap. 属性

// 序列化id// 头结点, 用于遍历整个集合, 以此结点为终点// 迭代的顺序 如果为true则以访问的顺序[get 或者set访问之后会将该元素”移动”到最后],false则以插入元素的顺序    private static final long serialVersionUID = 3801124242820219131L;    private transient Entry<K,V> header;    private final boolean accessOrder;

LinkedHashMap. LinkedHashMap()

public LinkedHashMap(int initialCapacity, float loadFactor) {    super(initialCapacity, loadFactor);    accessOrder = false;}public LinkedHashMap(int initialCapacity) {    super(initialCapacity);    accessOrder = false;}public LinkedHashMap() {    super();    accessOrder = false;}public LinkedHashMap(Map<? extends K, ? extends V> m) {    super(m);    accessOrder = false;}public LinkedHashMap(int initialCapacity,                     float loadFactor,                     boolean accessOrder) {    super(initialCapacity, loadFactor);    this.accessOrder = accessOrder;}

LinkedHashMap. transfer(HashMap.Entry[] newTable, boolean rehash)

    @Override    void transfer(HashMap.Entry[] newTable, boolean rehash) {        int newCapacity = newTable.length;        // 重写自HashMap  将        // 遍历当前LinkedHashMap            // 如果要重新计算hashCode  则重新计算            // 根据index 将e添加到指定的bucket中        // 这里与HashMap的不同在于, 遍历所有的元素的方式不同, HashMap是使用遍历每一个桶的链表, 而这里是从header.after开始遍历        for (Entry<K,V> e = header.after; e != header; e = e.after) {            if (rehash)                e.hash = (e.key == null) ? 0 : hash(e.key);            int index = indexFor(e.hash, newCapacity);            e.next = newTable[index];            newTable[index] = e;        }    }

LinkedHashMap. containsValue(Object value)

public boolean containsValue(Object value) {    // 重写该方法 使用更快的迭代方式 [因为 HashMap的桶的个数对于元素的遍历存在影响, 而这里直接使用header遍历, 就与桶的个数无关了, 效率比HashMap的遍历高一些]            // 对于null元素  “==” 判定            // 否则  equals 判定        // Overridden to take advantage of faster iterator        if (value==null) {            for (Entry e = header.after; e != header; e = e.after)                if (e.value==null)                    return true;        } else {            for (Entry e = header.after; e != header; e = e.after)                if (value.equals(e.value))                    return true;        }        return false;    }

这里写图片描述

LinkedHashMap. clear()

public void clear() {    // super.clear() + 更新header的after, before引用        super.clear();        header.before = header.after = header;    }

LinkedHashMap. get(Object key)

public V get(Object key) {    // 使用从HashMap中继承的getEntry方法 找到key对应的entry        // 如果没找到        // 如果accessOrder为true 则将该元素”移动”到该LinkedHashMap的最后面        Entry<K,V> e = (Entry<K,V>)getEntry(key);        if (e == null)            return null;        e.recordAccess(this);        return e.value;    }HashMap. getEntry(Object key)    final Entry<K,V> getEntry(Object key) {        if (size == 0) {            return null;        }        int hash = (key == null) ? 0 : hash(key);        for (Entry<K,V> e = table[indexFor(hash, table.length)];             e != null;             e = e.next) {            Object k;            if (e.hash == hash &&                ((k = e.key) == key || (key != null && key.equals(k))))                return e;        }        return null;    }LinkedHashMap$Entry. recordAccess(HashMap<K,V> m)        void recordAccess(HashMap<K,V> m) {            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;            // 如果accessOrder为true 则将该元素”移动”到最后[最后遍历]            if (lm.accessOrder) {                lm.modCount++;                remove();                addBefore(lm.header);            }        }LinkedHashMap$Entry.remove()        // “移除”该元素[更改after,before等元素 由recordRemoval调用]        private void remove() {            before.after = after;            after.before = before;        }LinkedHashMap$Entry. addBefore(Entry<K,V> existingEntry)        private void addBefore(Entry<K,V> existingEntry) {            // 本entry的after指向existingEntry  before指向existingEntry的前一个元素            // existingEntry的前一个元素.after指向当前元素            // existingEntry.before指向当前元素            after  = existingEntry;            before = existingEntry.before;            before.after = this;            after.before = this;        }

Entry. addBefore
这里写图片描述

LinkedHashMap.get(Object key)
这里写图片描述

LinkedHashMap. addEntry(int hash, K key, V value, int bucketIndex)

This override alters behavior of superclass put method. It causes newly     * allocated entry to get inserted at the end of the linked list and     * removes the eldest entry if appropriate.    void addEntry(int hash, K key, V value, int bucketIndex) {        super.addEntry(hash, key, value, bucketIndex);        // Remove eldest entry if instructed        Entry<K,V> eldest = header.after;        // 没看懂这个是干什么的.. // 是否删除最久未使用的元素[第一个元素][LRU机制] 应该在accessOrder为true的时候有一些作用吧..        if (removeEldestEntry(eldest)) {            removeEntryForKey(eldest.key);        }    }LinkedHashMap. removeEldestEntry(Map.Entry<K,V> eldest)    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {        return false;    }LinkedHashMap. createEntry(int hash, K key, V value, int bucketIndex)void createEntry(int hash, K key, V value, int bucketIndex) {    // 新建一个entry 其next为table[bucketIndex]// 将其添加到header之前  成为输入顺序的最后一个元素        HashMap.Entry<K,V> old = table[bucketIndex];        Entry<K,V> e = new Entry<>(hash, key, value, old);        table[bucketIndex] = e;        e.addBefore(header);        size++;    }

LinkedHashMap. put(K key, V value) :
这里写图片描述

LinkedHashMap. putAll(Map< ? extends K,? extends V> m)
这里写图片描述

HashMap. remove(Object key)

public V remove(Object key) {    Entry<K,V> e = removeEntryForKey(key);    return (e == null ? null : e.value);}HashMap. removeEntryForKey(Object key)final Entry<K,V> removeEntryForKey(Object key) {    if (size == 0) {        return null;    }    int hash = (key == null) ? 0 : hash(key);    int i = indexFor(hash, table.length);    Entry<K,V> prev = table[i];    Entry<K,V> e = prev;    while (e != null) {        Entry<K,V> next = e.next;        Object k;        if (e.hash == hash &&            ((k = e.key) == key || (key != null && key.equals(k)))) {            modCount++;            size--;            if (prev == e)                table[i] = next;            else                prev.next = next;        // 注意这里, 在LinkedHashMap中, 还需要维护输入顺序的链表, 所以Entry.recordRemoval, 需要做些什么..            e.recordRemoval(this);            return e;        }        prev = e;        e = next;    }    return e;}Entry. recordRemoval(HashMap<K,V> m)        void recordRemoval(HashMap<K,V> m) {            remove();        }Entry. remove()        // 在输入顺序链表中移除当前entry         private void remove() {            before.after = after;            after.before = before;        }

这里写图片描述

Entry[InnerClass]

private static class Entry<K,V> extends HashMap.Entry<K,V> {    // 继承自HashMap.Entry  添加两个引用用于保存插入元素的顺序[通过这两者遍历]    // 并且重写了更新元素之后的回调, 删除元素之后的回调        // These fields comprise the doubly linked list used for iteration.        Entry<K,V> before, after;        Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {            super(hash, key, value, next);        }        // 在输入顺序链表中移除当前entry 由recordRemoval[HashMap.remove(obj)], recordAccess调用         private void remove() {            before.after = after;            after.before = before;        }    // 在existingEntry前 添加本entry        private void addBefore(Entry<K,V> existingEntry) {            after  = existingEntry;            before = existingEntry.before;            before.after = this;            after.before = this;        }        // 在Map.get/set 方法中被调用        void recordAccess(HashMap<K,V> m) {            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;            if (lm.accessOrder) {                lm.modCount++;                remove();                addBefore(lm.header);            }        }        // 在Map. removeMapping/ removeEntryForKey (Object o)中被调用        void recordRemoval(HashMap<K,V> m) {            remove();        }    }

LinkedHashIterator[InnerClass]

private abstract class LinkedHashIterator<T> implements Iterator<T> {    // 初始的的nextEntry为header.after, 然后沿着这条输入顺序链表走下去        Entry<K,V> nextEntry    = header.after;        Entry<K,V> lastReturned = null;        /**         * The modCount value that the iterator believes that the backing         * List should have.  If this expectation is violated, the iterator         * has detected concurrent modification.         */        int expectedModCount = modCount;    // 判断是否存在下一个元素的依据就是 nextEntry是否为header 因为after和before将所有的元素组成了一个双向链表        public boolean hasNext() {            return nextEntry != header;        }        // 移除本entry            // 调用的是HashMap.remove(key)            // 其中调用了e.recordRemoval(this); 清除该entry相应的after,before关系        public void remove() {            if (lastReturned == null)                throw new IllegalStateException();            if (modCount != expectedModCount)                throw new ConcurrentModificationException();            LinkedHashMap.this.remove(lastReturned.key);            lastReturned = null;            expectedModCount = modCount;        }        // 并发修改检查        // 保留一个nextEntry的引用用于返回 然后直接将nextEntry 赋值为nextEntry.after        Entry<K,V> nextEntry() {            if (modCount != expectedModCount)                throw new ConcurrentModificationException();            if (nextEntry == header)                throw new NoSuchElementException();            Entry<K,V> e = lastReturned = nextEntry;            nextEntry = e.after;            return e;        }    }

这里写图片描述

EntryIterator[InnerClass]

    private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {        public Map.Entry<K,V> next() { return nextEntry(); }    }

KeyIterator[InnerClass]

    private class KeyIterator extends LinkedHashIterator<K> {        public K next() { return nextEntry().getKey(); }    }

ValueIterator[InnerClass]

    private class ValueIterator extends LinkedHashIterator<V> {        public V next() { return nextEntry().value; }    }

LinkedHashMap. new Entry/ Key/ Value Iterator()

    // 重写了下面的三个方法, 使其应用LinkedHashMap的相应的迭代器    // 用于HashMap中的方法多态调用 keyset,valueSet,entrySet方法// These Overrides alter the behavior of superclass view iterator() methods    Iterator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); }    Iterator<K> newKeyIterator()   { return new KeyIterator();   }    Iterator<V> newValueIterator() { return new ValueIterator(); }

-> end

LinkedHashMap在拥有HashMap的查找性能的同时, 还维护了顺序, 难点 主要在于区分Entry结点的三部分数据, 数据域, 链表结点的next域, 维护顺序的before/ after域
以及在添加元素, 或者删除元素的时候 : next指针域维护链表的性质, before/ after指针域维护顺序的链表的性质
还有一个是关于迭代数据的优化, 使用维护的顺序链表进行迭代

java.util.LinkedHashSetLinkedHashMap : 前者继承自HashSet 并且基于后者的 [因为初始化的时候 调用了超类的构造方法[该构造方式是包权限的, 外部不能调用], 创建了一个LinkedHashMap的实例], LinkedHashSet中维护了一个LinkedHashMap的实例, 该HashMap的每一个Entry< key, value > 中value均为 初始化HashSet.class 的时候创建的一个PRESENT[new Object()]
使用该LinkedHashMap的所有的key来存储数据 [利用了LinkedHashMap的key唯一性保证了LinkedHashSet中元素的唯一性]

0 0
原创粉丝点击