之前面试时,经常被问到ArrayList的原理,今天整理了一些ArrayList的使用原理和必问的知识点。

ArrayList的继承关系

定义一个ArrayList的方法

ArrayList的三个构造函数:

1.无参

   //默认创建一个ArrayList集合
ArrayList<String> arrayList1 = new ArrayList<>();

2.参数为整数

   //创建一个初始长度为20的ArrayList集合
ArrayList<String> arrayList2 = new ArrayList<>(20);

3.参数为集合

//将其它类型的集合转化为ArrayList
ArrayList<String> arrayList3 = new ArrayList<>(new HashSet());

我们读ArrayList的构造函数源码之前,先看看ArrayList的属性情况:

 1     /**
2 * Default initial capacity.
3 */
4 private static final int DEFAULT_CAPACITY = 10;
5
6 /**
7 * Shared empty array instance used for empty instances.
8 */
9 private static final Object[] EMPTY_ELEMENTDATA = {};
10
11 /**
12 * Shared empty array instance used for default sized empty instances. We
13 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
14 * first element is added.
15 */
16 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
17
18 /**
19 * The array buffer into which the elements of the ArrayList are stored.
20 * The capacity of the ArrayList is the length of this array buffer. Any
21 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
22 * will be expanded to DEFAULT_CAPACITY when the first element is added.
23 */
24 transient Object[] elementData; // non-private to simplify nested class access
25
26 /**
27 * The size of the ArrayList (the number of elements it contains).
28 *
29 * @serial
30 */
31 private int size;

可以看到,ArrayList是非线程安全的容器,底层实现是Object[],数据会添加到ArrayList的elementData数组中,而且默认容量DEFAULT_CAPACITY=10

。但其实在jdk1.7之后,ArrayList的默认容量就是0了,而且DEFAULT_CAPACITY在扩容的过程中才会用到。

我们再看看ArrayList的三种构造函数。

ArrayList有参构造函数-参数类型为整型

 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);
}
}

可以看到,如果传入正整数,则elementData数组容量初始化为initiaCapacity;如果传入0,则elementData数组赋值为一个空数组。可能有读者发现

ArrayList类中有两个属性定义为空数组。

为什么ArrayList会定义两个空数组?

