Java集合相关问题

news2024/11/16 12:54:36

java集合框架体系

在这里插入图片描述

数据结构

算法复杂度分析

  • 时间复杂度分析:对代码运行时间所消耗时间多少进行分析
  • 空间复杂度分析:对代码运行所占用的内存的大小进行分析

时间复杂度

时间复杂度分析:来评估代码的执行耗时
在这里插入图片描述

  1. 假如执行每行代码的执行耗时一样:1ms

  2. 分析这段代码总执行多少行?

    3n + 3

  3. 代码耗时总时间:T(n)=(3n + 3) * 1ms

  • 大O表示法:不具体表示代码真正的执行时间,而表示代码执行时间随数据规模增长的变化趋势

  • T(n)与代码的执行次数成正比(代码的行数越多,执行时间越长)

    T(n) = O(3n + 3) —》 T(n)= O(n)

  • 当n很大时,公式中的低阶,常量,系数三部分并不左右其增长的趋势,因此可以忽略,我们只需要记录一个最大的量级就可以了
    常见的复杂度表现形式
    在这里插入图片描述
    一般高于n的二次方或三次方就说明复杂度是相对较高的了,需要优化
    在这里插入图片描述
    总结:只要代码的执行时间不随着n的增大而增大,这样的复杂度就是O(1)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

第一个因为int类型默认是占用4个字节

因此不管怎么运算都是4个字节,所以复杂度为O(1)

第二个因为int[] a 的长度需要n来决定,所以时间复杂度为O(n)

我们常见的空间复杂度就是O(1),O(n),O(n^2),其他的像对数阶复杂度几乎用不到,因此空间复杂度比时间复杂度分析要简单的多

什么是算法时间复杂度?

时间复杂度表示了算法的执行时间与数据规模之间的增长关系

常见的时间复杂度有哪些

O(1),O(n),O(n^2),O(log n)

速记口诀:常对幂指阶

什么是算法的空间复杂度?

表示算法占用的额外存储空间和数据规模之间的增长关系

常见的空间复杂度:O(1),O(n),O(n ^2)

List集合

List集合是基于数据去实现的想要了解List集合,就应该先了解数组

数组

数组(Array)是一种连续的内存空间存储相同的数据类型数据和线性数据结构。
在这里插入图片描述
数组是如何获取其他元素的值的呢?
在这里插入图片描述采用寻址公式:

  • baseAddress:数组的首地址
  • dataTypeSize:代表数组中元素类型的大小,int类型,dataTypeSize=4个字节

为什么数组索引从0开始呢?假如从1开始不行吗?
在这里插入图片描述

如果从1开始的话根据寻址公式那么索引i就必须进行减1的操作,因此索引从0开始效率更高

  • 在根据数组索引获取元素的时候,会用索引和寻址公式来计算内存中所对应的元素数据,寻址公式是:数组的首地址+索引乘以存储数据的类型的大小

  • 如果数组的索引从1开始,寻址公式中,就需要增加一次减法操作,对于CPU来说就多了一次指令,性能不高。

操作数组的时间复杂度(查询)

  1. 随机查询(根据索引查询)

数组元素的访问是通过索引来访问的,计算机通过对数组的首地址和寻址公式能够很快速的找到想要访问的元素
在这里插入图片描述2. 未知索引查询

情况二:查找排序后数组内的元素,查找55号数据

不排序的情况下:复杂度O(n)
在这里插入图片描述
如果进行排序之后使用二分查找法,将数据分成两部分进行比较查询:
在这里插入图片描述
时间复杂度O(log n)

操作数组的时间复杂度(插入和删除)

数组是一段连续的内存空间,因此为了保证数组的连续性会使得数组的插入和删除的效率变得很低。
在这里插入图片描述因为在插入一个元素的时候,后面的元素需要整体向后移动

而在删除一个元素的时候,后面的元素需要整体前向移动

最好的情况下是O(1),最坏的情况下O(n),平均情况下的时间复杂度是O(n)

  1. 数组(Array)是一种用连续的内存空间存储相同的数据类型数据的线性数据结构。

  2. 数组的下标为什么从0开始

    寻址公式:baseAddress+i*dataTypeSize,计算下标的存储地址效率较高

  3. 查询的时间复杂度

    • 随机(通过下标)查询的时间复杂度是O(1)
    • 查找元素(未知下标)的时间复杂度是O(n)
    • 查找元素(未知下标但排序)通过二分查找法的时间复杂度是O(log n)
  4. 插入和删除时间复杂度

    插入和删除的时候,为了保证数组的内存连续性,需要挪动数组的元素,平均时间复杂度是O(n)

