Java集合类学习--ConcurrentHashMap(三)

Java面试篇基础部分-Java中的集合类 Java集合是面试中经常被问到的一块内容,很多人在这个地方被面试官吊打。Java集合类被定义在java.util包中,主要有四种集合,分别是List、Queue、Set和Map,每种集合分类如下图所示。 阅读详情

常用方法

3、扩容和协助扩容
每次添加元素后,元素数量加1,并判断是否达到扩容门槛,达到了则进行扩容或协助扩容。
参考:http://cmsblogs.com/?p=4777

private final void addCount(long x, int check) {
    CounterCell[] as; long b, s;
    // 这里使用的思想跟LongAdder类是一模一样的(后面会讲)
    // 把数组的大小存储根据不同的线程存储到不同的段上(也是分段锁的思想)
    // 并且有一个baseCount,优先更新baseCount,如果失败了再更新不同线程对应的段
    // 这样可以保证尽量小的减少冲突

    // 先尝试把数量加到baseCount上,如果失败再加到分段的CounterCell上
    if ((as = counterCells) != null ||
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
        CounterCell a; long v; int m;
        boolean uncontended = true;
        // 如果as为空
        // 或者长度为0
        // 或者当前线程所在的段为null
        // 或者在当前线程的段上加数量失败
        if (as == null || (m = as.length - 1) < 0 ||
                (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
                !(uncontended =
                        U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
            // 强制增加数量(无论如何数量是一定要加上的,并不是简单地自旋)
            // 不同线程对应不同的段都更新失败了
            // 说明已经发生冲突了,那么就对counterCells进行扩容
            // 以减少多个线程hash到同一个段的概率
            fullAddCount(x, uncontended);
            return;
        }
        if (check <= 1)
            return;
        // 计算元素个数
        s = sumCount();
    }
    if (check >= 0) {
        Node<K,V>[] tab, nt; int n, sc;
        // 如果元素个数达到了扩容门槛,则进行扩容
        // 注意,正常情况下sizeCtl存储的是扩容门槛,即容量的0.75倍
        while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                (n = tab.length) < MAXIMUM_CAPACITY) {
            // rs是扩容时的一个邮戳标识
            int rs = resizeStamp(n);
            if (sc < 0) {
                // sc<0说明正在扩容中
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                    // 扩容已经完成了,退出循环
                    // 正常应该只会触发nextTable==null这个条件,其它条件没看出来何时触发
                    break;

                // 扩容未完成,则当前线程加入迁移元素中
                // 并把扩容线程数加1
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            }
            else if (U.compareAndSwapInt(this, SIZECTL, sc,
                    (rs << RESIZE_STAMP_SHIFT) + 2))
                // 这里是触发扩容的那个线程进入的地方
                // sizeCtl的高16位存储着rs这个扩容邮戳
                // sizeCtl的低16位存储着扩容线程数加1,即(1+nThreads)
                // 所以官方说的扩容时sizeCtl的值为 -(1+nThreads)是错误的

                // 进入迁移元素
                transfer(tab, null);
            // 重新计算元素个数
            s = sumCount();
        }
    }
}

(1)元素个数的存储方式类似于LongAdder类,存储在不同的段上,减少不同线程同时更新size时的冲突;
(2)计算元素个数时把这些段的值及baseCount相加算出总的元素个数;
(3)正常情况下sizeCtl存储着扩容门槛,扩容门槛为容量的0.75倍;
(4)扩容时sizeCtl高位存储扩容邮戳(resizeStamp),低位存储扩容线程数加1(1+nThreads);
(5)其它线程添加元素后如果发现存在扩容,也会加入的扩容行列中来;

协助扩容(迁移元素):

final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
    Node<K,V>[] nextTab; int sc;
    // 如果桶数组不为空,并且当前桶第一个元素为ForwardingNode类型,并且nextTab不为空
    // 说明当前桶已经迁移完毕了,才去帮忙迁移其它桶的元素
    // 扩容时会把旧桶的第一个元素置为ForwardingNode,并让其nextTab指向新桶数组
    if (tab != null && (f instanceof ForwardingNode) &&
            (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
        int rs = resizeStamp(tab.length);
        // sizeCtl<0,说明正在扩容
        while (nextTab == nextTable && table == tab &&
                (sc = sizeCtl) < 0) {
            if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || transferIndex <= 0)
                break;
            // 扩容线程数加1
            if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                // 当前线程帮忙迁移元素
                transfer(tab, nextTab);
                break;
            }
        }
        return nextTab;
    }
    return table;
}

