LinkedHashMap /怎么保证顺序性

摘要:而假设是实行下面这段代码,将accessOrder改为false LinkedHashMap map = new LinkedHashMap(16, 0.75f, false); map.put("1月", 20); //此时就会调用到linkNodeLast()方法,也会调用afterNodeAccess()方法,但会被阻遏在 //if (accessOrder && (last = tail) != e) 之外 map.put("2月", 30); map.put("3月", 65); map.put("4月", 43); //这时不会调用linkNodeLast(),会调用afterNodeAccess()方法将key为“1月”的元素放到毕竟 map.put("1月", 35); map.get("2月"); //调用打印方法 for (Map.Entry entry : map.entrySet()){ System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue()); }

一. 前语           

 A8站源码交易平台

先看一个比方,我们想在页面展示一周内的消费改动情况,用echarts面积图进行展示。如下:

1.jpg

我们在后台将数据结构结束
HashMap map = new HashMap<>();
map.put("星期一", 40);
map.put("星期二", 43);
map.put("星期三", 35);
map.put("周四", 55);
map.put("星期五", 45);
map.put("星期六", 35);
map.put("周日", 30);


但是页面上一展示,发现并非如此,我们打印出来看,发现次第并非我们所想,先put进去的先get出来
for (Map.Entry

 entry : map.entrySet()){
    System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue());
}
/**
 * 作用如下:
 * key: 星期二, value: 40
 * key: 星期六, value: 35
 * key: 星期三, value: 50
 * key: 周四, value: 55
 * key: 星期五, value: 45
 * key: 周日, value: 65
 * key: 星期一, value: 30
 */


那么怎样保证预期展示作用如我们所想呢,这个时分就需要用到LinkedHashMap实体。

A8站源码交易平台

二. 初识LinkedHashMap
首要我们把上述代码用LinkedHashMap进行重构
LinkedHashMap map = new LinkedHashMap<>();
map.put("星期一", 40);
map.put("星期二", 43);
map.put("星期三", 35);
map.put("周四", 55);
map.put("星期五", 45);
map.put("星期六", 35);
map.put("周日", 30);
for (Map.Entry entry : map.entrySet()){
    System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue());
}


这个时分,作用正如我们所预期
key: 星期一, value: 40
key: 星期二, value: 43
key: 星期三, value: 35
key: 周四, value: 55
key: 星期五, value: 45
key: 星期六, value: 35


key: 周日, value: 30


LinkedHashMap继承了HashMap类,是HashMap的子类,LinkedHashMap的大多数方法的结束直接运用了父类HashMap的方法,关于HashMap在前面的章节现已讲过了,《HashMap原理(一) 概念和底层架构》,《HashMap原理(二) 扩容机制及存取原理》。
LinkedHashMap可以说是HashMap和LinkedList的调团体,既运用了HashMap的数据结构,又借用了LinkedList双向链表的结构(关于LinkedList可参看Java集结 LinkedList的原理及运用),那么这样的结构怎样结束的呢,我们看一下LinkedHashMap的类结构

2.jpg
我们看到LinkedHashMap中定义了一个Entry静态内部类,定义了5个结构器,一些成员变量,如head,tail,accessOrder,并继承了HashMap的方法,一同结束了一些迭代器方法。我们先看一下Entry类
static class Entry extends HashMap.Node {
    Entry before, after;
    Entry(int hash, K key, V value, Node next) {
        super(hash, key, value, next);
    }
}



A8站源码交易平台我们看到这个静态内部类很简单,继承了HashMap的Node内部类,我们知道Node类是HashMap的底层数据结构,结束了数组+链表/红黑树的结构,而Entry类保留了HashMap的数据结构,一同通过before,after结束了双向链表结构(HashMap中Node类只需next特征,并不具有双向链表结构)。那么before,after和next毕竟什么关系呢。


3.jpg



看上面的结构图,定义了头结点head,当我们调用迭代器进行遍历时,通过head初步遍历,通过before特征可以不断找到下一个,直到tail尾结点,然后结束次第性。而在同一个hash(在上图中表现了同一行)链表内部after和next作用是相同的。不同点在于before和after可以联接不同hash之间的链表。
前面我们发现数据结构现已完全支撑其次第性了,接下来我们再看一下结构方法,看一下比起HashMap的结构方法是否有不同。
// 结构方法1,结构一个指定初始容量和负载因子的、按照刺进次第的LinkedList
public LinkedHashMap(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor);
    accessOrder = false;
}
// 结构方法2,结构一个指定初始容量的LinkedHashMap,取得键值对的次第是刺进次第
public LinkedHashMap(int initialCapacity) {
    super(initialCapacity);
    accessOrder = false;
}
// 结构方法3,用默许的初始化容量和负载因子创建一个LinkedHashMap,取得键值对的次第是刺进次第
public LinkedHashMap() {
    super();
    accessOrder = false;
}
// 结构方法4,通过传入的map创建一个LinkedHashMap,容量为默许容量(16)和(map.zise()/DEFAULT_LOAD_FACTORY)+1的较大者,装载因子为默许值
public LinkedHashMap(Map m) {
    super(m);
    accessOrder = false;
}
// 结构方法5,依据指定容量、装载因子和键值对坚持次第创建一个LinkedHashMap
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}





