一、ArrayList简介
ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。
它继承于 AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。
在我们学数据结构的时候就知道了线性表的顺序存储,插入删除元素的时间复杂度为O(n),求表长以及增加元素,取第 i 元素的时间复杂度为O(1)
ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
ArrayList 实现了Cloneable 接口,即覆盖了函数 clone(),能被克隆。
ArrayList 实现java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。
和 Vector 不同,ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。
二、源码分析
一、属性:
- 默认初始容量大小
private static final int DEFAULT_CAPACITY = 10;
- 空数组(用于空实例)。
private static final Object[] EMPTY_ELEMENTDATA = {};
- 用于默认大小空实例的共享空数组实例。我们把它从
EMPTY_ELEMENTDATA
数组中区分出来,以知道在添加第一个元素时容量需要增加多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
elementData
数组用来存储ArrayList中的元素,从这个可以看出,ArrayList是底层是借组于数组来实现的。
transient Object[] elementData;
size
用来记录ArrayList中存储的元素的个数。
private int size;
二、构造方法:
- 带初始容量参数的构造函数。(用户自己指定容量)
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//创建initialCapacity大小的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//创建空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
- 默认构造函数,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray 可能返回的不是Object类型的数组所以加上下面的语句用于判断,
//这里用到了反射里面的getClass()方法
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
上面将容器Collection转化为数组赋给数组elementData,还对Collection转化是否转化为了Object[]进行了检查判断。如果Collection为空,则就将空的常量数组对象EMPTY_ELEMENTDATA赋给了elementData;
三、add方法:
add(E e)
:将元素加入到List的末尾。
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
上面代码调用了另一个add
方法
private void add(E e, Object[] elementData, int s) {
//s为size,当size等于数组长度时,数组扩容
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
add(int index, E element)
:将element
插入index
位置
public void add(int index, E element) {
//对index进行界限检查
rangeCheckForAdd(index);
modCount++;
final int s;
Object[] elementData;
//当size等于数组长度时,数组扩容
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
//arraycopy()这个实现数组之间复制的方法一定要看一下,下面就用到了arraycopy()方法实现数组自己复制自己
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
elementData[index] = element;
size = s + 1;
}
addAll(Collection<? extends E> c)
:按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
modCount++;
int numNew = a.length;
if (numNew == 0)
return false;
Object[] elementData;
final int s;
if (numNew > (elementData = this.elementData).length - (s = size))
elementData = grow(s + numNew);
System.arraycopy(a, 0, elementData, s, numNew);
size = s + numNew;
return true;
}
四、扩容核心方法:
ArrayList的扩容机制提高了性能,如果每次只扩充一个,那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。
- 要分配的最大数组大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
- 最少扩容为
size+1
private Object[] grow() {
return grow(size + 1);
}
- 把数组复制到扩容后的数组
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
- 扩容核心
private int newCapacity(int minCapacity) {
// oldCapacity为旧容量,newCapacity为新容量
int oldCapacity = elementData.length;
//将oldCapacity 右移一位,其效果相当于oldCapacity /2,
//我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,
int newCapacity = oldCapacity + (oldCapacity >> 1);
//然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,
if (newCapacity - minCapacity <= 0) {
//数组是DEFAULTCAPACITY_EMPTY_ELEMENTDATA时,那第一次增加元素时扩容为10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
//再检查新容量是否超出了ArrayList所定义的最大容量,
//若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,
//如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
//比较minCapacity和 MAX_ARRAY_SIZE
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE)
? Integer.MAX_VALUE
: MAX_ARRAY_SIZE;
}
四、remove方法
1.remove(int index)
:删除index的元素
public E remove(int index) {
Objects.checkIndex(index, size);
final Object[] es = elementData;
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
fastRemove(es, index);
return oldValue;
}
上面的代码用到了fastRemove
,将值
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;// clear to let GC do its work
}
remove(Object o)
:移除指定元素
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found: {
//如果为null,遍历与null比较
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
//如果不为空,遍历与o比较
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
fastRemove(es, i);
return true;
}
removeAll(Collection<?> c)
:从此列表中删除指定集合中包含的所有元素。
public boolean removeAll(Collection<?> c) {
return batchRemove(c, false, 0, size);
}
五、get方法
get(int index)
:获得index出的值
public E get(int index) {
//边界检查
Objects.checkIndex(index, size);
return elementData(index);
}
六、set方法
set(int index, E element)
:将element
复制给index
处
public E set(int index, E element) {
Objects.checkIndex(index, size);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
七、clear方法
clear()
:清除ArrayList中所有的元素。直接将数组中的所有元素设置为null即可,这样便于垃圾回收。
public void clear() {
modCount++;
final Object[] es = elementData;
for (int to = size, i = size = 0; i < to; i++)
es[i] = null;
}
三、几个问题
一、System.arraycopy()和Arrays.copyOf()方法
联系: 看两者源代码可以发现copyOf()内部调用了System.arraycopy()方法
区别:
-
arraycopy()需要目标数组,将原数组拷贝到你自己定义的数组里,而且可以选择拷贝的起点和长度以及放入新数组中的位置
-
copyOf()是系统自动在内部新建一个数组,并返回该数组。
二、ArrayList的交集,差集,并集,去重并集
-
交集:
list2.retainAll(list1)
,此时list2
中只包含之前list1
与list2
共有的元素(交集),如果lis2
中的元素有被修改过,会返回true
,否则返回false
-
差集:
list2.removeAll(list1);
,此时list2
中只包含之前属于list2
而不属于list1
的元素,如果lis2
中的元素有被修改过,会返回true
,否则返回false
-
并集:
list2.addAll(list1);
,此时list2
中包含之前list1
与list2
中的元素,如果lis2
中的元素有被修改过,会返回true
,否则返回false