面试题总结-Java集合类系列(1)-ArrayList
前言:为什么要写这篇文章?
这是我写的面试题系列第一篇文章,来说说原因:
现在是知识大爆炸的时代,任何面试题在网上都能找到一堆的答案,我为什么还需要写?
因为每个人的知识接受范围和接收程度是不一样的,你在网上找到的答案,简单了不稀罕看,复杂的又看不懂,或者文章语气看不习惯的,很少找到了非常适合自己的,很多知识还是碎片式的。
所以,建议各位朋友也整理一份属于自己的知识文档。
1. ArrayList 概述
ArrayList 是Java开发者最常用的集合类之一,底层是Object数组组成。
ArrayList 是继承了AbstractList 类,实现了 List 接口。AbstractList 是一个抽象类,也是实现了 List 接口。而 List 接口继承了 Collection 接口。所以,我们需要向上查看接口类,看 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 扩容
首先,ArrayList定义了有一个数组容量的最大值 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
扩容方法 grow 的参数是 minCapacity,表示要存储当前数据所需要的最小容量
oldCapacity 是数组的原始容量
newCapacity 是新容量,默认是 oldCapacity的1.5倍
还有两种特性情况:
(1) 如果计算出来的 newCapacity ,小于传入参数 minCapacity,就使用 minCapacity 赋值给 newCapacity
(2) 如果 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; }