ArrayList源码分析

源码分析:

List<Integer> list = new ArrayList<Integer>();
list.add(1);

主要从第三个方面进行分析:
在这里插入图片描述分析对象:jdk1.8

成员变量:
在这里插入图片描述
构造方法
在这里插入图片描述
将collection对象转化为数组,然后将数组的地址赋值给elementData。

第一次添加数据
在这里插入图片描述
在这里插入图片描述
因为没有超过默认的数组长度,因此默认不扩容

第十一次添加

在这里插入图片描述

ArrayList底层原理

  • ArrayList底层是用动态的数组实现的
  • ArrayList初始化容量为0,当第一次添加数据的时候才会初始化容量为10
  • ArrayList在进行扩容的时候是原来容量的1.5倍,每次扩容都需要拷贝数组
  • ArrayList在添加数据的时候
    • 确保数组已使用长度(size)加1之后足够存下下一个数据
    • 计算数组的容量,如果当前数组已使用长度+1后的大于当前的数组长度,则调用grow方法扩容(原来的1.5倍)
    • 确保新增的数据有地方存储之后,则将新元素添加到位于size的位置上。
    • 返回添加成功的布尔值
      ArrayList list = new ArrayList(10)中的list扩容几次
      在这里插入图片描述
      参考回答:

该语句只是声明和实例了一个ArrayList,指定了容量为10,未扩容

如何实现数组和List之间的转化

如何实现数组和List之间的转化呢?

// 数组转集合
public static void testArrayToList() {
    String[] strs = {"aaa", "bbb", "ccc"};
    List<String> list = Arrays.asList(strs);
    for (String s : list) {
        System.out.println(s);
    }
}
// 集合转数组
public static void testListToArray() {
    List<String> list = new ArrayList<>();
    list.add("aaa");
    list.add("bbb");
    list.add("ccc");
    String[] array = list.toArray(new String[list.size()]);
    for (String s : array) {
        System.out.println(s);
    }
}

参考回答:

  • 数组转List,使用JDK中java.util.Arrays工具类的asList方法
  • List转数组,使用List的toArray方法,无参toArray方法返回Object数组,传入初始化长度的数组对象,返回该对象数组

面试官再问:

  • 用Arrays.asList() 转list后,如果修改了数组内容,list受影响吗?
  • List用toArray()转数组后,如果修改了List内容,数组受影响吗?
// 数组转集合
public static void testArrayToList2() {
    String[] strs = {"aaa", "bbb", "ccc"};
    List<String> list = Arrays.asList(strs);
    for (String s : list) {
        System.out.println(s);
    }
    strs[1] = "ddd";
    System.out.println("===========================");
    for (String s : list) {
        System.out.println(s);
    }
}
// 集合转数组
public static void testListToArray2() {
    List<String> list = new ArrayList<>();
    list.add("aaa");
    list.add("bbb");
    list.add("ccc");
    String[] array = list.toArray(new String[list.size()]);
    for (String s : array) {
        System.out.println(s);
    }
    list.add("ddd");
    System.out.println("===========================");
    for (String s : array) {
        System.out.println(s);
    }
}

用Arrays.asList() 转list后,如果修改了数组内容,list受影响吗?受影响

因为asList中没有去创建数组,因此最后改变该是基于原来的数组,因此会受到影响

List用toArray()转数组后,如果修改了List内容,数组受影响吗?不受影响

toArray方法会重新创建一个新的数组,所以不受影响

再答:

  • Arrays.asList转化list之后,如果修改了数组的内容,list会受影响,因为他的底层使用的Arrays类中的一个内部类ArrayList来构造的集合,在这个集合的构造器中,把我们传入的这个集合进行了包装而已,最终指向的都是同一个内存地址
  • list用了toArray转数组后,如果修改了list内容,数组不会影响,当调用了toArray以后,在底层它是进行了数组的拷贝,跟原来的元素没啥关系了,所以即时list修改了之后,数组也不会受影响

LinkedList数据结构-链表

