目录
基本概念
底层实现原理
底层数据结构
容量初始化机制
自动扩容机制
增删改查操作原理
内存管理细节
常用方法
遍历方式
普通for循环
增强for循环
Iterator 迭代器
Java 8 Lambda
ArrayList与线程安全
ArrayList与数组
常见误区与注意点
Java 8 Stream 结合使
ArrayList
是 Java 集合框架中常用的 可变长度数组。
位于 java.util
包下。
实现了 List
接口,支持元素的 有序存储、重复元素。
特点:
动态数组结构(相比数组可自动扩容)
查询速度快(随机访问性能优)
插入、删除效率相对较低(涉及元素移动)
ArrayList的底层是动态数组Object[] elementData来进行存储元素
初始时并不立即分配空间(JDK 1.8 起,懒加载)。
数组中元素是对象引用,对象本身存储在堆上。
在ArrayList的源码当中有这么几个成员变量
// 默认的初始容量
private static final int DEFAULT_CAPACITY = 10;
//空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//用于默认尺寸的空尺寸的空数字实例。我们将其与empty_elementdata区分开,以知道添加第一个元素时会充气多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储元素的数组对象
transient Object[] elementData; // non-private to simplify nested class access
//大小
private int size;
无参构造函数:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
注意:这里无参构造函数把数组初始化成了成员变量中的空数组对象DEFAULTCAPACITY_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);
}
}
这里是初始化一个有大小的数组对象并且赋值给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;
}
}
这个构造函数是构建包含指定集合的元素列表,接受一个泛型的Collection对象
从源码中我们可以看到ArrayList中初始化了一个大小为10的成员变量
然后根据add方法中的源码:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
只有在第一次add元素的时候才会初始化大小为10
也可以在创建ArrayList的时候直接指定大小
//ArrayList中的add方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//确保内部容量
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//计算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//确保内部容量
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
}
//扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; //原数组的大小
int newCapacity = oldCapacity + (oldCapacity >> 1); //新数组大小为原来的1.5倍
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);
}
详细解释以上代码:在add方法中首先调用ensureCapacityInternal方法,在方法中调用ensureExplicitCapacity是否需要扩容,calculateCapacity给数组默认大小为10,ensureExplicitCapacity将数组大小与10比对,如果数组大小 > 10,则需要扩容。然后将需要添加的元素追加到列表末尾。
//查询元素
E elementData(int index) {
return (E) elementData[index];
}
//移除元素
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;
}
//修改元素
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
根据以上源码,我们可以看见查询的时间复杂度为O(1),修改也是O(1),移除元素的话需要移动后面的元素,时间复杂度为O(n)
我们知道ArrayList中的底层是动态数组,也是对象的一种,所以数组的指向是JAVA堆,所以我们将数组删除后需要将当前内存指向设置为NULL,有利于GC,不然容易导致内存泄漏。
方法 | 说明 |
---|---|
add(E e) |
添加元素到末尾 |
add(int index, E e) |
指定位置插入元素 |
remove(int index) |
删除指定索引元素 |
remove(Object o) |
删除指定对象(首次出现) |
get(int index) |
获取元素 |
set(int index, E e) |
替换指定位置的元素 |
clear() |
清空列表 |
contains(Object o) |
是否包含元素 |
size() |
返回当前元素数量 |
isEmpty() |
判断是否为空 |
indexOf(Object o) |
查找元素位置 |
toArray() |
转换为数组 |
List list = new ArrayList<>();
// 添加元素
list.add("Hello");
list.add("World");
list.add("Java");
//普通遍历
for (int i = 0; i < list.size(); i++) {
System.out.println("普通遍历元素 " + i + ": " + list.get(i));
}
//增强for循环
for (String item : list) {
System.out.println("增强for循环元素: " + item);
}
//iterator遍历
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println("Iterator元素: " + iterator.next());
}
//lambda表达式遍历
list.forEach(item -> System.out.println("Lambda元素: " + item));
ArrayList
本身 不是线程安全的。
多线程同时读写时需加锁。
线程安全的替代方式:
以下代码执行情况按理来说数组长度应该是0
package com.hyh;
import java.util.ArrayList;
import java.util.List;
public class ArrayListThreadSafetyDemo {
public static void main(String[] args) throws InterruptedException {
List list = new ArrayList<>();
// 创建线程1:不断添加元素
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
list.add(i);
}
});
// 创建线程2:不断读取元素
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
if (list.size() > 0) {
list.remove(0); // 可能触发数组越界异常
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终列表大小: " + list.size());
}
}
实际情况:
特性 | ArrayList | 数组 |
---|---|---|
大小 | 动态扩容 | 固定大小 |
类型 | 泛型对象 | 基本类型或对象 |
功能 | 支持增删查改 | 仅支持索引访问 |
安全性 | 支持越界检查 | 编译期固定大小 |
使用便捷性 | 方法丰富 | 操作较原始 |
List与数组的转换
//List转为数组
String[] array = list.toArray(new String[list.size()]);
//数组转为List
List list1 = Arrays.asList(array);
并发修改问题:
for (String item : list) {
list.remove(item); // 会抛 ConcurrentModificationException
}
正确做法:
Iterator it = list.iterator();
while (it.hasNext()) {
it.next();
it.remove(); // 合法
}
扩容频繁性能低:
建议使用构造函数设置初始容量:
new ArrayList<>(1000);
数组转列表陷阱:
Arrays.asList()
返回的是固定长度列表,不能进行 add/remove
操作。
基本类型不能直接使用:
必须使用包装类如 Integer
,不能用 int
。
List hello = list.stream()
.filter(s -> s.startsWith("H")) // 过滤出 "Hello"
.map(String::toUpperCase) // 转为大写
.collect(Collectors.toList());//将满足条件的收集成结合
hello.forEach(s -> System.out.println("转换后的元素: " + s));