本次源码解析,只暂时解析构造函数,add,remove方法。其余的等有时间再做补充
三种初始化方式
- 无参数
public ArrayList() { 无参数时将会初始化为一个空的数组 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; |
- int
public ArrayList(int initialCapacity) { 判断给定的int类型的值是否为0, 为0,那么将初始化为一个空的数组 不为0,初始化一个给定值长度的数组 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
private static final Object[] EMPTY_ELEMENTDATA = {}; |
- collection
public ArrayList(Collection<? extends E> c) { //将集合转换为数组 elementData = c.toArray(); //将数组的长度赋给当前arraylist的size,并判断是否为0 if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) //这句话是将,toArray这个方法可能不能返回一个object[]的数组,让看编号为6260652的bug if (elementData.getClass() != Object[].class) //如果这个返回的不是一个object[]的数组,那么将重新构造一个大小为size的object[]数组 elementData = Arrays.copyOf(elementData, size, Object[].class); } else { //这是一个空的数组,初始化为一个空的数组 // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } } |
注意点:
很多文章都说arraylist的初始长度为10,其实这是不正确的。
从三个初始化的方法来看,不带参数的初始化,会初始化为一个空的数组,带参数的初始化,如果参数为0,也将初始化为一个空的数组,如果参数不为0,那么就根据参数的值,初始化为一个长度为该值的数组。
如果传入的为一个集合。将会判断该集合的长度,如果长度为0,将初始化为一个空的数组,否则将初始化为一个该长度的数组。
那么在什么时间将arraylist的长度初始化为10的呢
是在添加方法的时候,也就是说,当你向一个空的arraylist中添加第一个数据的时候,这个时候会将长度初始化为10
具体的介绍可以看下面的添加方法的详细介绍。
添加方法
- 在末尾添加元素
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } |
主要是下面的这四个方法:
会判断当前的arraylist是不是一个空的数组。如果是空的数组,将会判断最小的容量和默认的容量,也就是10,的大小, 显然,当你第一次添加数据的时候,你的minCapacity(最小容量的值也就是1)的值是小于10的。 所以在给定一个默认的一个容量的值为10.但是呢,这个时候并没有给这个arraylist的size赋值,
private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }
private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
首先是modCount(修改次数)加1,这个在以后的技术博客中讲到,感兴趣的可以去搜一下Fail-Fast机制。 接下来判断我们的最小容量跟当前arraylist的长度做对比 防止溢出代码:确保指定的最小容量 > 数组缓冲区当前的长度 最后这个grow方法才是用来扩容的方法
private void ensureExplicitCapacity(int minCapacity) { modCount++;
// overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } |
private void grow(int minCapacity) { // overflow-conscious code 获取到当前的arraylist的数据,也就是0 int oldCapacity = elementData.length; 注意: 这个用到了右移运算符,这也就是我们常说的,当arraylist第一次进行扩容时候,会怎样进行扩容,扩容为多少倍,答案是1.5倍, 我们第一次的容量也就是10,二进制表示为:0000 1010 右移之后为:0000 0101 也就是5,所以扩容后,新容量为15.也就是1.5倍。 如果我们是第一次添加数据的时候,此时的oldCapacity为0,所以newCapacity也为0。 int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: 这里呢,也就是我们关键的扩容的地方了,这个将原先的数组从copy了一份,赋给了一个新的长度为10的一个数组, 这也就是我们扩容的时候经常被问到的,arraylist是如何进行扩容的呢,答案就是将原先的的数据复制一份, 赋值给一个新的长度为扩容后的数组。 copyOf方法为jdk底层的方法,这里就不展开了,有兴趣的可以去看一下。 elementData = Arrays.copyOf(elementData, newCapacity); } |
到这里我们的ensureCapacityInternal(size + 1); 这行代码已经执行完了。
接下来就是在我们的数组的后面添加一个元素elementData[size++] = e;
- 指定位置添加元素
public void add(int index, E element) { rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
这个跟我们在最后面添加元素就很像了。 首先第一个是判断当前给定的这个值是否大于我们数组的长度或者小于0。也就是判断数组是否越界,然后抛出异常。 ensureCapacityInternal(size + 1); 这里跟我们上面的那个在数组的最后添加元素是一样的,这里就不解析了。 System.arraycopy也是jdk的一个方法,意思就是从index位置开始,将后面的所有元素后移一位。前面的元素保持不变。 举个例子。 数组a:{1,2,3,4,5,0,0,0} 假设index为1,修改后 数组b:{1,2,2,3,4,5,0,0} 然后将数组下标为index的值改为传进来的值。 |
删除方法
- 根据位置删除元素
public E remove(int index) { 检查是否数组越界 rangeCheck(index); 同样的,感兴趣的可以去搜一下Fail-Fast机制。 modCount++; E oldValue = elementData(index); 要移动的元素的长度 int numMoved = size - index - 1; if (numMoved > 0) //同样,这个方法也是将元素的位置向前移动numMoved个位置,具体的代码可以看jdk的源码 System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work
return oldValue; } private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } |
- 根据元素的值删除元素
-
public boolean remove(Object o) {
这个判断要移动的元素是否为null,如果为null,循环数组的元素,判断是否为null,
然后进行删除。
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
如果不为null的时候循环数组元素,判断是否为object。如果是就删除。
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
值得注意的是这个fastRemove是干什么的,这个与我们上面根据元素的位置删除时很类似的,不同的是缺少rangeCheck(index);这个检查,为什么这个可以不用做这个检查呢。
是因为在执行我们这个fastRemove时,我们已经判断了当前元素是否等于我们传进来的object的值,所以这个index一定是存在的。
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
}