我们发现除了多了一个变量accessOrder之外,并无不同,此变量毕竟起了什么作用?

1 (2).png通过注释发现该变量为true时access-order,即按访问次第遍历,假设为false,则标明按刺进次第遍历。默许为false,在哪些地方运用到该变量了,一同怎样了解?我们可以看下面的方法介绍
二. put方法

前面我们说到LinkedHashMap的put方法沿用了父类HashMap的put方法,但我们也说到了像LinkedHashMap的Entry类就是继承了HashMap的Node类,相同的,HashMap的put方法中调用的其他方法在LinkedHashMap中现已被重写。我们先看一下HashMap的put方法,这个在《HashMap原理(二) 扩容机制及存取原理》中现已有说明,我们首要注重于其间的不同点。A8站源码交易平台



/**
 * Implements Map.put and related methods
 *
 * @param hash hash for key
 * @param key the key
 * @param value the value to put
 * @param onlyIfAbsent if true, don't change existing value
 * @param evict if false, the table is in creation mode.
 * @return previous value, or null if none
 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node

[] tab; Node p; int n, i;
    /**
     * 假设其时HashMap的table数组还未定义或许还未初始化其长度,则先通过resize()进行扩容,
     * 回来扩容后的数组长度n
     */
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //通过数组长度与hash值做按位与&运算得到对应数组下标,若该方位没有元素,则new Node直接将新元素刺进
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    //否则该方位现已有元素了,我们就需要进行一些其他操作
    else {
        Node e; K k;
        //假设刺进的key和原本的key相同,则替换一下就完事了
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        /**
         * 否则key不同的情况下,判别其时Node是否是TreeNode,假设是则实行putTreeVal将新的元素刺进
         * 到红黑树上。
         */
        else if (p instanceof TreeNode)
            e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
        //假设不是TreeNode,则进行链表遍历
        else {
            for (int binCount = 0; ; ++binCount) {
                /**
                 * 在链表毕竟一个节点之后并没有找到相同的元素,则进行下面的操作,直接new Node刺进,
                 * 但条件判别有或许转化为红黑树
                 */
                if ((e = p.next) == null) {
                    //直接new了一个Node
                    p.next = newNode(hash, key, value, null);
                    /**
                     * TREEIFY_THRESHOLD=8,因为binCount从0初步,也就是链表长度逾越8(包括)时,
                     * 转为红黑树。
                     */
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                /**
                 * 假设在链表的毕竟一个节点之前找到key值相同的(和上面的判别不冲突,上面是直接通过数组
                 * 下标判别key值是否相同),则替换
                 */
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            //onlyIfAbsent为true时:当某个方位现已存在元素时不去掩盖
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    //毕竟判别临界值,是否扩容。
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;

}


1. newNode方法A8站源码交易平台


首要:LinkedHashMap重写了newNode()方法,通过此方法保证了刺进的次第性。

90c0e47a1f9785f7330915e067274f4.png


A8站源码交易平台

2. afterNodeAccess方法
其次:关于afterNodeAccess()方法,在HashMap中没给具体结束,而在LinkedHashMap重写了,目的是保证操作过的Node节点永远在毕竟,然后保证读取的次第性,在调用put方法和get方法时都会用到。

3.png

4.png


A8站源码交易平台 

我们前面说到的linkNodeLast(Entry e)方法和现在的afterNodeAccess(Node e)都是将传入的Node节点放到毕竟,那么它们的运用场景怎样呢?
在前面说明HashMap时,说到了HashMap的put流程,假设在对应的hash方位上还没有元素,那么直接new Node()放到数组table中,这个时分对应到LinkedHashMap中,调用了newNode()方法,就会用到linkNodeLast(),将新node放到毕竟,而假设对应的hash方位上有元素,进行元素值的掩盖时,就会调用afterNodeAccess(),将原本或许不是毕竟的node节点拿到了毕竟。如
LinkedHashMap map = new LinkedHashMap<>(16, 0.75f, true);
map.put("1月", 20);
//此时就会调用到linkNodeLast()方法,也会调用afterNodeAccess()方法,但会被阻遏在
//if (accessOrder && (last = tail) != e) 之外
map.put("2月", 30);
map.put("3月", 65);
map.put("4月", 43);
//这时不会调用linkNodeLast(),会调用afterNodeAccess()方法将key为“1月”的元素放到毕竟
map.put("1月", 35);
//这时不会调用linkNodeLast(),会调用afterNodeAccess()方法将key为“2月”的元素放到毕竟
map.get("2月");
//调用打印方法
for (Map.Entry entry : map.entrySet()){
    System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue());
}



作用如下:
key: 3月, value: 65
key: 4月, value: 43
key: 1月, value: 35
key: 2月, value: 30
而假设是实行下面这段代码,将accessOrder改为false
LinkedHashMap map = new LinkedHashMap<>(16, 0.75f, false);
map.put("1月", 20);
//此时就会调用到linkNodeLast()方法,也会调用afterNodeAccess()方法,但会被阻遏在
//if (accessOrder && (last = tail) != e) 之外
map.put("2月", 30);
map.put("3月", 65);
map.put("4月", 43);
//这时不会调用linkNodeLast(),会调用afterNodeAccess()方法将key为“1月”的元素放到毕竟
map.put("1月", 35);
map.get("2月");
//调用打印方法
for (Map.Entry entry : map.entrySet()){
    System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue());
}



作用如下:
key: 1月, value: 35
key: 2月, value: 30
key: 3月, value: 65
key: 4月, value: 43


A8站源码交易平台

我们看到差异了吗,accessOrder为false时,你访问的次第就是按照你第一次刺进的次第;而accessOrder为true时,你任何一次的操作,包括put、get操作,都会改动map中已有的存储次第。
3. afterNodeInsertion方法
我们看到在LinkedHashMap中还重写了afterNodeInsertion(boolean evict)方法,它的目的是移除链表中最老的节点方针,也就是其时在头部的节点方针,但实际上在JDK8中不会实行,因为removeEldestEntry方法一向回来false。看源码:
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}
protected boolean removeEldestEntry(Map.Entry eldest) {
    return false;
}


