并发List、Set、ConcurrentHashMap底层原理

并发List、Set、ConcurrentHashMap底层原理

image-20240218223633770

ArrayList:

List特点:元素有放入顺序,元素可重复

存储结构:底层采用数组来实现

public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  • Cloneable:

支持拷贝:实现Cloneable接口,重写clone方法、方法内容默认调用父类的clone方法

浅拷贝

​ 基础类型的变量拷贝之后是独立的,不会随着原变量变动而改变

String类型拷贝之后也是独立的

引用类型拷贝的是引用地址,拷贝前后的变量引用同一个堆中的对象

public Object clone() throws CloneNotSupportedException {Study s = (Study) super.clone();return s;
}

深拷贝

  • 深拷贝是创建一个新的对象,并将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行逐位复制,如果字段是引用类型的,那么复制引用并指向一个新的对象,而不再是原有的对象。
  • 简单来说,深拷贝就是两个对象不共享内部状态,一个对象的修改不会影响到另一个对象。

ArrayList实现了深拷贝

    public Object clone() {try {ArrayList<?> v = (ArrayList<?>) super.clone();v.elementData = Arrays.copyOf(elementData, size);v.modCount = 0;return v;} catch (CloneNotSupportedException e) {// this shouldn't happen, since we are Cloneablethrow new InternalError(e);}}
  • Serialilzable

    • 序列化:将对象状态转换为可保持或传输的个数的过程
  • AbstractList

    • 继承了AbstractList,说明它是一个列表,有用相应的增、删、查、改等功能
  • List

    • 为什么继承了AbstractList还需要实现List接口
      • 在StackOverFlow 中:传送门 得票最高的答案的回答者说他问了当初写这段代码的 Josh Bloch,得知这就是一个写法错误。

基本属性

//序列化版本号(类文件签名),如果不写会默认生成,类内容的改变会影响签名变化,导致反序列化失败
private static final long serialVersionUID = 8683452581122892189L;//如果实例化时未指定容量,则在初次添加元素时会进行扩容使用此容量作为数组长度
private static final int DEFAULT_CAPACITY = 10;//static修饰,所有的未指定容量的实例(也未添加元素)共享此数组,两个空的数组有什么区别呢? 就是第一次添加元素时知道该 elementData 从空的构造函数还是有参构造函数被初始化的。以便确认如何扩容。空的构造器则初始化为10,有参构造器则按照扩容因子扩容
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // arrayList真正存放元素的地方,长度大于等于size

总结之EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别:EMPTY_ELEMENTDATA是为了优化创建ArrayList空实例时产生不必要的空数组,使得所有ArrayList空实例都指向同一个空数组。DEFAULTCAPACITY_EMPTY_ELEMENTDATA是为了确保无参构成函数创建的实例在添加第一个元素时,*最小的容量*是默认大小10。

添加元素 - 默认尾部添加

效率比较高

指定下标添加元素

public void add(int index, E element) {rangeCheckForAdd(index);//下标越界检查ensureCapacityInternal(size + 1);  //同上  判断扩容,记录操作数//依次复制插入位置及后面的数组元素,到后面一格,不是移动,因此复制完后,添加的下标位置和下一个位置指向对同一个对象System.arraycopy(elementData, index, elementData, index + 1,size - index);elementData[index] = element;//再将元素赋值给该下标size++;

时间复杂度为O(n),与移动的元素个数正相关

扩容:

private void grow(int minCapacity) {int oldCapacity = elementData.length;//获取当前数组长度int newCapacity = oldCapacity + (oldCapacity >> 1);//默认将扩容至原来容量的 1.5 倍if (newCapacity - minCapacity < 0)//如果1.5倍太小的话,则将我们所需的容量大小赋值给newCapacitynewCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)//如果1.5倍太大或者我们需要的容量太大,那就直接拿 newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE 来扩容newCapacity = hugeCapacity(minCapacity);elementData = Arrays.copyOf(elementData, newCapacity);//然后将原数组中的数据复制到大小为 newCapacity 的新数组中,并将新数组赋值给 elementData。

迭代器 iterator

public Iterator<E> iterator() {return new Itr();
}
private class Itr implements Iterator<E> {int cursor;       // 代表下一个要访问的元素下标int lastRet = -1; // 代表上一个要访问的元素下标int expectedModCount = modCount;//代表对 ArrayList 修改次数的期望值,初始值为 modCount//如果下一个元素的下标等于集合的大小 ,就证明到最后了public boolean hasNext() {return cursor != size;}@SuppressWarnings("unchecked")public E next() {checkForComodification();//判断expectedModCount和modCount是否相等,ConcurrentModificationExceptionint i = cursor;if (i >= size)//对 cursor 进行判断,看是否超过集合大小和数组长度throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length)throw new ConcurrentModificationException();cursor = i + 1;//自增 1。开始时,cursor = 0,lastRet = -1;每调用一次next方法,cursor和lastRet都会自增1。return (E) elementData[lastRet = i];//将cursor赋值给lastRet,并返回下标为 lastRet 的元素}public void remove() {if (lastRet < 0)//判断 lastRet 的值是否小于 0throw new IllegalStateException();checkForComodification();//判断expectedModCount和modCount是否相等,ConcurrentModificationExceptiontry {ArrayList.this.remove(lastRet);//直接调用 ArrayList 的 remove 方法删除下标为 lastRet 的元素cursor = lastRet;//将 lastRet 赋值给 cursolastRet = -1;//将 lastRet 重新赋值为 -1,并将 modCount 重新赋值给 expectedModCount。expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}

remove方法的弊端

  • 只能进行remove操作,add、clear等Iterator中没有
  • 调用remove之前必须调用next。因为remove开始就对lastRet做了校验。而lastRet初始化时为-1
  • next之后只可以调用一次remove。因为remove会将lastRet重新初始化为-1

什么是fail-fast

fail-fast机制是java集合中的一种错误机制。

当使用迭代器迭代时,如果发现集合有修改,则快速失败做出响应,抛出ConcurrentModificationException异常。

这种修改有可能是其它线程的修改,也有可能是当前线程自己的修改导致的,比如迭代的过程中直接调用remove()删除元素等。

另外,并不是java中所有的集合都有fail-fast的机制。比如,像最终一致性的ConcurrentHashMap、CopyOnWriterArrayList等都是没有fast-fail的。

fail-fast是怎么实现的:

ArrayList、HashMap中都有一个属性modcount,每次对集合的修改这个值都会加1,在遍历前记录这个值expert*count中,遍历中检查两者是否一致,如果出现不一致就说明有修改,则抛出ConcurrentModificationException异常。

底层数组存/取元素效率非常的高(get/set),时间复杂度是O(1),而查找(比如:indexOf,contain),插入和删除元素效率不太高,时间复杂度为O(n)。

插入/删除元素会触发底层数组频繁拷贝,效率不高,还会造成内存空间的浪费,解决方案:linkedList

查找元素效率不高,解决方案:HashMap(红黑树)

LinkedList

存储结构:底层采用链表来实现

HashSet(Set):

特点:元素无放入顺序,元素不可重复(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的)

存储结构

底层采用HashMap来实现

HashMap(Map):

特点:key,value存储,key可以为null,同样的key会被覆盖掉

存储结构:底层采用数组、链表、红黑树来实现

原理讲解:

哈希算法(也叫散列),就是把任意长度值(Key)通过散列算法变换成固定长度的key(地址)通过这个地址进行访问的数据结构它通过把关键码值映射到表中一个。位置来访问记录,以加快查找的速度。

image-20240219230658004

image-20240219230720082

链表查询的时候链表过长查询效率非常低,所以需要红黑树

JDK8中的HashMap与JDK7中的HashMap有什么不同
  • JDK8中新增了红黑树,JDK8是通过数组 + 链表 + 红黑树来实现的
  • JDK7中链表的插入是用的头插法,而JDK8中则改为了尾插法
  • JDK8中因为使用了红黑树保证了插入和查询的效率,所以实际上JDK8中的Hash算法实现的复杂度降低了
  • JDK8中数组扩容的条件也发生了变化,只会判断是否当前元素的个数是否查过了阈值,而不再判断当前put进来的元素对应的数组小标位置是否有值
  • JDK7是先扩容再添加新的元素、JDK8是先添加新元素然后再扩容
HashMap中put方法的流程
  • 通过key计算出一个hashcode
  • 通过hashcode与"与操作"计算出一个数组下标
  • 在把put进来的key、value封装成一个entry对象
  • 判断数组下标对应的位置,是不是为空,如果是空则把entry直接存在改数组位置
  • 如果改下标对应的位置不为空,则需要把entry插入到链表中
  • 并且还需要判断改链表中是否存在相同的key,如果存在,则更新value
  • 如果是JDK7使用头插法
  • 如果是JDK8,则会遍历链表,遍历链表的过程中,统计当前链表的元素个数,如果超过8个,则先把链表转变为红黑树、并且把元素插入到红黑树中
JDK中链表转变为红黑树的条件
  • 链表中的元素的个数为8个或超过8个
  • 同时,还需要满足当前数组的长度大于或等于64才会把链表转变为红黑树
    • 因为链表转变为红黑树的目的是为了解决链表过长,导致查询和插入效
      率慢的问题,而如果要解决这个问题,也可以通过数组扩容,把链表缩短也可
      以解决这个问题。所以在数组长度还不太长的情况,可以先通过数组扩容来解
      决链表过长的问题
HashMap扩容流程是怎样的?
  • HashMap的扩容指的就是数组的扩容, 因为数组占用的是连续内存空间,所以数组的扩容其实只能新开一个新的数组,然后把老数组上的元素转移到新
    数组上来,这样才是数组的扩容
  • 在HashMap中也是一样,先新建一个2被数组大小的数组
  • 然后遍历老数组上的没一个位置,如果这个位置上是一个链表,就把这个链表上的元素转移到新数组上去
  • 在这个过程中就需要遍历链表,当然jdk7,和jdk8在这个实现时是有不一样的,jdk7就是简单的遍历链表上的没一个元素,然后按每个元素的hashcode结合新数组的长度重新计算得出一个下标,而重新得到的这个数组下标很可能和之前的数组下标是不一样的,这样子就达到了一种效果,就是扩容之后,某个链表会变短,这也就达到了扩容的目的,缩短链表长度,提高了查询效率
  • 而在jdk8中,因为涉及到红黑树,这个其实比较复杂,jdk8中其实还会用到一个双向链表来维护红黑树中的元素,所以jdk8中在转移某个位置上的元素
    时,会去判断如果这个位置是一个红黑树,那么会遍历该位置的双向链表,遍历双向链表统计哪些元素在扩容完之后还是原位置,哪些元素在扩容之后在新位置,这样遍历完双向链表后,就会得到两个子链表,一个放在原下标位置,一个放在新下标位置,如果原下标位置或新下标位置没有元素,则红黑树不用拆分,否则判断这两个子链表的长度,如果超过八,则转成红黑树放到对应的位置,否则把单向链表放到对应的位置
  • 元素转移完了之后,在把新数组对象赋值给HashMap的table属性,老数组会被回收到
为什么HashMap的数组的大小是2的幂次方

JDK7的HashMap是数组+链表实现的,JDK8的HashMap是数组+链表+红黑树实现的

当某个key-value对需要存储到数组中时,需要先生成一个数组下标index,并且这个index不能越界。

在HashMap中,先得到key的hashcode,hashcode是一个数字,然后通过hashcode & (table.length - 1) 运算得到一个数组下标index,是通过与运算计算出来一个数组下标的,而不是通过取余,与运算相比于取余运算速度更快,但是也有一个前提条件,就是数组的长度得是一个2的幂次方数。

ConcurrentHashMap

特点:并发安全的HashMap,比HashTable效率更高

存储结构:底层采用数组、链表、红黑树、内部大量采用CAS操作。并发控制使用synchronized和CAS来操作来实现的。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C 语言基本语法及实用案例分享

一、什么是 C 语言&#xff1f; C语言是一种较早的程序设计语言&#xff0c;诞生于1972年的贝尔实验室。1972 年&#xff0c;Dennis Ritchie 设计了C语言&#xff0c;它继承了B语言的许多思想&#xff0c;并加入了数据类型的概念及其他特性。C语言是一门面向过程的计算机编程语…

Unity NavMesh 清除不可行走区域

通常场景中物体设置为static或Navigation Static后&#xff0c;打开Navigation使用默认设置烘焙NavMesh&#xff0c;模型顶部和底部会出现蓝色网格&#xff0c;但其中有部分属于不可能到达区域&#xff0c;如下图 本文介绍两种可去掉NavMesh中不需要网格的方法&#xff1a; 方…

OpenCV运行gstreamer管道获取相机数据,处理以后,再交给gstreamer显示(QT实现)

效果: 前言 无意中发现,OpenCV也可以运行gstreamer的命令管道,然后使用appsink来与OpenCV连接起来进行处理,在不断测试之下,先后实现了以下功能: 1. OpenCV运行gstreamer命令,通过appsink传递给OpenCV显示 2. OpenCV运行gstreamer命令,然后再把Mat图像数据通过appsrc传…

(3)(3.6) 用于OpenTX的Yaapu遥测脚本

文章目录 前言 1 安装和操作 2 参数说明 前言 这是一个开源 LUA 脚本&#xff0c;用于在使用 OpenTX 2.2.3 的 Horus X10、X12、Jumper T16、T18、Radiomaster TX16S、Taranis X9D、X9E、QX7 和 Jumper T12 无线电设备上显示 FrSky 的直通遥测数据(FrSky passthrough telem…

解决IntelliJ IDEA 2023版本创建Spring项目时Java只能选择17或21的问题

问题描述&#xff1a; 当使用IntelliJ IDEA2023版本中Spring Initializr新建Spring项目时&#xff0c;即使JDK配置项为1.8&#xff0c;Java配置项仍然只能选17或21. 在JDK为1.8版本情况下&#xff0c;Java选择17或21&#xff0c;点击NEXT按钮&#xff0c;则会弹窗提示SDK不支持…

航空航天5G智能工厂数字孪生可视化平台,推进航空航天数字化转型

航空航天5G智能工厂数字孪生可视化平台&#xff0c;推进航空航天数字化转型。随着科技的不断发展&#xff0c;数字化转型已经成为各行各业关注的焦点。航空航天业作为高端制造业的代表&#xff0c;也在积极探索数字化转型之路。为了更好地推进航空航天数字化转型&#xff0c;一…

安卓游戏开发之音频技术优劣分析

一、引言 在安卓游戏开发中&#xff0c;音频处理技术扮演着至关重要的角色&#xff0c;它不仅能够增强游戏的沉浸感和玩家体验&#xff0c;还能通过声音效果传达关键的游戏信息。以下将对几种常见的安卓游戏音频处理技术进行优劣分析&#xff0c;并结合应用场景来阐述其特点。 …

十九、图像的放缩和插值

项目功能实现&#xff1a;对一张图像进行放大和缩小操作 按照之前的博文结构来&#xff0c;这里就不在赘述了 一、头文件 resizing.h #pragma once#include<opencv2/opencv.hpp>using namespace cv;class RESIZING { public:void resizing(Mat& image); };#pragma…

【Linux】进程状态

进程状态 进程状态的简要介绍运行状态进程排队 阻塞状态挂起状态Linux中的进程状态 进程状态的简要介绍 进程状态指的是一个操作系统中正在运行的进程当前所处的状态。根据不同的操作系统&#xff0c;进程状态可能会有一些细微的差别&#xff0c;但最主要的是以下三种状态 运行…

电路设计(26)——速度表的multisim仿真

1.设计要求 设计一款电路&#xff0c;能够实时显示当前速度。 用输入信号模拟行驶的汽车&#xff0c;信号频率的1hz代表汽车速度的1m/s。最后速度显示&#xff0c;以km/h为单位。 2.电路设计 当输入信号频率为40HZ时&#xff0c;显示的速度应该为144KM/h&#xff0c;仿真结果为…

IP地址证书详解

IP地址证书是一种特殊的SSL/TLS证书&#xff0c;它直接绑定到服务器的公网IP地址而不是域名&#xff0c;这种类型的证书特别适合于那些没有域名只有公网IP或者不方便使用域名的企业或个人。证书允许通过特定的IP地址访问的Web服务提供HTTPS加密连接&#xff0c;确保在没有使用域…

深入理解flinksql执行流程,calcite与catalog相关概念,扩展解析器实现语法的扩展

深入理解Flink Sql执行流程 1 Flink SQL 解析引擎1.1SQL解析器1.2Calcite处理流程1.2.1 SQL 解析阶段&#xff08;SQL–>SqlNode&#xff09;1.2.2 SqlNode 验证&#xff08;SqlNode–>SqlNode&#xff09;1.2.3 语义分析&#xff08;SqlNode–>RelNode/RexNode&#…

EfficientNet环境搭建网络修改

引子 在深度学习CV领域&#xff0c;最初2012年突破的就是图像分类&#xff0c;发展这么多年&#xff0c;基本上已经没有什么进展了。此篇作为之前EfficientNet挽留过的总结&#xff0c;现在整理下&#xff0c;OK&#xff0c;让我们开始吧。 一、EfficientNet安装 1、pytorch…

Window部署Exceptionless

Exceptionless Elasticsearch 版本&#xff1a; Exceptionless&#xff1a;8.1.0 Elasticsearch&#xff1a;7.17.5 JDK&#xff1a;11.0.10 目录 一、Elasticsearch运行 二、 Exceptionless 一、Elasticsearch运行 bin目录下elasticsearch.bat 直接运行 访问 http://lo…

线性代数:向量空间

目录 向量空间 Ax 0 的解空间S Ax b 的全体解向量所构成集合不是向量空间 基、维数、子空间 自然基与坐标 例1 例2 向量空间 Ax 0 的解空间S Ax b 的全体解向量所构成集合不是向量空间 基、维数、子空间 自然基与坐标 例1 例2

JAVA工程师面试专题-并发编程篇

目录 一、线程 1、并发与并行的区别 2、同步和异步的区别 3、Java中创建线程有哪些方式? 4、Thread和Runnable的区别 5、Java中的Runnable、Callable、Future、FutureTask的区别和联系&#xff1f; 6、说一下你对 CompletableFuture 的理解 7、volatile关键字有什么用&…

React18原理: React核心对象之Update、UpdateQueue、Hook、Task对象

Update 与 UpdateQueue 对象 1 ) 概述 在fiber对象中有一个属性 fiber.updateQueue是一个链式队列&#xff08;即使用链表实现的队列存储结构&#xff09;是和页面更新有关的 2 &#xff09;Update对象相关的数据结构 // https://github.com/facebook/react/blob/v18.2.0/pa…

台式电脑电源功率越大越费电吗?装机选购多少W电源

要组装一台电脑&#xff0c;我们首先需要选择硬件。 硬件搭配最关键的一点就是CPU和主板的兼容性。 硬件、电源等之间的平衡都需要仔细考虑。 那么台式电脑电源多大功率合适呢&#xff1f; 下面分享组装电脑电源瓦数选购指南&#xff0c;教您正确选择合适的电源瓦数。 让我们来…

PULpy安装与使用

今天试一下安装PULpy GitHub - WatsonLab/PULpy: Open prediction of Polysaccharide Utilisation Loci (PUL) 下载下面这个文件 https://github.com/WatsonLab/PULpy/blob/master/envs/PULpy.yaml mkdir PULpy cd PULpy #将刚刚下的文件放到PULpy文件夹中 conda env crea…

win系统下安装php8.3版本并配置环境变量的详细教程

本篇文章主要讲解在win系统下安装和配置php8.3版本&#xff0c;并配置环境变量的详细教程。 日期&#xff1a;2024年2月22日 作者&#xff1a;任聪聪 一、下载php8.3版本包 php8.3版本官方下载地址&#xff1a;https://windows.php.net/download#php-8.3 步骤一、打开下载地址…