1、java面试 集合中知识点 ArrayList源码+扩容机制分析 整理1. ArrayList 简介ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。ArrayList继承于 AbstractList ,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。public class ArrayList extends AbstractList
2、 implements List, RandomAccess, Cloneable, java.io.Serializable RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。ArrayList 实现了 Cloneable 接口 ,即覆盖了函数clone(),能被克隆。ArrayList 实现了 java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。1.1. Arraylist 和 Vector 的区别
3、?1.ArrayList 是 List 的主要实现类,底层使用 Object 存储,适用于频繁的查找工作,线程不安全 ;2.Vector 是 List 的古老实现类,底层使用 Object 存储,线程安全的。1.2. Arraylist 与 LinkedList 区别?3.是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;4.底层数据结构: Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是 双向链表 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面
4、有介绍到!)5.插入和删除是否受元素位置的影响: ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element))时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 LinkedList 采用链表存储,所以对于add(E e)方法的插入,删除元素时间复杂度不
5、受元素位置的影响,近似 O(1),如果是要在指定位置i插入和删除元素的话((add(int index, E element)) 时间复杂度近似为o(n)因为需要先移动到指定位置再插入。6.是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。7.内存空间占用: ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要
6、存放直接后继和直接前驱以及数据)。2. ArrayList 核心源码解读package java.util;import java.util.function.Consumer;import java.util.function.Predicate;import java.util.function.UnaryOperator;public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable private static final long se
7、rialVersionUID = 8683452581122892189L; /* * 默认初始容量大小 */ private static final int DEFAULT_CAPACITY = 10; /* * 空数组(用于空实例)。 */ private static final Object EMPTY_ELEMENTDATA = ; /用于默认大小空实例的共享空数组实例。 /我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。 private static final Object DEFAULTCAPACITY_EMPTY_ELE
8、MENTDATA = ; /* * 保存ArrayList数据的数组 */ transient Object elementData; / non-private to simplify nested class access /* * ArrayList 所包含的元素个数 */ private int size; /* * 带初始容量参数的构造函数(用户可以在创建ArrayList对象时自己指定集合的初始大小) */ public ArrayList(int initialCapacity) if (initialCapacity 0) /如果传入的参数大于0,创建initialCapaci
9、ty大小的数组 this.elementData = new ObjectinitialCapacity; else if (initialCapacity = 0) /如果传入的参数等于0,创建空数组 this.elementData = EMPTY_ELEMENTDATA; else /其他情况,抛出异常 throw new IllegalArgumentException(Illegal Capacity: + initialCapacity); /* *默认无参构造函数 *DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组
10、当添加第一个元素的时候数组容量才变成10 */ public ArrayList() this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; /* * 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。 */ public ArrayList(Collection c) /将指定集合转换为数组 elementData = c.toArray(); /如果elementData数组的长度不为0 if (size = elementData.length) != 0) / 如果elementData不是Object类型数据(c.
11、toArray可能返回的不是Object类型的数组所以加上下面的语句用于判断) if (elementData.getClass() != Object.class) /将原来不是Object类型的elementData数组的内容,赋值给新的Object类型的elementData数组 elementData = Arrays.copyOf(elementData, size, Object.class); else / 其他情况,用空数组代替 this.elementData = EMPTY_ELEMENTDATA; /* * 修改这个ArrayList实例的容量是列表的当前大小。 应用程序
12、可以使用此操作来最小化ArrayList实例的存储。 */ public void trimToSize() modCount+; if (size minExpand) ensureExplicitCapacity(minCapacity); /得到最小扩容量 private void ensureCapacityInternal(int minCapacity) if (elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA) / 获取“默认的容量”和“传入参数”两者之间的最大值 minCapacity = Math.max(DEFAULT_CAPA
13、CITY, minCapacity); ensureExplicitCapacity(minCapacity); /判断是否需要扩容 private void ensureExplicitCapacity(int minCapacity) modCount+; / overflow-conscious code if (minCapacity - elementData.length 0) /调用grow方法进行扩容,调用此方法代表已经开始扩容了 grow(minCapacity); /* * 要分配的最大数组大小 */ private static final int MAX_ARRAY_S
14、IZE = Integer.MAX_VALUE - 8; /* * ArrayList扩容的核心方法。 */ private void grow(int minCapacity) / oldCapacity为旧容量,newCapacity为新容量 int oldCapacity = elementData.length; /将oldCapacity 右移一位,其效果相当于oldCapacity /2, /我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍, int newCapacity = oldCapacity + (oldCapacity 1); /
15、然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量, if (newCapacity - minCapacity 0) newCapacity = hugeCapacity(minCapacity); / minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); /比较minCapacity和 MAX_ARRAY_SIZE private static int hugeCapacity(
16、int minCapacity) if (minCapacity MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; /* *返回此列表中的元素数。 */ public int size() return size; /* * 如果此列表不包含元素,则返回 true 。 */ public boolean isEmpty() /注意=和=的区别 return size = 0; /* * 如果此列表包含指定的元素,则返回true 。 */ public boolean contains(Object o) /indexOf()方法:返回此
17、列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1 return indexOf(o) = 0; /* *返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1 */ public int indexOf(Object o) if (o = null) for (int i = 0; i size; i+) if (elementDatai=null) return i; else for (int i = 0; i = 0; i-) if (elementDatai=null) return i; else for (int i = size-1; i = 0;
18、 i-) if (o.equals(elementDatai) return i; return -1; /* * 返回此ArrayList实例的浅拷贝。 (元素本身不被复制。) */ public Object clone() try ArrayList v = (ArrayList) super.clone(); /Arrays.copyOf功能是实现数组的复制,返回复制后的数组。参数是被复制的数组和复制的长度 v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; catch (CloneNo
19、tSupportedException e) / 这不应该发生,因为我们是可以克隆的 throw new InternalError(e); /* *以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。 *返回的数组将是“安全的”,因为该列表不保留对它的引用。 (换句话说,这个方法必须分配一个新的数组)。 *因此,调用者可以自由地修改返回的数组。 此方法充当基于阵列和基于集合的API之间的桥梁。 */ public Object toArray() return Arrays.copyOf(elementData, size); /* * 以正确的顺序返回一个包含此列表中
20、所有元素的数组(从第一个到最后一个元素); *返回的数组的运行时类型是指定数组的运行时类型。 如果列表适合指定的数组,则返回其中。 *否则,将为指定数组的运行时类型和此列表的大小分配一个新数组。 *如果列表适用于指定的数组,其余空间(即数组的列表数量多于此元素),则紧跟在集合结束后的数组中的元素设置为null 。 *(这仅在调用者知道列表不包含任何空元素的情况下才能确定列表的长度。) */ SuppressWarnings(unchecked) public T toArray(T a) if (a.length size) asize = null; return a; / Position
21、al Access Operations SuppressWarnings(unchecked) E elementData(int index) return (E) elementDataindex; /* * 返回此列表中指定位置的元素。 */ public E get(int index) rangeCheck(index); return elementData(index); /* * 用指定的元素替换此列表中指定位置的元素。 */ public E set(int index, E element) /对index进行界限检查 rangeCheck(index); E oldVa
22、lue = elementData(index); elementDataindex = element; /返回原来在这个位置的元素 return oldValue; /* * 将指定的元素追加到此列表的末尾。 */ public boolean add(E e) ensureCapacityInternal(size + 1); / Increments modCount! /这里看到ArrayList添加元素的实质就相当于为数组赋值 elementDatasize+ = e; return true; /* * 在此列表中的指定位置插入指定的元素。 *先调用 rangeCheckForA
23、dd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大; *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。 */ public void add(int index, E element) rangeCheckForAdd(index); ensureCapacityInternal(size + 1); / Increments modCount! /arraycopy()这个实现数组之间复制的方法一定要看一下,下面就用到了arraycopy()方法实现数组自己复制自己 S
24、ystem.arraycopy(elementData, index, elementData, index + 1, size - index); elementDataindex = element; size+; /* * 删除该列表中指定位置的元素。 将任何后续元素移动到左侧(从其索引中减去一个元素)。 */ public E remove(int index) rangeCheck(index); modCount+; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved 0) S
25、ystem.arraycopy(elementData, index+1, elementData, index, numMoved); elementData-size = null; / clear to let GC do its work /从列表中删除的元素 return oldValue; /* * 从列表中删除指定元素的第一个出现(如果存在)。 如果列表不包含该元素,则它不会更改。 *返回true,如果此列表包含指定的元素 */ public boolean remove(Object o) if (o = null) for (int index = 0; index size; index+) if (elementDataindex = null) fastRemove(index); return true; else for (int index = 0; index size; index+) if (o.equals(el
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1