Java-Collection - ArrayList
概念
- ArrayList实现了List接口,顺序存储,允许放入 null 元素,底层是通过数组实现 。除了Collection的15种抽象方法外(不直接继承,没有完全实现),额外增加一些关于该集合数据的操作(都不是很常用),详细API见MaTools-java8-ArryList
- ArrayList是List接口的实现类中最常用的实现类;查询效率高,底层使用数组实现;
- 线程不做同步处理(锁),不安全(作为对比Vector安全效率低)。
- 多线程并发情况,可以通过
synchronized自行包装,或者Collections工具类Collections.synchronizedList。 - 效率和安全性是需要取舍
- 多线程并发情况,可以通过
常用方法API
全API->java.util->【类摘要】ArrayList< E>
常用API:
| 返回值 | 方法 | 说明 |
|---|---|---|
| boolean | add(E e) | 将指定的元素追加到此列表的末尾 |
| void | add(int index, E element) | 在此列表中的指定位置插入指定的元素 |
| boolean | addAll(Collection<? extends E> c) | 按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾 |
| boolean | addAll(int index, Collection<? extends E> c) | 将指定集合中的所有元素插入到此列表中,从指定的位置开始 |
| void | clear() | 从列表中删除所有元素 |
| Object | clone() | 返回此 ArrayList实例的浅拷贝 |
| boolean | contains(Object o) | 如果此列表包含指定的元素,则返回 true |
| void | forEach(Consumer<? super E> action) | 对 Iterable的每个元素执行给定的操作,直到所有元素都被处理或动作引发异常。 |
| E | get(int index) | 返回此列表中指定位置的元素 |
| int | indexOf(Object o) | 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1 |
| boolean | remove(int index) | 删除该列表中指定位置的元素 |
| boolean | removeAll(Collection<?> c)) | 从此列表中删除指定集合中包含的所有元素 |
| boolean | remove(Object o) | 从列表中删除指定元素的第一个出现(如果存在) |
| boolean | removeIf(Predicate<? super E> filter) | 删除满足给定谓词的此集合的所有元素 |
| boolean | removeRange(int fromIndex, int toIndex) | 从这个列表中删除所有索引在 fromIndex (含)和 toIndex之间的元素 |
| E | set(int index, E element) | 用指定的元素替换此列表中指定位置的元素 |
| int | size() | 返回此列表中的元素数 |
| Object[] | toArray() | 以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组 |
| void | trimToSize() | 修改这个 ArrayList实例的容量是列表的当前大小 |
ArrayList源码
1.构造ArrayList
package java.util;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; //transient关键字定义的数组
// 无参构造
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 有参构造——集合直接转数组
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// 空集初始化为{}
elementData = 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);
}
}
有参构造函数初始化长度为集合数组长度 / 指定的int值为数组长;
无参构造函数初始化的时候并没有直接指明数组长度,而是初始化为{}
1.1 无参构造的初始化懒加载
在java8之前,ArrayList初始化长度 DEFAULTCAPACITY_EMPTY_ELEMENTDATA = 10(字节) ;
java8后,修改为动态添加载:也就是调用add()方法后再去创建一个长度为10的数组。也就是懒加载。
- 好处:省内存,java集合自动扩容机制下,新数组很难做到百分百匹配需要的内存空间。
// 默认数组长度也为10(字节)
private static final int DEFAULT_CAPACITY = 10;
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
多态add()方法,都是要调用ensureCapacityInternal()方法,而ensureCapacityInternal方法就是“自动扩容” + DEFAULT_CAPACITY
*/
add(.....);
2.自动扩容机制
每当调用add方法添加元素时,都会先检查需要添加的元素个数是否会超出当前数组的长度,调用ensureCapacityInternal()。如果超出,就会自动进行扩容操作。每次扩容会开辟一个新的空间,约为原来容量的1.5倍取整(算法步骤见↓注解),同时需要将原有数组中的数据复制到这新的数组中。
这新开辟的空间只会判断是否够用!多补不退,也就是会造成内存浪费
约定:开发中,尽量避免一次性add大于10字节数据;当确实需要这样做,可以在初始化ArrayList时候指明需要的空间容量
public void ensureCapacity(int minCapacity) {
//判断数组是有无参数构造的,无参构造初始化长度就为10
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0
: DEFAULT_CAPACITY;
//若大于给定的数组长度,就扩容操作
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
// 计算容量
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//JVM规定int数组最大空间为。其中,Integer.MAX_VALUE:为int类型容量,2^31-1
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// ① 位移运算,新容量右移1位:oldCapacity + (oldCapacity÷2^1) ≈ oldCapacity * 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) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

3.常用实现函数
add 和 addAll
操作值的插入。插入值可以是元素或者集合,如果参数没有指明索引(add(E e) / addAll(Collection<? extends E> c)),就直接插入到数组末端;参数指明索引(add(int index, E element) / addAll(int index, Collection<? extends E> c)),根据索引查询到数组地址,把后面元素向后移动,腾出空间并在该位置插入值。
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // 扩容机制判断和增容
System.arraycopy(elementData, index, elementData, index + 1, //复制数组。系统自带静态方法arraycopy实现②
size - index);
elementData[index] = element;
size++; //全局变量,记录该ArrayList包含元素个数
}
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // 扩容机制判断和增容
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
//其他add或addAll基本也类似
......
//避免add 和 addAll索引越界
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
get 和 set
既然底层数据结构为数组,自然设置了get和set、
//rangeCheck:检查数组下标是否越界
public E get(int index) {
rangeCheck(index);
return (E) elementData[index];
}
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element; //修改元素
// set方法是有返回,return (E) elementData[index]
return oldValue;
}
trimToSize()
将底层数组的容量调整为当前列表保存的实际元素的大小的功能
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
spliterator()
spliterator:并行遍历迭代器。有点类似顺序遍历迭代器Iterator
- Iterator是单线程内,对元素进行迭代
- spliterator针对并行情况。
- 主要特点是后期绑定(late-binding)和快速故障机制。
- 主要作用:做并行数据处理。将并行集合切割成好几段,每个线程执行一段,达到并行的目的。主要是搭配stream使用。详见《java8实战(英)CUrma》第7章7.3节 ③
- 前面提到ArrayList不是线程安全的,但通过手段让其规避线程问题。用spliterator处理并行需求也是一种手段
//就是创建新的spliterator
public Spliterator<E> spliterator() {
checkForComodification();
return new ArrayListSpliterator<E>(ArrayList.this, offset,
offset + this.size, this.modCount);
}
附录
① 位移运算(>>):
按二进制形式把所有的数字向右边移动对应位数,低位舍弃,高位补符号位(正数1,负数0);
也就是,正数右移结果为这个数除以2^n取整(注意非四舍五入)。例如:15>>1 = 15÷2^1 取整 = 7
② System.arraycopy
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length):
是系统自带的静态方法,Static修饰,在System类编译时创建,而System又是main入口的主类,所以arraycopy类是jdk提供实现方法。
作用:将指定源数组中的数组从指定位置开始复制到目标数组的指定位置 (顺带完成数组内元素移动)
③ 《java8实战(英)CUrma》实体书扫影PDF 链接: https://pan.baidu.com/s/1Wf2nE15-71Rgf9GCAVHnZg?pwd=6n6x 提取码: 6n6x 复制这段内容后打开百度网盘手机App,操作更方便哦

浙公网安备 33010602011771号