ArrayList源码分析
构造器
从源码的3个构造器中可以看出,都只是为了初始化数组elementData的大小
//当第一个元素被添加时的默认拓展的容量
private static final int DEFAULT_CAPACITY = 10;
//数组elementData设置为空的实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//数组elementData的初始化实例,与DEFAULT_CAPACITY相关联
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存放ArrayList元素的数组
transient Object[] elementData;
//元素的数量,不是elementData的大小
private int size;
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//构造容量为initialCapacity的数组
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//用Collection构造ArrayList,可猜测所有Collection的实现类之间的互相转换可以用这个类似的构造器实现转换。
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
// 比如c.toArray返回的是String[]
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
增删改查API
从增删改查API了解ArrayList原理, 通过数组的copy操作进行自动拓容。
增 :add(E e)
用new ArrayList<String>().add("xxx")
来说明添加元素的过程,首先是无参构造,然后调用add方法新增元素“xxx”。add方法代码如下:
//往数组末尾添加元素e
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
其中ensureCapacityInternal()
方法是通过自动拓容来确保足够的容量来添加元素。逻辑:
a . 先计算最小容量
b. 如果比原来的elementData长度大,则进行拓容,使用Arrays.copyOf(elementData, newCapacity);
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//根据最小容量来自动拓容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//计算最小的容量,如果ArrayList是无参构造的,返回DEFAULT_CAPACITY和minCapacity中大的一个
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//对elementData进行拓容,通过复制生成一个包含原数组元素和新容量大小的数组
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.MAX_VALUE : MAX_ARRAY_SIZE;
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
改:set(int index, E element)
set是将elementData中下标为index的元素替换为element, 并返回该下标的旧值
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
E elementData(int index) {
return (E) elementData[index];
}
查:get(int index)
返回数组elementData中下标为index的元素
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
删: remove(int index)
删除下标为index的元素,返回旧值。
public E remove(int index) {
rangeCheck(index);
modCount++;
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;
}
从上面的代码通过System.arraycopy(....)
数组复制来实现index后面的所有元素向左移动一位,来举例说明一下arraycopy
的逻辑
//假如要删除元素b
String[] array = {"a","b","c","d"};
int b_index = 1;
int size =array.length();
int numMoved = size - b_index - 1;//要移动的元素数量4-1-1
/**
* 源数组array中b_index+1后的2个元素是 [c,d];
* 将[c,d] 在目标数组array的b_index位置开始替换
* 复制后的数组就变为了 {"a","c","d","d"},然后将最后一个元素设为null。
* 这里的源数组和目标数组都是array
*/
System.arraycopy(array, b_index+1, array, b_index, numMoved);
//最终 array 就是 {"a","c","d",null}
array[--size] = null;
另外add(int index, E element)
, addAll(Collection<? extends E> c)
也是使用System.arraycopy
实现的。
序列化
ArrayList
实现了接口Serializable
,但是它的属性transient Object[] elementData;
使用了关键字transient
,会在序列化的时候忽略elementData
吗?如果真的忽略它,那反序列化的时候实际数据就没了,所以肯定是不行的。ArrayList
类里有2个私有方法实现了序列化。
private void writeObject(java.io.ObjectOutputStream s){
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
}
private void readObject(java.io.ObjectInputStream s){...}
拿为什么要这样做,而不直接使用elementData
来序列化呢?
从writeObject
的for循环中可以看出,用size作为最大值,而不是elementData.length().
因为size是实际的元素大小,而elementData.length()
有可能大于size的。 从add(E e)
方法可以看出,当添加第一个元素时,elementData
的长度是10,而size是1。
所以这样子实现序列化就会减少不必要的网络开销。
常用API DEMO
public class ArrayListDemo {
public static void main(String[] args) {
//Arrays.asList返回的ArrayList是Arrays的内部类,并不是java.util.ArrayList
List<String> stringArray =Arrays.asList(new String[]{"a","b","c","d"});
ArrayList<String> arrayList = new ArrayList<String>();
arrayList.addAll(stringArray);
//add arrayList: a,b,c,d
System.out.println("add arrayList: "+toArrayString(arrayList));
arrayList.addAll(2,stringArray);
//add arrayList: a,b,a,b,c,d,c,d
System.out.println("add arrayList: "+toArrayString(arrayList));
arrayList.sort((e1,e2) -> {
return e2.compareTo(e1);
});
//sort arrayList: d,d,c,c,b,b,a,a
System.out.println("sort arrayList: "+toArrayString(arrayList));
arrayList.replaceAll(e -> {return e+"1";});
//replace All : d1,d1,c1,c1,b1,b1,a1,a1
System.out.println("replace All : "+toArrayString(arrayList));
arrayList.removeAll(Arrays.asList(new String[]{"d1"}));
//remove All d1: c1,c1,b1,b1,a1,a1
System.out.println("remove All d1: "+toArrayString(arrayList));
arrayList.retainAll(Arrays.asList(new String[]{"a1","b1"}));
//retain All a1,b1: b1,b1,a1,a1
System.out.println("retain All a1,b1: "+toArrayString(arrayList));
List<String> subArray = arrayList.subList(1,3);
//subList : b1,a1
System.out.println("subList : "+toArrayString(subArray));
//[b1,a1]
String[] toArray = subArray.toArray(new String[0]);
}
private static String toArrayString(List<String> arrayList){
StringBuilder sb = new StringBuilder();
arrayList.forEach(e ->{
sb.append(e).append(",");
});
sb.deleteCharAt(sb.lastIndexOf(","));
return sb.toString();
}
}