《Java基础知识》Java ArrayList源码分析
前言
分析ArrayList 的源码为JDK8版本。
源码分析
我们先看看一个案例:
public class test2 { public static void main(String[] args) { int index = 10000000; ArrayList arrayList = new ArrayList(); LinkedList linkedList = new LinkedList(); long time0 = System.currentTimeMillis(); for (int i = 0; i < index ; i++) { arrayList.add(i); } long time1 = System.currentTimeMillis(); System.out.println(time1 - time0); long time2 = System.currentTimeMillis(); for (int i = 0; i < index ; i++) { linkedList.add(i); } long time3 = System.currentTimeMillis(); System.out.println(time3 - time2); } }
运行结果:(多次运行结果之后发现不一定谁插入快)
第二种情况,给ArrayList 设置长度。
public class test2 { public static void main(String[] args) { int index = 10000000; ArrayList arrayList = new ArrayList(index); LinkedList linkedList = new LinkedList(); long time0 = System.currentTimeMillis(); for (int i = 0; i < index ; i++) { arrayList.add(i); } long time1 = System.currentTimeMillis(); System.out.println(time1 - time0); long time2 = System.currentTimeMillis(); for (int i = 0; i < index ; i++) { linkedList.add(i); } long time3 = System.currentTimeMillis(); System.out.println(time3 - time2); } }
运行结果:
无法得出结论谁插入快。
我们来看看为什么ArrayList 插入也这么快呢? 先来看看ArrayList 的源码
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
先分析ArrayList 的对象定义,发现继承AbstractList,实现 List<E>, RandomAccess, Cloneable, java.io.Serializable 四个接口。
AbstractList : 抽象类,定义了list的公共抽象方法。
List<E>: 接口, 定义了一些list的公共接口。
RandomAccess:标记接口,表示实现该接口的子类支持角标访问,主要用于判断list 是否实现该接口来知道是否可以通过下标访问,做中间件非常常用。
Cloneable:标记接口,表示实现该接口的子类可以进行克隆:https://www.cnblogs.com/jssj/p/13767756.html
Serializable:标记接口,表示实现该接口的子类可以实现序列化和反序列化:https://www.cnblogs.com/jssj/p/11766027.html
接下来我们看看ArrayList的add方法的源码
public boolean add(E e) { ensureCapacityInternal(size + 1); // 判断list的长度够不够,不够就扩容 elementData[size++] = e; //elementData 是list真正存储数据的数组 return true; }
ok. 我们看看ArrayLIst是如何扩容的。
private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 这一步是判断ArrayList是在创建对象的时候是否传入指定长度。没有传入指定长度的. return Math.max(DEFAULT_CAPACITY, minCapacity); // 则返回默认创建数组的长度,为了后面可以直接扩容。 } return minCapacity; }
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) //判断当前数组的长度是否够用,不够用,调用grow方法扩容。 grow(minCapacity); }
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; //获取原长度 int newCapacity = oldCapacity + (oldCapacity >> 1); //扩大1.5倍得到新长度。 if (newCapacity - minCapacity < 0) //新获得的长度是否小于要出入的位置,如果小,则直接扩大到需要插入位置的大小。(不知道什么场景会进这里) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) // 数组的长度不能超过Integer 的长度-8. 8 指的是数组本身也需要暂用的空间。 newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); // 复制数组。真正的复制逻辑是native本地方法。 }
指定位置添加元素:
public void add(int index, E element) { rangeCheckForAdd(index); // 判断index是否有效 ensureCapacityInternal(size + 1); // 上面已经讲过原理。 System.arraycopy(elementData, index, elementData, index + 1, size - index); // 从该位置复制一份后面的值,全部往后移。 elementData[index] = element; // 最后在当前位置修改元素值。 size++; }
看看ArrayList 是如何删除的:
public E remove(int index) { rangeCheck(index); // 判断角标是否越界 modCount++; //操作计数器(用于迭代器迭代的时候如果这个list发送变化,能够及时感知到,提前报错,而不是获取错误数据) E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // 从删除位置开始到最后的值,全部向前移动。 elementData[--size] = null; // clear to let GC do its work 最后一位清空。 return oldValue; }
关于操作计数器(modCount),再通过案例说明一下
public class test2 { public static void main(String[] args) { int index = 100; ArrayList arrayList = new ArrayList(index); for (int i = 0; i < index ; i++) { arrayList.add(i); } Iterator<Integer> iterator = arrayList.iterator(); while (iterator.hasNext()){ int a = iterator.next(); if(a == 3){ arrayList.remove(a); // list 删除了元素 } } } }
运行结果:
从案例中我们可以看到,迭代的过程中是不允许删除或者添加元素的,修改没有问题,要保证长度不变。
再来看一个案例
public class test2 { public static void main(String[] args) { long[] long1 = new long[]{1,2,3,5}; List arrayList1 = Arrays.asList(long1); System.out.println(arrayList1.size()); Long[] long2 = new Long[]{1l,2l,3l,5l}; List arrayList2 = Arrays.asList(long2); System.out.println(arrayList2.size()); } }
运行结果:
我们要注意基本数据类型是不支持泛型化的。所以数组转list需要小心这种情况。
扩展知识,不可变集合
public class test2 { public static void main(String[] args) { // 不可变集合 List list = Collections.unmodifiableList(Arrays.asList("2","5","7")); list.add("9"); // 该操作是不允许的 } }
运行结果:
总结
1. ArrayList 的底层数据结构使用的是数组。
2. ArrayList是通过创建1.5倍长度的数组来进行扩容的。
3. ArrayList被迭代的时候是不能改变原list的长度的。
4. 使用Arrays.asList 方法的时候需要注意数组是否为基本类型。