Java 数据结构 -- 16.Java 8 数据结构 LinkedHashMap

前言

书接上文,上一篇中对 Map 接口的 最终实现类 HashMap 进行了介绍与分析,本篇将对 HashMap 的子类,实现了恒定顺序特性的 LinkedHashMap 进行介绍与分析。

这里就不再放图了,直接来看 LinkedHashMap 的源码

/**
 * Map 接口的 Hash table 和链接链表实现,伴随一个可预测的迭代操作顺序(恒等顺序)。这个实现与 HashMap 不
 * 同的地方在于维护了一个覆盖它的所有条目的双向链表。这个链接链表定义了迭代操作的顺序,通常是键s加入 map 的顺
 * 序(iteration-order)。注意迭代操作顺序不会手动重新加入 map 的键s的影响。(如果对于键 k,map m 在 
 * m.containsKey(k) 返回是 true 的时候马上调用 m.put(k, v) 那么 k 就是重新加入的。)
 *
 * 这个实现类省去了它的客户端从一个 {@link HashMap} (and {@link Hashtable}) 中不确定的,通常是混乱的顺
 * 序的打扰,而不会带来增加的有关 {@link TreeMap} 的性能消耗。他可以被用来产生一个与原始的,不用考虑原始 
 * map 的实现的,比如
 * <pre>
 *     void foo(Map m) {
 *         Map copy = new LinkedHashMap(m);
 *         ...
 *     }
 * </pre>
 * 的保持相同顺序的拷贝。
 *
 * 这个技术主要在一个以 map 为输入的模块,拷贝它,然后在稍后的时间返回一个顺序被拷贝对象定义的拷贝时有用。
 * (客户端通常对于它们提供的有相同顺序的东西心怀感激。)
 *
 * 一个特殊的 {@link #LinkedHashMap(int,float,boolean) constructor} 被提供来建立一个迭代操作顺序与
 * 最终访问顺序(access-order)相同的链接 hash map。这种 map 很适合建立 LRU 缓存。调用 {@code put}, 
 * {@code putIfAbsent}, {@code get}, {@code getOrDefault}, {@code compute}, {@code 
 * computeIfAbsent}, {@code computeIfPresent}, or {@code merge} 方法返回一个对应条目的访问(加入它
 * 在调用结束后存在)。{@code replace} 方法只在一个条目的值被替换时候返回访问。{@code putAll} 方法生为指
 * 定 map 中的每一个映射生成一个条目访问,以指定 map 的 entry set 迭代器提供的键值映射的顺序。没有其他方法
 * 生成条目访问。特别要指出的是,对于 collection 视图的操作不会影响提供支持的 map 的迭代操作。
 *
 * {@link #removeEldestEntry(Map.Entry)} 方法可以被重写来强制执行一个策略,在新的映射加入到 map 中时
 * 自动地移除过期的映射。
 *
 * 这个类提供所有可选的 Map 操作,并且允许 null 元素。就像 HashMap,它对于基础操作(add,contains 和 
 * remove)提供一个时间恒定的性能,假设 hash 功能很好的将元素分散到了各个桶中。性能看上去只比 HashMap 低一
 * 点,因为要加入维护链接链表的成本,伴随一个异常:对于一个 LinkedHashMap 的 collection 视图的迭代操作需
 * 要与 map 长度成比例的时间,不用考虑它的容量。涵盖一个 HashMap 的迭代操作看上去会更昂贵,需要与容量成比例
 * 的时间。
 * 
 * 一个链接 hash map 有两个要素会影响它的性能:
 * initial capacity(初始化容量)和 load factor(加载因子)。它们就像 HashMap 那样被精确地定义。注意,
 * 但是,选择过高的初始化容量值的惩罚对比 HashMap,这个类没有这么严重,因为对于这个类的迭代操作时间是不受容
 * 量影响的。
 *
 * 注意这个实现类不是线程安全的。如果多个线程同事访问一个 linked hash map,并且至少一个线程结构性地修改了 
 * map,它必须从外部实现线程安全。这通常是通过在一些对象中自然封装这个 map 来完成的。
 *
 * 如果这样的对象不存在,map 应该使用
 * {@link Collections#synchronizedMap Collections.synchronizedMap} 
 * 方法来“封装”。这最好在创建的时候做,来避免对于这个 map 意外的非线程安全的访问:<pre>
 *   Map m = Collections.synchronizedMap(new LinkedHashMap(...));</pre>
 *
 * 一个结构化的修改是任何添加或者删除一个或多个映射操作,在 access-ordered linked hash maps 的情况中,
 * 会影响迭代操作的顺序。在 insertion-ordered linked hash maps 中,仅仅改变 map 中已经存在键的值不是一
 * 个结构性改变。在 access-ordered linked hash maps 中,仅仅通过 get 查询 map 不是一个结构性改变。
 *
 * 所有这个类的 colleciton 视图返回的 collections 的 iterator 方法返回的迭代器都是 fail-fast 的:如果
 * 在迭代操作创建后的任何时间点这个 map 被结构性的更改了,以除了通过迭代器自己的 remove 方法之外的任何方
 * 式。迭代器将抛出一个  {@link ConcurrentModificationException}。因此,对于并发修改,迭代器失败的快速
 * 与干净,而不是在未来某个不确定的时间点冒险做任何不确定的行为。
 *
 * 注意一个迭代器的 fail-fast 行为不能提供像它所描述的那样的保证,一般来说,不可能对于不同步的并发操作做任何
 * 硬性的保证。基于最好性能的基础,fail-fast 迭代器抛出一个 ConcurrentModificationException。因此,编
 * 写一个依赖于这个异常来保证其正确性的程序是错误的:迭代器的 fail-fast 行为应该只被用于检查 bugs。
 *
 * 通过这个类的 collections 视图返回的 coillection 的 spliterator 方法返回的并行迭代器是延迟绑定,
 * fail-fast,和另外记录的{@link Spliterator#ORDERED}。
 *
 * LinkedHashMap 继承自 HashMap,实现了 Map 接口。
 *
 * 注意本类中全程看不到 table 与 tab,但是它还是 HashMap 散列表特性的,说明它直接使用了 HashMap 的底层结
 * 构,而没有改动,也就是数组结构(桶,可能是一个 Node,可能是一个 Node 链表,也可能是一颗红黑 Node 树),
 * 所谓的 Linked 链接的就是忽略了桶的种类,将所有的条目双向链接在一起而已。
 *
 * 和 HashMap 相同的部分已经在 HashMap 篇中做过介绍,这里只翻译与解释不同的部分。
 */