三. get方法

LinkedHashMap的get方法与HashMap中get方法的不同点也在于多了afterNodeAccess()方法
public V get(Object key) {
    Node e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}



A8站源码交易平台 在这儿就不再多讲了,getNode()方法在HashMap章节现已讲过,而前面刚把afterNodeAccess讲了。
四.remove方法
remove方法也直接运用了HashMap中的remove,在HashMap章节并没有说明,因为remove的原理很简单,通过传递的参数key计算出hash,据此可找到对应的Node节点,接下来假设该Node节点是直接在数组中的Node,则将table数组该方位的元素设置为node.next;假设是链表中的,则遍历链表,直到找到对应的node节点,然后建立该节点的上一个节点的next设置为该节点的next。
LinkedHashMap重写了其间的afterNodeRemoval(Node e),该方法在HashMap中没有具体结束,通过此方法在删去节点的时分调整了双链表的结构。
void afterNodeRemoval(Node e) {
    LinkedHashMap.Entry p = (LinkedHashMap.Entry)e, b = p.before, a = p.after;
    //将待删去节点的before和after都设置为null
    p.before = p.after = null;
    /**
     * 假设节点b为null,标明待删去节点p为头部节点,该节点拿掉后,该节点的下一个节点a就为头部节点head
     * 否则设置待删去节点的上一个节点b的after特征为节点a 
     */
    if (b == null)
        head = a;
    else
        b.after = a;
    /**
     * 假设节点a为null,标明待删去节点p为尾部节点,该节点拿掉后,该节点的上一个节点a就为尾部节点tail
     * 否则设置待删去节点的下一个节点a的before特征为节点b 
     */
    if (a == null)
        tail = b;
    else
        a.before = b;
}

A8站源码交易平台
五. 总结
LinkedHashMap运用的也较为一再,它依据HashMap,用于HashMap的特征,又增加了双链表的结构,然后保证了次第性,本文首要从源码的角度分析其怎样保证次第性,accessOrder的说明,以及常用方法的阐释。

A8站源码交易平台




  • 全部评论(0)
最新发布的资讯信息
【A8站-免费源码分享|直播源码】直播系统源码开发:关于安卓开发工具和obs直播推流(2020-10-30 09:41)
【计算机/互联网|】【独家修复】用户定制版短视频点赞系统,支持抖音+快手+刷宝+微视等所有主流短视频评论系统源码(2020-10-26 16:34)
【技术宅-为技术而疯|网站架设教程】【独家首发】最新更新已对接短信2020全新抖音快手点赞任务系统霸屏天下小红书头条威客兼职完整搭建架设视频教程(2020-10-25 13:56)
【技术宅-为技术而疯|网站架设教程】最新可用个人发卡网系统源码完整搭建架设视频教程(2020-10-25 10:53)
【技术宅-为技术而疯|网站架设教程】独家更新全新V10抢单系统唯品会京东淘宝自动抢单区块系统源码全开源抢单收单接单返利+搭建架设完整视频教程(2020-10-25 10:52)
【计算机/互联网|】柔丫纸尿裤云仓系统源码部署(2020-10-15 18:13)
【计算机/互联网|】侏罗纪软件模式定制开发(2020-10-14 15:33)
【计算机/互联网|】S2b2C供应商系统 营销闭环(2020-10-10 17:46)
【计算机/互联网|程序设计开发】盛都汇系统奖励机制(2020-10-10 17:20)
【A8站-免费源码分享|网站源码】积分商城系统APP开发(2020-09-23 14:56)
联系我们
Q Q:3101359898 点击直接对话
电话:18580901894
邮箱:admin#a8zhan.com
时间:09:00 - 24:00
手机二维码 访问手机版
返回顶部