java基础详解-ArrayList
一、适用场景
ArrayList就是数组列表,对于基本数据类型byte、short、int、long、float、double、char、boolean,存储他们对应的包装类Byte、Short、Integer 、Long、Float、Double、Character、Boolean,主要底层实现为Object[] elementData.
与LinkedList相比,查询效率高,增删效率低,线程不安全(更多在LinkedList介绍)
与Array相比,容量能动态改变,拥有更丰富的操作方法 :
存储内容比较
Array数组可以包含基本类型和对象类型,ArrayList却只能包含对象类型。
但是需要注意的是:Array数组在存放的时候一定是同种类型的元素。ArrayList就不一定了,因为ArrayList可以存储Object。
空间大小比较
Array它的空间大小是固定的,空间不够时也不能再次申请,所以需要事前确定合适的空间大小。
ArrayList的空间是动态增长的,如果空间不够,它会创建一个空间比原空间大约0.5倍的新数组,然后将所有元素复制到新数组中,接着抛弃旧数组。而且,每次添加新的元素的时候都会检查内部数组的空间是否足够。
方法上的比较
ArrayList作为Array的增强版,当然是在方法上比Array更多样化,比如添加全部addAll()、删除全部removeAll()、返回迭代器iterator()等。
适用场景
如果想要保存一些在整个程序运行期间都会存在而且不变的数据,我们可以将它们放进一个全局数组里
如果我们单纯只是想要以数组的形式保存数据,而不对数据进行增加等操作,只是方便我们进行查找的话,那么,我们就选择ArrayList。
而且还有一个地方是必须知道的,就是如果我们需要对元素进行频繁的移动或删除,或者是处理的是超大量的数据,那么,使用ArrayList不是一个好的选择,因为它的效率很低,使用数组进行这样的动作就很麻烦,那么,我们可以考虑选择LinkedList。如果需要线程安全,可使用Vector或者List list = Collections.synchronizedList(new ArrayList(...))包装List为一个线程安全的数组容器;
二、构造方法
1、public ArrayList(int initialCapacity) {} 构建指定参数大小的ArrayList,内部数组分配为固定大小,this.elementData = new Object[initialCapacity];
2、public ArrayList() {} 构建一个默认ArrayList,内部数组分配为空,this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA,默认创建一个空数组,第一次add时容量会变为10
3、public ArrayList(Collection<? extends E> c) {} 构造一个包含指定集合元素的列表
三、常规方法
1、增加操作
ArrayList的添加元素操作,有指定index新增和直接新增两类
a:指定index新增
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++;
}
b:直接新增,直接将e赋值到指定位置,然后size+1
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
添加过程中间涉及扩容操作,放在四
指定index新增主要涉及arraycopy,数组的复制操作
Params:
src – the source array 源数组
srcPos – starting position in the source array 源数组要复制起始位置
dest – the destination array 目标数组
destPos – starting position in the destination data 目标数组起始位置
length – the number of array elements to be copied 复制长度
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,int length);
所以index新增是将源数组从index位置到末尾开始复制,然后放到index+1的位置,这样index的位置就空余了出来,可以插入对应元素(参考敖丙)
比如在index为4的位置新增元素A

将index为4位置之后的元素复制,放在了index+1=5的位置,这样index等于4的位置空了出来,elementData[index] = element 就实现了目标的插入

综上,其实arraylist顺序插入的时候速度并不会很慢,指定位置插入时,涉及到数组的复制,如果数组较大,再加上扩容等操作,插入速度就会受到影响,这也是常说的ArrayList插入效率慢的原因,当然,这也取决删除和新增的元素离末端有多远
2、删除操作
删除操作也有按指定元素和指定位置删除两种,本质上也是数组的复制,与增加类似,在此不多细述
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;
}
按指定元素删除
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;
}
3、修改操作
修改操作set实现比较简单,就是替换index位置的指为指定的element
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
4、查询操作
查询操作get也不复杂,校验索引边界之后返回index所在元素
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
四、扩容原理
做添加操作时,两类方法都会执行ensureCapacityInternal(int minCapacity)方法,查看方法执行过程,如果判断当前列表长度不足,即当需要的长度大于自身最大容量时,需要进行扩容,方法最终执行到grow(int minCapacity)
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 判断是否需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
扩容流程,可以看到扩容原理,一般情况下(指不发生越界,或者hugeCapacity返回长度为Integer.MAX_VALUE或Integer.MAX_VALUE-8),将容量扩充为原来的1.5倍(oldCapacity + (oldCapacity >> 1) 右移一位相当于除以2)
private void grow(int minCapacity) {
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);
}
大小越界或者超过允许最大值判断
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
五、其他疑问
1、内部Object[] elementData为什么用transient修饰?
由于 ArrayList 是基于动态数组实现的,所以并不是所有的空间都被使用。因此使用了 transient 修饰,可以防止被自动序列化。ArrayList实现了writeObject和readObject方法,ArrayList在序列化的时候会调用writeObject,直接将size和element写入ObjectOutputStream,反序列化时调用readObject,从ObjectInputStream获取size和element,再恢复到elementData。
为什么不直接用elementData来序列化,而采用上诉的方式来实现序列化呢?原因在elementData是一个缓存数组,它通常会预留一些容量,等容量不足时再扩充容量,那么有些空间可能就没有实际存储元素,采用上诉的方式来实现序列化时,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间
2、ArrayList会不会初始化数组大小
会初始化数组大小,但list大小没变,因为list大小是返回size(),即打印list大小依然是0,set下标值时会报错,数组下标越限
3、实现implements RandomAccess, Cloneable, java.io.Serializable这些接口的作用
这些接口叫做标记接口
RandomAccess :RandomAccess接口, 用来表明其支持快速(通常是固定时间)随机访问,如果容器支持快速随机访问,则会使用基于索引的二分查找,否则使用基于迭代的二分查找(实现RandomAccess接口的List可以通过for循环来遍历数据比使用iterator遍历数据更高效,未实现RandomAccess接口的List可以通过iterator遍历数据比使用for循环来遍历数据更高效)
Cloneable 标记接口代表我们的对象可以被拷贝,ArrayList实现了clone方法
Serializable 序列化接口,表明ArrayList支持序列化和反序列化,ArrayList实现了writeObject和readObject方法

浙公网安备 33010602011771号