单项链表

  • 链表中的每个元素称之为节点(Node)
  • 物理存储单元上,非连续,非顺序的存储结构
  • 单项链表:每个节点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个节点的指针。记录下一个节点地址的指针叫做后继指针next
    在这里插入图片描述
    在这里插入图片描述
    链表中的某个节点为B,B的下一个节点为C

表示B.next == C

查询操作
在这里插入图片描述

  • 只有在查询头结点的时候不需要遍历链表,时间复杂度为O(1)
  • 查询其他节点需要遍历链表,时间复杂度为O(n)

插入或删除操作
在这里插入图片描述* 只有在添加和删除头结点的时候不需要遍历链表,时间复杂度是O(1)

  • 添加或删除其他节点需要遍历链表找到对应的节点后,才能完成新增和删除节点,时间复杂度是O(n)

双向链表

而双向链表,顾名思义,它支持两个方向

  • 每个结点不止有一个后继指针next指向后面的节点

  • 有一个前驱指针prev指向前面的结点
    在这里插入图片描述
    对比单项链表:

  • 双向链表需要额外的两个空间来缓存后继结点和前驱结点的地址

  • 支持双向遍历,这样也带来了双向链表操作的灵活性

查询操作

查询头尾结点的时间复杂度是O(1)

平均查询时间复杂度是O(n)

给定节点找前驱结点的时间复杂度为O(1)

添加和删除操作

头尾结点新增的时间复杂度为O(1)

其他部分节点增删的时间复杂度是O(n)

给定节点增删的时间复杂度为O(1)

  1. 单项链表和双向链表的区别是什么?

    • 单向链表只有一个方向,结点只有一个后继指针next
    • 双向链表它支持两个方向,每个结点不止有一个后继指针next指向后面的结点,还有一个前驱指针prev指向前面的结点
  2. 链表操作数据的时间复杂度是多少?

    链表查询新增删除
    单项链表头O(1),其他O(n)头O(1),其他O(n)
    双线链表头尾O(1),其他O(n),给定其他节点O(1)头尾O(1),其他O(n),给定其他节点O(1)

ArrayList和LinkedList的区别

底层数据结构

  • ArrayList 是动态数组的数据结构实现的
  • LinkedList 是双向链表的数据结构实现的
    在这里插入图片描述
  1. 操作数据效率

    • ArrayList按照下标查询的时间复杂度是O(1)【内存是连续的,根据寻址公式】,LinkedList不支持下标查询
    • 查找(未知索引):ArrayList需要遍历,链表也需要索引,时间复杂度都是O(n)
    • 新增和删除
      • ArrayList尾部插入和删除,时间复杂度是O(1);其他部分新增需要挪动数组,时间复杂度是O(n)
      • LinkedList头尾结点新增时间复杂度是O(1),其他都需要遍历链表,时间复杂度是O(n)
  2. 内存空间

    • ArrayList底层是数组,内存连续,节省内存
    • LinkedList是双向链表需要存储数据,和两个指针,更加占用内存
      在这里插入图片描述
      线程安全
  • ArrayList和LinkedList都不是线程安全的
  • 如果需要保证线程的安全,有两种解决方案:
    • 在方法内使用,局部变量则是线程安全的
    • 使用线程安全的ArrayList和LinkedList

当时相对应的性能也会下降

ArrayList和LinkedList的区别是什么?

  1. 底层数据结构
  2. 效率
  3. 空间
  4. 线程是否安全

Map集合

  • 数据结构
    • 二叉树
    • 红黑树
    • 散列表
  • 面试问题
    • HashMap实现原理
    • HashMap的put方法的具体流程
    • hashMap的寻址算法
    • 讲一讲HashMap的扩容机制
    • 为何HashMap的数组长度一定是2的次幂
    • hashMap在1.7情况下的多线程死循环的问题
    • HashSet与HashMap的区别
    • HashTable与HashMap的区别

二叉树

二叉树:

二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子结点和右子节点,不过,二叉树并不要求每个节点都有两个子节点,有的节点只有一个左子结点,有的节点只有一个右子节点。

二叉树的每个节点的左子结点和右子节点也分别满足二叉树的定义
在这里插入图片描述
java中有两种方式实现二叉树:数组存储,链式存储。

基本链式存储的数节点可定义如下:
在这里插入图片描述
二叉树分类

在二叉树中,比较常见的二叉树有:

  • 满二叉树
  • 完全二叉树
  • 二叉搜索树
  • 红黑树

二叉搜索树

