ArrayList源码阅读分析
本帖最后由 独怜 于 2021-10-13 14:00 编辑## ArrayList源码阅读分析
### 1.1 ArrayList介绍
!(https://wangchaoz97.oss-cn-shenzhen.aliyuncs.com/markdown/image/image-20211012160617267.png)
**ArrayList**一个**数组队列**,相当于 **动态数组**。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
**ArrayList **继承了**AbstractList**,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
**ArrayList** 实现了**RandmoAccess**接口,即提供了随机访问功能。**RandmoAccess**是java中用来被List实现,为List提供<span style="color:red;font-weight:bold"><u>快速访问功能</u></span>的。在**ArrayList**中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。
**ArrayList** 实现了**Cloneable**接口,即覆盖了函数**clone()**,能被克隆。
**ArrayList** 实现**java.io.Serializable**接口,这意味着**ArrayList**支持序列化,能通过序列化去传输。
和**Vector**不同,**ArrayList中的操作不是线程安全的**!所以,建议在单线程中才使用**ArrayList**,而在多线程中可以选择Vector或者**CopyOnWriteArrayList**。
### 1.2 ArrayList源码
```java
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity.
* 默认容量 10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
* 空数组,如果传入的容量为0时使用
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
* 空数组,传传入容量时使用,添加第一个元素的时候会重新初始为默认容量大小
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
* 存储元素的数组
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
* 集合中元素的个数
*/
private int size;
```
#### 1.2.1 ArrayList构造方法
##### (1).ArrayList()
1. 如果没有传入初始容量,则使用空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA
2. 使用这个数组是在添加第一个元素的时候会扩容到默认大小10
```java
/**
*使用 new ArrayList() 创建时默认使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个空数
**/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
```
#####(2). ArrayList(int initialCapacity)
```java
public ArrayList(int initialCapacity) {
//传入的初始容量大于0,初始化elementData数组对应的大小容量
if (initialCapacity > 0) {
this.elementData = new Object;
}
//传入的初始容量等于0,使用一个空数组 EMPTY_ELEMENTDATA
else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 如果传入的初始容量小于0,抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
```
##### (3). ArrayList(Collection<? extends E> c)
```java
public ArrayList(Collection<? extends E> c) {
// 将集合先转换成数组
Object[] a = c.toArray();
// 将size赋值为转换后数组的长度且判断转换后数组的长度是否等于0
if ((size = a.length) != 0) {
//判断传入的元素是否为ArratList类型,如果是,则直接将elementData赋值为传入的集合转换后的数组
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
// 如果传入的是不是ArrayList类型,重新拷贝成Object[].class类型
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// 传入的集合为一个空集合,初始化一个空数组 EMPTY_ELEMENTDATA
elementData = EMPTY_ELEMENTDATA;
}
}
```
####1.2.2 ArrayList添加元素(增)
##### (1). add(E e)
添加元素到末尾,平均时间复杂度为O(1)。
```java
public boolean add(E e) {
//初始化容量判断
ensureCapacityInternal(size + 1);// Increments modCount!!
//将元素插入到最后一位
elementData = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
//modCount 为一个全局性变量,记录结构性改变的次数
modCount++;
//当数组大小大于此时数组中元素个数,进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//判断是否是第一次添加数据,如果为第一次,则初始化默认大小10,反之则返回当前数组下标
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果是空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,就初始化为默认大小10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//ArrayList 扩容机制
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//每次扩容为之前容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果新容量发现比需要的容量还小,则以需要的容量为准
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新容量已经超过最大容量了,则使用最大容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 以新容量拷贝出来一个新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
```
##### (2). add(int index, E element)
添加元素到指定位置,平均时间复杂度为O(n)。
```java
public void add(int index, E element) {
// 检查索引是否越界
rangeCheckForAdd(index);
//初始化容量判断
ensureCapacityInternal(size + 1);// Increments modCount!!
// 将inex及其之后的元素往后挪一位,则index位置处就空出来了
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 将元素插入到index的位置
elementData = element;
// 大小增1
size++;
}
/**
* 检查索引是否越界
**/
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
```
##### (3). addAll(Collection<? extends E> c)
两个集合的第一个并集
```java
public boolean addAll(Collection<? extends E> c) {
// 将集合先转换成数组
Object[] a = c.toArray();
// 获取集合转换成数组的长度
int numNew = a.length;
// 检查是否需要扩容 (可跟踪查看add(E e)方法)
ensureCapacityInternal(size + numNew);// Increments modCount
// 将c中元素全部拷贝到数组的最后
System.arraycopy(a, 0, elementData, size, numNew);
// 大小增加c的大小
size += numNew;
// 如果c不为空就返回true,否则返回false
return numNew != 0;
}
```
##### (4). addAll(int index, Collection<? extends E> c)
在指定索引位置位置添加集合元素
两个集合的并集。
```java
public boolean addAll(int index, Collection<? extends E> c) {
// 检查索引是否越界
rangeCheckForAdd(index);
// 将集合先转换成数组
Object[] a = c.toArray();
// 获取集合转换成数组的长度
int numNew = a.length;
// 检查是否需要扩容 (可跟踪查看add(E e)方法)
ensureCapacityInternal(size + numNew);// Increments modCount
// 如果index不是最后一位,则将index之后的元素往后挪numNew个位置,将从numMoved的位置空出放入传入的元素
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
// 将c中元素全部拷贝到数组
System.arraycopy(a, 0, elementData, index, numNew);
// 大小增加c的大小
size += numNew;
// 如果c不为空就返回true,否则返回false
return numNew != 0;
}
// 判断索引是否越界
private void rangeCheckForAdd(int index) {
if (index < 0 || index > this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
```
#### 1.2.3 ArrayList删除元素(删)
##### (1). remove(int index)
删除指定索引位置的元素,时间复杂度为O(n)。
```java
public E remove(int index) { //检查索引是否越界 rangeCheck(index); //modCount 为一个全局性变量,记录结构性改变的次数 modCount++; //获取索引位置元素 E oldValue = elementData(index); // 如果index不是最后一位,则将index之后的元素往前挪一位 int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index,numMoved); // 将最后一个元素删除,帮助GC elementData[--size] = null; // clear to let GC do its work // 返回删除的旧元素 return oldValue;}
```
##### (2). remove(Object value)
删除指定元素值的元素,时间复杂度为O(n)。
```java
public boolean remove(Object o) { //如果要删除的元素为null if (o == null) { // 遍历整个数组,找到元素第一次出现的位置,并将其快速删除 for (int index = 0; index < size; index++) // 如果要删除的元素为null,则以null进行比较,使用== if (elementData == null) { fastRemove(index); return true; } } else { // 遍历整个数组,找到元素第一次出现的位置,并将其快速删除 for (int index = 0; index < size; index++) // 如果要删除的元素不为null,则进行比较,使用equals()方法 if (o.equals(elementData)) { fastRemove(index); return true; } } return false;}// 快速删除private void fastRemove(int index) { //modCount 为一个全局性变量,记录结构性改变的次数 modCount++; // 如果index不是最后一位,则将index之后的元素往前挪一位 int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // 将最后一个元素删除,帮助GC elementData[--size] = null; // clear to let GC do its work}
```
##### (3). removeAll(Collection<?> c)
从此列表中移除包含在指定集合中的所有元素
```java
public boolean removeAll(Collection<?> c) { //在运行时对对象做非null检查 Objects.requireNonNull(c); //批量删除 return batchRemove(c, false);}/** * 批量删除元素 * complement为true表示删除c中不包含的元素 * complement为false表示删除c中包含的元素*/private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; // 使用读写两个指针同时遍历数组 // 读指针每次自增1,写指针放入元素的时候才加1 // 这样不需要额外的空间,只需要在原有的数组上操作就可以了 int r = 0, w = 0; boolean modified = false; try { // 遍历整个数组,如果c中包含该元素,则把该元素放到写指针的位置(以complement为准) for (; r < size; r++) if (c.contains(elementData) == complement) elementData = elementData; } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. // 正常来说r最后是等于size的,除非c.contains()抛出了异常 if (r != size) { // 如果c.contains()抛出了异常,则把未读的元素都拷贝到写指针之后 System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { // 将写指针之后的元素置为空,帮助GC // clear to let GC do its work for (int i = w; i < size; i++) elementData = null; modCount += size - w; // 新大小等于写指针的位置(因为每写一次写指针就加1,所以新大小正好等于写指针的位置) size = w; // 有修改返回true modified = true; } } return modified;}
```
##### (4). clear()
清空列表中的元素,但是不修改列表的容量大小
```java
public void clear() { //modCount 为一个全局性变量,记录结构性改变的次数 modCount++; // clear to let GC do its work // 将数组内的元素都置空,等待垃圾收集器收集,不减小数组容量。 for (int i = 0; i < size; i++) elementData = null; size = 0;}
```
#### 1.2.4 ArrayList修改元素(改)
##### (1) set(int index)
根据索引修改元素
```java
public E set(int index, E element) { // 检查索引是否越界 rangeCheck(index); // 根据索引取出元素 E oldValue = elementData(index); // 根据索引修改数组元素 elementData = element; // 返回修改之前的值 return oldValue;}//检查索引是否越界 private void rangeCheck(int index) { if (index < 0 || index >= this.size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}
```
#### 1.2.5 ArrayList取出元素(查)
##### (1). get(int index)
获取指定索引位置的元素,时间复杂度为O(1)。
```java
//根据索引取出元素public E get(int index) { //(1) 检查索引是否越界 rangeCheck(index); //(2)返回索引位置处的元素; return elementData(index);}// 判断索引是否越界//(1)检查索引是否越界,这里只检查是否越上界,如果越上界抛出IndexOutOfBoundsException异常,如果越下界抛出的是ArrayIndexOutOfBoundsException异常。private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}//根据索引返回元素E elementData(int index) { return (E) elementData;}
```
##### (2). retainAll(Collection<?> c)
求两个集合的交集
```java
public boolean retainAll(Collection<?> c) { //在运行时对对象做非null检查 Objects.requireNonNull(c); // 调用批量删除方法,这时complement传入true,表示删除不包含在c中的元素 return batchRemove(c, true);}/** * 批量删除元素 * complement为true表示删除c中不包含的元素 * complement为false表示删除c中包含的元素*/private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; // 使用读写两个指针同时遍历数组 // 读指针每次自增1,写指针放入元素的时候才加1 // 这样不需要额外的空间,只需要在原有的数组上操作就可以了 int r = 0, w = 0; boolean modified = false; try { // 遍历整个数组,如果c中包含该元素,则把该元素放到写指针的位置(以complement为准) for (; r < size; r++) if (c.contains(elementData) == complement) elementData = elementData; } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. // 正常来说r最后是等于size的,除非c.contains()抛出了异常 if (r != size) { // 如果c.contains()抛出了异常,则把未读的元素都拷贝到写指针之后 System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { // 将写指针之后的元素置为空,帮助GC // clear to let GC do its work for (int i = w; i < size; i++) elementData = null; modCount += size - w; // 新大小等于写指针的位置(因为每写一次写指针就加1,所以新大小正好等于写指针的位置) size = w; // 有修改返回true modified = true; } } return modified;}
```
##### (3). indexOf(Object o)
查找元素在列表中第一次出现的索引位置
```java
public int indexOf(Object o) { // 判断元素是否为null,如果为null则查找null第一次出现在列表中的位置 if (o == null) { for (int i = 0; i < size; i++) if (elementData==null) return i; } else { // 查找元素在列表中第一次出现的位置 for (int i = 0; i < size; i++) if (o.equals(elementData)) return i; } // 查找不到该元素则返回-1 return -1;}
```
##### (4). lastIndexOf(Object o)
查找元素在列表中最后一次出现的索引位置
```java
public int lastIndexOf(Object o) { if (o == null) { // 倒序查找最后一次出现时的位置 for (int i = size-1; i >= 0; i--) if (elementData==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData)) return i; } // 查找不到该元素则返回-1 return -1;}
```
##### (5). contains(Object o)
查找列表是否包含该元素
```java
public boolean contains(Object o) { // 列表中存在该元素则返回该元素第一次出现的索引,否则返回-1 return indexOf(o) >= 0;}
```
### 1.3 ArrayList总结
(1)ArrayList内部使用数组存储元素,当数组长度不够时进行扩容,每次加一半的空间,ArrayList不会进行缩容;
(2)ArrayList支持随机访问,通过索引访问元素极快,时间复杂度为O(1);
(3)ArrayList添加元素到尾部极快,平均时间复杂度为O(1);
(4)ArrayList添加元素到中间比较慢,因为要搬移元素,平均时间复杂度为O(n);
(5)ArrayList从尾部删除元素极快,时间复杂度为O(1);
(6)ArrayList从中间删除元素比较慢,因为要搬移元素,平均时间复杂度为O(n);
(7)ArrayList支持求并集,调用addAll(Collection<? extends E> c)方法即可;
(8)ArrayList支持求交集,调用retainAll(Collection<? extends E> c)方法即可;
(7)ArrayList支持求单向差集,调用removeAll(Collection<? extends E> c)方法即可;
### 1.4 ArrayList常见面试题
1. ArrayList是什么?可以用来干嘛?
```
ArrayList就是有序的动态数组列表,主要⽤来装载数据,它的主要底层实现是数组Object[]查询效率高,增删效率低
```
2. ArrayList与LinkedList的区别?
```
1、ArrayList基于动态数组实现的非线程安全的集合;LinkedList基于链表实现的非线程安全的集合。2、对于随机index访问的get和set方法,一般ArrayList的速度要优于LinkedList。因为ArrayList直接通过数组下标直接找到元素;LinkedList要移动指针遍历每个元素直到找到为止。3、新增和删除元素,一般LinkedList的速度要优于ArrayList。因为ArrayList在新增和删除元素时,可能扩容和复制数组;LinkedList实例化对象需要时间外,只需要修改指针即可。4、LinkedList集合不支持 高效的随机随机访问(RandomAccess)5、ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间(扩容),而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
```
3. ArrayList 的默认长度
```
默认长度为10当我们第一次向数组中添加数据的时候才会初始化数组,相当于懒加载
```
4. ArrayList是怎么扩容的?
```
再add(E e)添加新数据的时候会进行一个判断,当我们添加的数据超过数组的长度时,数组就会进行扩容这里分为两步: 1.先创建一个原数组长度1.5倍的新数组 1.7: 是原来数组的1.5倍 + 1 1.8: 使用位移运算符向右一位,效率要高很多 2.然后再把旧的数组copy到新的数组
```
5. ArrayList插入、删除、查询元素的时间复杂度各是多少?
```
add(E e)方法添加元素到末尾,平均时间复杂度为O(1)。add(int index, E element)方法添加元素到指定位置,平均时间复杂度为O(n)。remove(int index)方法删除指定索引位置的元素,时间复杂度为O(n)。remove(Object o)方法删除指定元素值的元素,时间复杂度为O(n)。get(int index)方法获取指定索引位置的元素,时间复杂度为O(1)。总结:总的来说,不论是删除还是新增,其实本质上都是移动位置,当指定位置新增的时候,新增的索空出,后面向后移一位,然后赋值,当删除时,也是一样,将要删除的索引下标的值置为null。 (增删慢,查询快)
```
6. ArrayList的遍历和LinkedList遍历性能⽐较如何?
```
ArrayList要比LinkedList快得多,ArrayList遍历最大的优势在于内存的连续性,CPU的内部缓存结构会缓存连续的内存片段,可以大幅降低读取内存的性能开销。
```
7. 为啥线程不安全还使用ArrayList呢?
```
因为在我们正常得使用场景中,都是用来查询的,不会涉及太频繁的增删,如果涉及到频繁的增删,可以使用LinkedList,如果需要线程安全的就是可以使用Vector,CopyOrWriteArrayList
```
8. ArrayList的遍历如何?
```
ArrayList遍历最大的优势在于内存的连续性,CPU的内部缓存结构会缓存连续的内存片段,可以大幅降低读取内存的性能开销。
```
感谢大神分享。
感谢大佬分享知识 感谢大佬的分享 楼主发篇LinkedList解析的帖子吧 momo2021 发表于 2021-10-13 19:38
楼主发篇LinkedList解析的帖子吧
整理完了就发出来 momo2021 发表于 2021-10-13 19:38
楼主发篇LinkedList解析的帖子吧
LinkedList看下这个哈, https://www.52pojie.cn/forum.php?mod=viewthread&tid=1527949&page=1&extra=#pid40325053 谢谢分享 很详细,感谢分享 学习到了,谢谢大佬分享
页:
[1]