从源码分析为什么ArrayList查询快,LinkedList增删快,HashMap线程不安全

从源码分析为什么ArrayList查询快,LinkedList增删快,HashMap线程不安全

1.ArrayList为什么查询快,增删慢呢?

一说到ArrayList和LinkedList,相信大家都知道查询的场景用ArrayList,需要经常增删的场景用LinkedList,那到底是为什么呢?因为ArrayList是动态数组,查询快;LinkedList是链表结构,插入删除快?这个回答过于肤浅,再往细点深究一下,其实还蛮有意思的。先说说ArrayList。

ArrayList是动态数组,初始容量为10,元素大于容量会自动扩展,新容量 = 旧容量 + (旧容量 >> 1)
先来看看ArrayList是如何扩容的。
在这里插入图片描述
追踪ArrayList的add方法,最后可以找到一个grow方法,也就是用于扩容的方法。
在这里插入图片描述
elementData是一个Object数组,用于存放ArrayList里面的数据,newCapacity是扩容后数组的大小,调用的方法是数组的复制方法,而这个方法最底层调用的是System.arraycopy方法
在这里插入图片描述
新建一个数组来进行复制,最后才能达成新增的效果,显然是十分耗时的。删除也是同理。再来看下ArrayList的查询
在这里插入图片描述
rangeCheck是用来校验下标是否越界的
在这里插入图片描述
从源码来看,ArrayList通过下标直接获取对应元素,所以查询快。

2.LinkedList的快速增删是如何实现的?

先来看看LinkedList里面有什么
在这里插入图片描述
LinkedList是双向链表,包含一个非常重要的内部类Node,Node里包含节点数据、上一个节点、下一个节点
在这里插入图片描述
链表的结构大体如下
在这里插入图片描述
每个节点都会和上下两个节点相连,并且会有一个头节点。重点来了,因为有头节点的存在,所以在新增时,肯定会去判断链表是否有头节点。来看下源码
在这里插入图片描述
在这里插入图片描述
源码好像不太好理解,那就自己写一个把。
在这里插入图片描述
首先,插入的时候先判断是否有头节点,如果没有,那么第一个插入的元素就是头节点,并且让的上下节点都是自己。如果已经有了头节点,那么就改变节点的上下关联关系。在这里插入图片描述
测试下自己写的链表有没有问题
在这里插入图片描述
最后来看下链表的查询是如何实现的。
在这里插入图片描述
在这里插入图片描述
还是一样,先做下标是否越界的判断,然后在查询元素时,LinkedList在定位index时会先判断该位置是在1/2前还是后,然后在进行遍历依次查找。 相比于ArrayList直接根据index从数组中取出该位置的元素,不需要进行遍历,显然LinkedList的查询效率不如ArrayList。

3.HashMap和ConcurrentHashMap

HashMap存储的是键值对,底层结构是数组加单向链表。可以有一个为null的健。HashMap的容量是有限的。当经过多次元素插入,使得HashMap达到一定饱和度时,Key映射位置发生冲突的几率会逐渐提高。这时候,HashMap需要扩展它的长度,也就是进行Resize。至于Resize为什么会造成环形链表,我觉得自己水平不够,还解释不清,给大家推荐一篇文章。
https://mp.weixin.qq.com/s?__biz=MzIxMjE5MTE1Nw==&mid=2653192000&idx=1&sn=118cee6d1c67e7b8e4f762af3e61643e&chksm=8c990d9abbee848c739aeaf25893ae4382eca90642f65fc9b8eb76d58d6e7adebe65da03f80d&scene=21#wechat_redirect
总之,在高并发下,HashMap可能会发生环形链表,导致不可用。所以就有了ConcurrentHashMap这种数据类型。ConcurrentHashMap中引入了一新的理念 - Segment。Segment本身就是一个HashMap对象,同HashMap一样,Segment包含一个HashEntry数组,数组中的每一个HashEntry既是一个键值对,也是一个链表的头节点。在ConcurrentHashMap中,有2的N次方个Segment。共同保存在一个Segments数组中。来看下源码
在这里插入图片描述
在这里插入图片描述可以说,ConcurrentHashMap是一个二级哈希表。在一个总的哈希表下面,有若干个子哈希表。ConcurrentHashMap的优势是采用了锁分段技术,每一个Segment就好比一个自治区,读写操作高度自治,Segment之间互不影响。
当不同Segment的并发写入时,不同的Segment的写入是可以并发的。
当同一Segment的并发读写时,读写操作是可以并发执行的。
当同一Segment的并发写入时,Segment的写入是需要上锁的,因此对同一Segment的并发写入会被阻塞。
由此可见,ConcurrentHashMap当中每个Segment各自持有一把锁。在保证线程安全的同时降低了锁的粒度,让并发操作效率更高。

猜你喜欢

转载自blog.csdn.net/weixin_43776741/article/details/100007071