ArrayList源码分析

tech2023-11-09  89

ArrayList源码分析

在Arrays源码分析中,我们提到ArrayList是Array的内部类,并不是其子类。所以有的初始化方法是不对的,具体可以看Arrays.md。

线程不安全

1. ArrayList

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> public abstract class AbstractCollection<E> implements Collection<E>

Arraylist继承了AbstractList 实现了List等接口最终可以追述到Collection和iterator接口

1.1 私有属性

private int size; private static final int DEFAULT_CAPACITY = 10; private static final Object[] EMPTY_ELEMENTDATA = {}; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; transient Object[] elementData; DEFAULT_CAPACITY是该类默认的容器大小size这里并不表示arraylist的实际大小,而是表示其当前所包含的元素个数。the number of elements it contains***EMPTY_ELEMENTDATA、DEFAULTCAPACITY_EMPTY_ELEMENTDATA***是两个为空的object数组。elementData 是空的Object数组,作为Arraylist的缓冲区,缓冲区的容量和ArrayList的容量相同。注意 transient关键字

1.2 构造方法

1.2.1 无参构造
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
1.2.2 有参构造
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); } } 通过上述构造方法可以看出,当不指定初始化大小的时候,返回的是***DEFAULTCAPACITY_EMPTY_ELEMENTDATA***当指定初始化为0的时候,返回的是***EMPTY_ELEMENTDATA***如果初始化大小大于0,则初始化Object数组,并赋值给elementData
1.2.3 特定元素的构造器
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; } }

1.3方法

1.3.1 trimToSize()方法
public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } }

实现了当默认大小大于实际元素数时,截断。

1.3.2 ensureCapacity()方法

public void ensureCapacity(int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // any size if not default element table ? 0 // larger than default for default empty table. It's already // supposed to be at default size. : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } }

公共可用的方法用于用户扩容,可以指定一个容量,该方法通过调用ensureExplicitCapacity

方法进行扩容。

1.3.3 内部使用的方法
private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
1.3.4 ArrayList内部的内存分配
private void grow(int minCapacity) { // overflow-conscious code 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); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }

可以看到,当用户传入需要增加的容量大小时,grow方法将被调用。

记录最初的容量大小 int oldCapacity = elementData.length;

扩容至1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1);

如果扩容后的大小仍然小于需要分配的大小,则直接指定为用户需要的大小

if (newCapacity - minCapacity < 0) newCapacity = minCapacity;

如果大于最大容量,则直接指定最大容量进行分配。

1.3.5 public的常用方法
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) { 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; } //此方法从后往前查找 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; }
1.3.6 toArray()方法
public Object[] toArray() { return Arrays.copyOf(elementData, size); }
1.3.7 add() 方法
public boolean add(E e) { //末尾添加 //容量加一 ensureCapacityInternal(size + 1); // Increments modCount!! //赋值 指针后移 elementData[size++] = e; return true; } public void add(int index, E element) { //指定index添加元素 //范围检查 rangeCheckForAdd(index); //扩容 ensureCapacityInternal(size + 1); // Increments modCount!! // arraycopy方法 指定复制数组 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
1.3.8 remove方法
public E remove(int index) { // 指定inde 删除元素 // 做范围检查 rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) //做删除操作 System.arraycopy(elementData, index+1, elementData, index, numMoved); // 末尾元素为空 等待gc回收 elementData[--size] = null; // clear to let GC do its work return oldValue; } public boolean remove(Object o) { // 当直接remove元素的时候 是不需要做范围检查的 只需要查找 然后删除 if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } //清除所有 public void clear() { modCount++; // 将所有元素赋值为null // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }

2.ArrayList总结

2.1 ArrayList内存的分配策略

其实涉及到ArrayList的内存空间分配仅包括如下函数:

构造器 ArrayList() 无参构造ArrayList(int initialCapacity) 有参构造ArrayList(Collection<? extends E> c) 从已有的collection中构建ArrayList 显示扩容方法 ensureCapacity ensureExplicitCapacitygrow

无参的构造方法执行之后,默认返回

public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

所以elementData返回的是空Object数组。

那么此时容器并没有分配大小,但是当向容器中add元素的时候,容器的内存将会发生改变,可以看下列代码:

public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(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); 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之后引发的所有流程。可以看到内存的分配方法都是private修饰的,那么说明分配不允许用户干预,都是在类的内部操作。调用无参构造之后,size仍然是0,那么调用**ensureCapacityInternal(size + 1),那么在此方法内部,会对容器是否为空进行判断,然后调用ensureExplicitCapacity方法,修改modcount标志,最后调用grow()方法完成分配操作。

有参构造,实际上跟无参构造是一致的,只不过有参构造的时候,显示的指定了当前容器的容量。其余步骤都是一致的。size无论是有参还是无参,他都表示容器中元素的个数,而不是容器的容量。

第三种构造器,也很简单,只是在给定collection的基础上,去创建ArrayList。

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; } }

有三个点:

toArray()方法

collection的抽象方法,比如Arraylist中的实现如下:

public Object[] toArray() { return Arrays.copyOf(elementData, size); }

