常见集合篇

一,ArrayList和LinkList

区别和联系:

1.底层数据结构:ArrayList是基于动态数组的数据结构实现,LinkList是基于双向链表的数据结构实现。

2.操作效率:ArrayList按照下标查询的时间复杂度为O(1),因为内存是连续的,根据寻址公式(首地址+索引*存储数据类型的大小),LinkList不支持下标查询,但是都支持遍历查询,时间复杂度都为O(n)。
新增删除:两个头尾的增加和删除元素都比较简单,时间复杂度为O(1),但是中间部分的时间复杂度为O(n)。

3.内存空间的占用:ArrayList内存是连续的,底层是数组,节省内存,而LinkList需要存储数据,和两个指针,比较占用内存

4.线程安全:总的来说两个都不是线程安全的,但是如果说在方法内使用则可以保证线程安全,或者使用synchronized包装

二,HashMap

二叉树:

1.什么是二叉树?

每个节点最多有两个叉,分别是左右子树,不要求每个节点都有两个子节点,

2.什么是二叉搜索树?

又名二叉查找树,有序二叉树,该节点的左子树的值小于该节点的值,右子树的值大于该节点的值,没有键值相等的节点,时间复杂度为O(logn)

3.什么是红黑树?

又叫自平衡的二叉搜索树,节点要么红色要么黑色,根节点是黑色,,叶子节点都是黑色的空节点,红色的子节点都是黑色。

4.什么是散列表?

它又叫哈希表,根据键key直接访问在内存存储位置中的值value的数据结构,它是由数组演化而来的。散列函数:hashValue=hash(key)

哈希冲突:当不同的key经过hash函数得到的value的地址值相同时,就会产生冲突,

解决方法1:数组的每个下标位置称之为桶或者是槽,在桶的后面会有一个链表(当列表的长度大于8,数组长度大于64才会转换成红黑树),所有散列值相同的元素放在链表中。

HashMap实现原理

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

2.HashMap的put方法的具体流程?

  1. 首先,根据键的哈希值(通过调用键对象的hashCode方法获得)和HashMap的容量计算出该键值对应的数组索引位置。这个过程称为哈希定位。

  2. 然后,检查该索引位置是否已经存在元素。如果该位置没有元素,则直接将键值对插入到该位置。

  3. 如果该位置已经存在元素(可能是一个键值对,也可能是链表或者红黑树),则需要进行进一步的处理:

    • 如果该位置上的元素是一个链表或者红黑树,说明发生了哈希冲突,即不同的键具有相同的哈希值。这时候会根据键的equals方法比较键的值:
      • 如果存在相同的键,则更新该键对应的值。
      • 如果不存在相同的键,则将新的键值对添加到链表或红黑树的末尾。
    • 如果该位置上的元素不是链表或红黑树,且与要插入的键值对的键不相等,说明该位置已经存在一个键值对,需要进行扩容。HashMap会将数组容量增加一倍,并将原有的键值对重新哈希到新的数组中。
  4. 如果插入操作成功,HashMap会更新键值对的数量,并根据需要进行扩容操作。

HashMap的扩容机制

  1. 创建一个新的数组,其长度是当前数组长度的两倍。

  2. 遍历原数组中的每个位置,将其中的键值对重新计算哈希值,并放入新数组的对应位置中。

  3. 扩容后的数组中可能存在哈希冲突,即不同的键计算得到的哈希值相同,这时候需要进行链表转换或者树化操作。

    • 如果某个位置上的链表长度小于8,仍然使用链表存储键值对。
    • 如果链表长度达到8,则将链表转换为红黑树,以提高查询、插入、删除的性能。
  4. 扩容完成后,HashMap会更新容量和阈值(负载因子与容量的乘积),以便在下次插入操作时继续判断是否需要扩容。

扩容过程是一个相对耗时的操作,因为需要重新计算哈希值并重新分配元素的位置。但是它能够有效地减少哈希冲突,提高HashMap的性能。

HashMap的寻址算法

  1. 首先,计算键的哈希值。哈希值是一个32位的整数,用于表示键的特征码。

  2. 对哈希值进行再哈希(hash & (length-1))。这个操作的目的是为了将哈希值映射到数组的索引位置。这里的(length-1)是因为HashMap的容量总是2的幂次方,这样可以保证再哈希的结果在数组索引范围内。

  3. 得到的索引就是键值对在数组中的位置。如果该位置上已经存在元素,则可能会发生哈希冲突。

在发生哈希冲突时,HashMap采用的解决方法是链地址法(Separate Chaining):

  • 如果哈希冲突发生在数组的某个位置上,HashMap会将具有相同哈希值的键值对存储在同一个位置上,形成一个链表。
  • 当需要查找或插入键值对时,HashMap会根据键的哈希值定位到数组的位置,然后遍历该位置上的链表,通过比较键的equals方法找到对应的键值对。

HashMap的死循环问题

HashMap的死循环问题通常指的是在多线程环境下,对HashMap进行并发的插入、删除等操作时可能会导致的死循环情况。这种情况主要是由于HashMap在进行扩容(rehash)操作时,可能会出现环形链表,从而导致死循环。

具体来说,当多个线程同时对HashMap进行插入操作时,可能会触发HashMap的扩容操作。在扩容过程中,原数组中的元素需要重新分配到新的数组中,如果多个线程同时进行这个操作,并且它们互相干扰了对方正在进行的操作,就可能导致环形链表的产生。这种环形链表可能会导致某些线程在遍历HashMap时陷入死循环。

为了解决这个问题,可以采用以下几种方法:

  1. 使用并发安全的HashMap实现,例如ConcurrentHashMap。ConcurrentHashMap采用了分段锁(Segment)的机制,每个Segment上有一个锁,不同的Segment可以同时进行操作,从而减少了线程之间的竞争,避免了死循环问题。

  2. 在多线程环境下,尽量避免对HashMap进行并发的插入、删除操作。如果确实需要进行并发操作,可以采用显式的同步措施(如使用同步代码块或锁)来确保线程安全。

  3. 在Java 8及更新版本中,可以考虑使用并发工具类中提供的线程安全的集合实现,如ConcurrentHashMap、CopyOnWriteArrayList等,以减少在多线程环境下出现死循环的可能性。

  • 14
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值