前言:为什么要写这篇文章?
这是我写的面试题系列第一篇文章,来说说原因:
现在是知识大爆炸的时代,任何面试题在网上都能找到一堆的答案,我为什么还需要写?
因为每个人的知识接受范围和接收程度是不一样的,你在网上找到的答案,简单了不稀罕看,复杂的又看不懂,或者文章语气看不习惯的,很少找到了非常适合自己的,很多知识还是碎片式的。
所以,建议各位朋友也整理一份属于自己的知识文档。
1. ArrayList 概述
ArrayList 是Java开发者最常用的集合类之一,底层是Object数组组成。
AbstractList 类,实现了 List 接口。AbstractList 是一个抽象类,也是实现了 List 接口。而 List 接口继承了 Collection
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
1.1 ArrayList的常量
ArrayList 类中,定义了3个常量,官方解释如下:
/**
* Default initial capacity.
*
* 默认初始化容量,10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*
* 所有空数组共享的 空数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*
* 用于默认大小的空实例的共享空数组实例。
* 我们要与EMPTY_ELEMENTDATA 区分开,当添加第一个元素时,用于扩张数组
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
1.2 ArrayList 的属性
/**
* ArrayList 中保存的具体数据
* 如果实例化时指定了容量,数组长度与指定容量相同。
* 如果实例化时是空ArrayList,则使用elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
*/
transient Object[] elementData;
/**
* ArrayList 包含的元素数据量
*/
private int size;
1.3 构造函数
ArrayList 提供了3个构造方法
/**
* 构造一个具有指定初始容量的空列表。
* initialCapacity 是初始化的容量
* 当 initialCapacity > 0 时,this.elementData = new Object[initialCapacity];
* 当 initialCapacity == 0 时,this.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);
}
}
/**
* 构造一个初始容量为10的空列表。
* 初始化时就是空列表,只有在第一次调用add方法时,才把数组容量设为10
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 构造一个包含指定的元素的列表集合返回它们的顺序迭代器。
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
2. Add方法
Add方法的主要逻辑如下:
(1) 先判断当前的数组是否有空间添加此元素,判断参数是最小容量 minCapacity = size + 1
(1.1) 先判断当前数组是否为空:为空的情况,比较默认值容量 与 minCapacity 谁大,返回大的值;数组不为空就返回 minCapacity
(1.2) minCapacity 如果大于当前数组容量,则需要对数组扩容,扩容逻辑参见 grow 方法
(2) 如果是指定了index的add方法,需要复制数组的数据,原index位置的数据整体往后移。
(3) 添加元素到指定位置。
(4) size变量值加1
/**
* Appends the specified element to the end of this list.
* 将指定的元素追加到列表的末尾。
*/
public boolean add(E e) {
// 判断存储能力
ensureCapacityInternal(size + 1);
// 在队尾添加元素
elementData[size++] = e;
return true;
}
/**
* 添加元素到指定的index位置
*
*/
public void add(int index, E element) {
// 判断index范围,如果>size 或 <0 会抛异常
rangeCheckForAdd(index);
// 判断存储能力
ensureCapacityInternal(size + 1);
// 转移数字位置,把index位置的元素空出来
System.arraycopy(elementData, index, elementData, index + 1, size - index);
// 在index位置添加元素
elementData[index] = element;
size++;
}
/**
* 确认处理能力的方法
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/**
* 计算当前数组所需的存储容量
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 当数组还是空的时候,判断默认存储空间和 minCapacity的值,返回最大的
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
/**
* 处理存储能力
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
// grow是数组的扩容方法,有需要的自己看一下
grow(minCapacity);
}
3. Get方法
Get方法很简单,首先判断了index参数是否合法,再直接从数值取值。
/**
* Returns the element at the specified position in this list.
*/
public E get(int index) {
// 验证index参数是否大于等于size,是的话抛异常
rangeCheck(index);
// 根据数组原理,直接从index位置取值
return elementData(index);
}
4. Remove方法
4.1 remove(int index)
根据坐标删除数据,操作如下:
(1) 验证参数index是否合法
(2) 查询出要删除的值,最后返回使用
(3) 计算复制的数组元素数量
(4) 复制数组,用index后面的数据,整体向前移动,把原index位置的数据覆盖
(5) 把最后一个数据,设置为null
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).*/
public E remove(int index) {
// 验证index参数是否合法
rangeCheck(index);
modCount++;
// 取出要删除的值
E oldValue = elementData(index);
// 计算要复制的数组元素的数量
int numMoved = size - index - 1;
if (numMoved > 0)
// 复制数组,把index + 1位置开始的数据,整体往前复制
System.arraycopy(elementData, index+1, elementData, index, numMoved);
// 上面是复制操作,所以size--的位置上还保留着一条重复数据,需要设置为null
elementData[--size] = null; // clear to let GC do its work
// 返回删除的数据
return oldValue;
}
4.2 remove(Object o)
根据元素删除,先判断元素是否存在,存在就使用刚查到的index,使用快速删除方法删除元素。
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If the list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
*/
public boolean remove(Object o) {
if (o == null) {
// 如果元素为空,判断null元素的位置
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
// 删除方法
fastRemove(index);
return true;
}
} else {
// 元素不为空,使用equals 判断,元素的位置
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
// 删除方法
fastRemove(index);
return true;
}
}
return false;
}
/*
* 快速删除方法,原理与remove(int index) 一样
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null;
}
5 扩容
MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
minCapacity,表示要存储当前数据所需要的最小容量
oldCapacity
newCapacity 是新容量,默认是 oldCapacity的1.5倍
还有两种特性情况:
newCapacity ,小于传入参数 minCapacity,就使用 minCapacity 赋值给 newCapacity
newCapacity 大于 MAX_ARRAY_SIZE 时,就调用 hugeCapacity 方法判断新容量。如果 minCapacity 大于 MAX_ARRAY_SIZE 就返回 Integer.MAX_VALUE,否则返回 MAX_ARRAY_SIZE
结论:ArrayList扩容时,默认会扩容到原始容量的1.5倍;如果原始容量的1.5倍小于参数minCapacity,就按照 minCapacity 的值扩容; ArrayList的扩容最大值到 Integer.MAX_VALUE
/**
* 限制ArrayList的数组容量最大值
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 增加容量,以确保它至少能容纳最小容量参数指定的元素数量。
*
* @param minCapacity 最小容量
*/
private void grow(int minCapacity) {
// oldCapacity是数组的原始容量
int oldCapacity = elementData.length;
// newCapacity是新的容量,是原始容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果计算出来的newCapacity ,小于传入参数的minCapacity,就使用minCapacity 赋值给 newCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果 newCapacity 大于 MAX_ARRAY_SIZE时,使用minCapacity判断新容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 复制数组,把原始数据复制到newCapacity容量的新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // 参数小于0时,抛异常
throw new OutOfMemoryError();
// minCapacity 大于 MAX_ARRAY_SIZE时,返回Integer.MAX_VALUE, 否则返回 MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}