以下内容基于JDK1.8
以下内容将分为如下几个章节
List
接口List
相关的操作Vector
的方法来操作内部数组(用于存储列表)的大小 /**
* 默认初始容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空数组,如果传入的容量为0或传入集合元素个数为0时使用
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 空数据,当调用new ArrayList()构造函数时使用
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存储元素数据,以下简称存储数组
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* 集合中元素个数
* @serial
*/
private int size;
Object[] elementData
比较特殊的是修饰符transient
,这个之前未接触过,有必要了解下
1.1 transient修饰符
transient
作用是让被修饰的成员在对象进行序列化操作时不被序列化
但作为ArrayList的核心elementData
,其不被序列化那反序列化的的时候数据不就丢失了吗?
其原因是ArrayList重写了writeObject
方法,其在对象实例被序列化时调用
1.2 将对象实例写入流中writeObject(java.io.ObjectOutputStream s)
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
//写入非transient修饰的成员变量
s.defaultWriteObject();
// 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]);
}
//判断对象实例是否被篡改
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
这么做的目的是因为存储数组的长度不是真实的元素个数,size
才是.
如果不声明elementData
为transient
,那么在存储数组中(elementData.length-size)
个空间是未被使用的,
所以需要将其声明为transient
并重写writeObject
方法,进而节省存储空间
readObject(java.io.ObjectInputStream s)
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
//默认存储数组为空数组
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
//读取ArrayList对应的大小等其他属性
s.defaultReadObject();
// Read in capacity
//读取容量
s.readInt(); // ignored
if (size > 0) {
//与clone()类似,根据大小而不是容量分配数组
//确保容量最终,肯定会发送扩容
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
//将流中的元素依次写入到存储数组中
a[i] = s.readObject();
}
}
}
还有个问题,writeObject
和readObject
方法都是私有的,在ArrayList或者整个rt.jar
都没有找到调用记录,那么在序列化的时候是如何被调用到的呢?
public class ArrayListTest4 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
List<String> list2 = new ArrayList<>();
list2.add("a");
list2.add("b");
list2.add("c");
//将对象序列化到字节数组中
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(list2);
byte[] byteArray = byteArrayOutputStream.toByteArray();
//从字节数组中反序列化对象
ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(byteArray));
Object o = inputStream.readObject();
System.out.println(o.toString());
}
}
通过调试上述代码可知,将对象序列化时的调用顺序为:
oos—ObjectOutputStream
osc—ObjectStreamClass
oos.writeObject
—>oos.writeObject0
—>osc.lookup—>oos.writeOrdinaryObject
—>oos.writeSerialData
—>osc.invokeWriteObject
->ArrayList.writeObject
其中,ArrayList.writeObject
是通过反射的方式调用的
而为什么能找到writeObject这个方法呢?答案就在ObjectOutputStream的私有构造方法中,上代码
private ObjectStreamClass(final Class<?> cl) {
this.cl = cl;
name = cl.getName();
isProxy = Proxy.isProxyClass(cl);
isEnum = Enum.class.isAssignableFrom(cl);
serializable = Serializable.class.isAssignableFrom(cl);
externalizable = Externalizable.class.isAssignableFrom(cl);
Class<?> superCl = cl.getSuperclass();
superDesc = (superCl != null) ? lookup(superCl, false) : null;
localDesc = this;
if (serializable) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (isEnum) {
suid = Long.valueOf(0);
fields = NO_FIELDS;
return null;
}
if (cl.isArray()) {
fields = NO_FIELDS;
return null;
}
suid = getDeclaredSUID(cl);
try {
fields = getSerialFields(cl);
computeFieldOffsets();
} catch (InvalidClassException e) {
serializeEx = deserializeEx =
new ExceptionInfo(e.classname, e.getMessage());
fields = NO_FIELDS;
}
if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {
cons = getSerializableConstructor(cl);
//通过'writeObject'获取到类重写的序列化方法
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
//通过'readObject'获取到类重写的反序列化方法
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
return null;
}
});
} else {
suid = Long.valueOf(0);
fields = NO_FIELDS;
}
try {
fieldRefl = getReflector(fields, this);
} catch (InvalidClassException ex) {
// field mismatches impossible when matching local fields vs. self
throw new InternalError(ex);
}
if (deserializeEx == null) {
if (isEnum) {
deserializeEx = new ExceptionInfo(name, "enum type");
} else if (cons == null) {
deserializeEx = new ExceptionInfo(name, "no valid constructor");
}
}
for (int i = 0; i < fields.length; i++) {
if (fields[i].getField() == null) {
defaultSerializeEx = new ExceptionInfo(
name, "unmatched serializable field(s) declared");
}
}
initialized = true;
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
此时的操作有:
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);
}
}
此时的操作有:
IllegalArgumentException
Collection
子类的构造函数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;
}
}
此时的操作有:
toArray
方法转为数据后赋值给存储数组EMPTY_ELEMENTDATA
空数组的值替换掉存储数组Object[].class
即[Ljava.lang.Object
,如果不是,则调用Arrays.copyOf
方法将传入数组复制对应的Class类的数组后再赋值给存储数组elementData.getClass() != Object[].class)
这个判断呢?import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ArrayListTest1 {
public static void main(String[] args) {
List<String> list1 = Arrays.asList("test", "s");
System.out.println(list1.getClass()
+" toArray.getClass="+ list1.toArray().getClass());
List<String> list2 = new ArrayList<>();
list2.add("test");
System.out.println(list2.getClass()
+" toArray.getClass="+ list2.toArray().getClass());
}
}
执行结果
class java.util.Arrays$ArrayList toArray.getClass=class [Ljava.lang.String;
class java.util.ArrayList toArray.getClass=class [Ljava.lang.Object;
通过Arrays.asList
生成的List虽然也叫ArrayList但却是Arrays.java
里面的一个内部类,且通过其toArray()
方法返回的数组对应的Class类为[Ljava.lang.String
2).为什么要调用Arrays.copyOf方法覆盖原存储数组呢,查了下旁边的注释c.toArray might (incorrectly) not return Object[] (see 6260652)
,正是由于ArrayList
是通过数组存储数据,当数组中元素在进行向下转型时由于不安全而抛出异常的bug
如下所示
public class ArrayListTest2 {
public static void main(String[] args) {
Object[] objects = new Object[2];
System.out.println(objects.getClass());
SubObject[] subObjects = new SubObject[2];
System.out.println(subObjects.getClass());
objects = subObjects;
objects[0] = new Object();
}
}
class SubObject{}
执行结果
class [Ljava.lang.Object;
class [Lcom.learn.list.SubObject;
Exception in thread “main” java.lang.ArrayStoreException: java.lang.Object
at com.learn.list.ArrayListTest2.main(ArrayListTest2.java:21)
综上可知,代码
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
是为了避免在通过传入Collection子类构造出的ArrayList实例在新增元素时,由于传入元素是存储数组类型父类而发生向下转型而抛出异常
public boolean add(E e) {
//在元素个数+1的情况下确保容量足够
ensureCapacityInternal(size + 1); // Increments modCount!!
//将元素添加存储数组索引为size+1出,且数组元素个数+1
elementData[size++] = e;
return true;
}
重点都在ensureCapacityInternal
方法中,那么就重点分析下吧
ensureCapacityInternal(int minCapacity)
方法private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//当存储数组为空数组时,从默认初试容量DEFAULT_CAPACITY和传入容量minCapacity中取最大值
//赋给传入容量
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//确保明确的容量
ensureExplicitCapacity(minCapacity);
}
继续分析ensureExplicitCapacity
方法
ensureExplicitCapacity(int minCapacity)
方法private void ensureExplicitCapacity(int minCapacity) {
//定义于ArrayList的父类AbstractList,用于存储结构修改次数
modCount++;
//判断是否需要扩容
if (minCapacity - elementData.length > 0){
//如果传入容量大于存储数组长度,则扩容
grow(minCapacity);
}
}
接着分析grow
扩容方法
grow(int minCapacity)
扩容方法private void grow(int minCapacity) {
//存储数组长度
int oldCapacity = elementData.length;
//新容量=存储数组长度+存储数组长度由移一位(/2),及新容量=1.5倍存储数组长度
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0){
//如果传入容量大于新容量,则新容量=传入容量
newCapacity = minCapacity;
}
if (newCapacity - MAX_ARRAY_SIZE > 0){
//如果新容量大于Integer.MAX_VALUE - 8,则获取最大容量
newCapacity = hugeCapacity(minCapacity);
}
//将原存储数组中的数据复制到新数组(长度=新容量)中后赋给存储数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
hugeCapacity(int minCapacity)
获取超大容量方法private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0){
//此时传入容量小于0说明超过Integer的最大值,即内存溢出
throw new OutOfMemoryError();
}
//你懂的
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
通过分析完add(E e)
,可以得到的结论是
ArrayList
实例时,如果已知元素个数,最好通过new ArrayList(int initialCapacity)
构造函数来创建,既不浪费空间,又不影响性能add(int index, E element)
方法public void add(int index, E element) {
//检查传入索引是否越界
rangeCheckForAdd(index);
//在元素个数+1的情况下确保容量足够,见1.1
ensureCapacityInternal(size + 1); // Increments modCount!!
//数组复制方法,即将存储元素中的元素从index开始后移一位,给新增元素腾位置
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//将传入元素的值放入数组中的指定位置
elementData[index] = element;
size++;
}
先来看看rangeCheckForAdd
方法
rangeCheckForAdd(index)
索引校验private void rangeCheckForAdd(int index) {
if (index > size || index < 0){
//若传入索引是否大于当前元素个数或者小于0,则抛出数组越界异常
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
}
System.arraycopy
数组复制方法src[srcPos]
至src[srcPos+length]
间的元素复制到dest[destPos]
至dest[destPos+length]
间/***
* src-源数组
* srcPos-原数组复制起始位置
* dest-目标数据
* destPos-目标数组存放起始位置
* length-复制元素个数
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
通过分析完add(int index, E element)
,可以得到的结论是
addAll(Collection extends E> c)
方法public boolean addAll(Collection<? extends E> c) {
//将参数转为新数组
Object[] a = c.toArray();
int numNew = a.length;
//确保在插入数组对应个数的元素后存储数组的容量足够
ensureCapacityInternal(size + numNew); // Increments modCount
//将新数组中的所有元素复制到存储数组末尾
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
由此可见,
该方法也是非线程安全的,并发操作存在数据缺失的问题
remove(int 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);
}
//将存储数据末尾元素置为null,尽快让其被GC回收释放空间
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
同理
remove(Object o)
方法public boolean remove(Object o) {
if (o == null) {
//如果对象引用为空,则迭代存储数据中的数据,将为空的数据全部删除
for (int index = 0; index < size; index++){
if (elementData[index] == null) {
fastRemove(index);
return true;
}
}
} else {
//如果对象引用不为空,则通过其equals比较是否,若为true,则删除
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
5.1 快速删除fastRemove(int index)
方法
private void fastRemove(int index) {
modCount++;
//计算需要移动格式
int numMoved = size - index - 1;
if (numMoved > 0){
//将制定位置后的元素向前挪动一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
}
//将存储数据末尾元素置为null,尽快让其被GC回收释放空间
elementData[--size] = null; // clear to let GC do its work
}
综上可知
removeAll(Collection> c)
方法public boolean removeAll(Collection<?> c) {
//判断请求是否为空
Objects.requireNonNull(c);
//匹配删除方法
return batchRemove(c, false);
}
6.1 匹配删除batchRemove(Collection> c, boolean complement)
方法
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
//迭代存储数据
for (; r < size; r++){
//判断传入Collection子类对象实例中是否未包含存储元素对应的值,
//由于complement=false,即从存储数组中找出与Collection子类对象实例中不一致的值
//那么当迭代完后,索引小于w的数据是不匹配的,不需要删除,而>=w的则需要删除,好巧妙啊
if (c.contains(elementData[r]) == complement){
elementData[w++] = elementData[r];
}
}
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
//当上述迭代完成后,r=size,除非c.contains()发生异常,否则不可能出现r != size
if (r != size) {
//将未迭代的元素向前挪动r-w个位置,与之前不匹配的合成一个完成的存储数组
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
//说明传入实例中的元素与存储数组中的元素不全匹配
// clear to let GC do its work
for (int i = w; i < size; i++){
//将大于等会w之后的元素清空,好让GC尽快回收
elementData[i] = null;
}
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
removeRange(int fromIndex, int toIndex)
方法protected void removeRange(int fromIndex, int toIndex) {
modCount++;
//计算需要移动元素个数
int numMoved = size - toIndex;
//将索引为toIndex及之后的元素移动到fromIndex之后
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
//计算删除后的存储元素个数newSize
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
//将newSize-size之间的元素删除
elementData[i] = null;
}
size = newSize;
}
综上,
ArrayList
的优点