迁移元素:

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
    int n = tab.length, stride;
    if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
        stride = MIN_TRANSFER_STRIDE; // subdivide range
    if (nextTab == null) {            // initiating
        // 如果nextTab为空,说明还没开始迁移
        // 就新建一个新桶数组
        try {
            // 新桶数组是原桶的两倍
            @SuppressWarnings("unchecked")
            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
            nextTab = nt;
        } catch (Throwable ex) {      // try to cope with OOME
            sizeCtl = Integer.MAX_VALUE;
            return;
        }
        nextTable = nextTab;
        transferIndex = n;
    }
    // 新桶数组大小
    int nextn = nextTab.length;
    // 新建一个ForwardingNode类型的节点,并把新桶数组存储在里面
    ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
    boolean advance = true;
    boolean finishing = false; // to ensure sweep before committing nextTab
    for (int i = 0, bound = 0;;) {
        Node<K,V> f; int fh;
        // 整个while循环就是在算i的值,过程太复杂,不用太关心
        // i的值会从n-1依次递减,感兴趣的可以打下断点就知道了
        // 其中n是旧桶数组的大小,也就是说i从15开始一直减到1这样去迁移元素
        while (advance) {
            int nextIndex, nextBound;
            if (--i >= bound || finishing)
                advance = false;
            else if ((nextIndex = transferIndex) <= 0) {
                i = -1;
                advance = false;
            }
            else if (U.compareAndSwapInt
                    (this, TRANSFERINDEX, nextIndex,
                            nextBound = (nextIndex > stride ?
                                    nextIndex - stride : 0))) {
                bound = nextBound;
                i = nextIndex - 1;
                advance = false;
            }
        }
        if (i < 0 || i >= n || i + n >= nextn) {
            // 如果一次遍历完成了
            // 也就是整个map所有桶中的元素都迁移完成了
            int sc;
            if (finishing) {
                // 如果全部迁移完成了,则替换旧桶数组
                // 并设置下一次扩容门槛为新桶数组容量的0.75倍
                nextTable = null;
                table = nextTab;
                sizeCtl = (n << 1) - (n >>> 1);
                return;
            }
            if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                // 当前线程扩容完成,把扩容线程数-1
                if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                    // 扩容完成两边肯定相等
                    return;
                // 把finishing设置为true
                // finishing为true才会走到上面的if条件
                finishing = advance = true;
                // i重新赋值为n
                // 这样会再重新遍历一次桶数组,看看是不是都迁移完成了
                // 也就是第二次遍历都会走到下面的(fh = f.hash) == MOVED这个条件
                i = n; // recheck before commit
            }
        }
        else if ((f = tabAt(tab, i)) == null)
            // 如果桶中无数据,直接放入ForwardingNode标记该桶已迁移
            advance = casTabAt(tab, i, null, fwd);
        else if ((fh = f.hash) == MOVED)
            // 如果桶中第一个元素的hash值为MOVED
            // 说明它是ForwardingNode节点
            // 也就是该桶已迁移
            advance = true; // already processed
        else {
            // 锁定该桶并迁移元素
            synchronized (f) {
                // 再次判断当前桶第一个元素是否有修改
                // 也就是可能其它线程先一步迁移了元素
                if (tabAt(tab, i) == f) {
                    // 把一个链表分化成两个链表
                    // 规则是桶中各元素的hash与桶大小n进行与操作
                    // 等于0的放到低位链表(low)中,不等于0的放到高位链表(high)中
                    // 其中低位链表迁移到新桶中的位置相对旧桶不变
                    // 高位链表迁移到新桶中位置正好是其在旧桶的位置加n
                    // 这也正是为什么扩容时容量在变成两倍的原因
                    Node<K,V> ln, hn;
                    if (fh >= 0) {
                        // 第一个元素的hash值大于等于0
                        // 说明该桶中元素是以链表形式存储的
                        // 这里与HashMap迁移算法基本类似
                        // 唯一不同的是多了一步寻找lastRun
                        // 这里的lastRun是提取出链表后面不用处理再特殊处理的子链表
                        // 比如所有元素的hash值与桶大小n与操作后的值分别为 0 0 4 4 0 0 0
                        // 则最后后面三个0对应的元素肯定还是在同一个桶中
                        // 这时lastRun对应的就是倒数第三个节点
                        // 至于为啥要这样处理,我也没太搞明白
                        int runBit = fh & n;
                        Node<K,V> lastRun = f;
                        for (Node<K,V> p = f.next; p != null; p = p.next) {
                            int b = p.hash & n;
                            if (b != runBit) {
                                runBit = b;
                                lastRun = p;
                            }
                        }
                        // 看看最后这几个元素归属于低位链表还是高位链表
                        if (runBit == 0) {
                            ln = lastRun;
                            hn = null;
                        }
                        else {
                            hn = lastRun;
                            ln = null;
                        }
                        // 遍历链表,把hash&n为0的放在低位链表中
                        // 不为0的放在高位链表中
                        for (Node<K,V> p = f; p != lastRun; p = p.next) {
                            int ph = p.hash; K pk = p.key; V pv = p.val;
                            if ((ph & n) == 0)
                                ln = new Node<K,V>(ph, pk, pv, ln);
                            else
                                hn = new Node<K,V>(ph, pk, pv, hn);
                        }
                        // 低位链表的位置不变
                        setTabAt(nextTab, i, ln);
                        // 高位链表的位置是原位置加n
                        setTabAt(nextTab, i + n, hn);
                        // 标记当前桶已迁移
                        setTabAt(tab, i, fwd);
                        // advance为true,返回上面进行--i操作
                        advance = true;
                    }
                    else if (f instanceof TreeBin) {
                        // 如果第一个元素是树节点
                        // 也是一样,分化成两颗树
                        // 也是根据hash&n为0放在低位树中
                        // 不为0放在高位树中
                        TreeBin<K,V> t = (TreeBin<K,V>)f;
                        TreeNode<K,V> lo = null, loTail = null;
                        TreeNode<K,V> hi = null, hiTail = null;
                        int lc = 0, hc = 0;
                        // 遍历整颗树,根据hash&n是否为0分化成两颗树
                        for (Node<K,V> e = t.first; e != null; e = e.next) {
                            int h = e.hash;
                            TreeNode<K,V> p = new TreeNode<K,V>
                                    (h, e.key, e.val, null, null);
                            if ((h & n) == 0) {
                                if ((p.prev = loTail) == null)
                                    lo = p;
                                else
                                    loTail.next = p;
                                loTail = p;
                                ++lc;
                            }
                            else {
                                if ((p.prev = hiTail) == null)
                                    hi = p;
                                else
                                    hiTail.next = p;
                                hiTail = p;
                                ++hc;
                            }
                        }
                        // 如果分化的树中元素个数小于等于6,则退化成链表
                        ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                (hc != 0) ? new TreeBin<K,V>(lo) : t;
                        hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                (lc != 0) ? new TreeBin<K,V>(hi) : t;
                        // 低位树的位置不变
                        setTabAt(nextTab, i, ln);
                        // 高位树的位置是原位置加n
                        setTabAt(nextTab, i + n, hn);
                        // 标记该桶已迁移
                        setTabAt(tab, i, fwd);
                        // advance为true,返回上面进行--i操作
                        advance = true;
                    }
                }
            }
        }
    }
}

