ArrayList
一、概述
ArrayList是平时开发中最常用的集合,底层是数组,相当于动态数组。与java中的数组相比,它的长度能动态增长
1、ArrayList的特点
- 它是基于数组的List实现类
- 它的元素可以有null
- 它进出是有序的
- 不同步,线程不安全,效率高
- 查询快,增删慢
- 相较与底层是链表的LinkedList集合,不需要额外的空间去维护底层链表结构占用空间更小
- 可以动态的调整容量
2、所有方法
方法名 | 参数 | 作用 |
---|---|---|
add | 任何对象 | 添加元素到集合中 |
size | 无 | 获取当前集合中元素的数量 |
get | int 角标 | 获取传入角标下的元素 |
addAll | Colletion集合 | 将另一个集合合并到当前集合中 |
addAll | index插入位置的角标,Colletion,集合 | 将另一个集合从指定位置开始插入 |
contains | 元素对象 | 判断传入的元素是否在当前集合中存在 |
indexOf | 元素对象 | 根据传入的对象返回对象角标 |
remove | 元素对象 | 根据传入的对象判断是否存在,存在就删除返回true否则返回false |
remove | 角标 | 根据传入的角标判断是否存在,存在就删除并将删除的元素返回 |
removeAll | Coolletion集合 | 根据传入的集合批量删除 |
removeIf | Steam流对象 | 按条件删除 |
clear | 无 | 清空集合 |
3、继承体系
ArrayList继承了AbstractList,实现了Cloneble,RandomAccess,Serializable
- 实现List,提供了基础的添加,删除,遍历等操作
- 实现了RandomAccess,提供了随机访问的操作
- 实现了Cloneble,可以被克隆
- 实现了Serializable,可以被序列化
二、源码分析
1、成员变量
//序列号
private static final long serialVersionUID = 8683452581122892189L;
/**
* 数组初始容量为10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空对象数组,为了减少空数组的产生,共享空数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 默认空对象数组,确保空参构造器时初始化的容量等于10,
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 底层的数组对象
*/
transient Object[] elementData;
/**
* 数组元素个数,默认为0
*/
private int size;
2、构造方法
//根据指定的传入的大小创建集合(底层数组)
public ArrayList( int initialCapacity){
//如果大于0就创建initialCapacity大小的数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
//否则默认用空数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: " +
initialCapacity);
}
}
//默认空参构造器,初始化为空数组,只有当传入一条或一条以上的的元素才会扩容为10,否则默认为空
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//构造一个包含指定元素的列表
public ArrayList(Collection < ? extends E > c){
//将传入的集合转换为数组
Object[] a = c.toArray();
//如果传入的集合长度不等于0
if ((size = a.length) != 0) {
//如果是ArrayList,直接赋值
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
//转换成数组后再赋值
elementData = Arrays.copyOf(a, size, Object[].class);
}
//如果长度等于0,赋值空对象
} else {
elementData = EMPTY_ELEMENTDATA;
}
}
当以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加操作时,才能真正的分配容量。即向数组添加第一个元素时,数组扩容为10
3、内部类
(1)private class Itr implements Iterator<E>
(2)private class ListItr extends Itr implements ListIterator<E>
(3)private class SubList extends AbstractList<E> implements RandomAccess
(4)static final class ArrayListSpliterator<E> implements Spliterator<E>
ArrayList有四个实现类:
Itr是实现了Iterator接口,同时重写了里面的hasNext(), next(), remove() 等方法;
ListItr是实现了ListIterator接口,同时重写了hasPrevious(), nextIndex(), previousIndex(), previous(), set(E e), add(E e);
4、核心方法
4.1、4个添加元素相关的方法
== 增和删是ArrayList最重要的部分==
//添加一个元素到list的末尾
public boolean add(E e) {
//判断是否需要扩容
ensureCapacityInternal(size + 1);
//已有元素+1,并把元素添加在正确角标下(注意并不是添加在元素+1角标下,因为++在后)
elementData[size++] = e;
return true;
}
/**
*参数: 添加的元素,当前数组,当前元素个数长度
*重载的新增方法,调用add方法时,默认走这个方法
*/
private void add(E e, Object[] elementData, int s) {
//首先判断当前元素数量是否等于当前数组的长度,如果是进行扩容
if (s == elementData.length)
elementData = grow();
//不等于直接添加元素到数组中
elementData[s] = e;
//加一操作,记录当前元素数量
size = s + 1;
}
//在指定位置添加一个元素
public void add(int index, E element) {
//判断角标是否合法
rangeCheckForAdd(index);
//判断是否需要扩容
ensureCapacityInternal(size + 1);
//将传入的角标之后所有的数据向后移动一格
System.arraycopy(elementData, index, elementData, index + 1,size - index);
//在指定角标中添加元素
elementData[index] = element;
//元素长度+1
size++;
}
public boolean addAll(Collection<? extends E> c) {
//将传入的集合转为数组并赋值到a
Object[] a = c.toArray();
//获取当前传入集合的长度
int numNew = a.length;
//判断是否需要扩容
ensureCapacityInternal(size + numNew);
//将传入的集合添加到原集合的后面
System.arraycopy(a, 0, elementData, size, numNew);
//原集合长度+传入过来的集合长度
size += numNew;
return numNew != 0;
}
public boolean addAll(int index, Collection<? extends E> c) {
//判断角标是否合理(如果传入角标大于当前当前数据量并小于0不合理)
rangeCheckForAdd(index);
//将传入的集合转为数组并赋值到a
Object[] a = c.toArray();
//获取当前传入集合的长度
int numNew = a.length;
//判断是否需要扩容
ensureCapacityInternal(size + numNew);
int numMoved = size - index;
//判断是否需要后移原数组(逻辑如果传入的角标是刚好是当前集合的长度不需要后移)
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,numMoved);
//把新的集合添加到指定位置
System.arraycopy(a, 0, elementData, index, numNew);
//原集合长度+传入过来的集合长度
size += numNew;
return numNew != 0;
}
4.2、扩容相关方法
对集合进行添加元素时都会判断是否进行扩容,调用grow方法
//确保内部容量够用
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 判断是否需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//计算容量,判断原数组是否是空数组
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果是空返回默认扩容长度或传入数
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//扩容核心方法
private void grow(int minCapacity) {
//获取原数组长度
int oldCapacity = elementData.length;
//扩充1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果说传入的数大于扩充1.5倍的值
if (newCapacity - minCapacity < 0)
//赋值成传入数
newCapacity = minCapacity;
//如果需要扩容的数大于集合最大数 判断是给intger最大数还是集合最大数
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
4.3、大数据量插入
当大数据量插入时可以尝试两种方式:
1、使用ArrayList(int initialCapacity)这个有参构造器,在创建时就声明一个较大的大小,这样解决了频繁拷贝问题,但是需要我们提前预知数据量的大小,而且也会一直占用内存
2、使用ensureCapacity(int minCapacity)方法,其内部也是调用ensureExplicitCapacity(int minCapacity)进行扩容。
4.4、remove()方法
public E remove(int index) {
//检查index的合理性
rangeCheck(index);
modCount++;
//通过传入的角标找到对应的元素
E oldValue = elementData(index);
//开始计算是否需要移动和移动的位置,
int numMoved = size - index - 1;
if (numMoved > 0)
//将需要删除的元素后面那些元素整体向前移一位(这时候最后一位元素还在老位置)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将--size上的位置赋值为null,让gc(垃圾回收机制)更快的回收它。
elementData[--size] = null;
return oldValue;
}
4.5、set()方法
4.6、get()方法
4.7、indexOf()方法
4.8、contains()方法
4.9、toArray()方法
5、分析System.arraycopy()和 Arrays.copyOf()方法
5.1、分析System.arraycopy()方法
System.arraycopy():将指定数组中的数组从指定位置开始复制到目标数据的指定位置
// src:源对象
// srcPos:源对象对象的起始位置
// dest:目标对象
// destPost:目标对象的起始位置
// length:从起始位置往后复制的长度。
// 这段的大概意思就是解释这个方法的用法,复制src到dest,复制的位置是从src的srcPost开始,到srcPost+length-1的位置结束,复制到destPost上,从destPost开始到destPost+length-1的位置上
public static void arraycopy(Object src, int srcPos, Object dest, int destPos,
int length)