ArrayList集合底层原理
ArrayList集合特点及源码分析
ArrayList是List接口的实现类
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
继承了AbstractList
类,实现了Cloneable克隆接口、Serializable序列化接口、RandomAccess随机访问接口、List接口
特点:底层使用数组实现的 查询效率高,增删慢,线程不安全、允许为空、可重复
public static void main(String[] args) {
List<String> list=new ArrayList<String>();
boolean bool=list.add("ylc");//Collection接口添加元素
list.add(1,"ww");//根据索引添加元素
String el=list.get(1);//根据索引获取元素
list.size();//获取元素个数
list.remove(2);//根据元素位置删除
list.remove("ylc");//删除指定元素
list.set(1,"yy");//替换元素
list.clear();//清空集合
list.isEmpty();//判断元素是否为空
list.contains("ylc");//判断集合是否包含某个元素
list.indexOf("ylc");//查找所诉中所在的位置
list.lastIndexOf("ylc");//元素最后出现的索引位置
Object[] objects=list.toArray();//把集合转化为object数组
for (int i = 0; i < objects.length; i++) {
String str=(String) objects[i];
System.out.println(str);
}
String[] strings=list.toArray(new String[list.size()]);//把集合转化为指定类型数组
List<String> list2=new ArrayList<String>();
list2.add("s");
list.addAll(list2);//集合合并操作
list.retainAll(list2);//集合交集操作 list存储交集内容
list.removeAll(list2);//删除list中含有list2集合的元素
}
ArrayList源码分析
成员变量
private static final int DEFAULT_CAPACITY = 10;//数组默认长度
private static final Object[] EMPTY_ELEMENTDATA = {};//给定一个空数组
transient Object[] elementData;//存储ArrayList元素的临时数组 不会被存到磁盘
private int size;//记录数组中元素的个数
protected transient int modCount = 0; // 集合数组修改次数的标识(fail-fast机制)
transient关键字对于不想进⾏序列化的字段,使⽤ transient 关键字修饰
JDK7中,只要创建ArrayList数组,就会默认创建一个长度为10的空数组。
JDK8中,做了一个延迟加载,在创建ArrayList数组时,创建一个长度为0的空数组,只有在用到这数组才会对长度进行改变,做了一个延迟加载
构造函数
里面包含三种构造函数
1.无参构造函数
初始化数组时默认赋值一个空数组
//无参构造函数
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//默认为0
}
2.int类型的构造函数
如果传入数值大于0就创建指定容量大小的数组,数值为0为空对象数组,否则抛出异常
//带容量大小的构造函数
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);
}
}
3.集合类型构造函数
把传入的集合变成数组,赋值给elementData
集合不为空,传入数组类型转为object的话赋值给elementData
否则就直接复制到elementData中
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();//变成数组
if ((size = a.length) != 0) {//集合不为空的话
if (c.getClass() == ArrayList.class) {//判断与ArrayList类型是否一致
elementData = a;//赋值
} else {
elementData = Arrays.copyOf(a, size, Object[].class);//否则直接复制到数组中
}
} else {
elementData = EMPTY_ELEMENTDATA;//设置为空数组
}
}
增加方法
add(E e)方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); //当size=0时
elementData[size++] = e;//Size还是为0,给为0的size赋值
return true;
}
//确保集合数组内部容量
private void ensureCapacityInternal(int minCapacity) {//1
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));//返回10
}
//确保显式容量
private void ensureExplicitCapacity(int minCapacity) {//minCapacity=10
modCount++;
if (minCapacity - elementData.length > 0)// 10-0>0 判断扩容关键代码 当第11个数组加入时执行grow代码
grow(minCapacity);//10
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//计算数组容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//0=0
return Math.max(DEFAULT_CAPACITY, minCapacity);// DEFAULT_CAPACITY=10,minCapacity=1 返回最大值作为数组容量
}
return minCapacity;
}
//扩容方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length;//0
int newCapacity = oldCapacity + (oldCapacity >> 1); //数组长度加上位移运算(数组长度的一半)每次扩容增加1.5倍 0=0+0
if (newCapacity - minCapacity < 0)//0-10<0
newCapacity = minCapacity; //newCapacity=0
if (newCapacity - MAX_ARRAY_SIZE > 0)//10-MAX_ARRAY_SIZE<0
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);//复制操作 把一个长度为10复制到空的数组中,生成了一个新的长度为10的空数组
}
Size默认是0,add方法给Size加上1,并传入ensureCapacityInternal
方法,其中ensureCapacityInternal
方法中又调用了ensureExplicitCapacity
方法,并传入了两个参数,第一个是elementData
代表一个为空的数组,第二个是数组个数。在calculateCapacity
方法中,有个if判断如果数组大小等于默认大小,就返回其中最大的数值,默认数组大小为10的话,DEFAULT_CAPACITY
也等于10,所以返回的是10。把10传入ensureExplicitCapacity
方法中,再把10 传入grow
方法中,生成了一个新的长度为10的空数组。ensureCapacityInternal
方法执行完毕。add
方法Size依然为0,给为下标为0索引赋值,新增一个成功。
扩容模拟
当添加第11个元素时,add方法Size=10+1=11,传入ensureCapacityInternal方法,进入calculateCapacity方法,这时elementData=10,而DEFAULTCAPACITY_EMPTY_ELEMENTDATA=0,不满足直接返回minCapacity=11,值11进入ensureExplicitCapacity方法内部,满足11-10>0进grow方法,grow方法赋值oldCapacity=10,newCapacity=10+10的位移一位操作=10+5=15,最后使用copyOf方法,把原来10大小的数组扩容到15。
add(int index, E element)方法
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); //集合增加容量
//elementData:原数组 index:从原数组这个索引开始复制 elementData:目标数组 index + 1:目标数组的第一个元素 size - index:复制size-index个元素
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;//把当前所有数组替换
size++;//索引增加
}
//判断索引是否越界
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
这样每增加一个元素就要把当前索引复制到该元素的后面,开销很大
删除方法
remove(int index)方法
public E remove(int index) {
rangeCheck(index);//检查索引是否越界
modCount++;//记录的是集合被修改的次数
E oldValue = elementData(index);//根据索引获取元素
int numMoved = size - index - 1;//要移动的元素数量
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);//复制
elementData[--size] = null; //集合数量减1
return oldValue;
}
E elementData(int index) {
return (E) elementData[index];//返回当前索引的元素
}
进行删除前检查索引是否合理,然后记录集合被修改的次数,根据索引获取到需要删除的这个元素,然后该索引后面的元素复制到当前索引,进行覆盖,最后集合元素减去。
remove(Object o)方法
public boolean remove(Object o) {
if (o == null) {//判断对象是否为空
for (int index = 0; index < size; index++)//遍历整个集合
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)//遍历整个集合
if (o.equals(elementData[index])) {//在集合中找到该元素
fastRemove(index);//删除
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;//记录的是集合被修改的次数
int numMoved = size - index - 1;//要移动的元素数量
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);//复制
elementData[--size] = null; // 集合数量减1
}
该方法利用集合遍历找到该元素,根据索引再进行删除,fastRemove
方法同remove(int index)方法类似。
removeAll(Collection<?> c)方法
删除c集合中存在的元素
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);//判断集合是否为空
return batchRemove(c, false);
}
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)//complement=flase 如果elementData不包含c里面的某个元素
elementData[w++] = elementData[r];//不包含的记录下来! 赋值到elementData,满足就从索引0开始覆盖掉原来的数组
} finally {
//存在并发修改上面发生异常没遍历完elementData,这里就会不相等 如果不存在这里就会直接跳过
if (r != size) {
//elementData:原数组 r:从原数组这个索引开始复制 elementData:目标数组 w:目标数组的第一个元素 size - r:复制元素数量
System.arraycopy(elementData, r,elementData, w,size - r);//把没有遍历到的元素赋值到已更新元素的后面
w += size - r;//现有的数据
}
if (w != size) {//w容量是不包含的 除非集合里删除一个元素都不包含这里才会跳过
for (int i = w; i < size; i++)
elementData[i] = null;//把不包含的后面多余的数组置为空
modCount += size - w;//增加修改次数
size = w;//当前数据长度
modified = true;//修改成功
}
}
return modified;
}
场景模拟代入数值:在一个10长度的数组a中删除一个3长度的数组b,删除到一半的时候,由于多线程导致数组赋值失败进入finally代码块,此时w=3,r=6,size=10,w存入的是不包含在b数组中的元素现有3个,r是遍历到中断的索引,进入(r != size)判断不相等,arraycopy方法把后面没有遍历到的重新加进来数组,w+=size - r=7;这样w中存储的就是c集合中不包含的全部元素,再遍历整个Size集合,把不包含元素后面的容量置为空。
其他方法
indexOf(Object o)方法
获取索引
public int indexOf(Object o) {
if (o == null) {//判断对象是否为空
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))//判断相等
return i;
}
return -1;//未匹配
}
lastIndexOf(Object o)
倒序获取索引
public int lastIndexOf(Object o) {
if (o == null) {//等于空
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))//判断相等
return i;
}
return -1;//未匹配
}
set(int index, E element)方法
public E set(int index, E element) {
rangeCheck(index);//检查索引
E oldValue = elementData(index);//根据索引获取元素
elementData[index] = element;//把元素赋值给当前索引
return oldValue;
}
get(int index)方法
public E get(int index) {
rangeCheck(index);//检查索引
return elementData(index);//返回根据索引获取当前元素
}
clear方法
public void clear() {
modCount++;//修改次数+1
for (int i = 0; i < size; i++)
elementData[i] = null;//遍历赋值为null
size = 0;//数组中元素的个数置为0
}
总结
- 是一个动态数组,其容量能自动增长,但每次增删都要copy,性能不高,访问的时候通过索引是最快的
- ArrayList线程不安全,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类
ArrayList集合底层原理的更多相关文章
- JAVA ArrayList集合底层源码分析
目录 ArrayList集合 一.ArrayList的注意事项 二. ArrayList 的底层操作机制源码分析(重点,难点.) 1.JDK8.0 2.JDK11.0 ArrayList集合 一.Ar ...
- 深度解析HashMap集合底层原理
目录 前置知识 ==和equals的区别 为什么要重写equals和HashCode 时间复杂度 (不带符号右移) >>> ^异或运算 &(与运算) 位移操作:1<&l ...
- ArrayList 从源码角度剖析底层原理
本篇文章已放到 Github github.com/sh-blog 仓库中,里面对我写的所有文章都做了分类,更加方便阅读.同时也会发布一些职位信息,持续更新中,欢迎 Star 对于 ArrayList ...
- Java集合:ArrayList的实现原理
Java集合---ArrayList的实现原理 目录: 一. ArrayList概述 二. ArrayList的实现 1) 私有属性 2) 构造方法 3) 元素存储 4) 元素读取 5) 元素删除 ...
- 集合总结一(ArrayList的实现原理)
一.概述 一上来,先来看看源码中的这一段注释,我们可以从中提取到一些关键信息: Resizable-array implementation of the List interface. Implem ...
- 深入java8的集合:ArrayList的实现原理
一.概述 一上来,先来看看源码中的这一段注释,我们可以从中提取到一些关键信息: Resizable-array implementation of the List interface. Implem ...
- ArrayList的底层实现原理
ArrayList源码分析 1.java.util.ArrayList<E> : List 接口的大小可变数组的实现类 ArrayList 内部基于 数组 存储 各个元素. 所谓大小可变数 ...
- 理解java容器底层原理--手动实现ArrayList
为了照顾初学者,我分几分版本发出来 版本一:基础版本 实现对象创建.元素添加.重新toString() 方法 package com.xzlf.collection; /** * 自定义一个Array ...
- 红黑树规则,TreeSet原理,HashSet特点,什么是哈希值,HashSet底层原理,Map集合特点,Map集合遍历方法
==学习目标== 1.能够了解红黑树 2.能够掌握HashSet集合的特点以及使用(特点以及使用,哈希表数据结构) 3.能够掌握Map集合的特点以及使用(特点,常见方法,Map集合的遍历) 4.能够掌 ...
随机推荐
- 解决umount: /home: device is busy
取消挂载/home时出现umount: /home: device is busy, 原因是因为有程序在使用/home目录,我们可以使用fuser查看那些程序的进程, 然后 ...
- 01_初识C语言
第一章 - 初识C语言 基本了解C语言的基础知识,对C语言有一个大概的认识. 每个知识点就是简单认识,不做详细讲解. 1. 什么是C语言? C语言是一门通用计算机编程语言,广泛应用于底层开发.C语言的 ...
- 字符串出现的topK问题
/** * return topK string * @param strings string字符串一维数组 strings * @param k int整型 the k * @return str ...
- STM32L0系列EEPROM中结构体的读取
在STM32L0中操作EEPROM本来参考了上篇操作FLASH的方法,多多少少都有些问题.我觉得可能是结构体在转换成其他变量的时候出了问题. 比如下面这段代码,在Windows上可以正常运行(使用g+ ...
- 【PHP数据结构】插入类排序:简单插入、希尔排序
总算进入我们的排序相关算法的学习了.相信不管是系统学习过的还是没有系统学习过算法的朋友都会听说过许多非常出名的排序算法,当然,我们今天入门的内容并不是直接先从最常见的那个算法说起,而是按照一定的规则一 ...
- 记一次 .NET 某上市工业智造 CPU+内存+挂死 三高分析
一:背景 1. 讲故事 上个月有位朋友加wx告知他的程序有挂死现象,询问如何进一步分析,截图如下: 看这位朋友还是有一定的分析基础,可能玩的少,缺乏一定的分析经验,当我简单分析之后,我发现这个dump ...
- Docker系列(27)- 容器互联--link
思考 思考一个场景,我们编写了一个微服务,database url=IP:,项目不重启,数据库ip换掉了,我们希望可以处理这个问题,可以使用名字来进行访问容器吗 实践 [root@localhost ...
- mysql允许别人通过ip访问本机mysql数据
要想允许别人通过ip访问本机mysql数据库,首先要是本机的mysql允许别的ip访问,也就是授权:其次别人的代码里,要写对本机的ip. 一.本机mysql的授权操作 1.cmd 进入控制台 2.输入 ...
- 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么 | 百篇博客分析OpenHarmony源码 | v16.02
百篇博客系列篇.本篇为: v16.xx 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么 | 51.c.h .o 内存管理相关篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有哪 ...
- P4643-[国家集训队]阿狸和桃子的游戏【结论】
正题 题目链接:https://www.luogu.com.cn/problem/P4643 题目大意 给出\(n\)个点\(m\)条边的无向图,两个人轮流选择一个未被选择的点加入点集. 然后每个人的 ...