ArrayList详解
- 初识ArrayList
- 成员变量
- 方法列表
- 按照定义顺序介绍
- public ArrayList(int initialCapacity)
- public ArrayList()
- public ArrayList(Collection<? extends E> c)
- public void trimToSize()
- public void ensureCapacity(int minCapacity) //手动对集合进行扩容
- · private static int calculateCapacity(Object[] elementData, int minCapacity)
- · private void ensureCapacityInternal(int minCapacity)
- · private void ensureExplicitCapacity(int minCapacity)
- · private void grow(int minCapacity)
- · private static int hugeCapacity(int minCapacity)
- public int size()
- public boolean isEmpty()
- public boolean contains(Object o)
- public int indexOf(Object o)
- public int lastIndexOf(Object o)
- public Object clone()
- public Object[] toArray()
- public T[] toArray(T[] a)
- E elementData(int index)
- public E get(int index)
- public E set(int index, E element)
- public boolean add(E e)
- public void add(int index, E element)
- public E remove(int index)
- public boolean remove(Object o)
- private void fastRemove(int index)
- public void clear()
- public boolean addAll(Collection<? extends E> c)
- public boolean addAll(int index, Collection<? extends E> c)
- protected void removeRange(int fromIndex, int toIndex)
- private void rangeCheck(int index)
- private void rangeCheckForAdd(int index)
- private String outOfBoundsMsg(int index)
- public boolean removeAll(Collection<?> c)
- public boolean retainAll(Collection<?> c)
- private boolean batchRemove(Collection<?> c, boolean complement)
- private void writeObject(ObjectOutputStream s) throws IOException
- private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException
- 精解列表
- 3-ArrayList实现序列化接口就是为了在网络上传输,那为什么存放数据的elementData,要用transient修饰呢?writeObject()/readObject()又是做什么的呢?
- 4-当通过构造方法ArrayList(Collection<? extends E> c)创建得到ArrayList对象后,为什么对集合c元素属性的的所有修改都体现在生成的ArrayList上?
- 5-通过toArray()将集合转为Object[] 可能会有哪些问题?为什么《阿里巴巴编程规范》中强制要求使用toArray(T array)方法来将集合转为数组?
- 6-用户自己调用容器的扩容方法,为什么不能全凭用户想法扩容到指定大小呢?而是要与默认1.5倍扩容大小进行比较,如果1.5倍扩容后的大小比用户定义的大,那么就按照1.5倍进行扩容,而不按照用户想要的大小进行扩容?
初识ArrayList
- ArrayList的继承和实现的结构为
ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- 抽象类AbstractList中定义了modCount属性,这个参数是指当前列表的结构被修改的次数
- 结构上的修改指的是那些改变了list的长度大小或者使得遍历过程中产生不正确的结果的其它方式
- 请留心每一个操作ArrayList中改变存储内容或者改变结构的方法,都会对modCount属性进行++操作
protected transient int modCount = 0;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int expectedModCount = modCount;
- ArrayList在创建对象的时候并不会初始化容量,当在add或者其他方法调用的时候才会初始化容量【参见精解1】
- System.arraycopy复制数组为浅克隆【参见精解2】
- System.arraycopy是一个native函数,复制数组时并不是传统意义的——通过数组下标从最后一位依次向后移动一位,而是直接对内存中的数据块进行复制的,是一整块一起复制的,这样效率会大大提升【参见精解2】
成员变量
//序列化ID
private static final long serialVersionUID = 8683452581122892189L;
//ArrayList的默认初始容量大小
private static final int DEFAULT_CAPACITY = 10;
//空对象数组,用于空实例的共享空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//空对象数组,使用无参构造器实例化对象时,将此值赋给elementData
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存放当前数据,ArrayList底层使用的是数组来保存元素
//ArrayList实现Serializable接口就是允许序列化的,那么为什么该变量却用transient修饰呢?【参见精解3】
transient Object[] elementData;
//记录list中存放数据的大小,而不是指数组的length
//比如 elementData = {"gao","shao",null,null,null}
//elementData.length 的结果是5;而size保存的数值是2
private int size;
//集合所允许的最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
方法列表
按照定义顺序介绍
public ArrayList(int initialCapacity)
—— 自定义初始化容器大小
//自定义初始化容器大小
//当传入的值<0时,抛出异常
//当传入的值=0时,使用默认大小的数组初始化
//当传入的值>0时,根据自定义大小初始化容器
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);
}
}
public ArrayList()
—— 无参构造
//无参构造方法,使用默认大小的数组初始化
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c)
—— 通过传入集合来初始化当前集合
//通过传入集合来初始化当前集合
//将传入的集合中的元素引用复制到当前生成的ArrayList集合中,并且对原先集合中元素的任何修改都会体现到当前集合上【参见精解4】
public ArrayList(Collection<? extends E> c) {
//将传入的集合转为Object数组赋值给elementData
elementData = c.toArray();
if ((size = elementData.length) != 0) {
//已经通过上面的c.toArray()将放回的Object[]赋值到elementData上了,为什么还要进行判断呢?
//因为c.toArray()返回的内容类型有可能不是Object[]数组,这是一个小bug,如果不进行判断处理的话,客户使用时会有隐患【参见精解5】
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//将空数组赋值给elementData
this.elementData = EMPTY_ELEMENTDATA;
}
}
public void trimToSize()
—— 将此ArrayList实例的容量修剪为列表的当前size的大小。
//将此ArrayList实例的容量修剪为列表的当前size的大小。
//简言之:去除ArrayList的末尾的空余容量
public void trimToSize() {
modCount++;
//如果ArrayList中存在空余容量进行修剪,从而去掉空余容量
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
public void ensureCapacity(int minCapacity) //手动对集合进行扩容
—— 手动对集合进行扩容
//手动对集合进行扩容
//如果参数大于低层数组长度的1.5倍,那么这个数组的容量就会被扩容到这个参数值,如果参数小于低层数组长度的1.5倍,那么这个容量就会被扩容到低层数组长度的1.5倍。原因参见【参见精解6】
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
//方法内部判断是否需要扩容,需要扩容则进行扩容
ensureExplicitCapacity(minCapacity);
}
}
· private static int calculateCapacity(Object[] elementData, int minCapacity)
—— 获取需要的最小容量
//获取需要的最小容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果elementData是默认的空数组,则取默认大小【10】与minCapacity两者的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//否则返回minCapacity
return minCapacity;
}
· private void ensureCapacityInternal(int minCapacity)
—— 判断是否需要扩容,如果需要进行扩容
//判断是否需要扩容,如果需要进行扩容
//和方法ensureExplicitCapacity()的区别在于,
//在数组elementData为默认数组时,当前方法会根据传入的需要的最小容量与初始化大小10做比较
private void ensureCapacityInternal(int minCapacity) {
//calculateCapacity(elementData, minCapacity) 获取需要的最小容量
//有人一定会说,最小容量不就是minCapacity吗?为什么还要计算呢?
// 一般情况是你想的这样子,不过你忽略了--当ArrayList刚开始创建并且size为0的情况,如果根据传入的最小容量小于集合的初始化大小时,应该以我的初始化大小为准
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
· private void ensureExplicitCapacity(int minCapacity)
—— 判断是否需要扩容,如果需要进行扩容
//判断是否需要扩容,如果需要进行扩容
//注意:当需要的容量等于当前容量时,会进行扩容(例如:当请朋友到家里吃饭,做的饭一点没剩下,总觉得怪怪的)
private void ensureExplicitCapacity(int minCapacity) {
//暂时不知道干啥用的
//如果需要扩容modCount自增,这个参数是指当前列表的结构被修改的次数
modCount++;
//判断elementData容量是否够用,不够进行扩容
if (minCapacity - elementData.length > 0)
//扩容方法
grow(minCapacity);
}
· private void grow(int minCapacity)
—— 扩容方法
//扩容方法
private void grow(int minCapacity) {
//记录扩容前的数组长度
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);
//根据当前需要的容量将原先数组进行扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
· private static int hugeCapacity(int minCapacity)
—— 将扩容长度设置为最大可用长度
// 将扩容长度设置为最大可用长度
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//为什么定义了这个属性之后MAX_ARRAY_SIZE,还要赋值int的最大值呢?【一直没有想到合理解释】
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
public int size()
—— 返回当前集合大小
//返回当前集合大小
//注:存放数据部分的大小,不是elementData[]的容量
public int size() {
return size;
}
public boolean isEmpty()
—— 判断当前集合是否为空
//判断当前集合是否为空
public boolean isEmpty() {
return size == 0;
}
public boolean contains(Object o)
—— 判断是否包含某个元素
//判断是否包含某个元素
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o)
—— 查找集合中某个元素的索引【正向查找】
//【正向】查找集合中某个元素的索引【从0开始】,当有多个重复的元素时,只返回【第一个】元素所在的索引
//return -1;不存在该元素
//因为ArrayList允许null值,所以此处也可以查询集合中null值的索引
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;
}
public int lastIndexOf(Object o)
—— 查找集合中某个元素的索引【逆向查找】
//【逆向】查找集合中某个元素的索引【从0开始】,当有多个重复的元素时,只返回【最后一个】元素所在的索引
//return -1;不存在该元素
//因为ArrayList允许null值,所以此处也可以查询集合中null值的索引
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
public Object clone()
—— 将该ArrayList实例克隆一份进行返回
//将该ArrayList实例克隆一份进行返回
//注意:这里克隆是浅克隆
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
public Object[] toArray()
—— 将集合转为数组返回(尽量不要使用该方法,而使用 toArray(T[] a)方法 )
//将集合转为数组返回
//尽量不要使用该方法【参见详解5】
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
public T[] toArray(T[] a)
—— 将集合转为数组返回
//warning警告虽然不是会让项目停止,但是却是不规范,留下隐患,
// 而@suppresswarnings就是告诉编译器忽略警告。不用在编译完成后出现警告。
// ("unchecked")告诉编译器忽略 unchecked 警告信息。
@SuppressWarnings("unchecked")
//将集合转为数组时推荐使用该方法【参见5】
public <T> T[] toArray(T[] a) {
if (a.length < size)
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
E elementData(int index)
—— 获取索引处的元素【未检查索引是否越界】
//获取索引处的元素,未检查索引是否越界
//注意此处的未检查是未检查如下情况: size < index && index < elementData.length【即为在数组elementData定义范围内而在ArrayList的size范围外的时候】
E elementData(int index) {
return (E) elementData[index];
}
public E get(int index)
—— 获取索引处的元素
//获取索引处的元素
public E get(int index) {
//检查索引是否越界
rangeCheck(index);
return elementData(index);
}
public E set(int index, E element)
—— 将指定位置的元素替换为传入的元素
//将指定位置的元素替换为传入的元素
//返回值为被替换的元素
public E set(int index, E element) {
//检查索引是否越界
rangeCheck(index);
//获取被替换的元素
E oldValue = elementData(index);
//存新元素
elementData[index] = element;
//返回被替换的元素
return oldValue;
}
public boolean add(E e)
—— 添加单个元素
//添加单个元素,添加元素之前会先检查容量,容量不足进行扩容
public boolean add(E e) {
//添加元素之前的容量时size,那么添加完之后的容量最小为:size+1
//判断是否需要扩容,如果需要进行扩容
ensureCapacityInternal(size + 1);
//将元素添加到集合中
elementData[size++] = e;
return true;
}
public void add(int index, E element)
—— 在指定位置添加元素
//在指定位置添加元素
public void add(int index, E element) {
//判断下标是否越界,如果是抛出IndexOutOfBoundsException
//注意此处不是数组下标越界
rangeCheckForAdd(index);
//判断是否需要扩容,如果需要进行扩容
ensureCapacityInternal(size + 1);
// 拷贝数组,将下标后面的元素全部向后移动一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 将元素插入到当前下标的位置
elementData[index] = element;
size++;
}
public E 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;
return oldValue;
}
public boolean 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 {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index)
—— 删除元素,跳过边界检查且不返回已删除值的私有删除方法
//跳过边界检查且不返回已删除值的私有删除方法
//用户ArrayList自己使用,减少了必要的检查从而提升效率
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
}
public void clear()
—— 清空集合
//清空集合
public void clear() {
modCount++;
//数组全部置为null,数组中的元素没有了引用,就可以被GC回收
//这里没有对数组容量进行处理,也就是说清空之后的容量与清空之前的容量相同,只是将elementData[]中的所有元素置为null
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
public boolean addAll(Collection<? extends E> c)
—— 在集合末尾添加多个元素
//在集合末尾添加多个元素
public boolean addAll(Collection<? extends E> c) {
//将集合转为Object[]
Object[] a = c.toArray();
//记录数组长度
int numNew = a.length;
//判断是否需要扩容,如果需要进行扩容
ensureCapacityInternal(size + numNew);
//拷贝数组:System.arraycopy是一个native函数,复制数组时并不是传统意义的——通过数组下标从最后一位依次向后移动一位
//而是直接对内存中的数据块进行复制的,是一整块一起复制的,这样效率会大大提升
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
public boolean addAll(int index, Collection<? extends E> c)
—— 在集合的index位置处添加多个元素
///在集合的某个位置【index】添加多个元素
public boolean addAll(int index, Collection<? extends E> c) {
//判断是否索引下标越界,检查完之后index的范围为: 0 <= index && index <= size
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
//判断是否需要扩容,如果需要进行扩容
ensureCapacityInternal(size + numNew);
int numMoved = size - index;
//当index索引位置在集合中,即为 0 <= index && index < size
//将 (index,size) 范围内的数据向后复制,腾出传入的集合c所需的位置
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
//如果:0 <= index && index < size ,则将传入的集合放入前两行代码腾出的位置
//index = size,则会在ArrayList的末尾放入集合c【也就是数组a】
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
protected void removeRange(int fromIndex, int toIndex)
—— 移除索引范围内的元素
//移除索引范围内的元素
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
//直接将toIndex之后位置的元素移动到fromIndex处即可
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
//将多余的末尾元素制null,没有引用的对象就可以被GC回收了
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
private void rangeCheck(int index)
—— 检查索引是否越界,适用于get/set/remove方法
//检查索引是否越界,适用于get/set/remove方法
//方法内没有进行负数校验,而是交给了Array来完成,因为get/set/remove方法操作数组时,都会对数组下标越界进行检查
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private void rangeCheckForAdd(int index)
—— 检查索引是否越界,适用于add方法
//检查索引是否越界,适用于add方法
//同样的检查为什么要和get/set/remove分开呢?
//因为向集合中添加元素前可能会对其进行扩容,如果把负数的检查像get/set/remove一样交由Array来做的话,可能造成不必要的扩容
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private String outOfBoundsMsg(int index)
—— 定义“索引越界异常”打印格式
//定义“索引越界异常”打印格式
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
public boolean removeAll(Collection<?> c)
—— 从当前集合中【移除】传入的集合中的所有元素
//从当前集合中 【移除】传入的集合中的 所有元素
public boolean removeAll(Collection<?> c) {
//检查 c 是否为null,如果为null,抛出NullPointerException
Objects.requireNonNull(c);
//批处理移除
return batchRemove(c, false);
}
public boolean retainAll(Collection<?> c)
—— 从当前集合中【保留】传入的集合中的所有元素,并将其余的所有元素移除
//从当前集合中 【保留】传入的集合中的 所有元素 将其余的所有元素移除
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
private boolean batchRemove(Collection<?> c, boolean complement)
—— 批处理从ArrayList移除【或保留】集合c中的元素
//批处理从ArrayList移除【或保留】集合c中的元素
//complement == true-->【保留】集合c【存在】内容,其余部分全部【移除】
//complement == false-->【保留】集合c【不存在】内容,其余部分全部【移除】
//注意:该方法是【重新整理】需要保留的数据
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
// r--> 当前元素遍历的索引
// w--> 需要保留的数据的索引
int r = 0, w = 0;
//是否有改变的标识
boolean modified = false;
try {
for (; r < size; r++)
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()有可能抛出异常,当抛出异常时循环终止,所以就不相等了
//那么未循环的元素如何处理呢?【删除还是保留?亦或是选择性保留?】
//jdk的处理方式为:将未处理的元素保留下来,不进行处理
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
//w 索引移动相应位置
w += size - r;
}
//w记录的索引之前是需要保存的元素,之后则置为null
if (w != size) {
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
private void writeObject(ObjectOutputStream s) throws IOException
—— 将ArrayList写入到流中
//将ArrayList写入到流中【详见精解9】
private void writeObject(ObjectOutputStream s)
throws IOException{
// 记录当前的modCount
int expectedModCount = modCount;
//序列化非静态和非transient字段【个人感觉该方法包括序列化size,目前不知道为啥后面又定义了s.writeInt(size);】
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]);
}
//如果在写入流期间modCount发生变化,则抛出并发修改异常
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException
—— 从流中读出并还原ArrayList
//反序列化,过程类似于序列化
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
//先初始化空数组
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
//反序列化非静态和非transient字段
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
//获得所需要的最小的容量大小
int capacity = calculateCapacity(elementData, size);
//SharedSecrets.getJavaOISAccess()返回类型为【sun.misc.JavaOISAccess】
//但是个空方法,没有return,只有如下注释,之后待我细细研究再进行补充【 /* compiled code */】
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
//判断是否需要扩容,如果需要进行扩容
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
//将数据依次读出
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
精解列表
1-ArrayList什么时候将存放数据的数组elementData初始化到初始大小10?
- 在容器进行扩容时才可能会将容器初始化到默认大小10【可以是用户调用扩容方法
ensureCapacity(int minCapacity)
,也可能是新增数据时】 - 有三个构造方法均没有将容器容量初始化到10
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);
}
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
- 全局搜索该属性DEFAULT_CAPACITY,只有这两个方法中存在
ensureCapacity(int minCapacity)
和calculateCapacity(Object[] elementData, int minCapacity)
,这两个方法均为扩容时调用
2-为什么说System.arraycopy复制数组为浅复制(浅克隆)?
3-ArrayList实现序列化接口就是为了在网络上传输,那为什么存放数据的elementData,要用transient修饰呢?writeObject()/readObject()又是做什么的呢?
- ArrayList实际上是动态数组,如果数组大小设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证不会序列化一堆null,ArrayList把元素数组elementData用transient修饰,并自己定义writeObject()/readObject()进行序列化和反序列化。
- 如果没有定义writeObject()/readObject(),默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法
- writeObject()/readObject()是如何进行调用的呢?为了节省篇幅该处只举出序列化的例子,并且只给出调用栈和关键代码,可自行跟jdk源码
- 序列化的调用栈writeObject【ObjectOutputStream】—> writeObject0【ObjectOutputStream】 —>writeOrdinaryObject【ObjectOutputStream】—>writeSerialData【ObjectOutputStream】—>invokeWriteObject【ObjectStreamClass】
- 其中invokeWriteObject方法的这行代码是调用的关键【writeObjectMethod.invoke(obj, new Object[]{ out });】
void invokeWriteObject(Object obj, ObjectOutputStream out)
throws IOException, UnsupportedOperationException
{
requireInitialized();
if (writeObjectMethod != null) {
try {
writeObjectMethod.invoke(obj, new Object[]{ out });
} catch (InvocationTargetException ex) {
Throwable th = ex.getTargetException();
if (th instanceof IOException) {
throw (IOException) th;
} else {
throwMiscException(th);
}
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}
- 那么为什么还要实现序列化接口呢?我都自己定义了writeObject()/readObject()方法?ObjectOutputStream类中方法writeObject0()有如下代码。从其中可以看出如果不是列出的几种类型,就会抛出异常,所以必须实现序列化接口
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
- 稍稍拓展一下:请脑海中请有一个概念,序列化只是序列化了对象此刻的状态,而非结构。
- 如何解释呢?例如ArrayList序列化时,并不会完整序列化整个容器,而支持序列化容器内的值和一些状态属性数据
- 例如:A、B两个系统进行数据通信时,如果传输的是ArrayList,那么AB两个系统都会存在ArrayList.class,我只要知道ArrayList此刻存放的数据和此刻状态的属性数据,我就可以在另外一个系统内还原
- 为了节约带宽,所以序列化时数据越少越好,则只序列化状态数据而不序列化结构数据
4-当通过构造方法ArrayList(Collection<? extends E> c)创建得到ArrayList对象后,为什么对集合c元素属性的的所有修改都体现在生成的ArrayList上?
5-通过toArray()将集合转为Object[] 可能会有哪些问题?为什么《阿里巴巴编程规范》中强制要求使用toArray(T array)方法来将集合转为数组?
6-用户自己调用容器的扩容方法,为什么不能全凭用户想法扩容到指定大小呢?而是要与默认1.5倍扩容大小进行比较,如果1.5倍扩容后的大小比用户定义的大,那么就按照1.5倍进行扩容,而不按照用户想要的大小进行扩容?
▄█▀█●各位同仁,如果我的代码对你有帮助,请给我一个赞吧,为了下次方便找到,也可关注加收藏呀
如果有什么意见或建议,也可留言区讨论