zoukankan      html  css  js  c++  java
  • LinkedHashMap

    LinkedHashMap

    概述

    LinkedHashMap数据结构相比较于HashMap来说,添加了双向指针,分别指向前一个节点——before和后一个节点——after,从而将所有的节点已链表的形式串联一起来

    //HashMap里面的方法在LinkedHashMap进行了重写
    	void afterNodeAccess(Node<K,V> p) { }
        void afterNodeInsertion(boolean evict) { }
        void afterNodeRemoval(Node<K,V> p) { }
    

    Entry节点类

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

    继承HashMap的node节点增加了before和after两个指针。

    类属性

    // 用于指向双向链表的头部
        transient LinkedHashMap.Entry<K,V> head;
    //用于指向双向链表的尾部
        transient LinkedHashMap.Entry<K,V> tail;
     * 用来指定LinkedHashMap的迭代顺序,true则表示按照基于访问的顺序来排列,意思就是最近使用的entry,放在链表的最末尾false则表示按照插入顺序来
     */ 
        final boolean accessOrder;
    

    构造方法

    多了一个 accessOrder的参数,用来指定按照LRU排列方式还是顺序插入的排序方式。其他的HashMap一样

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

    put方法

    // LinkedHashMap 中重写
    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);
        // 将 Entry 接在双向链表的尾部
        linkNodeLast(p);
        return p;
    }
    // LinkedHashMap 中实现
    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        // last 为 null,表明链表还未建立
        if (last == null)
            head = p;
        else {
            // 将新节点 p 接在链表尾部
            p.before = last;
            last.after = p;
        }
    }
    
    //LinkedHashMap 中重写
    TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
      TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
      linkNodeLast(p);
      return p;
    }
    
    //插入后把最老的Entry删除,不过removeEldestEntry总是返回false,所以不会删除
    void afterNodeInsertion(boolean evict) {
      LinkedHashMap.Entry<K,V> first;
      if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
      }
    }
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
      return false;
    }
    

    通过重写newNode创建了Entry,在通过linkNodeLast 将Entry放入链表的尾部。实现了双向链表的建立

    get方法

      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;
        }
    
    //此函数执行的效果就是将最近使用的Node,放在链表的最末尾
    void afterNodeAccess(Node<K,V> e) {
      LinkedHashMap.Entry<K,V> last;
      //仅当按照LRU原则且e不在最末尾,才执行修改链表,将e移到链表最末尾的操作
      if (accessOrder && (last = tail) != e) {
        //将e赋值临时节点p, b是e的前一个节点, a是e的后一个节点
        LinkedHashMap.Entry<K,V> p =
          (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        //设置p的后一个节点为null,因为执行后p在链表末尾,after肯定为null
        p.after = null;
        //p前一个节点不存在,情况一
        if (b == null) // ①
          head = a;
        else
          b.after = a;
        if (a != null) 
          a.before = b;
        //p的后一个节点不存在,情况二
        else // ②
          last = b;
        //情况三
        if (last == null) // ③
          head = p;
        //正常情况,将p设置为尾节点的准备工作,p的前一个节点为原先的last,last的after为p
        else {
          p.before = last;
          last.after = p;
        }
        //将p设置为将p设置为尾节点
        tail = p;
        // 修改计数器+1
        ++modCount;
      }
    }
    
    • 正常情况下:查询的p在链表中间,那么将p设置到末尾后,它原先的前节点b和后节点a就变成了前后节点。

    • 情况一:p为头部,前一个节点b不存在,那么考虑到p要放到最后面,则设置p的后一个节点a为head

    • 情况二:p为尾部,后一个节点a不存在,那么考虑到统一操作,设置last为b

    • 情况三:p为链表里的第一个节点,head=p

    如果链表指针修改的话,其实在桶里面的位置还是没有改变的,只是前后指针改变了

    remove()

    //调用的还是HashMap的。只不过从写了afterNodeRemoval
    void afterNodeRemoval(Node<K,V> e) {
      //与afterNodeAccess一样,记录e的前后节点b,a
      LinkedHashMap.Entry<K,V> p =
        (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
      //p已删除,前后指针都设置为null,便于GC回收
      p.before = p.after = null;
      //与afterNodeAccess一样类似,一顿判断,然后b,a互为前后节点
      if (b == null)
        head = a;
      else
        b.after = a;
      if (a == null)
        tail = b;
      else
        a.before = b;
    }
    

    总结

    有序但是增加了时间和空间的开销。节点增加了前后指针。迭代顺序有两种,插入顺序和访问顺序,有着HashMap的所有功能。

  • 相关阅读:
    CentOS 8下安装hadoop-3.3.0
    c# log4net安装时在AssemblyInfo中提示找不到log4net解决办法
    C#自定义类型数组排序
    C#中的隐藏方法
    VBA 按列查找小工具类似lookUp函数
    欢迎来到网络对抗路 实验八 Web综合
    利用GMSSL中ZUC算法实现对序列密码的结构性检测(代码)
    第二十一章学习笔记
    欢迎来到网络对抗路 实验七 网络欺诈防范
    数据转换考试qwq
  • 原文地址:https://www.cnblogs.com/yangk1996/p/12653373.html
Copyright © 2011-2022 走看看