JAVA 集合Null 初始化_java 集合 1 - ArrayList

本文详细介绍了Java中的ArrayList特性,包括允许存储null值和重复元素,有序但不线程安全。ArrayList基于数组实现,支持快速随机访问。文章通过源码分析了ArrayList的克隆、序列化过程,以及add方法、扩容策略和get方法的工作原理。
摘要由CSDN通过智能技术生成

java 集合 1 - ArrayList

特性

是否存取null值

可以

元素是否可以重复

可以

是否有序

有序

是否线程安全

不安全

ArrayList使用的数据结构是数组,即使用数组保存元素。

特性

是否存取null值

可以

元素是否可以重复

可以

是否有序

有序

是否线程安全

不安全

接下来直接通过源码(jdk1.8)分析。

可克隆、随机访问、序列化

先来看看ArrayList的定义:

public class ArrayList extends AbstractList

implements List, RandomAccess, Cloneable, java.io.Serializable

可见实现了RandomAccess接口,这是一个标志接口,表示可以对该集合执行随机存取操作。

实现了Clonable接口,并且覆写了Object的clone方法(若实现了接口却不覆盖该方法而直接调用clone方法将受到CloneNotSupportedException异常),表明该集合可以被复制,克隆。

实现了Serializable接口,且覆写了readObject和writeObject方法,表明可被序列化。

数组实体

/**

* The array buffer into which the elements of the ArrayList are stored.

* The capacity of the ArrayList is the length of this array buffer. Any

* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA

* will be expanded to DEFAULT_CAPACITY when the first element is added.

*/

transient Object[] elementData; // non-private to simplify nested class access

elementData为ArrayList中存放元素的实体,该成员变量被transient修饰,表示在进行序列化时忽略该变量(不序列化该成员)。这样修饰的原因可以在writeObject和readObject方法中找到。

clone 方法

/**

* Returns a shallow copy of this ArrayList instance. (The

* elements themselves are not copied.)

*

* @return a clone of this ArrayList instance

*/

public Object clone() {

try {

ArrayList> v = (ArrayList>) super.clone(); // 1

v.elementData = Arrays.copyOf(elementData, size); // 2

v.modCount = 0;

return v;

} catch (CloneNotSupportedException e) {

// this shouldn't happen, since we are Cloneable

throw new InternalError(e);

}

}

注释:返回当前对象的浅拷贝,集合的元素自身将不会进行拷贝。

第1句将在堆中新建一个ArrayList对象,第2句将当前对象的数组实体复制给新的集合,这里Arrays.copyOf方法只将数组实体中元素的引用挨个复制给新集合的数组(浅拷贝的原因在此,只复制了元素的引用,元素自身并没有拷贝)。

writeObject 方法

private void writeObject(java.io.ObjectOutputStream s)

throws java.io.IOException{

// Write out element count, and any hidden stuff

int expectedModCount = modCount;

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

s.writeObject(elementData[i]);

}

if (modCount != expectedModCount) {

throw new ConcurrentModificationException();

}

}

在将ArrayList序列化到文件中时通过该方法决定对象中的哪些成员需要被序列化,主要看// Write out all elements in the proper order.注释的下一个for循环,注释意思为将所有元素写入(e.g. 文件)到正确的位置,这与readObject(从文件中反序列化)是对应的。

ArrayList中存储元素的数组并不是时时刻刻都被完全使用的,比如数组大小为10,但实际只有4个元素,剩余的6个元素为null,而writeObject方法中for循环只将实际存储的4个元素(size个)进行序列化,即那些为null的元素是不进行序列化的,相反,如果不覆写writeObject方法,elementData也不使用transient修饰,那么在进行序列化时将使用默认的序列化方法处理,6个null元素也将被序列化,占用一定设备资源,从而造成浪费。

readObject 方法

/**

* Reconstitute the ArrayList instance from a stream (that is,

* deserialize it).

*/

private void readObject(java.io.ObjectInputStream s)

