HashMap初解

HashMap初解

前言

HashMap是Java集合框架中Map接口的一个实现类,它存储键值对,并允许使用键来检索对应的值。HashMap基于哈希表实现,提供了快速的插入、删除和查找操作。它允许空键和空值,并且不保证元素的顺序。在Java中,HashMap是一个非同步的类,因此在多线程环境下需要进行额外的同步处理。

HashMap在JAVA8中使用数据+链表+红黑树实现。

源码解析

构造方法

    //无参构造方法  this.loadFactor是哈希表的负载系数。hash表的扩容和当前负载系数*容量有关系
	//默认的负载因子为 static final float DEFAULT_LOAD_FACTOR = 0.75f;
	public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
	//带有初始容量的构造函数
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
	//带有初始容量和负载因子的构造函数
    public HashMap(int initialCapacity, float loadFactor) {
        //初始容量小于0 抛出异常
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        //MAXIMUM_CAPACITY = 1 << 30; 是hash数组的最大值
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
        //赋值负载因子                                       loadFactor);
        this.loadFactor = loadFactor;
        //hash数组的长度必须为2的n次方 
        //tableSizeFor(initialCapacity)计算initialCapacity值对应的最小的2的n次方的值   
        //this.threshold字段在Hash数组初始化之前表示要初始化的数组的长度,在数组初始化之后表示当前数组需要扩容的阈值                             
        this.threshold = tableSizeFor(initialCapacity);
    }
    //通过移位的方法计算                                           
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        // 将n的第一个1往后移动1位使n的前两位变为1
        n |= n >>> 1;
        // 将n的前两个1往后移动2位使n的前四位变为1
        n |= n >>> 2;
        // 将n的前四个1往后移动4位使n的前8位变为1
        n |= n >>> 4;
        // 将n的前8个1往后移动8位使n的前16位变为1
        n |= n >>> 8;
        // 将n的前16个1往后移动16位使n的前32位变为1
        n |= n >>> 16;
        //n的二进制数全为1 n+1则为 2的幂
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }                                           
	//带有Map集合的构造函数
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }
                                               
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        if (s > 0) {
            // 数组桶为空 初始化
            if (table == null) { // pre-size
                //确保放入这个map不需要进行扩容
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                //要放入的集合的容量(根据负载因子计算的不需要扩容的容量)大于已经初始化的容量 重新计算threshold的值
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            // 要放入的map的数量大于 需要扩容的数量 进行扩容
            else if (s > threshold)
                resize();
            //循环放入
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict);
            }
        }
    }                                           

总结

  • 包含四个构造方法,可以分别传入初始容量、负载因子、Map集合登
  • HashMap默认的负载因为为0.75
  • HashMap数组长度最大为2的30次方
  • HashMap数组长度必须为2的n次方
  • this.loadFactor表示负载因子
  • this.threshold字段在Hash数组初始化之前表示要初始化的数组的长度,在数组初始化之后表示当前数组需要扩容的阈值