Arrays.copyOf()方法

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { @SuppressWarnings("unchecked") T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }

上述方法的思想很简单,首先根据反射获得传入进来的数组类型,从而新建一个与原数组大小相同、类型相同的数组,然后利用System.arraycopy复制来赋值。

2.2 Fast-Fail机制

Fast-fail机制是java中的错误检测机制,当多个或者单个线程在结构上对集合进行改变的时候,就会抛出==ConcurrentModificationException==异常。这就是fast-fail机制。

分析fast-fail机制:

public Iterator<E> iterator() { return new Itr(); } private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); .... } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }

这是涉及到的Iterator的代码,其属于ArrayList内部的方法,不允许外部访问。

ArrayList对象可以调用此方法,完成遍历。示例代码:

ArrayList<Integer> list = new ArrayList<>(); for(int i =0;i<20;i++) { list.add(i); } Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()) { System.out.println(iterator.next()); }

当ArrayList对象调用iterator方法时,则会返回new Itr(),然后继续向下调用。

现在可以分析一下,我们可以看到在Ite这个类中next()和remove方法在执行时都会先调用***checkForComodification*** 方法。而此方法中,则会检查modCount和expectedModCount是否相等,如果不等,则会返回***ConcurrentModificationException*** 异常。这边是fast-fail的检测机制。

下面我们讨论一下modCount的意义。

在上述测试代码中,我们可以看到,当ArrayList对象调用iterator()方法的时候,将会在***class Itr*** 内部对属性***expectedModCount***赋值,即 ***int expectedModCount = modCount;***,那么此时二者的值是相等的。我们说只要二者相等,那么就不会引发fast-fail机制。

引发Fast-Fail机制:

package com.wangye.Test; import java.util.*; public class MyTest { final static int tmp = 10; public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); for(int i =0;i<20;i++) { list.add(i); } Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()) { list.remove(0); //新加的代码 System.out.println(iterator.next()); } } } Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851) at com.wangye.Test.MyTest.main(MyTest.java:20)

当使用ArrayList对象自带的remove方法时,出现异常,引发Fast-Fail机制。

原因:

ArrayList中add、remove、clear方法对容器结构做出了改变,所以当这些方法被调用时,一定会改变modCount,其他操作则不会。但是ArrayList中modCount改变后,这时expectedModCount 仍然等于初始的modCount值。所以引发Fast-Fail。

什么时候不会引发Fast-fail:

当且仅当使用Iterator的remove方法时,不会引发Fast-Fail,那是因为,Iterator在执行remove过程中,重新赋值 expectedModCount = modCount

2.3 CopyOf函数的理解 注意反射机制

2.4 三种构造函数 尤其是最后一种

2.3 ArrayList的增删改查如何实现(算法角度)

2.4 System.arraycopy()函数

2.5 ArrayList和Vector的区别


2020年8月24日补充

什么是序列化?

在java中,将对象转换成字节序列的形式,进行持久化和传输操作。常用于网络传输。

实现方法:实现Serializable接口即可。

反序列化之后,字节序列可以被转换成java对象。

ArrayList中的关键字解析——transient

作用:让某些被修饰的变量不被序列化操作,一旦变量被关键字transient修饰,则当前变量将不参与序列化,也就是说反序列化之后将得到空值。

问题:ArrayList中的缓存数组elementData是用transient修饰的,那么序列化和反序列化操作之后,岂不是拿到了空值?

transient Object[] elementData;
解答:

原来在ArrayList中,序列化操作采用的并不是实现Serializable接口,而是在内部定义私有方法,如下:

private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone() s.writeInt(size); // Write out all elements in the proper order. for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } /** * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is, * deserialize it). */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff s.defaultReadObject(); // Read in capacity s.readInt(); // ignored if (size > 0) { // be like clone(), allocate array based upon size not capacity ensureCapacityInternal(size); Object[] a = elementData; // Read in all elements in the proper order. for (int i=0; i<size; i++) { a[i] = s.readObject(); } } }

也就是说,ArrayList在序列化的时候,调用的是writeObject方法,将size和elementData写入了ObjectOutputStream,反序列化只是调用readObject方法,从ObjectInputStream中读取size和element,从而恢复elementData。

我的思考:为什么不直接对elementData缓存数组做序列化?

我的答案:

我认为,elementData是缓存数组,那么数组的长度在很多时候是大于实际的存储元素数的,如果直接序列化,可能造成空间上的浪费。

) { // be like clone(), allocate array based upon size not capacity ensureCapacityInternal(size);

Object[] a = elementData; // Read in all elements in the proper order. for (int i=0; i<size; i++) { a[i] = s.readObject(); } } } 也就是说,ArrayList在序列化的时候,调用的是writeObject方法,将size和elementData写入了ObjectOutputStream,反序列化只是调用readObject方法,从ObjectInputStream中读取size和element,从而恢复elementData。 #### 我的思考:为什么不直接对elementData缓存数组做序列化? #### 我的答案: 我认为,elementData是缓存数组,那么数组的长度在很多时候是大于实际的存储元素数的,如果直接序列化,可能造成空间上的浪费。
最新回复(0)