基于jdk1.8的ArrayList源码分析
前言
ArrayList作为一个常用的集合类,这次我们简单的根据源码来看看AarryList是如何使用的。
ArrayList拥有的成员变量
1 public class ArrayList<E> extends AbstractList<E> 2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable 3 { 4 private static final long serialVersionUID = 8683452581122892189L; 5 6 /** 7 * Default initial capacity. 8 */ 9 private static final int DEFAULT_CAPACITY = 10; 10 11 /** 12 * Shared empty array instance used for empty instances. 13 */ 14 private static final Object[] EMPTY_ELEMENTDATA = {}; 15 16 /** 17 * Shared empty array instance used for default sized empty instances. We 18 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when 19 * first element is added. 20 */ 21 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 22 23 /** 24 * The array buffer into which the elements of the ArrayList are stored. 25 * The capacity of the ArrayList is the length of this array buffer. Any 26 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 27 * will be expanded to DEFAULT_CAPACITY when the first element is added. 28 */ 29 transient Object[] elementData; // non-private to simplify nested class access 30 31 /** 32 * The size of the ArrayList (the number of elements it contains). 33 * 34 * @serial 35 */ 36 private int size;
根据源码显示
1. 实现的接口看出,支持随机访问,克隆,序列化;
2. 第9行代码表示默认初始的容器大小DEFAULT_CAPACITY为10;
3. 第29行代码中的elementData存储数组数据,是 Object[] 类型的数组;
4. size为实际数组大小;
构造函数
1 /** 2 * 构造一个指定初始容量的空列表 3 * @param initialCapacity ArrayList的初始容量 4 * @throws IllegalArgumentException 如果给定的初始容量为负值 5 */ 6 public ArrayList(int initialCapacity) { 7 if (initialCapacity > 0) { 8 this.elementData = new Object[initialCapacity]; 9 } else if (initialCapacity == 0) { 10 this.elementData = EMPTY_ELEMENTDATA; 11 } else { 12 throw new IllegalArgumentException("Illegal Capacity: "+ 13 initialCapacity); 14 } 15 } 16 17 // 构造一个默认的空列表,但是没有分配大小,直到第一次添加元素的时候才给数组赋初始大小为10 18 public ArrayList() { 19 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 20 } 21 22 /** 23 * 构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回的顺序排列的 24 * @param c 包含用于去构造ArrayList的元素的collection 25 * @throws NullPointerException 如果指定的collection为空 26 */ 27 public ArrayList(Collection<? extends E> c) { 28 elementData = c.toArray(); 29 if ((size = elementData.length) != 0) { 30 // c.toArray()可能不会正确地返回一个 Object[]数组,那么使用Arrays.copyOf()方法 31 if (elementData.getClass() != Object[].class) 32 //Arrays.copyOf()返回一个 Object[].class类型的,大小为size,元素为elementData[0,...,size-1] 33 elementData = Arrays.copyOf(elementData, size, Object[].class); 34 } else { 35 // replace with empty array. 36 this.elementData = EMPTY_ELEMENTDATA; 37 } 38 }
添加元素源码解析
执行add方法
1 public boolean add(E e) { 2 ensureCapacityInternal(size + 1); // Increments modCount!! 3 elementData[size++] = e; 4 return true; 5 }
ensureCapacityInternal(size + 1)方法表示是一个扩容操作,进入方法显示
1 private void ensureCapacityInternal(int minCapacity) { 2 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); 3 } 4 5 private void ensureExplicitCapacity(int minCapacity) { 6 modCount++; 7 8 // overflow-conscious code 9 if (minCapacity - elementData.length > 0) 10 grow(minCapacity); 11 }
modCount用于记录ArrayList的结构性变化的次数,add()、remove()、addall()、removerange()及clear()方法都会让modCount增长。这个参数暂时可以忽略,主要是用在集合的Fail-Fast机制(即快速失败机制)的判断中使用的,限于篇幅原因就不展开了
进入calculateCapacity方法可看到以下代码段
1 private static int calculateCapacity(Object[] elementData, int minCapacity) { 2 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 3 return Math.max(DEFAULT_CAPACITY, minCapacity); 4 } 5 return minCapacity; 6 }
当elementData为空时,也就是我们在第一次添加添加元素的时候,默认赋给容器的大小为DEFAULT_CAPACITY,也就是我们前面说的初始大小为10
新增元素后的大小minCapacity是否超过当前集合的容量elementData.length,如果超过,则调用grow方法进行扩容。我们进入该方法进行查看:
1 private void grow(int minCapacity) { 2 // overflow-conscious code 3 int oldCapacity = elementData.length; 4 int newCapacity = oldCapacity + (oldCapacity >> 1); 5 if (newCapacity - minCapacity < 0) 6 newCapacity = minCapacity; 7 if (newCapacity - MAX_ARRAY_SIZE > 0) 8 newCapacity = hugeCapacity(minCapacity); 9 // minCapacity is usually close to size, so this is a win: 10 elementData = Arrays.copyOf(elementData, newCapacity); 11 }
扩容操作比较简单,基本的思路就是新加入元素后所需的的容器大小与原先容器的大小相比,小了则扩容oldCapacity + (oldCapacity >> 1),也就是1.5倍,右移相当于除以2,然后将老容器的数据赋值到新容器上,多次的扩容比较消耗性能,所以如果一开始知道数据量比较大可以先赋一个合适的初始容量
其他向某个位置插入元素与addAll方法都与上面add方法类似,基本思路都是先比较容量大小,或者增加一个索引越界判断,然后将数据插入适当的位置,对后面的数据进行移位操作。
remove方法源码解析
根据索引删除
1 public E remove(int index) { 2 rangeCheck(index); 3 4 modCount++; 5 E oldValue = elementData(index); 6 7 int numMoved = size - index - 1; 8 if (numMoved > 0) 9 System.arraycopy(elementData, index+1, elementData, index, 10 numMoved); 11 elementData[--size] = null; // clear to let GC do its work 12 13 return oldValue; 14 }
remove方法原理add方法类似
1.查找相应位置的数据
2.将index后面的数据向前移动一位,也就是把原先索引在index位置的对象覆盖
3.第11行代码将多出的一位置为null,clear to let GC do its work,让回收器能够回收对象数据
4.返回删除的对象
根据对象删除
1 public boolean remove(Object o) { 2 if (o == null) { 3 for (int index = 0; index < size; index++) 4 if (elementData[index] == null) { 5 fastRemove(index); 6 return true; 7 } 8 } else { 9 for (int index = 0; index < size; index++) 10 if (o.equals(elementData[index])) { 11 fastRemove(index); 12 return true; 13 } 14 } 15 return false; 16 }
1.循环查询list中的对象是否与传入的对象相同
2.相同则执行fastRemove(index)方法
3.fastRemove方法与根据索引删除的方法一致,简单的赋值移位。
private void fastRemove(int index) { modCount++; 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 }
contain方法源码解析
public boolean contains(Object o) { return indexOf(o) >= 0; } public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; }
1.便利查询是否有该对象,有则返回对应索引,否则返回-1
在上述的删除操作中,常用到以下方法
1 public static native void arraycopy(Object src, int srcPos, 2 Object dest, int destPos, 3 int length);
这是一个native关键字修饰的本地方法本地方法和其它方法不一样,本地方法意味着和平台有关,因此使用了native的程序可移植性都不太高。另外native方法在JVM中运行时数据区也和其它方法不一样,它有专门的本地方法栈。native方法主要用于加载文件和动态链接库,由于Java语言无法访问操作系统底层信息(比如:底层硬件设备等),这时候就需要借助C语言来完成了。被native修饰的方法可以被C语言重写。
参数 | 说明 |
src | 源数组 |
srcPos | 源数组索引,也就是说要从哪里开始复制 |
dest | 目标数组 |
destPos | 目标数组索引,从第几个位置放置新复制过来的数组 |
length | 要复制的数组长度 |