ArrayList源码阅读笔记

举报
三分恶 发表于 2021/04/23 23:09:13 2021/04/23
【摘要】 简介 ArrayList是基于数组实现的一种列表。 ArrayList继承体系如下: 图一:ArrayList继承体系 ArrayList实现了List, RandomAccess, Cloneable, java.io.Serializable等接口。 ArrayList实现了List,提供了基础的添加、删除、遍历等操作。 ArrayList实...

简介

ArrayList是基于数组实现的一种列表。

ArrayList继承体系如下:

图一:ArrayList继承体系

在这里插入图片描述

  • ArrayList实现了List, RandomAccess, Cloneable, java.io.Serializable等接口。

  • ArrayList实现了List,提供了基础的添加、删除、遍历等操作。

  • ArrayList实现了RandomAccess,提供了随机访问的能力。

  • ArrayList实现了Cloneable,可以被克隆。

  • ArrayList实现了Serializable,可以被序列化。


源码分析

属性

首先看看ArrayList的属性。

 /** * 默认初始化容量 */ private static final int DEFAULT_CAPACITY = 10; /** * 空数组,如果传入的容量为0时使用 */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** *空数组,传传入容量时使用,添加第一个元素的时候会重新初始为默认容量大小 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** *存储数据元素的数组 */ transient Object[] elementData; // non-private to simplify nested class access /** * ArrayList的大小(包含数据元素的个数) */ private int size;

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