二叉搜索树(Binary Search Tree,BST)又名二叉查找树,有序二叉树或者排序二叉树,是二叉树中比较常用的一种类型二叉查找树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。

在这里插入图片描述
二叉搜索树-时间复杂度分析

实际上由于二叉查找树的形态各异,时间复杂度也不尽相同,我画了几棵树,我们来看一下插入,查找,删除的时间复杂度
在这里插入图片描述
插入,查找和删除的时间复杂度是O(log n)

但是二叉树可能会退化成链表,那么时间复杂度就会变成O(n)
在这里插入图片描述

  1. 什么是二叉树
  • 每个节点最多有两个"叉",分别是左子结点和右子节点
  • 不要求每个节点都有两个子节点,有的节点只有左子结点,有的节点只有右子节点
  • 二叉树每个节点的左子树和右子树也分别满足二叉树的定义
  1. 什么是二叉搜索树
  • 二叉搜索树(Binary Search Tree,BST)又名二叉查找树。有序二叉树
  • 树中的任一节点,其左子树的每个节点的值,都要小于这个节点的值而右子树的值都大于这个节点的值
  • 没有键值相等的节点
  • 通常情况下二叉树搜索的时间复杂度是O(log n)

红黑树

**红黑树(Red Black Tree):**也是一种自平衡的二叉搜索树(BST),之前叫做平衡二叉B树(Symmetric Binary B-Tree)
在这里插入图片描述

红黑树的性质

性质1:节点要么是红色,要么是黑色

性质2:根节点是黑色

性质3:叶子节点都是黑色的空节点

性质4:红黑树中红色节点的子节点都是黑色

性质5:从任一节点到叶子节点的所有路径都包含相同树木的黑色节点

在这里插入图片描述

在添加和删除节点的时候,如果不符合这些性质会发生旋转,以达到所有的性质

红黑树的一大原则就是保证平衡

红黑树的复杂度

  • 查找:
    • 空黑树也是一种BST(二叉搜索树)树,查找操作的时间复杂度是:O(log n)
  • 添加:
    • 添加先要从根节点开始找到元素添加的位置,时间复杂度是O(log n)
    • 添加完成后涉及到复杂度为O(1)的旋转调整操作
    • 故整体的复杂度为:O(log n)
  • 删除:
    • 首先从根节点开始找到被删除的元素,时间复杂度是O(log n)
    • 删除完整后涉及到复杂度为O(1)的旋转调整操作
    • 故而整体的复杂度为O(log n)

什么是红黑树?

  • 红黑树(Red Black Tree):也是一种自平衡的二叉搜索树(BST)
  • 所有的红黑树规则都是希望红黑树能够保证平衡
  • 红黑树的时间复杂度:查找,添加,删除都是O(log n)

散列表

散列表(Hash Table)

散列表(Hash Table)又名哈希表/Hash表,是根据键(key)直接访问在内存存储位置值(Value)的数据结构,它是由数组演化而来的,利用了数组支持按照下标进行随机访问数据的特性

下面举一个例子:

在这里插入图片描述

现在换成Hash的方式进行存储
在这里插入图片描述

使用hash方法对key进行Hash然后将结果存入到链表中

在这里插入图片描述

将键(key)映射为数组下标的函数叫做散列寒素,可以表示为:hashValue = hash(key)

散列表的基本要求:

  • 散列函数计算得到的散列值必须是大于等于0的正整数,因为hashValue需要作为数组的下标。
  • 如果key1 == key2,那么经过hash后得到的哈希值也未必相同:hash(key1) == hash(key2)
  • 如果key1 != key2,那么经过hash后得到的哈希值也必须不相同:hash(key1) != hash(key2)

散列冲突

实际情况下想找一个散列函数能够做到对于不同的key计算得到的散列值都不同几乎是不可能的,即便像著名的MD5,SHA等哈希算法也无法避免这种情况,这就是散列冲突(或者哈希冲突,哈希碰撞,就是指多个key映射到同一个数组下标位置)

在这里插入图片描述

上图表示当两个key分别进行hash运算之后它计算出来的值是相同的,那么就会出现冲突的问题,简称:哈希冲突或者哈希碰撞

散列冲突解决方式-链表法(拉链法)

在散列表中,数组的每一个下标位置我们可以称之为桶(bucket)或者槽(slot),每个桶(槽)会对应一条链表,所有散列值相同的元素我们都放在相同的槽位对应的链表中。

在这里插入图片描述