(1)新桶数组大小是旧桶数组的两倍;
(2)迁移元素先从靠后的桶开始;
(3)迁移完成的桶在里面放置一ForwardingNode类型的元素,标记该桶迁移完成;
(4)迁移时根据hash&n是否等于0把桶中元素分化成两个链表或树;
(5)低位链表(树)存储在原来的位置;
(6)高们链表(树)存储在原来的位置加n的位置;
(7)迁移元素时会锁住当前桶,也是分段锁的思想;

扩容过程图解

参考:https://blog.youkuaiyun.com/ZOKEKAI/article/details/90051567

Java集合类框架学习 5.3—— ConcurrentHashMap(JDK1.8) 以下内容,如有问题,烦请指出,谢谢! 现在看下1.8版本的ConcurrentHashMap,改动很大。目前本人也有些地方没有弄懂,具体来说就是扩容有关的那一块,有几个地方还不太对得上,单独理解是没问题的,联合起来发现存在些矛盾的地方。所以下面的扩容有关的,各位看官还是细看,自己也要想下。 零、主要改动 参照对象为jdk1.7的ConcurrentHashMap,当然,熟悉jdk1. 阅读详情

相关推荐

Java集合类框架学习 5.1 —— ConcurrentHashMap(JDK1.6)

以下内容,如有问题,烦请指出,谢谢! 这一篇讲1.6的ConcurrentHashMap ConcurrentHashMap是Java程序员接触得最多的有关并发和线程安全的类,它兼顾了并发的两个基本点——安全,高效,在很多地方都有用到,每一个Java程序员都应该真正掌握这个类。 虽然1.6版本过了10年多了,现有的1.8跟1.6的差异很大。不过1.6的最适合用来学习,它实现得简单直接