构造方法

  • 无参构造方法
 /** * 创建一个初始容量为10的空列表 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } 
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 指定初始容量的构造方法
  /**
  * 创建一个指定容量的list
  */ public ArrayList(int initialCapacity) { // 如果传入的初始容量大于0,就新建一个数组存储元素 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { // 如果传入的初始容量等于0,使用空数组EMPTY_ELEMENTDATA this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 从其它集合构造
   /** * 把传入集合的元素初始化到ArrayList中 */ public ArrayList(Collection<? extends E> c) { //把集合转为数组 elementData = c.toArray(); if ((size = elementData.length) != 0) { //  检查c.toArray()返回的是不是Object[]类型,如果不是,重新拷贝成Object[].class类型 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // 如果是空集合,则初始化为空数组EMPTY_ELEMENTDATA this.elementData = EMPTY_ELEMENTDATA; } }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

add(E e)

添加元素到末尾,平均时间复杂度为O(1)。
在这里插入图片描述

 /** * 在末尾添加元素 */ public boolean add(E e) { // 检查是否需要扩容 ensureCapacityInternal(size + 1); // 把元素插入到末尾 elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } 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; } private void ensureExplicitCapacity(int minCapacity) { // 修改次数 +1,用于 fail-fast 处理 modCount++; // 如果 minCapacity 大于 elementData 的长度,则进行扩容处理 if (minCapacity - elementData.length > 0) grow(minCapacity); } /** *  扩容 */ private void grow(int minCapacity) { // 有整形溢出风险的代码 int oldCapacity = elementData.length; //新容量=旧容量+(旧容量右移1位(除以2)),新容量为原来的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); }


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

这里我们稍微看一下Arrays.copyOf的源码:

 public static <T> T[] copyOf(T[] original, int newLength) { return (T[]) copyOf(original, newLength, original.getClass()); }

  
 
  • 1
  • 2
  • 3
 public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { @SuppressWarnings("unchecked") T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

最终调用的是System.arraycopy方法,这是一个Native方法:

 public static native void arraycopy(Object src,  int  srcPos, Object dest, int destPos, int length);

  
 
  • 1
  • 2
  • 3

关于数组拷贝,谷歌了一下,说法不一,有说是深拷贝的,有说是浅拷贝的。暂时先放下,未来有机会再研究。


add(int index, E element)

add(int index, E element)在特定位置插入元素,时间复杂度为O(n)。

 public void add(int index, E element) { // 检查是否越界 rangeCheckForAdd(index); //检查是否需要扩容 ensureCapacityInternal(size + 1);  // Increments modCount!!  将 elementData 中位置为 index 位置及其后面的元素都向后移动一个下标(底层是 native 方法,使用 cpp 直接操作内存。) System.arraycopy(elementData, index, elementData, index + 1, size - index); // 将元素插入到index的位置  elementData[index] = element;  大小增1 size++; }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

addAll

addAll用于批量添加。

 public boolean addAll(Collection<? extends E> c) { // 集合转化成数组 Object[] a = c.toArray(); int numNew = a.length; //检查是否需要扩容 ensureCapacityInternal(size + numNew);  // Increments modCount //将集合内的元素复制到 elementData 中,覆盖 [size, size+numNew) 的元素 System.arraycopy(a, 0, elementData, size, numNew); //数组大小增加numNew size += numNew; return numNew != 0; } public boolean addAll(int index, Collection<? extends E> c) { //检查下标是否越界 rangeCheckForAdd(index); //转换为数组 Object[] a = c.toArray(); int numNew = a.length; //检查是否需要扩容 ensureCapacityInternal(size + numNew);  // Increments modCount int numMoved = size - index; if (numMoved > 0) // 将 elementData 中位置为 index 及其以后的元素都向后移动 numNew 个位置 System.arraycopy(elementData, index, elementData, index + numNew, numMoved); // 将集合内的元素复制到 elementData 中,覆盖 [index, index+numNew) 的元素  System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

get(int index)

获取指定位置元素,时间复杂度为O(1)。

 public E get(int index) { rangeCheck(index); return elementData(index); }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

remove(int index)

删除指定索引位置的元素,时间复杂度为O(n)。

 public E remove(int index) { //检查下标是否越界 rangeCheck(index); 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; }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

remove(Object o)

删除指定元素,时间复杂度为O(n²)。

 public boolean remove(Object o) { //元素为null if (o == null) { //遍历数组 for (int index = 0; index < size; index++) //快速删除所有为null的元素 if (elementData[index] == null) { fastRemove(index); return true; } } else { //元素不为null,遍历数组 for (int index = 0; index < size; index++) //找到对应元素,快读删除 if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } //和remove(int index)基本相同,少了检查越界的方法,无返回值 private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; // 如果index不是最后一位,则将index之后的元素往前挪一位 if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // 将最后一个元素删除,帮助GC  elementData[--size] = null; // clear to let GC do its work }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

removeAll

用于批量删除元素。

   //批量删除 ArrayList 和集合 c 都存在的元素 public boolean removeAll(Collection<?> c) { //非空校验 Objects.requireNonNull(c); //批量删除  return batchRemove(c, false); } 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) elementData[w++] = elementData[r]; } finally { if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { // 跟 fastRemove(int index) 里面的操作类似,防止内存泄漏 for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified; }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

set

用于更改特定下标的值,时间复杂度为O(1)。

 public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

序列化

注意观察,ArrayList 有两个属性被 transient 关键字 修饰,transient 关键字的作用:让某些被修饰的成员属性变量不被序列化

transient Object[] elementData;
protected transient int modCount = 0;

  
 
  • 1
  • 2

为什么最重要的元素数组要被transient 修饰呢?

因为ArrayList 并没有用Java序列化机制的默认处理来序列化 elementData 数组,而是通过 readObject、writeObject 方法自定义序列化和反序列化策略。

之所以要用自定义的序列化和反序列化策略,是因为效率的问题。如果用默认处理来序列化的话,如果 elementData 的长度有100,但是实际只用了50,其实剩余的50是可以不用序列化的,这样可以提高序列化和反序列化的效率,节省空间。

   /** *  自定义序列化 */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // fail-fast,后续判断是否有并发处理 int expectedModCount = modCount; // 序列化没有标记为 static、transient 的字段,包括 size 等。 s.defaultWriteObject(); s.writeInt(size); // 序列化数组的前size个元素 for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } /** *  自定义反序列化 */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { elementData = EMPTY_ELEMENTDATA;  反序列化没有标记为 static、transient 的字段,包括 size 等 s.defaultReadObject(); s.readInt(); // ignored if (size > 0) { // 数组扩容 int capacity = calculateCapacity(elementData, size); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity); ensureCapacityInternal(size); Object[] a = elementData; // 反序列化元素并填充到数组中 for (int i=0; i<size; i++) { a[i] = s.readObject(); } } }



  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

线程安全

在上面的源码里我们看到很多方法里有快速失败的机制,例如判断扩容的方法:

private void ensureExplicitCapacity(int minCapacity) { // 修改次数 +1,用于 fail-fast 处理 modCount++;

  
 
  • 1
  • 2
  • 3

这种快读失败的机制一定程度上避免了线程安全问题。

"快速失败”即fail-fast,它是java集合的一种错误检测机制。当多钱程对集合进行结构上的改变或者集合在迭代元素时直接调用自身方法改变集合结构而没有通知迭代器时,有可能会触发fast-fail机制并抛出异常。

当然,fail-fast机制只是可能触发,实际上,ArrayList的线程安全还是没有保证的。一般,保证ArrayList的线程安全可以通过这些方案:

  • 使用 Vector 代替 ArrayList。(不推荐,Vector是一个历史遗留类)
  • 使用 Collections.synchronizedList 包装 ArrayList,然后操作包装后的 list 即可。
  • 使用 CopyOnWriteArrayList 代替 ArrayList。
  • 在使用 ArrayList 时,应用程序通过同步机制去控制 ArrayList 的读写(不推荐)。




纸上得来终觉浅,绝知此事要躬行。



参考:

【1】:【死磕 Java 集合】— ArrayList源码分析
【2】:java 集合之 ArrayList 源码解读
【3】:ArrayList 源码分析

文章来源: blog.csdn.net,作者:三分恶,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/sinat_40770656/article/details/108033187

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。