1,概述
先来简单聊聊对ArrayList的理解,后续再去源码中求证。
首先底层的数据结构是数组。因为是数组,所以缺点和优点都很明显:
缺点1:因为数组是定长的,所以当超过数组长度,就会数组扩容和元素拷贝,这个很消耗性能。
缺点2:当随机插入或者删除数据的时候,肯定会伴随着大量的数据移动。这个肯定也会很消耗性能。
优点:同样是因为数组,基于角标读取数据,所以随机读写性能非常高。
基于上述,所以ArrayList适合的场景应该是读多写少的场景。
2,ArrayList核心成员变量
/** * #默认的数组大小:10 */ private static final int DEFAULT_CAPACITY = 10; /** * 空对象数组 */ private static final Object[] EMPTY_ELEMENTDATA = {}; /**
*默认的空对象数组 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /**
*实际存储的对象数组 */ transient Object[] elementData; // non-private to simplify nested class access /** * 数组大小 * * @serial */ private int size;
3,构造函数
3.1 ArrayList()
public ArrayList() {
#初始化的时候将数组指向一个默认的空对象数组。也就是说ArrayList初始化的时候数据并不是10,那么为啥经常听到默认的大小是10呢?下面会看到 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
3.2 ArrayList(int initialCapacity)
public ArrayList(int initialCapacity) {
#如果传参大于0,则会初始化一个指定大小的数据。 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
3.3 ArrayList(Collection<? extends E> c)
public ArrayList(Collection<? extends E> c) {
#将传进来的集合转化为数组 elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
4,核心方法
4.1 add(E e)
public boolean add(E e) {
#判断是否需要扩容等操作,核心在这个方法。 ensureCapacityInternal(size + 1); // Increments modCount!!
#元素赋值
elementData[size++] = e; return true; }
那就继续看看ensureCapacityInternal()方法都有啥操作
private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
calculateCapacity()方法:
#这个方法如果是无参构造函数刚传入的时候,返回10;如果是有参构造函数,传入size+1
private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }
ensureExplicitCapacity()方法:
private void ensureExplicitCapacity(int minCapacity) { modCount++; // #从这里就明显看出来了。如果传入的是无参构造函数的时候,会进行扩容,扩容的数组大小就是10了,这就是为啥有人说初始化的数组大小是10。另外就是每次都判断一下,size+1大于数组长度,也要进行扩容。 if (minCapacity - elementData.length > 0) grow(minCapacity); }
grow()方法:可以大胆的猜测了,这个方法一个就是实现了数组的扩容,一个就是实现了元素拷贝了。
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); // 这一段话就是将数组扩大1.5倍,然后将原来的元素再放入新的数组中 elementData = Arrays.copyOf(elementData, newCapacity); }
add()方法需要学习到的点:
1,无参构造函数并不是开始就给了一个长度为10的数组,而是在add()方法执行的时候,判断扩容的时候初始化了一个长度为10的数组。
2,数组扩容的大小是1.5倍:newCapacity = oldCapacity + (oldCapacity >> 1)。记得HashMap的数组扩容是2倍。
3,学学Arrays工具类的使用。将数组扩容到多大,然后将元素赋值到新数组的方法:elementData = Arrays.copyOf(elementData, newCapacity);
4.2 get(int index)
public E get(int index) {
#判断角标是否越界 rangeCheck(index); #通过数组角标获取值 return elementData(index); }
get()方法很简单,没啥特别的关注
4.3 add(int index,E element)
public void add(int index, E element) {
#判断角标是否越界 rangeCheckForAdd(index); #这个方法在add()见过了,功能就是判断是否需要扩容,如果扩容就需要把之前的元素赋值到新的数组中去。 ensureCapacityInternal(size + 1); // Increments modCount!!
#这个方法是一个native方法,它的功能是在index角标位置处,将元素后移一位,也就是把index角标的位置腾出来。可以看到这些数据的大量移动都是消耗性能的 System.arraycopy(elementData, index, elementData, index + 1,size - index);
#将元素插入到index角标处 elementData[index] = element; size++; }
add(int index,E element)方法需要学习的点:
1,在指定角标插入数据,往往就伴随着数据的移动了,数组越长,移动的数据就越多。这都是很消耗性能的。
2,学着用System.arraycopy(elementData, index, elementData, index + 1,size - index)方法。
public static void main(String[] args) { // String[] arr = {}; // arr=Arrays.copyOf(arr,10); // System.out.println(arr.length); String arr[] = new String[10]; int index = 3; //{"a","b","c","e","f"} arr[0] = "a"; arr[1] = "b"; arr[2] = "c"; arr[3] = "e"; arr[4] = "f"; System.arraycopy(arr, index, arr, index + 1, 2); arr[index] = "d"; for (String ss : arr) { System.out.println(ss); } }
效果:
5,小结
其他还有remove(),set()方法等等就不详细说了,都是挺简单的,就是围绕着操作这个数组,然后数据是否需要移动,是否需要扩容等等。总而言之,使用ArrayLIst最好事先知道大概量,给个预定值,减少它的数组扩容和元素移动。