ArrayList是我们平时开发中很常见的一种集合,那么它的底层是如何实现的呢?下面介绍一下ArrayList主要属性和方法。
源码分析
属性:
1. private static final long serialVersionUID = 8683452581122892189L;
// 序列化的标识符,用于在序列化过程中标识类的版本。当您对一个对象进行序列化时,Java运行时
// 会在序列化的字节流中保存这个序列号,以便在反序列化过程中验证类的版本是否一致
2. private static final int DEFAULT_CAPACITY = 10;
//默认的容量是10
3. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//空数组,通过new ArrayList(0)创建。
4. private static final Object[] EMPTY_ELEMENTDATA = {};
//空数组,通过new ArrayList()创建。
5. transient Object[] elementData;
//真正存储数据的地方
6. private int size;
//数组大小
注意
Object[] elementData
使用了transient
修饰的
在Java中,transient
关键字是一个修饰符,用于修饰类的成员变量。当一个成员变量被声明为transient
时,它意味着这个变量不应该被序列化。序列化是Java中一个将对象状态转换为字节流的过程,以便可以将其保存到文件中或通过网络传输。当一个对象被序列化时,它的所有非transient
字段都会被保存到字节流中,而transient
字段则会被忽略,保持其默认值。
在序列化 ArrayList
时,我们通常只关心实际存储在列表中的元素,而不是那些为了数组扩容而预留的、未被使用的空间。如果 elementData
没有被声明为 transient
,那么序列化机制会默认序列化整个数组,包括那些未被使用的空间,这将浪费空间并且降低序列化性能。通过将 elementData
声明为 transient
,ArrayList
可以确保只有实际存储的元素被序列化。ArrayList
类提供了自定义的序列化方法 writeObject
和反序列化方法 readObject
,这些方法会控制哪些部分被序列化以及如何反序列化。在 writeObject 方法中,ArrayList 只序列化了实际元素的数量和这些元素值,而不是整个 elementData
数组。在 readObject
方法中,ArrayList
根据序列化的元素数量和元素值重新构造 elementData
数组,这样就避免了序列化多余的空间。
方法:
构造方法:
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
//大于0则直接创建数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
//创建一个空数组
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//小于0则抛出异常
}
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
//创建一个空数组
}
//根据集合创建数组
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
add(E e)方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1); //检查是否需要扩容
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);
//如果使用new ArrayList()则后面需要创建一个容量为10数组
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// 扩容1.5倍
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);
//拷贝数组
}
add(int index, E element)方法
public void add(int index, E element) {
//检查下标是否异常
rangeCheckForAdd(index);
// 检查是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//将index~size位置的元素全部后移
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//添加元素
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
可以看到这二个方法的逻辑还是比较简单的。
那么上面的
modcount
变量有什么作用呢?
modCount
是 java.util.ArrayList
类的一个保护字段,它用来记录 ArrayList
对象结构性变化的次数。所谓的结构性变化是指那些改变了 ArrayList
大小(例如添加或删除元素)或者以其他方式改变其内部结构(例如 clear
操作)的操作。
这个字段的主要用途是作为迭代过程中的一个安全检查机制。当使用迭代器(Iterator
)遍历 ArrayList
时,如果在迭代过程中 ArrayList
被结构性修改了,迭代器会检测到 modCount
的变化,并立即抛出 ConcurrentModificationException
异常,以防止发生不可预料的行为。
get(int index)方法
public E get(int index) {
// 检查是否越界
rangeCheck(index);
// 返回数组index位置的元素
return elementData(index);
}
private void rangeCheck(int index) {
//数组下标从0开始,所以index >= size就算越界
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E elementData(int index) {
return (E) elementData[index];
}
ArrayLIst遍历方法
1.for循环遍历
ArrayList<String> list = new ArrayList<>();
// 填充列表
for (int i = 0; i < list.size(); i++) {
String element = list.get(i);
// 处理元素
}
2. 使用增强型 for 循环(Java 5及以上)
ArrayList<String> list = new ArrayList<>();
// 填充列表
for (String element : list) {
// 处理元素
}
3. 使用 Iterator 接口
ArrayList<String> list = new ArrayList<>();
// 填充列表
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
// 这个next()方法返回当前指向的元素,并移到下一个元素的位置
}
4. 使用 ListIterator 接口(允许在遍历过程中修改列表)
ArrayList<String> list = new ArrayList<>();
// 填充列表
ListIterator<String> listIterator = list.listIterator();
while (listIterator.hasNext()) {
String element = listIterator.next();
// 处理元素
}
5. 使用 forEach 方法和 Lambda 表达式(Java 8及以上):
ArrayList<String> list = new ArrayList<>();
// 填充列表
list.forEach(element -> {
// 处理元素
});
其实遍历ArrayList
还有很多方法,这里就不一一列举了。