1、背景
我们咨询一下阿里通义大模型,什么是“LinkedList”。
以下是通义大模型的回答:
ArrayList 是 Java 编程语言中的一种动态数组实现,它是 java.util 包中的一个类。
ArrayList 实现了 List 接口,能够存储元素的有序集合,并且允许插入重复项。
与传统的固定大小的数组不同,ArrayList 的大小可以根据需要动态增长或缩小。当添加新的元素时,如果当前容量不足以容纳新元素,
ArrayList 会自动调整其内部存储空间的大小以适应更多的元素。
以下是一些 ArrayList 的基本特点和方法:
动态大小:
ArrayList
的大小不是固定的,可以随着元素的添加和移除而改变。索引访问:可以通过索引来访问或修改列表中的任何元素。
插入和删除操作:可以在列表的任意位置插入或删除元素。
常用方法:
add(E e)
:在列表末尾添加一个元素。remove(Object o)
:从列表中移除第一次出现的指定元素。get(int index)
:返回指定索引处的元素。set(int index, E element)
:将指定位置的元素替换成另一个元素。size()
:返回列表中的元素数量。isEmpty()
:检查列表是否为空。clear()
:清空列表中的所有元素。contains(Object o)
:检查列表是否包含指定元素。indexOf(Object o)
:返回列表中指定元素首次出现的位置。lastIndexOf(Object o)
:返回列表中指定元素最后一次出现的位置。
2、源码分析
类定义
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{}
代码分析:
ArrayList<E>
: 这是类名,其中<E>
表示这是一个泛型类,E
代表元素类型。这意味着ArrayList
可以用来存储任何类型的对象,具体类型由使用者指定。AbstractList
提供了一些通用的实现细节,比如基本的迭代器实现和其他辅助方法,这些方法对于所有的List
实现都是有用的。RandomAccess
: 这是一个标记接口,用于指示该列表支持快速随机访问。这使得某些算法(如排序)可以采用更高效的实现方式。
基本属性
// 默认容量大小
private static final int DEFAULT_CAPACITY = 10;
// 空长度数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 区别于EMPTY_ELEMENTDATA,两者使用场景不同
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// buffer数组
transient Object[] elementData;
// buffer数组的数据长度
private int size;
//
protected transient int modCount = 0;
// 指定容量大小
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);
}
}
// 没指定容量大小
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
代码分析:
DEFAULT_CAPACITY:默认容器长度为10
EMPTY_ELEMENTDATA
:这是一个空的数组,用于表示一个空的ArrayList
。当ArrayList
初始容量为 0 时,会使用这个数组。当ArrayList
中的元素被清空后,也会将内部数组设置为EMPTY_ELEMENTDATA
。这样做的好处是避免了创建新的空数组对象,从而节省内存。DEFAULTCAPACITY_EMPTY_ELEMENTDATA
:这是一个空的数组与
EMPTY_ELEMENTDATA
的区别在于,当ArrayList
使用这个数组时,它的初始容量是默认值(即10
)。当
ArrayList
刚创建时,如果指定了初始容量为 0,那么会使用EMPTY_ELEMENTDATA
;如果没有指定初始容量或者指定了一个大于 0 的值,那么会使用
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
。这样做的好处是避免了在创建
ArrayList
时立即分配内存,从而提高了性能。两者都是为了提高
ArrayList
的性能而设计的。它们在不同的场景下被使用,以确保内存使用的高效性。
size:数组真实长度,充当数组索引下标,快速获取数据
modCount:线程不安全,提供快速失败机制
基本操作:增删改查
(1)增加元素
解析add(E)方法源码
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
private void add(E e, Object[] elementData, int s) {
// 【1】
if (s == elementData.length)
// 【2】
elementData = grow();
// 【3】
elementData[s] = e;
// 【4】
size = s + 1;
}
private Object[] grow() {
// 【2.1】快速扩容
return grow(size + 1);
}
private Object[] grow(int minCapacity) {
// 【2.2】拷贝至新数组里
return elementData = 、
Arrays.copyOf(elementData, newCapacity(minCapacity));
}
private int newCapacity(int minCapacity) {
// 数据溢出大小
int oldCapacity = elementData.length;
// 【2.3】旧的容量,扩容1/2
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 【2.4】如果扩容后的结果,还是不够
if (newCapacity - minCapacity <= 0) {
// 【2.5】此前没有指定容量大小
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// 【2.5】返回动态最大容量
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0)
// 【2.6】内存溢出
throw new OutOfMemoryError();
// 【2.7】返回最小容量
return minCapacity;
}
// 【2.8】正常情况(正常返回最大容量大小)
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
分析:
【1】如果插入下表,等于数据长度,说明容器数组已经满了,要扩容grow
【2.5】如果
elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,
说明ArrayList并没有被初始化过,那么则返回需要初始化的数据长度最大值(DEFAULT_CAPACITY, minCapacity)
【2.6】minCapacity小于0,说明内存溢出了
【2.7】极端情况,返回minCapacity
【2.1】扩容最小容量为size+1,传入grow方法
【2.3】计算newCapacity:扩容的增量大小为一半
【2.4】扩容后还是比 最小容量 要小或者刚刚好
【2.8】如果newCapacity> 最小容量,则说明扩容有效,扩容后的长度,最大不超过MAX_ARRAY_SIZE
(2)删除元素
解析remove(int)方法源码
public E remove(int index) {
// 【1】
Objects.checkIndex(index, size);
final Object[] es = elementData;
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
// 【2】
fastRemove(es, index);
return oldValue;
}
private void fastRemove(Object[] es, int i) {
//【3】
modCount++;
final int newSize;
// 【4】
if ((newSize = size - 1) > i)
//【5】
System.arraycopy(es, i + 1, es, i, newSize - i);
// 【6】
es[size = newSize] = null;
}
代码分析:
【1】检查下标是否超限
【2】快速移除
【5】如果不是(i数值,比真实长度数值,小),直接把i+1后的(newSize - i)个元素,从i下标开始拷贝一份
【6】如果是,则直接把尾部元素,设置为null
【3】操作总数+1
【4】newSize赋值;比较:i和newSize大小 -> 判断是否是移除尾部元素
(3)修改元素
解析set(int index, E element)
public E set(int index, E element) {
Objects.checkIndex(index, size);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
E elementData(int index) {
return (E) elementData[index];
}
源码分析:
设置index下标的数据,时间复杂度为O(1)
返回老数据
(4)获取元素
解析get(int)
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
源码分析:
直接通过数组下表返回,时间复杂度为O(1)
3、总结
使用 ArrayList
可以非常方便地处理数据集合,尤其是在需要频繁添加或删除元素的情况下。由于它的灵活性和便利性,ArrayList
在实际开发中被广泛使用。
我们可以对比跟之前的数据结构LinkedList 链表,得出两者差异。