如果两个key分别hash之后得到桶给一个值,那么就在这个值下标所对应的数组中使用链表的方式去存储数据,这样就解决了哈希冲突的问题。

散列冲突-链表法(拉链)-时间复杂度

  1. 插入操作,通过散列函数计算处对应的散列槽位,将其插入对应的链表中即可,插入的时间复杂度是O(1)

在这里插入图片描述

当查找和删除一个元素的时候,我们同样通过散列函数计算处对应的槽,然后遍历链表查找或者删除

  • 平均情况下基于链表解决冲突时查询的时间复杂度是O(1)

  • 但散列链表中链接的数据过长的时候,退化成链表的时候,查询的时间复杂度就从O(1)退化成O(n)

在这里插入图片描述

  • 针对这种情况呢,我们可以对链表进行改造成其他的高效的动态数据结构,比如红黑树,查询的时间复杂度就是O(log n)

在这里插入图片描述

将链表法中的链表改造红黑树还有一个非常重要的原因,可以防止DDos攻击

DDos攻击:
分布式拒绝服务攻击(英文意思:Distributed Denial of Service,简称DDos)
指处于不用位置的多个攻击同时向一个或数个目标发动的攻击,或者一个攻击控制了位于不同位置的多台机器并利用这些机器对受害者同时实施攻击,由于攻击的发出点分布在不同的地方,这类攻击成为分布式拒绝服务攻击,其中的攻击者可以是多个。

什么是散列表?

  • 散列表(Hash Table)又名哈希表/Hash表
  • 根据(key)直接访问在内存存储位置值(Value)的数据结构
  • 由数组演化而来的,利用了数组支持按照下标进行随机访问数据

散列冲突

  • 散列冲突又称哈希冲突,哈希碰撞
  • 指多个key映射到同一个数组下标位置

散列冲突-链表法(拉链)

  • 数组的每个下标位置称为桶(bucket)或者槽(slot)
  • hash冲突后的元素都放在相同槽位对应的链表或红黑树中

HashMap的实现原理

HashMap的数据结构:底层是使用了hash表数据结构,即数组和链表或红黑树

  1. 当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
  2. 存储时,如果出现hash值相同的key,此时有两种情况,
    1. 如果key相同,则覆盖原始的值
    2. 如果key不同(出现了冲突),则将当前的key-value放入到链表或红黑树中
  3. 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应的值。

在这里插入图片描述

追问:jdk1.7和jdk1.8有什么区别?

HashMap的jdk1.7和1.8有什么区别

  • JDK1.8之前采用的是拉链法。拉链法:将链表和数组相组合,也就是创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将哈希冲突的值加到链表中即可。
  • JDK1.8在解决哈希冲突时有了较大的变化,当链表的长度大于阈值(默认8)时并且数组长度达到64时,将链表转化为红黑树,以减少搜索时间。扩容resize()时,红黑树拆分成的节点数小于等于临界值6个,则退化成链表。

在这里插入图片描述

说一下HashMap的实现原理

  • 底层使用hash表数据结构,即数组+(链表|红黑树)
  • 添加数据时,计算key的值确定元素在数组中的下标
    • key相同则替换
    • 不同则存入链表或红黑树中
  • 获取数据通过key的hash计算数组下标获取元素

HashMap的jdk1.7和jdk1.8的区别

  • jdk1.8之前采用的拉链法,数组+链表
  • jdk1.8之后采用的数组+链表+红黑树,链表长度大于8且数组长度大于64则会从链表转化为红黑树

HashMap的put方法

HashMap源码分析-常见的属性

static final int DEFAULT_INITIAL_CAPACITY = 1 << 2; // aka 16
static final float DEFAULT_LOAD_FACTOR = 0.75f;
transient HashMap.Node<K,V>[] table;
transient int size;
  • DEFAULT_INITIAL_CAPACITY 默认的初始容量
  • DEFAULT_LOAD_FACTOR 默认负载因子

扩容阈值 == 数组容量 * 加载因子

Node实体类构成:

在这里插入图片描述

HashMap的源码分析

在这里插入图片描述

  • HashMap是懒加载,在创建对象时并没有初始化数组
  • 在无参构造函数中,设置了默认的加载因子是0.75

在这里插入图片描述
在这里插入图片描述

