提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
ArrayList 是一个动态数组,实现了 List 接口以及 list相关的所有方法,它允许所有元素的插入,包括 null。另外,ArrayList 和 Vector 除了线程不同步之外,大致相等。
提示:以下是本篇文章正文内容,下面案例可供参考
一、ArrayList概述
首先看如下注释(为ArrayList类开头注释内容):
/**
* Resizable-array implementation of the <tt>List</tt> interface. Implements
* all optional list operations, and permits all elements, including
* <tt>null</tt>. In addition to implementing the <tt>List</tt> interface,
* this class provides methods to manipulate the size of the array that is
* used internally to store the list. (This class is roughly equivalent to
* <tt>Vector</tt>, except that it is unsynchronized.)
*/
该注释提供的信息:
1.ArrayList集合是对List接口的实现
2.实现了List接口中所有可选的操作。
3.该集合可以存入任何元素内容,包括null空值
4.除了实现list集合外,该类还提供了一些方法用来操作内部存储列表的数组。
5.ArrayList与Vector集合大致相同,不同的是ArrayList是线程不安全的,而Vector是线程安全的
ArrayList集合实际上就就是一个可变数组,是对List接口的实现。该集合可以存储任何内容元素,包括null值。它实现了List接口中所有的方法,并且提供了一些独有的方法来操作内部存储列表的数组。
每个ArrayList集合初始化时都有一个容量,该容量代表值集合底层数组的长度。ArrayList初始化时,集合底层数组的长度默认值为0,当ArrayList实际要存储的容量大于等于数组的长度时,就需要对数组进行扩容处理。随着数组元素的不断增加,数组的容量也会进行自增长。集合容量自增长(实际上就是创建一个新的数组)完成后,就会将老数组中的数据拷贝到新的数组中。数组自增长时,会自动调用ensureCapacityInternal方法来对集合进行扩容处理。
注意的时ArrayList集合是一个线程不安全的集合,若多个线程同时对一个集合进行操作,那么在操作的外部需要synchronized进行同步。
二、ArrayLIst的实现
ArrayList底层实际上就是数组,所以ArrayList中绝大都数方法都是对数组进行增删改查的操作。
1.ArrayList的属性
// 集合的默认容量
private static final int DEFAULT_CAPACITY = 10;
// 底层数组默认为空
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 底层数组
transient Object[] elementData;
// 集合的大小(集合实际存储的元素)
private int size;
// 集合可扩容的最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
通过属性可以分析出:
1.实例化一个空参集合时,其底层默认会创建一个空数组。
2.ArrayList集合中的size()方法,实际上就是获取成员属性的size值。
3.集合最大可以扩容到整型的最大值Interger>MAX_VALUE。
4.底层数组的类型时Object类,所以集合可以插入任意元素,包括null值。
2.ArrayList集合的构造方法
第一种:空参构造器
// 第一种:空参构造器
public ArrayList() {
// 初始化集合时将集合的底层数组初始化为一个空数组
// this.element = {}
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
第二种:创建一个包含Collection集合的一个ArrayList集合列表
// 第二种:创建一个包含Collection集合的一个ArrayList集合列表
public ArrayList(Collection<? extends E> c) {
// 将Collection集合转成数组,然后直接赋值给集合底层数组,
// 这时候ArrayList集合内的元素就是Collection集合内的元素了
elementData = c.toArray();
// 判断集合容器中是否有元素
if ((size = elementData.length) != 0) {
// 判断字节码文件是否相同
if (elementData.getClass() != Object[].class)
// 对数组进行扩容处理
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 没有元素就将底层数组默认为空,达到实例化集合的目的
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);
}
}
3.集合扩容原理解析(add(E e)方法)
核心的代码
1.elementData[size++] = e; // 向数组中添加元素
2.int newCapacity = oldCapacity + (oldCapacity >> 1); // 每次创建新数组的长度
3.elementData = Arrays.copyOf(elementData, newCapacity); // 将数组扩容 最为核心的代码
核心思想
1.初始化集合时或创建一个集合时,源码会创建一个长度为0的Object数组。
2.当向集合中添加第一个元素时,此时源码会创建一个长度为10的新数组来替换旧的数组。
3.当向集合中添加第十一个元素时,此时源码就会创建一个长度为15的新数组来替换旧的数组。
4.至此以后,每当集合要扩容时,新的数组长度都是旧数组长度的1.5倍。
5.当底层数组长度没有超过Integer.MAX_VALUES - 8(整型的最大值 - 8) ,则新数组的长度为:MAX_ARRAY_SIZE
(也就是整型的最大值-8),当底底层数组的长度超过Integer.MAX_VALUES - 8时,则新数组的长度为整型最大值
(Integer.MAX_VALUES)。
扩容底层代码:
对于图中第13条语句中: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;
}
4.get(int index) 方法
// 根据索引查找相应的元素值
public E get(int index) {
// 该方法是用来判断index是否在集合索引的范围内
rangeCheck(index);
return elementData(index);
}
// 该方法是用来判断,输入的索引值是否越界
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 获取索引所对应的值
E elementData(int index) {
return (E) elementData[index];
}
总结:因为 ArrayList 是采用数组结构来存储的,所以它的 get 方法非常简单,先是判断一 下有没有越界,之后就可以直接通过数组下标来获取元素了,所以 get 的时间复杂 度是 O(1)。
5.set(int index , E e) 方法
// 修改集合index对应的元素值
public E set(int index, E element) {
// 还是判断输入的index是否超出数组的索引值
rangeCheck(index);
// 获取到数组中旧的元素
E oldValue = elementData(index);
// 将新的值赋值给index对应的位置
elementData[index] = element;
// 返回旧的值
return oldValue;
}
总结:set 方法的作用是把下标为 index 的元素替换成 element,跟 get 非常类似,所以就不 在赘述了,时间复杂度度为 O(1)。
6.remove(int index) 方法
// 删除index所对应的集合中的个元素
public E remove(int index) {
// 查看输入的index索引是否在集合所包含的范围内
rangeCheck(index);
// 计算数组修改的次数,在多线程中可以规避一些问题
modCount++;
// 获取index对应的旧元素
E oldValue = elementData(index);
// index后面还有几个元素
int numMoved = size - index - 1;
if (numMoved > 0) // 判断index后面是否还有元素
// 改变原来的数组,将指定源数组中的数组从指定位置复制到目标数组的指定位置。
// 调用一个 native 的复制方法,把 index 位置开始的元素都往前挪一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//等待GC回收
elementData[--size] = null;
// 返回旧的值
return oldValue;
}
总结:remove 方法与 add 带指定下标的方法非常类似,也是调用系统的 arraycopy 方法来 移动元素,时间复杂度为 O(n)
7.size()方法
public int size() {
return size;
}
总结:实际上就是调用成员方属性中的size属性,所以时间复杂度为O(1),值得注意的是这里返回的是实际容量,而不是数组的大小。
8.indexOf(Object o) \ lastIndexOf(Object o)方法
// 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;
}
// 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;
}
总结:indexOf 方法的作用是返回第一个等于给定元素的值的下标。它是通过遍历比较数组 中每个元素的值来查找的,所以它的时间复杂度是 O(n)。 lastIndexOf 的原理跟 indexOf 一样,而它仅仅是从后往前找起罢了。
三、总结
1.ArrayList集合底层结构实际上就是Object[]的数组。
2.当实例化空参构造是,其默认将底层数组指向一个空的数组。
3.除了第一次添加扩容的容量为10外,其它的每次扩容都是旧数组长度的1.5倍
4.ArrayList集合中的方法,实际上就是对数组的增删改的操作。
5.当ArrayList集合扩容到整型的最大值时,旧无法再继续扩容。此时就会报出overflow内存溢出异常