十二页的博客 2018

[Java]ConcurrentHashMap中的sizeCtl变量

隐藏着bug的并发集合ConcurrentHashMap,依旧保持着线程安全?

shichimiyasatone的博客 1714

哪些集合类是线程安全的?

Java 早期线程安全集合(如VectorHashtable)因性能较低,通常不推荐使用。推荐使用 java.util.concurrent包中的集合类(如)来替代传统同步集合,性能更优。如果没有现成的线程安全实现,可以使用方法对非线程安全集合进行包装。

weixin_37782784的博客 1183

ConcurrentHashMap的sizeCtl含义纠正

引言: 本文基于JDK1.8版本,而且对ConcurrentHashMap有一定了解的人,本文并非科普该类的用法,而是针对sizeCtl的含义做出纠正 科普文章推荐: 深入浅出ConcurrentHashMap1.8 sizeCtl定义及注释 /** * Table initialization and resizing control. When negative, the ...

Unknownfuture的博客 9107

高薪程序员&面试题精讲系列48之说说JDK8中ConcurrentHashMap的sizeCtl是什么意思?最大容量、负载因子是多少?

ConcurrentHashMap核心属性及构造方法解读

一一哥 1536

使用Java实现CRC16 MODBUS 格式校验

CRC16校验,测试如下 String str = "a5 98 03 00 00 5f c6 0f 7c 40 78 06 09 46 dc 6e db 00 16 3e 12 5c 98 00 19 98 0e 10 03 06 09 46 dc 6e db 03 0b bf 5f c6 0f 7c 43 bd b9 61 ee 96 76 99"; 校验规则: /** * CRC16_CCITT:多项式x16+x12+x5+1(0x1021),初始值0x0000,低位在前,高位在后,结

weixin_45063710的博客 1313

ConcurrentHashMap简单解释

如果有另一个线程进入到这里,就会协助扩容。该指令概念上存在 3 个参数, 第一个参数【目标地址】, 第二个参数【值1】, 第三个参数【值2】, 指令会比较【目标地址存储的内容】和 【值1】 是否一致, 如果一致, 则将【值 2】 填写到【目标地址】, 其语。与原HashMap不同的地方是如果该索引为空的话,则会执行cas操作,即两个线程同时进入该方法,如果线程1成功,则2不成功。在此设置数组初始化,如在此多次加入还是不成功,就会扩容(前提是数组为不为空),如果数组为空,就会一直加做cas操作,

O_Dentist的博客 526

【原创,解决CLodop安装后无法调用的问题】已经安装过CLodop,但是网页上仍然提示“clodop云打印服务(localhost本地)未安装启动,点击这里执行安装....”

在某些国内web网站中,需要安装一个名为“CLodop”的插件;但在实际使用过程中,即使安装了CLodop插件,网页试图调用它时还是会提示“clodop云打印服务(localhost本地)未安装启动,点击这里执行安装...”,且无法完成相关打印任务 本文属原创博客,分析了解决CLodop无法被调用的问题,并附上了原理和解决方法,本质上是和跨域请求以及CORS类安全组策略相关

link_in_csdn的博客 2万+

ConcurrentHashMap1.8 - 扩容详解

简介        ConcurrenHashMap 在扩容过程中主要使用 sizeCtl 和 transferIndex 这两个属性来协调多线程之间的并发操作,并且在扩容过程中大部分数据依旧可以做到访问不阻塞,具体是如何实现的,请继续 。   说明:该源码来自于 jdk_1.8.0_162 版本 。   特别说明:不想看源码可直接跳到后面直接看...

y41992910的博客 6584

Java使用easyExcel生成excel文件直接写入邮件附件并发送

1.引入pom依赖 <!-- email --> <dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>1.4.7</version> </dependency> <!-- easyexcel需要依赖poi jar包 --> <depe

weixin_50493320的博客 3909

从零到一:手把手教你构建自定义ReaxFF力场参数

本文手把手教你从零构建自定义ReaxFF力场参数,详解反应力场的动态键序机制与参数化逻辑。通过LAMMPS实战演示,涵盖训练数据准备、参数优化算法选择及性能优化技巧,助力研究人员精准模拟化学反应过程。适用于材料科学、催化研究等领域,特别适合已掌握分子动力学基础的进阶用户提升力场开发能力。

weixin_28365523的博客 1056

Java中的线程安全的集合类(如果想知道Java中有关线程安全的集合类的知识,那么只看这一篇就足够了!)

Java中的线程安全的集合类(如果想知道Java中有关线程安全的集合类的知识,那么只看这一篇就足够了!)

秋刀鱼不做梦的编程日常 7989

《派出你的AI同事:WorkBuddy案例实战》062:投研资讯摘要与预警

投研资讯摘要与预警:新闻、公告、行情交给 AI 同事提炼与预警,投研从“刷资讯”变成“读摘要”。

LuckyHanMo的博客 181

Java集合类

Java集合类是Java为我们提供的一系列数据结构的实现。

weixin_54514490的博客 2058

【全志V821_FoxPi】6-2 IMX219 MIPI摄像头适配

全志V821芯片适配IMX219 MIPI摄像头(2lane)教程,基于FoxPi开发板V1.2 SDK。文章详细介绍了IMX219的技术参数(800万像素、1/4英寸CMOS、支持1080P@30fps输出)及模组接口原理图。重点展示了设备树配置方法,包括PIO引脚定义(TWI0使用PA3/PA4,复位引脚PA2)、TWI0节点设置(100kHz时钟)以及VIND0节点中sensor0的完整配置(I2C地址0x20、MCLK0、RAW格式输出等)。配置过程强调需注意IMX219的器件地址转换(0x10→0

第四维度的博客 2248

Windows 下 Codex 安装全记录:从零到一的保姆级图文教程

本文主要介绍了Windows 下 Codex 安装全记录:从零到一的保姆级图文教程,希望能对学习使用 Codex 的同学们有所帮助。 文章目录 1. 背景 2. 实战教程 2.1 方案一 2.2 方案二 3. 报错解决方案

weixin_43178406的博客 14万+

【JAVA】哪些集合类是线程安全的

在多线程编程中,对共享数据的安全访问是一个关键的问题。Java提供了一些线程安全的集合类,以便开发者能够在多线程环境下安全地进行数据操作。深入理解这些线程安全的集合类的特性和适用场景,对于构建高效且稳定的多线程应用至关重要。线程安全的集合类为我们提供了在多线程环境中安全地共享和操作数据的解决方案。从传统的Vector和HashTable到现代的等,这些集合类为不同的并发场景提供了不同的选择。在实际开发中,根据应用程序的需求,选择合适的线程安全集合类是确保多线程程序正确运行的关键。

AliceNo的博客 3235

基于深度学习的航拍图像中地物变化检测

随着无人机技术的飞速发展,航拍图像成为获取地表信息的重要手段之一。然而,由于地球表面的不断变化,监测和识别航拍图像中的地物变化成为一项具有挑战性的任务。在人工智能领域,深度学习技术的兴起为地物变化检测提供了全新的解决方案。本文将介绍基于深度学习的航拍图像中地物变化检测的方法,并附上相应的代码实例。

一键难忘的博客 4175

Java多线程基础-18:线程安全的集合类与ConcurrentHashMap

详细介绍JUC的集合类以及ConcurrentHashMap与HashTable的区别

wyd_333的博客 1557

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

【Java集合三】ConcurrentHashMap 详解

本文深入解析了 Java 中的 ConcurrentHashMap,对比了 Java 7 和 Java 8 的版本差异,重点介绍了其线程安全机制、数据结构、扩容机制以及 put 和 get 操作的原理。

叉烧钵钵鸡的博客 1055

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

解释Java中的并发集合类,比如ConcurrentHashMap和CopyOnWriteArrayList

ConcurrentHashMap 和 CopyOnWriteArrayList 是Java中常用的并发集合类,它们提供了线程安全的操作,并且针对不同的场景和需求提供了不同的实现策略。在读操作远远多于写操作的场景下,使用 CopyOnWriteArrayList 可以保证线程安全性,并且避免了使用显式锁带来的性能开销,例如在事件监听器列表、观察者列表等场景。在需要高并发读写操作的场景下,使用 ConcurrentHashMap 可以提高性能和可伸缩性,例如在多线程环境下的缓存管理、计数器统计等场景。

qiuyehuanghun的博客 1009

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

【多线程】线程安全集合类,ConcurrentHashMap实现原理

ArrayListQueueHsahMap… 都是线程不安全的VectorStackHashtable都是线程安全的(内置了 synchronized),实际上这几个东西并。

Yeeear的博客 1673

java学习(4)-- 集合类

一、集合类组成架构 二、ArrayList、LinkedList、Vector 的区别 1)首先普及一基础知识,数组和链表 数组和链表都是线性表(数据成线性排列),只有前后两个方向; 数组是连续的内存空间、存储相同的数据类型; 链表是可以非连续的内存空间存储相同的数据类型; 2)简单说一下ArrayList、Linke...

敲代码的小蜗牛的专栏 221

Java中线程安全的集合类

多线程环境下使用ArrayList,多线程环境使用哈希表

m0_74209411的博客 2796

java集合类总结

说明:本文结合其他文章以及自己的一些实例写成 一、总体结构 下图可以很清楚的说明 二、常用集合类对比和说明 list、set、map 的区别就不说了,直接相同类型具体类的比较 ArrayList、LinkedList、Vector、CopyOnWriteArrayList ArrayList、LinkedList:实现分别是数组和链表,所以区别就是数组和链表的区别。多搜索用Ar...

zzw1531439090的博客 2425

【Java基础】-【集合类】

在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历。所以即便是需要使用线程安全的集合类,也建议将线程不安全的集合类包装成线程安全集合类的方式,而不是直接使用这些古老的API。从Java5开始,Java在。这个设计确实非常的巧妙,既省去了重新计算hash值的时间,而且同时,由于新增的1bit是0还是1可以认为是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。

CaraYQ的博客 2096
上一篇: Java集合类学习--ConcurrentHashMap(二)
下一篇: Java集合类学习--ConcurrentHashMap(四)
CodersCoder
博客等级 码龄10年 90粉丝 258原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值