HashMap的put方法的具体流程

  1. 判断键值对数组table是否为空或为null,否则执行resize()进行扩容(初始化)
  2. 根据键值key计算hash值得到数组索引
  3. 判断table[i] == null,条件成立,直接新建节点添加
  4. 如果table[i] == null,不成立
    1. 判断table[i]的首个元素是否和key一样,相同就直接覆盖value
    2. 判断table[i]是否为treeNode,即table[i]是否是红黑树,如果是红黑树,则直接在树中插入键值对
    3. 遍历table[i],在链表的尾部插入数据,然后判断链表的长度是否大于8,大于8的话把链表转化成红黑树,在红黑树中执行插入操作,遍历过程中若发现key已经存在直接覆盖value

HashMap扩容

在这里插入图片描述

讲一讲HashMap的扩容流程

  • 在添加元素或初始化的时候需要调用resize方法进行扩容,第一次添加数据初始化数组长度为16,以后每次扩容都是达到了扩容的阈值(数组长度 * 0.75)
  • 每次扩容的时候,都是扩容之前容量的2倍
  • 扩容之后,会创建一个新的数组,需要把老数组中的数据源挪动到新的数组中
    • 没有hash冲突的节点,则直接使用e.hash & (newCap - 1) 计算新数组的索引位置
    • 如果有红黑树,走红黑树的添加
    • 如果是链表,则需要遍历链表,可能需要拆分链表,判断(e.hash & oldCap)是否为0,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上

在这里插入图片描述

HashMap的寻址算法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

进行二次hash的原因:

因为一次hash之后很多值为存在固定的下标之上,会造成hash存储不均匀,因此使用二次hash的方式,对原来的hash值进行位移的运算,使得数组中的数据存储的更加的均匀

这个也被称为扰动算法,使得hash值更加均匀,减少hash冲突

(n-1)&hash:得到数组中的索引,代替取模,性能更好,

数组的长度必须是2的n次幂

在这里插入图片描述

为何HashMap的数组长度一定是2的次幂?

  1. 计算索引时效率更高:如果是2的n次幂可以使用位于运算代替取模
  2. 扩容时重新计算索引效率更高:hash & oldCap == 0 的元素留在原来的位置,否则新的位置 = 旧位置 + oldCap

HashMap的寻址算法

  • 计算对象的hashCode()
  • 再进行调用hash()方法进行二次哈希,hashcode值右移16位再异或运算,让哈希分布更加均匀
  • 最后(capacity - 1)& hash 得到索引

为何HashMap的数组长度一定是2的次幂?

  • 计算索引时效率更高:如果是2的n次幂可以使用位与运算代替取模
  • 扩容时重新计算索引效率更高:hash & oldCap == 0 的元素留在原来的位置,否则新的位置 = 旧位置 + oldCap

HashMap在jdk1.7情况下多线程死循环问题

jdk1.7的数据结构是:数组+链表

在数组进行扩容的时候,因为链表是头插法,在进行数据迁移的过程中,有可能导致死循环

在这里插入图片描述

  • 变量e指向的是需要迁移的对象

  • 变量next指向的是下一个需要迁移的对象

  • jdk1.7中的链表采用的头插法

  • 在数据迁移的过程中并没有新的对象产生,只是改变了对象的引用

死循环问题的复现:

在这里插入图片描述

线程2扩容后,由于头插法,链表顺序颠倒,但是线程1的临时变量e和next还引用了这两个节点

在这里插入图片描述

因为线程2使用的是头插法,索引在迁移之后原来的A B 会变成B A

由于线程2迁移的时候已经把B的next指向了A

随后线程1 进行操作,然后嫌迁移A 迁移A之后将next中的B赋值给e

在这里插入图片描述

在这里插入图片描述

这是就会产生一个问题 A指向B,B也指向了A

总结:

HashMap在jdk1.7情况下多线程死循环问题

在jdk1.7的hashmap中在数组进行扩容的时候,因为链表是头插法,在进行数据迁移的过程中,有可能会导致死循环

比如说,现在有两个线程

线程一:读取到当前的hashMap数据,数据中的一个链表,在准备扩容时,线程二介入

线程二:也读取了hashmap,直接进行扩容,因为是头插法,链表的顺序会颠倒过来,比如原来的顺序是AB,扩容后的顺序是BA,线程二执行结束

线程一:继续执行的时候就会出现死循环问题。

线程一:现将A移入新的链表,再将B插入到链头,由于另外一个线程的原因,B的next指向了A,所以B->A->B,形成了循环。

