【Java】- 源码解析——ArrayList
一、ArrayList简介
由于ArrayList底层是通过数组进行实现的,所以我们在说ArrayList之前我们先说下数组
数组:
优点: a、有序 ---- > 存储的顺序位置和访问取出的顺序一致
b、查询取值速度快 ---- > 根据索引可以直接查询定位索要的value值
缺点: a、数组长度定义后不可改变,即不可扩容
b、数组由于是有序,所以在中间进行插入删除值时会很慢
ArrayList:
a、由于ArrayList底层时通过数组来实现的List类,ArrayList集合满足了数组的所有有点,同时改善了数据的部分缺点,比如可以自动扩容
b、该类定义了一个Object[]的数组,来达到存储任何值的效果,并且类中通过capacity属性来记录数组的长度,若是在数组中添加数据,
那么capacity就会自动增长来统计Object[]的数组长度
c、若是该类有大量数据存储,可以在创建对象时传入capacity值,来定义集合的长度(内部Object[])数组的长度,从而建少扩容次数,减少
重分配的次数,提高性能
d、ArrayList 和 vector 提供了一模一样的方法,唯一的缺点就是 vector 类是线程安全的,而 ArrayList 是线程非安全的,所以效率来说
ArrayList 比 Vector 更快
二、ArrayList继承关系
1 public class ArrayList<E> extends AbstractList<E> 2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable 3 4 public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>
有代码可知:
ArrayList 继承 AbstractList 继承 AbstractCollection
实现 List RandomAccess Cloneable Serializable
三、源码讲解
1、类属性讲解
1 public class ArrayList<E> extends AbstractList<E> 2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable 3 { 4 // 版本号用于序列反序列化时版本匹配 5 private static final long serialVersionUID = 8683452581122892189L; 6 // 缺省容量 7 private static final int DEFAULT_CAPACITY = 10; 8 // 空对象数组 9 private static final Object[] EMPTY_ELEMENTDATA = {}; 10 // 缺省空对象数组 11 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 12 // 元素数组用例存储arraylist的value值 13 transient Object[] elementData; 14 // 实际元素大小,默认为0 15 private int size; 16 // 最大数组容量 17 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 18 }
2、构造方法
2.1、无参构造方法
1 /** 2 * 无参构造方法, 同时为 elementData 进行初始化,类型为Object[], 3 * 且数组长度为空,后面会进行赋默认值10 4 */ 5 public ArrayList() { 6 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 7 }
2.2、自定义数组长度构造方法
1 public ArrayList(int initialCapacity) { 2 if (initialCapacity > 0) { // 自定义容量大小>0时,将自定义容量作为数组初始化容量大小 3 this.elementData = new Object[initialCapacity]; 4 } else if (initialCapacity == 0) {// 自定义容量大小=0时,通过空对象数组EMPTY_ELEMENTDATA来初始化数组 5 this.elementData = EMPTY_ELEMENTDATA; 6 } else { // 自定义容量大小<0时, 抛出IllegalArgumentException不合法的参数异常 7 throw new IllegalArgumentException("Illegal Capacity: "+ 8 initialCapacity); 9 } 10 }
2.3、通过一个集合来调用构造函数
1 public ArrayList(Collection<? extends E> c) { // 传参为继承Collection的数组 2 // object[] toArray() 从第一个到最后一个返回数组来初始化elementData 转换为数组 3 elementData = c.toArray(); 4 if ((size = elementData.length) != 0) {// 若是elementData.length不为0且将elementData.length赋值给size 5 // 通过反射判断若是elementData对象不是Object[].class的对象 6 if (elementData.getClass() != Object[].class) 7 // 通过Arrays.copyOf来处理,返回新数组对象,且数组类型为Object[] 8 elementData = Arrays.copyOf(elementData, size, Object[].class); 9 } else { 10 this.elementData = EMPTY_ELEMENTDATA; // 通过空对象数组EMPTY_ELEMENTDATA来初始化数组 11 } 12 }
总结:arrayList的构造方法就做一件事情,就是初始化一下储存数据的容器,其实本质上就是一个数组,在其中就叫elementData。
2.4、提供的核心方法
2.4.1、add()方法:
1 // 在末尾添加元素<E>类型元素 2 public boolean add(E e) { 3 ensureCapacityInternal(size + 1); // 判断数组容量是否够用 size代表数组中的元素个数 4 elementData[size++] = e; // 将原则添加在数组中,且size自增1 5 return true; 6 }
1 private void ensureCapacityInternal(int minCapacity) { // minCapacity=size+1 size代表数组中的元素个数 2 // 若是elementData为空: 3 // 1、无参函数创建 new ArrayList() 4 // 2、有参自定义值为0,调用构造函数 new ArrayList(0) 5 // 3、传入集合,但集合为空,new ArrayList(new LinkList(0)) 6 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 7 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 将 minCapacity 赋值为10 默认值 8 } 9 // 具体判定容量方法 10 ensureExplicitCapacity(minCapacity); 11 }
1 private void ensureExplicitCapacity(int minCapacity) { 2 modCount++; 3 // minCapacity 解析:(minCapacity代表elementData数组存储数组所需的最小容量) 4 //若是数组初始化后第一次调用 由ensureCapacityInternal()方法中判断可知minCapacity赋值为10 5 //而elementData.length=0 6 //若是数组初始化后非第一次调用 由ensureCapacityInternal()方法中判断可知minCapacity = size+1 7 if (minCapacity - elementData.length > 0) 8 grow(minCapacity); 9 }
1 private void grow(int minCapacity) { 2 // 获取elementData数组原长度 放入oldCapacity变量中 3 int oldCapacity = elementData.length; 4 // 扩容elementData.lenth*1.5倍后的值放入变量newCapacity中, oldCapacity >> 1 有位移相当于oldCapacity/2 5 int newCapacity = oldCapacity + (oldCapacity >> 1); 6 if (newCapacity - minCapacity < 0) // 扩容后的容量值<最小所需容量值 适用于elementData为空数组时 7 newCapacity = minCapacity; // 就直接用最小容量进行扩容 8 //private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 9 if (newCapacity - MAX_ARRAY_SIZE > 0) // 原数组扩容1.5倍后的值>最大数组容量 10 newCapacity = hugeCapacity(minCapacity); // 将最大容量赋值给newCapacity 11 elementData = Arrays.copyOf(elementData, newCapacity);// 通过newCapacity进行扩容 12 }
1 private static int hugeCapacity(int minCapacity) { 2 if (minCapacity < 0) // overflow 抛出OutOfMemoryError堆内存溢出 3 throw new OutOfMemoryError(); 4 // Integer.MAX_VALUE:2147483647 MAX_ARRAY_SIZE:2147483639 5 // 若是minCapacity > MAX_ARRAY_SIZE则返回Integer.MAX_VALUE 否则返回MAX_ARRAY_SIZE 6 return (minCapacity > MAX_ARRAY_SIZE) ? 7 Integer.MAX_VALUE : 8 MAX_ARRAY_SIZE; 9 }
Arrays.copyOf() 方法说明
1 //方法传回的数组是新的数组对象,改变传回数组中的元素值,不会影响原来的数组。 2 // copyOf()的第二个自变量指定要建立的新数组长度,如果新数组的长度超过原数组的长度,则保留数组默认值 3 // copyOf()的第二个自变量指定要建立的新数组长度,如果新数组的长度小于原数组的长度,则舍弃多出值 4 public static void main(String[] args) { 5 int[] arr1 = {1, 2, 3, 4, 5}; 6 int[] arr2 = Arrays.copyOf(arr1, 5); 7 int[] arr3 = Arrays.copyOf(arr1, 10); 8 int[] arr4 = Arrays.copyOf(arr1, 4); 9 System.out.print("原数组为:"); 10 for(int i : arr1){ 11 System.out.print(i+" "); 12 } 13 System.out.print("\n"); 14 System.out.print("等于原数组为:"); 15 for(int i : arr2){ 16 System.out.print(i+" "); 17 } 18 System.out.print("\n"); 19 System.out.print("多于原数组为:"); 20 for(int i : arr3){ 21 System.out.print(i+" "); 22 } 23 System.out.print("\n"); 24 System.out.print("少于原数组为:"); 25 for(int i : arr4){ 26 System.out.print(i+" "); 27 } 28 }
1 public void add(int index, E element) { 2 rangeCheckForAdd(index); //校验索引有效性 3 ensureCapacityInternal(size + 1); // 校验数组容量是否需要扩容 参考以上详细讲解 4 System.arraycopy(elementData, index, elementData, index + 1, 5 size - index); 6 elementData[index] = element; 7 size++; 8 }
1 public void add(int index, E element) { 2 rangeCheckForAdd(index); //校验索引有效性 3 ensureCapacityInternal(size + 1); // 校验数组容量是否需要扩容 参考以上详细讲解 4 System.arraycopy(elementData, index, elementData, index + 1, 5 size - index); 6 elementData[index] = element; 7 size++; 8 }
1 private void rangeCheckForAdd(int index) { 2 //若是索引信息>数组的容量或着索引信息<0 则抛出异常IndexOutOfBoundsException数组越界异常 3 if (index > size || index < 0) 4 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 5 }
2.4.2、删除方法:
1 public E remove(int index) { 2 rangeCheck(index); // 索引校验 当index索引值>size数组容量时 IndexOutOfBoundsException 抛出数组越界异常 3 modCount++; 4 E oldValue = elementData(index); // 取出数组索引对应的value值 5 int numMoved = size - index - 1; // 计算需要数组往前移动的位数 6 if (numMoved > 0) 7 System.arraycopy(elementData, index+1, elementData, index, 8 numMoved); // 此方法为System类静态方法且native修饰,作用将删除索引位置后的所有元素,往前移动一位 9 elementData[--size] = null; // size数组真实容量-1 且 将数组最后一位置为null 等待GC回收器删除 10 return oldValue; // 返回被remove指定索引的值 11 }
1 // 该方法不用解释,简单来说就是通过传参Object o 值来循环遍历数组, 2 // 若是存在这个元素就将该元素的索引传给fastRemove(index)来进行删除 3 // 通过此方法 remove(Object o)可以知道 ArrayList可以存储null值 4 public boolean remove(Object o) { 5 if (o == null) { 6 for (int index = 0; index < size; index++) 7 if (elementData[index] == null) { 8 fastRemove(index); 9 return true; 10 } 11 } else { 12 for (int index = 0; index < size; index++) 13 if (o.equals(elementData[index])) { 14 fastRemove(index); 15 return true; 16 } 17 } 18 return false; 19 }
fastRemove(index)分析
1 // 此方法不做过多介绍,和 remove(int index) 几乎相同,只不过remove(int index) 获取了index对应的元素 2 private void fastRemove(int index) { 3 modCount++; 4 int numMoved = size - index - 1; 5 if (numMoved > 0) 6 System.arraycopy(elementData, index+1, elementData, index, 7 numMoved); 8 elementData[--size] = null; // clear to let GC do its work 9 }
1 public boolean removeAll(Collection<?> c) { 2 Objects.requireNonNull(c); // 校验集合不能为null 否则抛出空指针异常 3 return batchRemove(c, false); 4 }
1 private boolean batchRemove(Collection<?> c, boolean complement) { 2 // complement 为false 则为removeAll()调用使用 为true 则为retainAll()用 3 // retainAll() 是用来检测两个集合是否有交集的 4 final Object[] elementData = this.elementData; //将原集合,记名为 elementData 5 int r = 0, w = 0; //r用来控制循环,w是记录有多少个交集/差集 removeAll 调用记录差集 retainAll()记录交集 6 boolean modified = false; 7 try { 8 for (; r < size; r++) 9 if (c.contains(elementData[r]) == complement) // 校验c集合元素是否在elementData中是否为false/true 10 // 若是 complement = true 则将 c中包含elementData中的元素 替换 elementData[w++]位置值 达到元素前移效果 11 // 若是 complement = false则将 c中不包含elementData中的元素 存入 elementData[w++]位置值 达到元素前移效果 12 elementData[w++] = elementData[r]; 13 } finally { 14 if (r != size) { // r != size 只会在 c.contains()直接报错,否则r==size 一直不走此段逻辑 15 System.arraycopy(elementData, r, 16 elementData, w, 17 size - r); 18 w += size - r; 19 } 20 if (w != size) { // 将 自 elementData[w] 到 elementData[size-1]的值全部置为null 21 for (int i = w; i < size; i++) 22 elementData[i] = null; 23 modCount += size - w; // 标记elementData数组修改次数 24 size = w; // 修改 size 实例容量 25 modified = true; 26 } 27 } 28 return modified; 29 }
2.4.3、clear方法
从列表中删除所有元素。
1 // 循环遍历数组将数组元素全部置为null 等等GC回收机制回收 2 public void clear() { 3 modCount++; 4 // clear to let GC do its work 5 for (int i = 0; i < size; i++) 6 elementData[i] = null; 7 8 size = 0; 9 }
2.4.4、ensureCapacity(int minCapacity)方法
public void ensureCapacity(int minCapacity) { // 如果elementData不为DEFAULTCAPACITY_EMPTY_ELEMENTDATA(常量{})那么minExpand =0否则minExpand = DEFAULT_CAPACITY(常量10) int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY; if (minCapacity > minExpand) { // 修改elementData容量 ensureExplicitCapacity(minCapacity); } }
ensureExplicitCapacity(minCapacity)解析
1 private void ensureExplicitCapacity(int minCapacity) { 2 modCount++; 3 if (minCapacity - elementData.length > 0) // 若是 minCapacity>当前数组的容量 则进行扩容 4 grow(minCapacity); 5 }
1 public E set(int index, E element) { 2 // 检验索引是否合法 3 rangeCheck(index); 4 // 旧值 5 E oldValue = elementData(index); 6 // 赋新值 7 elementData[index] = element; 8 // 返回旧值 9 return oldValue; 10 }
四、ArrayList 总结:
感谢!!!!