throws java.io.IOException, ClassNotFoundException {

elementData = EMPTY_ELEMENTDATA;

// Read in size, and any hidden stuff

s.defaultReadObject();

// Read in capacity

s.readInt(); // ignored

if (size > 0) {

// be like clone(), allocate array based upon size not capacity

ensureCapacityInternal(size);

Object[] a = elementData;

// Read in all elements in the proper order.

for (int i=0; i

a[i] = s.readObject();

}

}

}

重点在// Read in all elements in the proper order.注释的下一个for循环,与writeObject对应,这里只需反序列化size个元素。

add 方法

e36ffedf7a50

这里写图片描述

一个参数的add方法先对数组实体容量进行确定,然后直接将新元素添加到数组最后。

两个参数的add方法用于在指定下标插入新元素,在插入之前先检查目标下标是否越界(越界将收到IndexOutOfBoundsException)异常,之后确定容量,最后将元素插入指定下标。

数组初始大小及扩容策略

三种初始化方式

ArrayList(int initialCapacity):

初始指定长度的数组实体:elementData = new Object[initalCaacity];

ArrayList():

初始默认长度的数组实体:this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;可见是长度为0的数组。

ArrayList(Collection extends E> c):

用给定的集合初始化ArrayList

扩容

对数组实体的扩容主要涉及到三个方法:

ensureCapacityInternal:

确定出需要扩容的大小。

ensureExplicitCapacity:

对扩容操作进行最后确定

grow:

执行扩容操作

ensureCapacityInternal 方法

private void ensureCapacityInternal(int minCapacity) {

if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

}

ensureExplicitCapacity(minCapacity);

}

根据上面add方法可知,形参minCapactiy的值为默认为size+1。

这里先判断elementData是否为空数组(通过Arraylist无参构造器创建,且为第一次调用),此时确定需要扩容大小为DEFAULT_CAPACITY,其值为10。

ensureExplicitCapacity 方法

private void ensureExplicitCapacity(int minCapacity) {

modCount++;

// overflow-conscious code

if (minCapacity - elementData.length > 0)

grow(minCapacity);

}

最终确认扩容操作,自增modCount,modCount用于记录对列表结构的修改次数,结构上的修改是指改变列表的大小,在集合进行遍历的同时修改列表结构可能会使遍历结果产生错误,因此通过该值来指示(阻值)在遍历时的修改行为(以抛出ConcurrentModificationException异常的方式)。

调用grow方法执行扩容操作,这部分代码虚拟机会向操作系统申请内存,如果没有申请到足够的内存,将有可能会触发 OutOfMemoryError 错误。

grow 方法(扩容策略)

/**

* Increases the capacity to ensure that it can hold at least the

* number of elements specified by the minimum capacity argument.

*

* @param minCapacity the desired minimum capacity

*/

private void grow(int minCapacity) {

// overflow-conscious code

int oldCapacity = elementData.length;

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:

elementData = Arrays.copyOf(elementData, newCapacity);

}

grow方法对数组实体进行扩容,从而使ArrayList动态扩展集合大小,实现区别于数组的容量按需扩大能力。

扩容策略:

oldCapacity + (oldCapacity >> 1)一句是关键,它确定了新的数组容量:当前容量的3/2,即增加当前大小一半的数量。

如果按规则计算出的新的容量仍然小于指定的容量,则按指定的容量扩容。

如果通过以上两步计算出的容量大于了MAX_ARRAY_SIZE(其值为Integer.MAX_VALUE - 8),则执行hugeCapacity方法,调用了该方法则表示数组大小快要解决阀值,很有可能会触发OutOfMemoryError。

最后,在确定了新的容量之后,只需按新的大小创建新数组,将原数组中的元素复制到新数组即可。

get 方法

/**

* Returns the element at the specified position in this list.

*

* @param index index of the element to return

* @return the element at the specified position in this list

* @throws IndexOutOfBoundsException {@inheritDoc}

*/

public E get(int index) {

rangeCheck(index);

return elementData(index);

}

get方法用于从集合中获取指定下标的元素。

只需从数组实体中获取对应下标的元素即可,在此之前需要先判断数组下标是否越界。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值