注释中给出了官方的解释(下有翻译)

 /**
* 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.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

简而言之,EMPTY_ELEMENTDATA与DEFAULTCAPACITY_EMPTY_ELEMENTDATA在功能上有不同的意义,前者是单纯用于赋值为空数组,

后者是给elementData数组初始化的。

ArrayList有参构造函数-参数类型为集合类

 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;
}
}

可以看到,只要实现了Collection的集合类,都会调用toArray()将集合类中的数组赋给elementData。而且toArray()返回的数组类型不是Object[]类型时

...etc,toArray()返回的数组类型为什么会不是Object[]类型?举个栗子:

public class Test<E> extends ArrayList{
@Override
public Integer[] toArray() {
return new Integer[] {0,23};
}
public static void main(String[] args) {
Object[] elementData = new Test<Integer>().toArray();
System.out.println(elementData.getClass());
System.out.println(Object[].class);
System.out.println(elementData.getClass()==Object[].class);
}
}

运行结果为:

好,我们接着说当toArray()返回的数组类型不是Object[]类型时,会调用Arrays.copyOf()将原数组拷贝到新数组去,而且类型还可以定义为Object类:

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}

ArrayList无参构造函数

  /**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

在这里,我们可以看到,jdk1.8以后ArrayList的默认容量为0.

如果ArrayList的默认容量为0,还可以添加数组吗?

当然可以,通过扩容机制可以扩充ArrayList的容量:

 public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY; if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}

在这里我们可以看到,如果elementData数组依然为DEFAULTCAPACITY_EMPTY_ELEMENTDATA(即初始状态),则通过

ensureExplicitCapacity()方法将ArrayList容量扩充为10(DEFAULT_CAPACITY)。

为什么无参构造函数对ArrayList容量的初始化改变了呢?

虽然jdk1.8中的ArrayList默认容量为0,但在jdk1.6的无参构造函数的ArrayList默认容量为10:

public ArrayList(){
this(10);
}

个人认为,jdk1.8中延迟初始化ArrayList的实际容量,应该是考虑如果一开始就初始化为10,那么大小为1o的数组中存的全部是null,这种数组多了也会占用大量的

空间,所以这是为了节省不必要浪费的空间,体现了懒加载的思想。

怎样使用ArrayList?

既然我们已经知道了怎么去定义一个ArrayList,接下来就是要使用ArrayList了,而ArrayList提供了常见的方法有:add、addAll、set、get、remove、size、isEmpty等。

因为ArrayList的一些方法会涉及到数据位置的变换,为了更直观的感受这些变化,在这里我们来举个栗子:

    ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("吕布");
arrayList.add("貂蝉");
arrayList.add("董卓");
arrayList.add("刘备");
arrayList.add("赵云");

也就是说,elementData数组中有这些数据:

如果在这些人物中加入”曹操“,我们来看看这个操作在源码中是怎么样的过程:

 public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

首先,会先通过ensureCapacityInternal(size+1)检查elementData数组的容量是否充足,然后在将数据放入数组中,我们来看看ArrayList是怎么进行容量检查的。

ensureCapacityInternal()

public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY; if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}

在这个方法中,确定elementData数组是不是空数组,如果是的话,将形参minCapacity赋值为10(DEFAULT_CAPACITY),然后进入ensureExplicitCapacity()方法。

  private void ensureExplicitCapacity(int minCapacity) {
modCount++; // overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

此方法中的modCount变量是从AbstractList继承下来的,用于记录对ArrayList的对象操作的次数:

private transient int modCount=0;

可以看到当形参minCapacity的数值比当前的elementData数组的长度大,则要调用grow()方法进行扩容操作。

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);
}

可以看到,扩容规则为“数组当前的容量+(数组当前的容量/2)”,即扩容后的数组容量为之前数组容量的1.5倍。当然,如果超过了最大值,在hugeCapacity()方法

中会对其进行处理。

 private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

如果形参minCapacity大于MAX_ARRAY_SIZE,则赋值为interger.MAX_VALUE,实际上MAX_ARRAY_SIZE与Integer.MAX_VALUE相差8.

回到之前的例子中,elementData数组的容量为10,而数组中的数据只有6个,所以“曹操”的加入并不会引起扩容,此时elementData数组中的数据如下:

与add()类似的方法还有:

add(0,"马超")

此方法将数组中的元素各自往后移动一位之后,再将“马超”放在第一个位置上:

addAll(1,list..."张飞",“马谡”,“黄忠”)

将"张飞",“马谡”,“黄忠”放到“吕布”之后,那么数组第二个位置后的元素都需要往后移动三位,而且明显看到数组的容量已经不够了,按规则数组会扩容到16.

如果需要在既定位置安插数据,则需要通过rangeCheckForAdd()方法判断数组是否越界。

  private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

set(int index,E element)

在add()方法中,如果要在数组中(除了数组末尾)安插一个数据,需要将安插位置之后的数据往后移动一位,但如果需要替换数组某个位置的数据,则需要找到对应位置,替换

元素即可。

 public E set(int index, E element) {
rangeCheck(index); E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}

在set()方法执行过程中,需要进行rangeCheck()数组范围检查之后,再将元数据取出用于返回,并在该数组位置替换新数据,如将“马超”替换为“马良”:

ArrayList中的get()方法获取数据的方法也很简单,只需要先判断传入的数组下标是否越界,并通过下标查找,转换类型即可。

 public E get(int index) {
rangeCheck(index); return elementData(index);
}

其中的elementData()方法的实现如下:

  E elementData(int index) {
return (E) elementData[index];
}

remove(int index)和remove(Object o)

ArrayList有两种删除方式,一种是通过下标选择删除的元素,另一个是通过值对象删除元素。我们来了解一下前者:

 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;
}

通过下标选择删除的元素的remove(int index)方法中,在删除元素之前,需要先对下标进行范围检查rangeCheck(),然后在计算出需要移动的元素个数,

通过arraycopy()即数组复制方式,将既定位置之后的元素都向前移动一位,最后在设置elementData[size-1]为null,使GC回收对其起作用。

接下来我们来看后者:

 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;
}

通过值对象删除元素的remove(Object o)方法中,程序将null单独进行处理,因为null是一种状态,不属于任何类型的值。再选中了既定值对象对应的下标之后,删除操作

将会在fastRemove()方法中进行,而fastRemove()方法中的操作过程就跟remove(int index)类似,

在看一下ArrayList的其它方法

--size():用于获取集合的长度。

--isEmpty():用于判断ArrayList是否为空。

--contains():用于判读ArrayList是否包含某个元素。

--clear():从列表删除所有元素

ArrayList使用及原理的更多相关文章

  1. ArrayList/Vector的原理、线程安全和迭代Fail-Fast

    疑问 * ArrayList是非线程非安全的,具体是指什么?具体会产生什么问题?* ArrayList的内部原理是什么?为什么可以动态扩容?* Vector是线程安全的,具体是如何实现的?为什么不再推 ...

  2. Java基础知识强化之集合框架笔记23:ArrayList的实现原理

    1. ArrayList的实现原理: 这个可以直接参考网友的博客:http://www.cnblogs.com/ITtangtang/p/3948555.html

  3. Java集合:ArrayList的实现原理

    Java集合---ArrayList的实现原理   目录: 一. ArrayList概述 二. ArrayList的实现 1) 私有属性 2) 构造方法 3) 元素存储 4) 元素读取 5) 元素删除 ...

  4. ArrayList的实现原理

    ArrayList的线性复杂度是1.想确定一个数据,直接通过索引进行访问.实际上这个过程和数组是非常相似的.ArrayList在整个使用过程中,如果想要高效操作,最好设置一个数组的大小.在个数固定的情 ...

  5. 简单复习一下ArrayList的扩容原理

    刚刚跟几个好朋友喝完小酒回家,简单大概复习一下ArrayList的扩容原理,由于头有点小晕,就只大概说一下扩容的原理哈: 首先ArrayList实现了List接口,继承了AbstractList,大家 ...

  6. Java集合---ArrayList的实现原理

    目录: 一. ArrayList概述 二. ArrayList的实现 1) 私有属性 2) 构造方法 3) 元素存储 4) 元素读取 5) 元素删除                 6) 调整数组容量 ...

  7. ArrayList的实现原理--转

    1. ArrayList概述: ArrayList是List接口的可变数组的实现.实现了所有可选列表操作,并允许包括 null 在内的所有元素.除了实现 List 接口外,此类还提供一些方法来操作内部 ...

  8. ava集合---ArrayList的实现原理

    一.ArrayList概述 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存 ArrayList不是线程安全的,只能用在单线程环境下,多 ...

  9. 集合总结一(ArrayList的实现原理)

    一.概述 一上来,先来看看源码中的这一段注释,我们可以从中提取到一些关键信息: Resizable-array implementation of the List interface. Implem ...

随机推荐

  1. 060 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 07 冒泡排序

    060 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 07 冒泡排序 本文知识点:冒泡排序 冒泡排序 实际案例分析冒泡排序流程 第1轮比较: 第1轮比较的结果:把最 ...

  2. vs中CString的用法,以及所需的头文件

    转载:https://blog.csdn.net/shizhandong50/article/details/13321505 1.CString类型的头文件#include <afx.h> ...

  3. 更简易的机器学习-pycaret的安装和环境初始化

    1.安装 pip install pycaret 在谷歌colab中还要运行: from pycaret.utils import enable_colab enable_colab() 2.获取数据 ...

  4. centos7 下 kafka的安装和基本使用

    首先确保自己的linux环境下正确安装了Java 8+. 1:取得KAFKA https://mirrors.bfsu.edu.cn/apache/kafka/2.6.0/kafka_2.13-2.6 ...

  5. 技术分享丨华为鲲鹏架构Redis知识二三事

    摘要:华为云鲲鹏Redis,业界首个基于自研ARM-Based全栈整合的Redis云服务,支持双机热备的HA架构,提供单机.主备.Proxy集群.Cluster集群实例类型,满足高读写性能场景及弹性变 ...

  6. 实验 4:Open vSwitch 实验——Mininet 中使用 OVS 命令

    一.实验目的 Mininet 安装之后,会连带安装 Open vSwitch,可以直接通过 Python 脚本调用Open vSwitch 命令,从而直接控制 Open vSwitch,通过实验了解调 ...

  7. 什么是 C 和 C ++ 标准库?学编程的你应该知道这些知识!

    简要介绍编写C/C ++应用程序的领域,标准库的作用以及它是如何在各种操作系统中实现的. 我已经接触C++一段时间了,一开始就让我感到疑惑的是其内部结构:我所使用的内核函数和类从何而来? 谁发明了它们 ...

  8. k8s-获取kuboardtoken

    master节点执行命令 echo $(kubectl -n kube-system get secret $(kubectl -n kube-system get secret | grep kub ...

  9. lumen中间件 Middleware

    app/http 下新建 TestMiddleware.php <?php namespace App\Http\Middleware; use Closure; class TestMiddl ...

  10. 第十二章 LNMP架构之分离数据库

    一.课程回顾 1.搭建LNMP环境 1.配置官方源2.yum安装依赖3.yum安装nginx4.配置nginx5.创建用户6.启动并加入开机自启​7.上传安装包8.解压安装包9.卸载旧版本PHP10. ...