put方法

    //放入键值对 返回旧值
	public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
	//计算key的hash值
    static final int hash(Object key) {
        int h;
        //取hash值,并且右移并进行异或运算 (^异或运算)
        //将Key的Hash值的高16位与低16位进行异或,在大多数情况下,将key的hash值都参与到计算HashMap的数组桶索引中
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

	//onlyIfAbsent表示是否是不存在才放入
	//evict为false表示是创建模式  和LinkedHashMap相关联
	final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 数组为空进行初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // (n - 1) & hash 将k的hash与(桶长度-1)按位相与 取hash的后几位 将索引落在桶长度区间内
        // 如果当前位置索引为null 直接存入
        if ((p = tab[i = (n - 1) & hash]) == null)
            //数组中保存的是Node节点 
            tab[i] = newNode(hash, key, value, null);
        //当前数组桶存在节点
        else {
            Node<K,V> e; K k;
            //判断当前节点的 k和要插入的key是否是同一个
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //树操作
            else if (p instanceof TreeNode)
                //向红黑树中插入节点
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //遍历查找是否包含相同节点 或者插到链表尾部
                for (int binCount = 0; ; ++binCount) {
                    //查到链表尾部
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        // 判单链表长度是否符合变为树的长度
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            // e !=null 表示在链表中存在当前k需要进行修改
            // e表示当前要更新的节点
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                //节点访问后钩子函数
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        //插入后 长度+1 判断是否到达需要扩容的长度 需要则进行扩容
        if (++size > threshold)
            resize();
        //节点插入后钩子函数
        afterNodeInsertion(evict);
        return null;
    }

总结

  • put流程
    1. 判断当前数组桶是否存在如果不存在则先进行扩容操作
    2. 根据Key的hash值和桶的长度计算出该key需要在数组同中的位置
    3. 如果数组桶中对应的位置没有节点则直接放入
    4. 对应的位置有值判断当前节点key和要放入的key是否相同 调用 == 和equals方法 相同则记录 当前节点为 e
    5. 不相同则判断当前节点是否为树节点 如果为树节点则代表当前为红黑树调用树节点的put方法 并将返回设为 e
    6. 不是树节点则代表当前节点为链表,遍历链表查找是否有相同的key如果有 则将节点设为e 如果没有则在链表尾部插入一个新节点,并判断当前链表长度是否需要转化为红黑树。
    7. 判断节点e是否为空,如果为空则表示是新增的节点,将siez+1并与threshold比较看是否需要扩容并返回null
    8. e不为空则表示是更新节点 根据onlyIfAbsent参数判断是否需要更新,并返回旧值
  • key的hash值是将key的hashcode的低16位是将key的高16位与低16位异或计算出来的
  • 数组同长度始终为2的n次方是方便计算取余结果,key落在hash桶中的索引
  • 红黑树中的节点为TreeNode
  • 数组链表使用尾插法将节点放入
  • 链表转化为红黑树的阈值为 TREEIFY_THRESHOLD = 8,当链表长度大于等于8则转化为红黑树

链表转化为红黑树

    
	final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        //数组桶长度是否小于最小可以树化的数组长度 如果小于 则先进行扩容 
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            //将该hash对应的节点以及链表上的节点 转为树节点 并构建一个双向链表
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

总结

  • 当一个链表的长度到达需要转化为红黑树的时候 会先判断当前数组桶的长度是否小于64 如果小于64则先进行扩容
  • 大于或等于64则需要进行树化

扩容

    //返回新的数组桶
	final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        //为0或者为原来数组的长度
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        // 原来的扩容阈值 为0或者为 oldcap对应的2的幂
        int oldThr = threshold;
        int newCap, newThr = 0;
        //进行扩容原来数组有长度 oldCap表示为原来数组桶长度 oldThr为原来的扩容阈值
        if (oldCap > 0) {
            //原来的容量大于等于最大容量 不进行操作
            if (oldCap >= MAXIMUM_CAPACITY) {
                //扩容阈值变为Integer.MAX_VALUE
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //新容量扩大为原原来容量的2倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                //扩容阈值也变为原来的两倍
                newThr = oldThr << 1; // double threshold
        }
        //原来数组没有长度 但初始化是传入了容量 传入初始容量调用tableSizeFor方法初始了threshold 对应有initialCapacity参数的两个构造方法
        else if (oldThr > 0) // initial capacity was placed in threshold
            //新容量等于tableSizeFor方法初始后的threshold
            newCap = oldThr;
        //原来数组没有长度 空的hashmap
        else {               // zero initial threshold signifies using defaults
            //默认的容量 16
            newCap = DEFAULT_INITIAL_CAPACITY;
            // threshold 负载因子*16(默认容量)
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        //
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        // threshold = cap * loadFactor  当cap >= MAXIMUM_CAPACITY threshold = Integer.MAX_VALUE
        threshold = newThr;
        //新建一个节点桶数组
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        //如果原来的不为空 需要进行数据迁移
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    //当前hashcode值对应的数只有一个,在数组中
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    //如果使树节点 则进行树节点操作
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        // 如果e 元素是链表类型,则需要遍历链表,将其一拆为二
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            //将节点的hash与原容量做按位与
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

总结

  • 扩容中新的数组桶长度与新的扩容阈值计算流程
    • oldCap有值表示原来的数组桶有值进行扩容
      1. 判断oldCap是否大于等于最大的数组长度如果等于则将threshold 扩容阈值设置为Integer.MAX_VALUE直接返回oldTab
      2. oldCap不大于最大的数组长度 则newCap = oldCap * 2 、如果newCap <MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY newThr = oldThr*2 否则newThr 还是0
    • 否则就是原数组桶没有值oldThr > 0表示在构造函数初始化了数组桶长度 newCap = oldThr newThr = 0
    • 原数组桶没有值并且构造函数也没有初始化数组长度 newCap = 16 、 newThr = (int)(0.75 * 16);
    • 如果newThr = 0则使用newCap与负载因子计算newThr
      1. 如果newCap小于数组最大值并且计算后的thr也小于数组最大值则为计算后的值
      2. 否则为Integer.MAX_VALUE
    • 计算结束后将threshold赋值为newThr
  • 以newCap为大小创建一个数组
  • 如果原数组不为空则将原数组的数据转移到新数组,遍历原数组桶的每个节点
    • 当前节点只有一个node(node的next == null)
      • 则使用key的hash值与(newCap-1)做与操作重新计算索引
    • 当前节点为树节点
      • 调用树节点的拆分,将一个红黑树拆成两个分别放入新数组的不同位置,与链表拆分相似,红黑树有一个判断是否要从红黑树变为链表的逻辑
    • 当前节点为链表节点,将链表拆成低位链表和高位链表(例如原数组长度为1000则key索引位置的只与hash值的后三位有关系,扩容后则与后四位有关系)
      • 将节点的hash与oldCap(二进制为100000)进行按位与,取e的hash值与oldCap对应的1位的值
      • 如果结果位0则将节点放入低位链表,否则放入高位链表
      • 低位链表放入newTab[j]处(hash值倒数第四位为0则索引位置还是j)、高链表放入newTab[j + oldCap]处(hash值倒数第四位为1)
  • 红黑树退化为链表的阈值为UNTREEIFY_THRESHOLD = 6

get方法

根据Key查找对应的value

    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
流程
  1. 如果数组为空或者长度为0,或者根据key的hash和数组桶的长度定位到数组桶对应的索引 节点 为null 则返回null
  2. 判断该节点的key与要查找的key是否相同,== 或 equals方法 如果相同则返回对应value
  3. 如果当前节点的next不为空则去红黑树或者链表中查找,找到后返回找不到返回null

remove方法

    public boolean remove(Object key, Object value) {
        return removeNode(hash(key), key, value, true, true) != null;
    }
	//matchValue – 如果为 true,则仅当值相等时删除(区分是按key删除 还是 k,v删除)
	//movable – 如果为 false,则在删除时不要移动其他节点(和树相关)
    final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        //数组桶不为空 并且 长度大于0 并且要删除的key对应的位置不为空
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            Node<K,V> node = null, e; K k; V v;
            //判断桶节点数据是否为当前要删除的数据
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            else if ((e = p.next) != null) {
                if (p instanceof TreeNode)
                    //如果是树节点则在树中获取要删除的key
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                    //链表中查找要删除的key
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        //保存p为要删除的节点的上一个
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            // node为要删除的节点
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                //树节点删除
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                // 桶索引上的元素
                else if (node == p)
                    tab[index] = node.next;
                //删除链表元素
                else
                    p.next = node.next;
                ++modCount;
                --size;
                //删除节点后的钩子函数
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

LinkedHashMap

LinkedHashMap继承了HashMap,实现了Map接口,LinkedHashMap与HashMap保存的数据结构相同,只不过LinkedHashMap节点是另一个节点有一个前后指针,在每次操作完之后都会维护这个节点的前后指针,形成一个链表,保证插入顺序

Hash中包含下面几个函数

    // 新建一个节点
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
        return new Node<>(hash, key, value, next);
    }

    // 替换一个节点
    Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
        return new Node<>(p.hash, p.key, p.value, next);
    }

    // 创建一个树节点
    TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
        return new TreeNode<>(hash, key, value, next);
    }

    // 替换树节点
    TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
        return new TreeNode<>(p.hash, p.key, p.value, next);
    }

    // 节点被访问后调用的方法
    void afterNodeAccess(Node<K,V> p) { }
	//节点被插入后调用的方法
    void afterNodeInsertion(boolean evict) { }
	//节点被移除后调用的方法
    void afterNodeRemoval(Node<K,V> p) { }

LinkedHashMap重写以上的几个方法来实现自己的逻辑

	//双向链表中的头节点
	transient LinkedHashMap.Entry<K,V> head;

    /**
     * 双向链表中的尾节点
     */
    transient LinkedHashMap.Entry<K,V> tail;

	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<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);
        //新节点默认插在最后
        linkNodeLast(p);
        return p;
    }
    // 替换节点
    Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
        LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
        LinkedHashMap.Entry<K,V> t =
            new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next);
        transferLinks(q, t);
        return t;
    }
    // 新建树节点
    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;
    }
    // 替换树节点
    TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
        LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
        TreeNode<K,V> t = new TreeNode<K,V>(q.hash, q.key, q.value, next);
        transferLinks(q, t);
        return t;
    }
    // 节点移除后 hashmap的钩子函数
    void afterNodeRemoval(Node<K,V> e) { // unlink
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.before = p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a == null)
            tail = b;
        else
            a.before = b;
    }
    // 插入新节点之后
    // evict 为FALSE表示创建模式
    void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        //removeEldestEntry删除最头部的节点  用于固定数目的缓存 当插入新节点后需要将最近未被访问的节点删除
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }
    //节点访问后
    void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        // 如果是按访问顺序 并且最后一个节点不是当前节点  将最近访问的这个节点放在尾部
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)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;
        }
    }
	//重写父类的方法 添加访问方法
    public V get(Object key) {
        Node<K,V> e;
        // 查找调用 父类HashMap的方法 时间复杂度为O(1)
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }

    /**
     * //重写父类的方法 添加访问方法
     */
    public V getOrDefault(Object key, V defaultValue) {
       Node<K,V> e;
       if ((e = getNode(hash(key), key)) == null)
           return defaultValue;
       if (accessOrder)
           afterNodeAccess(e);
       return e.value;
   }

