源码获取
jdk 源码在我们 jdk 的安装目录下即可找到:
-
jdk1.8
在 jdk1.8 及之前的版本中,jdk的源码均可在安装目录的根目录下找到 src.zip,此包即为 jdk 源码 -
jdk11
从 jdk11 开始,jdk的源码包放在 jdk 安装目录下的 lib 目录下,在 lib 目录下找到 src.zip 即为源码
近几个月收集了收集了N份精校过的PDF版的Java八股文大全,涉及Java后端的方方面面,分享给大家。希望能帮助到大家面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。
如果需要面试题答案↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓与面试题的视频讲解
所有面试题答案: 直接获取方式点我(点我即可)
135页答案文档版,如下图:
视频版(部分)如下图:
实现接口
ArrayList 实现了以下接口:
-
List
实现了 List 接口,Java集合框架中常用接口 -
RandomAccess
用于实现随机访问功能,因为底层使用数组实现,可以通过下标实现随机访问 -
Cloneable
克隆接口,用于实现两个 List 之间的克隆 -
Serializable
序列化接口
核心属性
// 定义默认List大小
private static final int DEFAULT_CAPACITY = 10;
// 用于存储实际 ArrayList 中的数据
transient Object[] elementData;
// 用于存储当前 ArrayList 的大小
private int size;
从上述代码中我们可以得出以下结论:
- ArrayList 默认初始化大小为 10
- ArrayList 实际大小使用 size 变量存储
- ArrayList 底层数据使用 Object 数组存储
为什么数组使用transient关键字
transient关键字用于修饰类成员变量,不能用于修饰方法,用于标记对应的类成员变量不是类持久化状态的一部分。
jvm默认序列化和反序列创建字节流的过程中会忽略 transient 关键字修饰的类成员变量
ArrayList 中底层数组使用 transient 修饰是出于性能考虑,因为底层数据结构是数组,因此必然存在很多数组空间未使用的情况,直接序列化将导致序列化性能大大降低和资源的浪费
虽然因为底层数组使用 transient 修饰导致数组不能由jvm默认序列化,但是通过定义writeObject和readObject方法,自定义实现了列表元素的序列化与反序列化。此内容文章后续讲解
核心方法
构造方法
此处以指定大小为例,其他构造方法类似
用于初始化 ArrayList
// 默认空数组
private static final Object[] 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);
}
}
- 如果初始化大小 > 0 ,则创建一个对应大小的 Object 数组
- 如果初始化大小 == 0,则使用默认的静态空数组,此处是为了减少空间占用,避免多个空数组占用内存
- 如果初始化大小 < 0,则抛出异常
indexOf(Object o)
查找元素出现的第一个位置
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
- 判断元素是否为空
- 遍历数组列表,找到值相同的索引返回,此处比较是否相同使用的 equals 方法
- 如果未找到对应的元素,返回 -1
lastIndexOf(Object o)
查找元素出现的最后一个位置
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
- 判断元素是否为空
- 从数组末尾向前遍历,找到第一个值相同的索引返回,同上,使用 equals 方法
- 如果未找到对应的元素,返回 -1
clone()
克隆出一个新的 List
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
- 底层是使用 System.arraycopy 实现的
- System.arraycopy 是native方法
- System.arraycopy 当数组是基础数据类型或者是 String 类型的时候是深拷贝
- System.arraycopy 当数组是非基础数据类型且非 String 类型的时候是浅拷贝
- System.arraycopy 是不安全的
get(int index)
获取指定位置的元素
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
- 进行范围检查,如果出现数组越界,直接抛出异常
- 返回数组对应下标位置的数据
set(int index, E element)
设置指定位置的元素
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
- 进行范围检查,如果出现数组越界,直接抛出异常
- 获取对应索引位置的值
- 将对应索引位置的值设置为新值
- 返回旧值
add(E e)
在 ArrayList 的末尾添加元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
- 当前大小+1作为最小数组容量
- 检查容量是否需要扩容,如果需要扩容,则执行扩容操作
- 设置数组后面一个元素为对应的元素
- 返回结果
扩容操作
数组扩容操作
/**
* 检查容量,不满足则扩容
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/**
* 计算应该扩容到的容量
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
/**
* 判断是否需要扩容
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* 扩容操作
*/
private void grow(int minCapacity) {
// overflow-conscious code
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);
}
- 计算当前应该扩容到的容量
a. 数组是否是默认的空数组,是则返回当前最小容量和默认容量(10)的最大值
b. 否则,返回当前最小容量
- 判断是否需要扩容
a. 先对 modCount 增加,modCount 用于标识数组是否被修改过,用于提供快速失败的实现
b.判断应该扩容到的容量是否超过了当前底层数组的实际大小,如果超过了才需要扩容
- 如果需要扩容则执行扩容
a. 计算出新的数组大小 = 旧的数组的大小 + 旧的数组的大小 / 2,即,扩容后的大小为原数组大小的 1.5 倍,采用 >> 1
的写法是为了提升性能
b. 如果新的数组的大小 < 应该扩容的数组的大小,则新的数组的大小设置为应该扩容的数组的大小
c.如果新的数组的大小超出了最大数组的大小(Integer.MAX_VALUE - 8 = 2147483639),则新数组的大小设置为
Integer.MAX_VALUE,否则为数组的最大大小(Integer.MAX_VALUE - 8 = 2147483639)
d.如果扩容的数组的大小出现了数据溢出,则会抛出 OutOfMemoryError 内存溢出异常
e.创建新的数组,将旧数组数据复制到新的数组中
add(int index, E element)
在指定索引位置插入元素
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++;
}
- 范围检查,如果出现数组越界,抛出异常
- 当前大小+1作为数组最小容量
- 检查容量是否需要扩容,如果需要扩容,则执行扩容操作
- 将指定位置的数组内容向后移动一位
- 在指定位置设置数组的元素值
- 数组size+1
remove(int index)
移除指定位置的元素
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;
}
- 进行范围检查,如果出现数组越界,直接抛出异常
- 获取对应索引位置的数据
- 计算出需要移动的位置的索引,如果索引 > 0,则将后面的数组的内容向前移动一位
- 将数组的最后一个元素设置为null
- 返回旧数据
remove(Object o)
移除指定元素,仅会移除第一次出现的元素
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;
}
/**
* 快速移除指定位置的元素
*/
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; // clear to let GC do its work
}
- 判断要移除的元素是否为null
- 从开始位置遍历数组元素,判断元素是否相等,如果相等,则执行快速移除(同 remove(int index) 方法的实现),移除后返回结果
clear()
清空 ArrayList
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
- 修改数量+1,用于提供快速失败机制
- 从开始位置遍历数组,将数组中的元素全部设置为null
- 设置 size 大小为 0
addAll(Collection<? extends E> c)
添加集合元素至 ArrayList 结尾
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
- 将集合转换为数组
- 当前大小+新数组的大小作为最小容量
- 根据最小容量判断是否需要扩容,如果需要扩容,执行扩容操作
- 将新数组复制到数组的末尾
- size大小更新为当前新的大小
- 返回新增结果
addAll(int index, Collection<? extends E> c)
在指定位置添加集合元素
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
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;
}
- 进行范围检查,如果出现数组越界,直接抛出异常
- 将集合转换为数组
- 当前大小 + 新数组的大小作为最小容量
- 根据最小容量判断是否需要扩容,如果需要扩容,执行扩容操作
- 将当前数组从index开始位置的元素全部向后移动新数组大小的位置
- 将新数组的元素从index位置开始复制
- size大小更新为当前新的大小
- 返回新增结果
removeRange(int fromIndex, int toIndex)
移除指定区间内的元素
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
- 修改数量+1,用于提供快速失败机制
- size-toIndex是需要移动的元素的个数
- 将需要移动的元素移动至 fromIndex 位置
- size - (toIndex-fromIndex) 是现在新的数组的实际大小
- 从新的大小位置开始,将后面的元素全部设置为null
- size设置为新的大小
removeAll(Collection<?> c)
移除包含集合中的所有元素
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
/**
* 批量移除元素
*/
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
注意 complement 参数值为 false
- 检查集合元素是否为空,为空则抛出异常
- 遍历数组,如果集合中不包含当前元素(通过 complement 值来判断),则将当前元素依次从 r 位置排列
- 当 r != size 的时候说明 c.contains 可能出现了异常,做了一个兼容操作,此处不详细讲述
- w表示移动后的集合的元素的长度,如果 w != size 则证明存在元素被修改,将 w 位置后面的元素全部设置为 null, 并将 size 设置为 w
- 返回修改结果
retainAll(Collection<?> c)
ArrayList 和 集合取交集
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
注意 complement 参数值为 true
步骤和上面 removeAll 方法实现逻辑一样,只是 complement 参数值不一样,通过 complement 参数来控制是保留包含的元素还是不包含的元素
writeObject & readObject
序列化使用,上面内容我们讲到为了保证性能和避免资源浪费,底层数组使用了 transient 关键字修复,导致无法使用 jvm 的序列化,通过 writeObject 和 readObject 方法实现了 ArrayList 的序列化和反序列化
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
总结
从上述源码中我们可以得出以下结论:
- ArrayList也是List 的一种
- ArrayList底层使用 Object 数组实现
- ArrayList比较适合读多写少的情况(写需要挪动元素)
- ArrayList初始容量大小为10
- ArrayList每次扩容大小为原容量的 1.5 倍