public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>
{
   

    /*
     * 实现类注意。这个类与它的前置版本在内部结构上有一点小差异。因为父类 HashMap 现在对于一些它的 nodes 
     * 使用 tress,LinkedHashMap.Entry 类现在被当做中间 node 类对待使它也可以转换成 tree 的形式。这个
     * 类的名字,LinkedHashMap.Entry,在它当前的上下文中在多个方面都费解的,但是不能被改变了。不然的话,
     * 即使他没有在当前包外部被传播,一些已知的依赖代码会发生错误(这一段太长了不是很好翻译,就简写了)。所
     * 以,我们保持名称来保存未修改的可编译性。
     * 
     * node 类s的改变也需要使用到两个类属性(head,tail)而不是一个指向 header node 的指针来维护双向链表 
     * before/after 链表。这个类先前也是在访问,插入和移除时使用使用不同风格的回调方法。

    /**
     * 面向普通 LinkedHashMap 条目s 的 HashMap.Node 子类,继承自 HashMap.Node。
     */
    static class Entry<K,V> extends HashMap.Node<K,V> {
   
        Entry<K,V> before, after; //新增了 before 与 after 属性,注意这里的 after 与 next 是两个属性,为了与 next(后节点区分开),一下对于 before 与 after 的称呼为前条目和后条目,head 与 tail 的称呼为头条目与尾条目
        Entry(int hash, K key, V value, Node<K,V> next) {
   
            super(hash, key, value, next);
        }
    }

    private static final long serialVersionUID = 3801124242820219131L;

    /**
     * 双向链接链表的头(最老旧的)条目
     */
    transient LinkedHashMap.Entry<K,V> head;

    /**
     * 双向链接链表的尾(最年轻的)条目
     */
    transient LinkedHashMap.Entry<K,V> tail;

    /**
     * 当前 linked hash map 的迭代操作排序方法:
     * 对于 access-order true
     * 对于 insertion-order false
     */
    final boolean accessOrder;

    // 内部工具

    // 链接在链表尾部
    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
   
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        if (last == null) //如果当前 LinkedHashMap 的尾条目为 null
            head = p; //p 赋值给头条目
        else {
    //如果当前 LinkedHashMap 的尾条目不为 null
            p.before = last; //链接参数条目与原来的尾条目
            last.after = p; //链接原来的尾巴条目与参数条目
        }
    }

    // 应用 src 的链接s到 dst
    private void transferLinks(LinkedHashMap.Entry<K,V> src,
                               LinkedHashMap.Entry<K,V> dst) {
   
        LinkedHashMap.Entry<K,V> b = dst.before = src.before; //获取 src 的前条目关系,更新给 dst
        LinkedHashMap.Entry<K,V> a = dst.after = src.after; //获取 src 的后条目关系,更新给 dst
        if (b == null)//如果前条目为 null
            head = dst; //dst 就是头条目
        else //如果前条目不为 null
            b.after = dst; //更新 b 的链接关系,将 dsc 链接到 src 条目前条目的后面
        if (a == null) //如果后条目为 null
            tail = dst; //dst 就是尾条目
        else //如果后条目不为 null
            a.before = dst; //更新 a 的链接关系,将 dst 连接到 src 条目后条目的前面
    }

    // 重写 HashMap 钩子方法
		//重新初始化方法
    void reinitialize() {
    
        super.reinitialize(); //调用 HashMap 的 reinitialize 方法
        head = tail = null; //头条目与尾条目置为 null
    }

		//获取新 Node 方法
    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); //构造新 LinkedHashMap 条目
        linkNodeLast(p); //调用 linkNodeLast 方法
        return p; //返回 p
    }

		//替换 Node 方法
    Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
   
        LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p; //强转参数 p
        LinkedHashMap.Entry<K,V> t =
            new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next); //构造新 LinkedHashMap 条目
        transferLinks(q, t); //调用 transferLinks 方法
        return t; //返回 t
    }

		//获取新的 TreeNode 方法
    TreeNode<K,V> newTreeNode(int
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值