总结

  1. LinkedHashMap中的节点为自己的内部类Entry,该类新增了Entry<K,V> before, after两个字段维护双向链表
  2. LinkedHashMap类中保存了双向链表的头节点和尾节点,用于维护双向链表
  3. LinkedHashMap重写了newNode方法每次新建节点会将该节点放到双向链表的尾部
  4. replacementNode、newTreeNode、replacementTreeNode的重写与3功能类似,都是为了维护双向链表
  5. afterNodeRemoval表示在节点删除后,解除该节点在双向链表中的关联
  6. 在LinkedHashMap中有一个 final boolean accessOrder; 属性可用于实现LRU缓存 。当accessOrder为true时每次访问都会将此次访问的节点放在双向链表的队尾
  7. 调用 put, putIfAbsent, get, getOrDefault, compute, computeIfAbsent, computeIfPresent, or merge方法将导致对相应条目的访问(假设它在调用完成后存在)。仅当替换值时,这些 replace方法才会导致对条目的访问。该 putAll方法为指定映射中的每个映射生成一个条目访问,其顺序是键值映射由指定映射的条目集迭代器提供。没有其他方法生成条目访问。具体而言,对集合视图的操作不会影响后备映射的迭代顺序。
  8. 在每次afterNodeInsertion方法调用 removeEldestEntry(first)方法来判断是否需要删除头节点 默认为false
  9. 当accessOrder设为true ,重写removeEldestEntry(first)方法可以实现自己逻辑的LRU缓存
  • 21
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值