注意:本文基于JDK1.8进行记录。
1 简介最常用的集合,允许任何符合规则的元素插入,包括null和重复元素。
底层是数组结构,提供了索引机制,查找效率高,增删效率低。
线程不安全。
2 扩容机制数组结构会有容量的概念,ArrayList的初始容量默认为10,负载因子是1,表示当插入元素后个数超出原有长度时会进行扩增,扩容增量是0.5,所以扩增后容量为1.5倍,可使用方法手动扩容和缩减。
最好指定初始容量值,避免过多的进行扩容操作而浪费时间和效率。
3 方法说明 3.1 构造方法// 空参构造器,返回默认容量为10的集合。 public ArrayList(); // 指定长度的构造器,如果长度为0,则返回容量为0的集合。 public ArrayList(int initialCapacity); // 传入了一个集合的构造器,如果集合长度为0,返回容量为0的集合。 public ArrayList(Collection extends E> c);3.2 常用方法
// 获取个数。 public int size(); // 判断是否为空。 public boolean isEmpty(); // 判断是否包含指定数据。 public boolean contains(Object o); // 计算指定数据首次出现的下标。 public int indexOf(Object o); // 计算指定数据最后出现的下标。 public int lastIndexOf(Object o); // 获取指定下标的元素。 public E get(int index); // 设置指定下标的指定元素,并返回旧元素。 public E set(int index, E element); // 添加元素,并返回是否成功。 public boolean add(E e); // 在指定位置添加指定元素。 public void add(int index, E element); // 删除指定位置的元素,并返回删除的元素。 public E remove(int index); // 删除元素,并返回是否成功。 public boolean remove(Object o);4 源码分析 4.1 属性
// 默认的初始容量为10。
private static final int DEFAULT_CAPACITY = 10;
// 空数组。
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认容量的空数组。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 数组。
transient Object[] elementData;
// 元素个数。
private int size;
4.2 构造方法
// 空参构造器,返回默认容量为10的集合。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 指定长度的构造器,如果长度为0,则返回容量为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);
}
}
// 传入了一个集合的构造器,如果集合长度为0,返回容量为0的集合。
public ArrayList(Collection extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// Object[]数组里的类型不一定都是Object类型的,有可能是Object的子类,所以要判断一下。
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
4.3 常用方法
// 获取个数。
public int size() {
return size;
}
// 判断是否为空。
public boolean isEmpty() {
return size == 0;
}
// 判断是否包含指定数据。
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
// 计算指定数据首次出现的下标。
public int indexOf(Object o) {
// 如果指定数据为null。
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
// 如果指定数据不为null。
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
// 计算指定数据最后出现的下标。
public int lastIndexOf(Object o) {
// 如果指定数据为null。
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
// 如果指定数据不为null。
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
// 获取指定下标的元素。
public E get(int index) {
// 检查指定下标是否越界。
rangeCheck(index);
// 返回指定下标的元素。
return elementData(index);
}
// 设置指定下标的指定元素,并返回旧元素。
public E set(int index, E element) {
// 检查指定下标是否越界。
rangeCheck(index);
// 设置指定元素并返回旧元素。
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
// 添加元素,并返回是否成功。
public boolean add(E e) {
// 扩容并增加操作数。
ensureCapacityInternal(size + 1);
// 元素个数增加并设置指定元素。
elementData[size++] = e;
// 返回成功。
return true;
}
// 在指定位置添加指定元素。
public void add(int index, E element) {
// 检查指定下标是否越界。
rangeCheckForAdd(index);
// 扩容并增加操作数。
ensureCapacityInternal(size + 1);
// 拷贝数组并设置元素,增加元素个数。
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
// 删除指定位置的元素,并返回删除的元素。
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);
// 将最后一个元素设置为null并减少容量大小。
elementData[--size] = null;
// 返回指定下标的元素。
return oldValue;
}
// 删除元素,并返回是否成功。
public boolean remove(Object o) {
// 删除null对象
if (o == null) {
for (int index = 0; index < size; index++)
// 找到并删除null对象
if (elementData[index] == null) {
fastRemove(index);
return true;
}
// 删除非null对象
} else {
for (int index = 0; index < size; index++)
// 找到并删除非null对象
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
// 快速删除指定位置元素,不检查下标,不返回数据。
private void fastRemove(int index) {
// 操作数自增。
modCount++;
// 需要移动的元素个数。
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
// 将最后一个元素设置为null并减少容量大小。
elementData[--size] = null;
}
4.4 扩容方法
// 对集合进行扩容。
public void ensureCapacity(int minCapacity) {
// 如果集合不为空,则设置扩充值为0,如果为空,则设置扩充值为10。
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;
// 如果指定容量大于扩充值,则进行扩容。
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
// 对集合进行扩容。
private void ensureCapacityInternal(int minCapacity) {
// 如果集合为空,则在默认大小和最小值之间取最大的。
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 进行扩容。
ensureExplicitCapacity(minCapacity);
}
// 对集合进行扩容,实际操作。
private void ensureExplicitCapacity(int minCapacity) {
// 操作数自增。
modCount++;
// 如果最小值大于数组长度,则进行扩容。
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 数组作为一个对象,需要一定的内存存储对象头信息,对象头信息最大占用内存不可超过8字节。
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 扩容计算。
private void grow(int minCapacity) {
// 获取原容量大小。
int oldCapacity = elementData.length;
// 右移一位,变为原来的0.5倍,然后加上原容量,扩容后变为1.5倍。
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果扩容后小于最小值,则设置容量为最小值。
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果扩容后大于最大值,则进一步设置容量。
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 复制数组。
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 扩容后容量过大的处理方法。
private static int hugeCapacity(int minCapacity) {
// 如果最小值小于零,则表示溢出,抛出内存溢出异常。
if (minCapacity < 0)
throw new OutOfMemoryError();
// 如果扩容后的值大于最大值,则使用Integer的最大值,否则就使用最大值。
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
4.5 序列化方法
// 序列化
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
int expectedModCount = modCount;
s.defaultWriteObject();
s.writeInt(size);
for (int i=0; i 0) {
ensureCapacityInternal(size);
Object[] a = elementData;
for (int i=0; i
4.6 遍历方法
ArrayList提供了两种迭代器,实现了Iterator接口的Itr类,以及实现了ListIterator接口并继承了Itr类的ListItr类。
ListItr类对Itr类的方法进行了扩展,提供了添加和修改的方法,这里只学习Itr类。
// 获取Iterator迭代器。
public Iterator iterator() {
return new Itr();
}
// Iterator内部类,实现了Iterator接口。
private class Itr implements Iterator {
// 下一个元素的位置。
int cursor;
// 最后一个元素的位置。
int lastRet = -1;
// 预期的操作数。
int expectedModCount = modCount;
// 是否存在下一个元素。
public boolean hasNext() {
return cursor != size;
}
// 获取下一个元素。
public E next() {
// 校验预期操作数和实际操作数。
checkForComodification();
// 将下一个元素的位置赋值给i。
int i = cursor;
// 如果i大于或等于集合大小,说明没有元素了,抛出异常。
if (i >= size)
throw new NoSuchElementException();
// 获取集合数组。
Object[] elementData = ArrayList.this.elementData;
// 如果i大于或等于数组长度,说明有其他线程改过了,抛出异常。
if (i >= elementData.length)
throw new ConcurrentModificationException();
// 下一个元素位置加一。
cursor = i + 1;
// 获取元素并返回,设置最后一个元素位置。
return (E) elementData[lastRet = i];
}
// 删除当前元素。
public void remove() {
// 如果当前位置小于零,抛出异常。
if (lastRet < 0)
throw new IllegalStateException();
// 校验预期操作数和实际操作数。
checkForComodification();
try {
// 移除当前位置上的元素。
ArrayList.this.remove(lastRet);
// 将当前位置赋值给下一个元素位置。
cursor = lastRet;
// 将当前位置设置为-1,表示没有此位置。
lastRet = -1;
// 同步预期操作数和操作数。
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
// 循环遍历一次,JDK1.8新增方法。
@Override
public void forEachRemaining(Consumer super E> consumer) {
// 判断非空。
Objects.requireNonNull(consumer);
// 集合容量大小。
final int size = ArrayList.this.size;
// 将下一个元素的位置赋值给i。
int i = cursor;
// 如果i大于或等于集合个数,则返回。
if (i >= size) {
return;
}
// 获取集合数组。
final Object[] elementData = ArrayList.this.elementData;
// 如果i大于或等于数组长度,说明有其他线程改过了,抛出异常。
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
// 循环遍历,当下一个元素不为集合的大小,并且预期操作数和实际操作数相等。
while (i != size && modCount == expectedModCount) {
// 执行方法,并且每次循环i加一。
consumer.accept((E) elementData[i++]);
}
// 将循环后的i赋值,导致下个元素的位置和集合长度相等,该迭代器不能再次遍历集合了。
cursor = i;
// 设置最后一个元素位置。
lastRet = i - 1;
// 校验预期操作数和实际操作数。
checkForComodification();
}
// 校验预期操作数和实际操作数。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
5 补充说明
5.1 数组没有private
查看ArrayList的源码,发现内部定义的elementData数组并没有像size一样被private修饰,表示访问权限是默认的包权限,这么做的原因被写在了声明后面的注释里,即非私有的访问权限是为了简化嵌套类的访问。
如果想知道如何简化嵌套类的访问,就需要写一个例子并反编译进一步查看。
代码如下:
public class DemoTest {
private int i = 1;
public Inner inner = new Inner();
class Inner {
public void in() {
i = 5;
}
}
public static void main(String[] args) {
DemoTest demoTest = new DemoTest();
demoTest.inner.in();
System.out.println(demoTest.i);
}
}
反编译private修饰的代码:
// 在外部类的汇编代码中,多了静态方法:
static int access$002(com.demo.mp.test.DemoTest, int);
Code:
0: aload_0
1: iload_1
2: dup_x1
3: putfield #1 // Field i:I
6: ireturn
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 x0 Lcom/demo/mp/test/DemoTest;
0 7 1 x1 I
// 内部类的汇编代码中,在方法中引用外部类变量的时候,是通过静态方法设置的:
public void in();
Code:
0: aload_0
1: getfield #1 // Field this$0:Lcom/demo/mp/test/DemoTest;
4: iconst_5
// 通过静态方法设置。
5: invokestatic #3 // Method com/demo/mp/test/DemoTest.access$002:(Lcom/demo/mp/test/DemoTest;I)I
8: pop
9: return
LineNumberTable:
line 8: 0
line 9: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/demo/mp/test/DemoTest$Inner;
反编译没有private修饰的代码:
// 内部类的汇编代码中,在方法中引用外部类变量的时候,是通过属性直接设置的:
public void in();
Code:
0: aload_0
1: getfield #1 // Field this$0:Lcom/demo/mp/test/DemoTest;
4: iconst_5
// 通过属性直接设置。
5: putfield #3 // Field com/demo/mp/test/DemoTest.i:I
8: return
LineNumberTable:
line 8: 0
line 9: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/demo/mp/test/DemoTest$Inner;
因此,去掉private修饰简化嵌套类的访问。
5.2 数组使用transient
查看ArrayList的源码,发现内部定义的elementData数组使用了transient修饰,transient表示该数组不参与序列化。
那ArrayList是如何实现序列化的呢?
这就涉及到了序列化的基础原理了,之前在学习输入输出流的时候,有一种对象流,主要的类是ObjectOutputStream和ObjectInputStream。
ObjectOutputStream类是用于序列化的输出流,ObjectInputStream类是用于反序列化的输入流。
在序列化和反序列化的时候,JVM会调用对象里的writeObject和readObject方法,进行用户自定义的序列化和反序列化。如果没有提供,那就调用ObjectOutputStream类的defaultWriteObject方法和ObjectInputStream类的defaultReadObject方法,进行默认的序列化和反序列化,这时就会忽略transient修饰的成员。
因此,在ArrayList内部自定义了writeObject和readObject方法,自定义序列化和反序列化,这么做是为了处理实际存储的元素,避免处理整个数组。



