【ArrayList】JDK1.8源码详细注释 以及如何实现线程安全的链表
ArrayList(JDK8)
- ArrayList有四个内部类,成员内部类Itr,成员内部类ListItr,静态内部类SubList,ArrayListSpliterator(暂时用不到)
- Itr是Iterator的实现类,支持正向遍历,ArrayList的iterator方法返回一个Itr对象
- ListItr是ListIterator的实现类,支持双向遍历,ArrayList的listIterator方法返回一个ListIterator类对象
Itr
-
增强 for 遍历数组时, 被编译成普通 for 循环, 增强 for 遍历集合时, 被编译成使用 Iterator; 无论是数组还是集合, 只用增强 for 都无法修改原本引用的指向;
-
单步迭代中, 不允许多次调用迭代器的 remove 方法; 逻辑上来说, 你迭代一次, 当然只能判断当前的对象是不是需要被删除, 干嘛要多次删除? 其次, 这样也能让迭代器的代码逻辑更简洁, 避免很多边界条件的判断, 也能避免很多潜在的错误;
-
如果要通过循环删除 List 中的所有元素, 可以这样做
for(int i = 0; i < list.size(); i++){list.remove(i);i--; }// 或者 // removeIf 的本质就是迭代器实现的; list.removeIf(i->true);// 或者通过迭代器;
// ArrayList的
public Iterator<E> iterator() {return new Itr();
}
// 作为ArrayList的成员内部类
private class Itr implements Iterator<E> {int cursor; // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no such// 显式赋值,让自己的expectedModCount = ArrayList.this.modCount// 在内部类的成员方法和构造函数中,隐含了ArrayList.this和this传参int expectedModCount = modCount;// prevent creating a synthetic constructorItr() {}public boolean hasNext() {// ArrayList.this.sizereturn cursor != size;}@SuppressWarnings("unchecked")public E next() {checkForComodification();int i = cursor;if (i >= size)throw new NoSuchElementException();// 创建一个引用指向外围类对象的底层数组,方便后面使用Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length)throw new ConcurrentModificationException();cursor = i + 1;return (E) elementData[lastRet = i];}public void remove() {if (lastRet < 0)throw new IllegalStateException();checkForComodification();try {// 局部内部类对象依附于外围类对象而存在,持有外围类对象指针ArrayList.this// 这里修改了所依附的外围类对象arrayList的modCountArrayList.this.remove(lastRet);// 删除时cursor要往前移动一位cursor = lastRet;// 防止连续删除lastRet = -1;// 更新自己的modCountexpectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}
}
sublist
public List<E> subList(int fromIndex, int toIndex) {subListRangeCheck(fromIndex, toIndex, size);return new SubList<>(this, fromIndex, toIndex);
}// 本身也实现了AbstracList,有add,remove等等常见方法,不再列出
// 但是要记住它的add,remove等等方法修改的都是源ArrayList!
// 重点看iterator()方法返回的匿名内部类对象
private static class SubList<E> extends AbstractList<E> implements RandomAccess {// 如果是从ArrayList投影来的,让root指向源集合private final ArrayList<E> root;// 如果是Sublist又subList来的,让parent = 源Sublist, root = parent.root// 从SubList再截取时,行为比较特殊,会继续向上去找源ArrayList,迭代器依旧在ArrayList上进行,像 双亲委派模型private final SubList<E> parent;// 在源中的偏移量,例如从ArrayList第二个元素截取,offset = 1private final int offset;// sublist的长度private int size;// 从ArrayList截取Sublistpublic SubList(ArrayList<E> root, int fromIndex, int toIndex) {this.root = root;this.parent = null;this.offset = fromIndex;this.size = toIndex - fromIndex;// this.modCount是从AbstractList继承来的this.modCount = root.modCount;}// Sublist自己可以继续sublistpublic List<E> subList(int fromIndex, int toIndex) {subListRangeCheck(fromIndex, toIndex, size);return new SubList<>(this, fromIndex, toIndex);}// 从Sublist截取private SubList(SubList<E> parent, int fromIndex, int toIndex) {// 无论subList几次,root始终指向源ArrayListthis.root = parent.root;this.parent = parent;// 记录的是相对于源ArrayList的偏移量this.offset = parent.offset + fromIndex;this.size = toIndex - fromIndex;// this.modCount = 上级modCount = 。。。最终 = 源ArrayList的modCount this.modCount = parent.modCount;}// SubList的removepublic E remove(int index) {Objects.checkIndex(index, size);checkForComodification();E result = root.remove(offset + index);// 更新自己的modCount和root的一样, 自己的size-1updateSizeAndModCount(-1);return result;}// Sublist无论是iterator还是listIterator,返回的都是listIterator子类对象public Iterator<E> iterator() {// 调用从AbstractList继承的listItoreator// 其实现是调用listIterator(0)return listIterator();}// index = 0public ListIterator<E> listIterator(int index) {checkForComodification();rangeCheckForAdd(index);// 是Sublist的局部内部类// 只列出了next方法,其余的方法大同小异,最终也都是在原ArrayList上操作return new ListIterator<E>() { int cursor = index;int lastRet = -1;// 也就是源ArrayList的modCountint expectedModCount = SubList.this.modCount;public boolean hasNext() {return cursor != SubList.this.size;}@SuppressWarnings("unchecked")public E next() {// 检查并发修改异常时也是和ArrayList对比checkForComodification();int i = cursor;if (i >= SubList.this.size)throw new NoSuchElementException();Object[] elementData = root.elementData;if (offset + i >= elementData.length)throw new ConcurrentModificationException();cursor = i + 1;// 真正遍历时实际上是用 offset + cursor 去遍历源集合return (E) elementData[offset + (lastRet = i)];}public void remove() {if (lastRet < 0)throw new IllegalStateException();checkForComodification();try {// 调用外围类Sublist的remove方法,最终还是修改了原ArrayListSubList.this.remove(lastRet);cursor = lastRet;lastRet = -1;expectedModCount = SubList.this.modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}final void checkForComodification() {if (root.modCount != expectedModCount)throw new ConcurrentModificationException();}}; // return匿名内部类对象} // public ListIterator<E> listIterator(int index)
} // Sublist
容量
-
add 和 addAll 会检查容量是否够用,即 size 是否已经 ==capacity 或者能否放得下加入的集合,不够的时候才扩容;
-
无论add还是 addAll, 扩容机制都是在需要增长到的容量和原容量1.5倍之间选择大的进行扩容;除了无参构造首次扩容有些特殊, 直接扩容到10 ;
-
如果是无参构造创建的ArrayList,首次添加第一个元素时,扩容到10,如果首次直接使用 addAll 添加集合c,会有特殊判断, 扩容到 max { c.length , 10 }
-
如果是有参构造,指定多少就立即分配多少, 指定 size == 0 时除外
/**
* 首先区分容量capacity(底层数组能放多少)
* 和大小size(集合的逻辑大小,底层数组实际使用了多少)
*/// 默认容量10
private static final int DEFAULT_CAPACITY = 10;//
private static final Object[] EMPTY_ELEMENTDATA = {};//
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};transient Object[] elementData;private int size;// 1. 无参构造时,使用空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA作为底层数组,初次扩容时作为标记
// 初始容量实际上为0
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}// 2. 有参构造时,如果传入的容量为0,底层使用空数组EMPTY_ELEMENTDATA,初次扩容时和
// DEFAULTCAPACITY_EMPTY_ELEMENTDATA采用不同的策略
public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {this.elementData = EMPTY_ELEMENTDATA;} else {throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);}
}// 1. 无参构造首次添加元素时elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; size = 0;
public boolean add(E e) {modCount++;add(e, elementData, size);return true;
}// 1. if 条件满足,调用grow扩容
private void add(E e, Object[] elementData, int s) {if (s == elementData.length)elementData = grow();elementData[s] = e;
}public boolean addAll(Collection<? extends E> c) {Object[] a = c.toArray();modCount++;int numNew = a.length;if (numNew == 0)return false;Object[] elementData;final int s;// 如果elementData不足以放下所有元素,扩容if (numNew > (elementData = this.elementData).length - (s = size))elementData = grow(s + numNew);System.arraycopy(a, 0, elementData, s, numNew);size = s + numNew;return true;
}// 1. 调用grow(1)
private Object[] grow() {return grow(size + 1);
}// 1.1 如果是add方法添加单个元素,minCapacity = 1
// 1.2 如果是addAll方法添加集合c,minCapacity = size + c.length
private Object[] grow(int minCapacity) {// 1. oldCapacity = 0int oldCapacity = elementData.length;// 2. 首次添加单个元素,minCapacity = 1,oldCapacity = 0; minCapacity是指总容量最少为多少// minGrowth = 1; old/2 = 0; 最终增长1// 再加一个元素, minGrowth=1;old/2 = 0, 增长1,size=2// 再加,只要是add(E e),minGrowth就是1, old/2 = 1; 增长1, size = 3// 再加,加1; 再加,加2, 直到第五次添加元素, 开始加 > 1// 而如果添加多个元素,要对比添加元素个数和原本容量的0.5倍哪个更大if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity, /* minimum growth */oldCapacity >> 1 /* preferred growth */);return elementData = Arrays.copyOf(elementData, newCapacity);} else {// 1. 在minCapacity 和 DEFAULT_CAPACITY(10) 之间选大的那个// 这里只会在无参构造的ArrayList首次扩容的时候执行到return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];}
}// ArraysSupport类
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {// 在最小需要增量和0.5倍原容量中选择大的那个,加上原容量,作为新容量的大小// 例如原容量为10,addAll添加了一个长度为6的集合,那么传入的oldLength = 10;// minGrowth = 6,此方法返回 10 + 6int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflowif (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {return prefLength;} else {// put code cold in a separate methodreturn hugeLength(oldLength, minGrowth);}
}// 可以手动调用ensureCapacity来无条件扩容,参数是总容量,不是要扩展的容量
// 手动调用时,如果给出的总容量小于现有容量,do nothing
// 如果当前是刚用无参构造构造出的ArrayList还没有扩容过,而且给出的总容量小于等于10的话,不会扩容
// 因为多此一举,反正等到添加第一个元素的时候就会扩容到10
public void ensureCapacity(int minCapacity) {if (minCapacity > elementData.length&& !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA&& minCapacity <= DEFAULT_CAPACITY)) {modCount++;grow(minCapacity);}
}
线程安全的 List
一 自己加 Synchronized 进行控制
二 CopyOnWriteArrayList
写时复制的 List; 非常适合读多写少又要求线程安全的场景;
其基本工作原理是,当对列表进行写操作(如添加、删除、更新元素)时,它会 new 数组 + System.ArrayCopy , 创建一个底层数组的副本,然后在新数组上执行写操作。修改完成后,用副本替换掉原有的数组。
其底层数组由 volatile 修饰, 保证可见性;
CopyOnWriteArrayList 的迭代器在迭代的时候,如果数组内容被修改了,CopyOnWriteArrayList 不会报 ConcurrentModificationException 的异常,因为迭代器使用的依然是旧数组,只不过迭代的内容可能已经过时了。迭代器并不支持 remove 方法;
原理是: 创建 COWIterator 的时候, 会将底层数组的引用传进入, 这样, 即使有其他线程更换了底层数组, 也不会影响到当前的迭代器;
remove 方法还是会加锁, 使用的是 ReentrantLock, 整个 list 对象就一个;
三 Collections.synchronizedList()
对 get set add 等等这些方法都加了 synchronized, 和我们自己控制, 没啥区别; synchronized 作用的对象是链表本身;
public E get(int index) {synchronized (mutex) {return list.get(index);}
}
需要注意, 拿到 Iterator 进行遍历的时候, 必须手动保证线程安全, 比如可以用 synchronized(list);
不然还是会并发修改异常;
synchronized (list) {Iterator i = list.iterator(); // Must be in synchronized blockwhile (i.hasNext())foo(i.next());
}
相关文章:
【ArrayList】JDK1.8源码详细注释 以及如何实现线程安全的链表
ArrayList(JDK8) ArrayList有四个内部类,成员内部类Itr,成员内部类ListItr,静态内部类SubList,ArrayListSpliterator(暂时用不到)Itr是Iterator的实现类,支持正向遍历,ArrayList的i…...
[python]rasterio运行代码警告proj_create_from_database: Cannot find proj.db
这个报错要分原因还有rasterio版本讨论,因此官方给出了十分具体回答 Frequently Asked Questions What does "RasterioIOError: file.ecw not recognized as a supported file format." mean? This exception is raised when none of rasterios format …...
ThinkPHP5.1.C+CmsEasy-SQL注入
目录 1、ThinkPHP 中存在的 SQL注入 漏洞( select 方法注入) 1.1环境配置 1.1.1将 composer.json 文件的 require 字段设置成如下: 1.1.2设置application/index/controller/Index.php 文件 1.1.3在 application/database.php 文件中配置…...
Python 绘图进阶之词云图:文本数据的可视化艺术
Python 绘图进阶之词云图:文本数据的可视化艺术 引言 在数据科学和自然语言处理领域,词云图(Word Cloud)是一种常用的可视化工具。它通过直观的图形展示文本数据中的高频词汇,使得我们能够快速抓住文本内容的核心主题…...
【Windows】Q-Dir(资源管理器)软件介绍
软件介绍 Q-Dir是一款免费的文件管理器软件,它可以让您更方便地浏览和管理计算机上的文件和文件夹。与Windows自带的资源管理器相比,Q-Dir具有更多的功能和选项。 安装教程 软件下载完成,解压软件。 点击Q-Dir.exe即可打开软件。 功能…...
什么是令牌桶算法?工作原理是什么?使用它有哪些优点和注意事项?
大家好,我是鸭鸭! 此答案节选自鸭鸭最近弄的面试刷题神器面试鸭 ,更多大厂常问面试题,可以点击下面的小程序进行阅读哈! 目前这个面试刷题小程序刚出,有网页和小程序双端可以使用! 回归面试题…...
C++-类与对象(中上篇)
一、目标 1. 类的 6 个默认成员函数 2. 构造函数 3. 析构函数 二、对目标的介绍 1. 类的6个默认成员函数 如果一个类中什么成员都没有,简称为空类。 空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生…...
链表 206.反转链表
一般方法 不需要一个个来回换,只需要改变链表的指向,即可完成 一个链表的头节点,也代表了整个链表 class Solution {public ListNode reverseList(ListNode head) {ListNode temp;ListNode cur head;ListNode pre null;while(cur ! null…...
Ubuntu18.04 配置EtherCAT主站IGH SOEM
IGH IGH 是开源的EtherCAT 主站软件 一、安装依赖 sudo apt update sudo apt install build-essential linux-headers-$(uname -r) mercurial autoconf libtool 也不知道安装的完全不完全 uname -r 可以查看内核,我安装的ubuntu18.04的内核版本是 5.4.0-84-gen…...
航空航天构型管理
构型管理(CM)被定义为在产品的生命周期中应用的SE技术和管理规程。CM的五个原则是:CM计划与执行、配置识别、配置变更和差异控制、配置状态核算和配置验证。 广义上的构型管理规划和管理是有效实施配置管理的关键。特别是在不同项目之间的差异中,构型管理…...
Visual Studio Code 安装与 C/C++ 语言运行总结
大家好,我是程序员小羊! 前言: Visual Studio Code(简称 VS Code)是由微软开发的一款轻量级、强大的代码编辑器,支持多种编程语言和开发框架。由于其丰富的插件生态系统和灵活的配置选项,VS…...
Science Robotics 受鳞片启发的可编程机器人结构,可同时进行形状变形和刚度变化
一、前言速览 生物有机体通常凭借复杂的结构表现出显著的多功能性,例如章鱼具有可以同时改变形状和刚度的能力。现有的仿生软体机器人要想实现这样的能力,往往需要繁琐的结构和复杂的控制系统。为此,来自新加坡南洋理工大学的研究人员从覆盖…...
SpringBoot 自定义 Starter 实现
一、定义,什么是Starter SpringBoot Starter 是”一站式服务(one-stop service)“的依赖 Jar 包: 包含 Spring 以及相关技术(比如Redis)的所有依赖提供了自动配置的功能,开箱即用提供了良好的…...
「Spring MVC」Session、Cookie
🎇个人主页:Ice_Sugar_7 🎇所属专栏:JavaEE 🎇欢迎点赞收藏加关注哦! Spring MVC 🍉Session & Cookie🍌联系与区别 🍉获取 Cookie🍉存储 & 获取 Sess…...
Java虚拟机:垃圾回收器
大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 037 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进一步完善自己对整个 Java 技术体系来充实自…...
ES6-ES13学习笔记
初识ES6 ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。 1997年:EC…...
【Qt开发】QtCharts图表——在ui上添加QChartView控件并进行绘图配置
【Qt开发】QtCharts图表——在ui上添加QChartView控件并进行绘图配置 文章目录 控件安装和模块导入在ui上添加QChartView控件QChartView图表配置附录:C语言到C的入门知识点(主要适用于C语言精通到Qt的C开发入门)C语言与C的不同C中写C语言代码…...
Android14 屏幕录制(屏幕投影)和音频播放采集
Android 5开始支持屏幕采集, Android 10支持音频播放采集,不过Android 14用前台服务做屏幕录制时要增加一些处理. 1. app manifest 需要增加: <manifest><uses-permission android:name"android.permission.FOREGROUND_SERVICE" /><uses…...
一行实现88个群智能算法优化混合核极限学习机HKELM的多特征输入单输出的数据回归预测Matlab程序全家桶
一行实现88个群智能算法优化混合核极限学习机HKELM的多特征输入单输出的数据回归预测Matlab程序全家桶 文章目录 前言一行实现88个群智能算法优化混合核极限学习机HKELM的多特征输入单输出的数据回归预测Matlab程序全家桶 一、HKELM模型1. 极限学习机(ELM࿰…...
redis面试(十五)公平锁队列重排
队列重拍 先说一下当前的加锁状态 anyLock由客户端A持有队列中是客户端B、客户端C并且客户端B现在是排在头部 那么队列重拍就是队列中某个客户端长时间没有重新申请加锁,没有刷新分数,就会被队列中挤掉。 假设这个长时间没有加锁的客户端是B。 总结 …...
C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...