当然在jdk 8 将扩容算法做了调整,不再将元素加入链表头(而是保持与扩容前一样的顺序),尾插法,就避免了jdk7中的死循环的问题。

笔记是对黑马课程中的知识进行的个人总结,图片借鉴了课程视频中的资料,感谢黑马程序员的开源精神,哈哈,如有问题联系我删除!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/692867.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

electron实现子窗口中创建右键菜单

后续可能会用electron开发一些工具&#xff0c;包括不限于快速生成个人小程序、开发辅助学习的交互式软件、帮助运维同学一键部署的简易版CICD工具等等。 开发进度&#xff0c;取决于我懒惰的程度。 不过不嫌弃的同学还是可以先关注一波小程序&#xff0c;真的发布工具了&…

Shopee(虾皮)运营没流量?没销量?只因你没掌握店铺引流方法大全

一、站内引流 - 类目 选品&#xff1a;侧重高性价比的潮流商品&#xff0c;及时上架销售热卖商品&#xff1b; 根据目标客户群选品&#xff1a;比如&#xff0c;如果60%-70%的用户为年轻女性&#xff0c;则关注性价比高的潮流商品&#xff1b; 根据重点品类选品&#xff1a;流…

C语言笔记-小智课堂-常用语法

嵌入式常用C语言语法 - 小智课程 类型&字节转换 define语法 define只是单纯替换&#xff0c;如果是运算记得加括号 防止多个文件调用重定义问题 define与typedef&#xff08;替换与别名&#xff09; enum语法 enum用于变量的枚举。 定义枚举类型的变量&#xff0c;变量…

MSP430G2553 Proteus仿真0~5V电压表数码管显示报警系统-0046

MSP430G2553 Proteus仿真0~5V电压表数码管显示报警系统-0046 Proteus仿真小实验&#xff1a; MSP430G2553 Proteus仿真0~5V电压表数码管显示报警系统-0046 功能&#xff1a; 硬件组成&#xff1a;51单片机 8位数码管MAX7219数码管驱动模块多个按键LED灯蜂鸣器 1.准确测量信…

43 # buffer 的应用

buffer Buffer 代表的都是二进制数据&#xff0c;代表是内存&#xff0c;它不能扩容&#xff08;java 数组不能扩容&#xff0c;想扩容可以使用动态数组&#xff0c;或者生成一个新的内存拷贝过去&#xff09; 服务端可以操作二进制&#xff0c;Buffer 可以和字符串进行相互转…

学习adaboost(二,第一次迭代,c#实现)

我觉得这两个公式推导的特别好。我们来搞第一次迭代&#xff1a; 我们取x<2.5,标签1&#xff0c;else&#xff0c;标签-1这个分类器&#xff0c;发现分错的是5&#xff0c;7&#xff0c;8三组数据 &#xff0c;正确的都由0.1变为0.0714了&#xff0c;降低了&#xff0c;错误…

课程20:API项目重构

🚀前言 本文是《.Net Core从零学习搭建权限管理系统》教程专栏的课程(点击链接,跳转到专栏主页,欢迎订阅,持续更新…) 专栏介绍:以实战为线索,基于.Net 7 + REST + Vue、前后端分离,不依赖任何第三方框架,从零一步一步讲解权限管理系统搭建。 专栏适用于人群:We…

Debezium系列之:记录一次Debezium集群服务器端口打满的原因和对应的解决方法

Debezium系列之:记录一次Debezium集群服务器端口打满的原因和对应的解决方法 一、背景二、查看被占端口使用情况三、查看日志四、定位原因五、快速解决六、再次查看服务器端口使用情况七、总结一、背景 运维Debezium集群,停止Debezium集群后,再次启动Debezium集群提示端口被…

一个注解让你的项目减少30%SQL代码量

今天给大家介绍一个很好用的开源项目&#xff1a;easy_trans&#xff0c;它能让你的项目减少30%的SQL代码量&#xff0c;接下来让我们进一步了解它。 什么是Easy_Trans Easy Trans是一款用于做数据翻译的代码辅助插件&#xff0c;利用MyBatis Plus/JPA/BeetlSQL 等ORM框架的能…

算法----使二进制字符串字符交替的最少反转次数

题目 给你一个二进制字符串 s 。你可以按任意顺序执行以下两种操作任意次&#xff1a; 类型 1 &#xff1a;删除 字符串 s 的第一个字符并将它 添加 到字符串结尾。 类型 2 &#xff1a;选择 字符串 s 中任意一个字符并将该字符 反转 &#xff0c;也就是如果值为 ‘0’ &…

django框架-2

创建项目 创建项目文件夹创建项目 django-admin startproject BaseDjangoProject 创建应用 python manage.py startapp goods settings.py 在最后面添加上应用goods INSTALLED_APPS [django.contrib.admin,django.contrib.auth,django.contrib.contenttypes,django.contr…

Java字符串中字符的Unicode码点、编码

以前对于Java字符串中字符的Unicode码点、UTF编码没有仔细研究。今天研究了下。 Unicode是一个字符集&#xff0c;其实是一个映射&#xff0c;给每个字符映射了一个数值&#xff0c;称为码点&#xff08;Code Point&#xff09;。 而UTF-8、UTF-16、UTF-32则是对Unicode码点的转…

shiro反序列化漏洞

Shiro简述 Shiro 是 Java 的一个安全框架&#xff0c;执行身份验证、授权、密码、会话管理shiro默认使用了CookieRememberMeManager&#xff0c;其处理cookie的流程是&#xff1a;得到rememberMe的cookie值–>Base64解码–>AES解密–>反序列化 然而AES的密钥是硬编码…

今日分享:ai绘画工具

凯蒂是一位充满梦想和热情的年轻女孩。她从小就对艺术充满了浓厚的兴趣&#xff0c;尤其痴迷于绘画。然而&#xff0c;她一直感到自己的绘画技巧有限&#xff0c;无法将内心的想象力完美地呈现在画布上。她渴望找到一种方法来提升自己的创作能力&#xff0c;实现内心的艺术梦想…

ModaHub魔搭社区:详解向量数据库Milvus的Mishards:集群分片中间件(一)

目录 Mishards&#xff1a;集群分片中间件 Mishards 是什么 Mishards 简单工作原理 Mishards 目标场景 基于 Mishards 的集群方案 总体架构 主要构件 Mishards 配置 全局配置 Mishards&#xff1a;集群分片中间件 Mishards 是什么 Mishards 是一个用 Python 开发的 …

2020年全国硕士研究生入学统一考试管理类专业学位联考数学试题——纯题目版

2020 级考研管理类联考数学真题 一、问题求解&#xff08;本大题共 15 小题&#xff0c;每小题 3 分&#xff0c;共 45 分&#xff09;下列每题给出 5 个选项中&#xff0c;只有一个是符合要求的&#xff0c;请在答题卡上将所选择的字母涂黑。 1、某产品去年涨价 10%&#xf…

野蛮扩张结束,研发效能提升这场“仗”如何布局?

引言 从第一块石头被打磨成石器&#xff0c;到青铜铸造术被发明&#xff1b;从蒸汽机的改良与广泛应用&#xff0c;到交流电和发电机的问世&#xff0c;纵观人类发展史&#xff0c;人类文明的发展始终依托于生产力与生产效率的不断提升。 身处软件“吞噬”世界的数字化时代&am…

【软考网络管理员】2023年软考网管初级常见知识考点(28)-系统开发和项目管理

涉及知识点 软件生命周期&#xff0c;软件生命周期概念&#xff0c;软件开发模型&#xff0c;瀑布模型&#xff0c;演化、增量模型&#xff0c;喷泉模型&#xff0c;程序控制结构&#xff0c;控制结构种类&#xff0c;软考网络管理员常考知识点&#xff0c;软考网络管理员网络…

jmeter之对常数吞吐量定时器的理解与使用

详细看这个文章&#xff1a;jmeter之对吞吐量定时器的理解与使用_常数吞吐量定时器_xiaokanfuchen86的博客-CSDN博客 下面是我自己的进行的一点补充&#xff1a;如下图&#xff0c;两个请求中&#xff0c;一个下买单一个下卖单&#xff0c;在【下买单-buy】请求下的常数吞吐量…

略读 # SourcererCC: Scaling Code Clone Detection to Big Code

《SourcererCC: Scaling Code Clone Detection to Big Code》代码克隆检测基线方法之一&#xff1b; ABSTRACT 面向的问题&#xff1a;代码克隆检测在海量源码的场景下的扩展性问题&#xff1b; 提出的解决方案&#xff1a;SourcererCC&#xff0c;a token-based clone detec…