本文主要是介绍ArrayList源码解析(空间结构和特性、常用方法、遍历时删除元素注意事项等),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1、概念
ArrayList继承了AbstractList,实现了 List ,RandomAccess, Cloneable, Serializable接口,所以他有List的相关功能同时还有动态随机访问、复制和序列化等功能。他的底层是使用数组实现的,所以查询起来相对较快,而插入删除时相对较慢。由于ArrayList里面的方法没有使用synchronized修饰,所以不是线程安全的。
2、空间结构
对象序列化的UID,类序列化时会传入一个serialVersionUID。在反序列化时,JVM会把传来的字节流中的serialVersionUID于本地相应实体类的serialVersionUID进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即是InvalidCastException。
private static final long serialVersionUID = 8683452581122892189L;
默认的初始容量大小为10。
private static final int DEFAULT_CAPACITY = 10;
一个空的数组实例,如果传进来的size是空,则使用这个空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
默认大小为空的数组实例,如果没传进来初始化size就是用这个空数组。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
用来装ArrayList元素的数组,所以他底层是使用数组实现。
transient Object[] elementData
集合实际的元素个数,与容量不同。
private int size;
计数器,用于记录对List的修改次数,这里涉及到一个fail-fast快速失败机制,单线程时迭代器遍历集合中remove元素和多线程中一个线程遍历另一个线程remove元素时都会抛出ConcurrentModificationException。因为迭代器遍历时候,内部会将modCount赋给expectedModCount ,当集合结构改变时,modCount会被修改,迭代器每次next()和hahNext()方法都会检查expectedModCount与modCount是否相等,被改变时,抛出ConcurrentModificationException。所以在对集合遍历操作时,建议大家在迭代器中增删元素,不要直接操作集合的remove。引出fail-safe是快速安全机制,在多线程中使用(ConcurrentHashMap中)。任何对集合结构的修改都会在一个复制的集合上进行,因此不会抛出ConcurrentModificationException,但无法保证最终读取的数据是正确的元素,且需要复制集合,开销大。(这个modCount方法中都在用,翻了一会子类父类源码没发现,然后藏在601行)
protected transient int modCount = 0;
三个构造方法:
- 初始化一个指定容量的构造器,直接指定elementData数组的容量,如果容量<0,默认还是用空的实例。
- 初始化一个空的数组,在添加元素时候再对数组elementData扩容。
- 初始化一个集合参数的数组,把集合通过Arrays.copyOf拷到elementData中,容量和指定集合容量相同,如果容量<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;}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;}}
3、常用方法
扩容
大概增加元素时候先判断是否要扩容,然后再判断扩容的大小,默认是扩容1.5倍,但是如果新的长度扩容1.5倍也不能容纳或者扩容得太大,就扩容到需要的容量。
//其他方法先调用此方法看看是否需要扩容private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}//如果是初始化时没有指定大小,然后调用add方法,默认返回默认大小和当前size+1的最大值private static int calculateCapacity(Object[] elementData, int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return Math.max(DEFAULT_CAPACITY, minCapacity);}return minCapacity;}//如果添加后的实际容量大于当前容量则需要调用grow方法扩容private void ensureExplicitCapacity(int minCapacity) {modCount++;if (minCapacity - elementData.length > 0)grow(minCapacity);}private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//扩容1.5倍后的容量和传入的容量取大值进行扩容,如果新的容量特别大时给Interger最大值或者Integer.MAX_VALUE - 8(搞不懂为啥是-8这样设计)private void grow(int minCapacity) {int oldCapacity = elementData.length;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();return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;}//这个方法是专为为DenseIntMapImpl类的extend方法提供。public void ensureCapacity(int minCapacity) {int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)table? 0: DEFAULT_CAPACITY;if (minCapacity > minExpand) {ensureExplicitCapacity(minCapacity);}}
数组元素数量
public int size() {return size;}
数组元素数量是否为0
public boolean isEmpty() {return size == 0;}
添加元素
传入元素,先判断扩容,然后添加到数组中。
public boolean add(E e) {ensureCapacityInternal(size + 1); elementData[size++] = e;return true;}
传入index和元素,先判断index在数组中是否存在,然后判断扩容,然后将原数组复制一份,在中间index位置加入element。
public void add(int index, E element) {rangeCheckForAdd(index);ensureCapacityInternal(size + 1); //复制一个数组,把index后得一段整体后移一位。System.arraycopy(elementData, index, elementData, index + 1,size - index);elementData[index] = element;size++;}/**
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
代码解释:Object src : 原数组int srcPos : 从元数据的起始位置开始Object dest : 目标数组int destPos : 目标数组的开始起始位置int length : 要copy的数组的长度
*/
传入集合,先判断容量,然后复制一个数组,把这个集合整体加到原集合后。
public boolean addAll(Collection<? extends E> c) {Object[] a = c.toArray();int numNew = a.length;ensureCapacityInternal(size + numNew); // Increments modCountSystem.arraycopy(a, 0, elementData, size, numNew);size += numNew;return numNew != 0;}
获取元素
先判断index是否存在,然后返回数组对应下标,可以看出直接从数组随机访问,速度比较快。
public E get(int index) {rangeCheck(index);return elementData(index);}
获取元素索引
返回给定元素第一次出现的位置,没有则返回-1。
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;}
返回给定元素最后一次出现的位置,没有则返回-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;}
是否包含指定元素
使用indexOf判断是否包含元素
public boolean contains(Object o) {return indexOf(o) >= 0;}
删除元素
传入index,先判断index是否存在,然后判断是否是最后一个元素,如果不是就重新拷贝一个数组,将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 workreturn oldValue;}
传入对象,如果o为空就,就删除所有null的元素,否则删除下标为index的元素。
public boolean remove(Object o) {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;}//与remove方法类似private void fastRemove(int index) {modCount++;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}
传入索引的一段范围,删除这段范围内的所有元素。
protected void removeRange(int fromIndex, int toIndex) {modCount++;int numMoved = size - toIndex;System.arraycopy(elementData, toIndex, elementData, fromIndex,numMoved);// clear to let GC do its workint newSize = size - (toIndex-fromIndex);for (int i = newSize; i < size; i++) {elementData[i] = null;}size = newSize;}
删除指定元素removeAll和保留指定元素retainAll,都调用的batchRemove函数,通过传入true或者false执行不同的功能(JDK源码有点子巧妙~)。
//删除集合中所有在集合c中出现过的元素。public boolean removeAll(Collection<?> c) {Objects.requireNonNull(c);return batchRemove(c, false);}//删除集合中所有没有在c集合中出现过的元素。public boolean retainAll(Collection<?> c) {Objects.requireNonNull(c);return batchRemove(c, true);}private boolean batchRemove(Collection<?> c, boolean complement) {final Object[] elementData = this.elementData;int r = 0, w = 0;boolean modified = false;try {for (; r < size; r++)//判断是存入包含的元素还是不包含的元素if (c.contains(elementData[r]) == complement)elementData[w++] = elementData[r];} finally {//正常情况下r=size是成立的,这里稍微有点不明白,自己理解大概就是出异常的时候将未判断的直接赋值给数组,凑齐数组长度,让w==size,好让后续if (w != size) 不执行,返回false,if (r != size) {System.arraycopy(elementData, r,elementData, w,size - r);w += size - r;}//如果方法最终w=size,证明没有删除过元素或者出现异常执行了if (r != size),所以返回false并释放内存if (w != size) {// clear to let GC do its workfor (int i = w; i < size; i++)elementData[i] = null;modCount += size - w;size = w;modified = true;}}return modified;}
复制集合
调用Arrays.copyOf方法进行元素复制。
public Object clone() {try {ArrayList<?> v = (ArrayList<?>) super.clone();v.elementData = Arrays.copyOf(elementData, size);v.modCount = 0;return v;} catch (CloneNotSupportedException e) {// this shouldn't happen, since we are Cloneablethrow new InternalError(e);}}
将集合转化为数组
public Object[] toArray() {return Arrays.copyOf(elementData, size);}
清空集合
把所有值赋为null让gc回收,并把size变为0。
public void clear() {modCount++;// clear to let GC do its workfor (int i = 0; i < size; i++)elementData[i] = null;size = 0;}
序列化和反序列化
通过writeObject方法和readObject方法来遍历elementData数组把数组中的元素写入ObjectOutputStream ,ObjectInputStream 中的。因为ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient。
private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException{int expectedModCount = modCount;s.defaultWriteObject();s.writeInt(size);for (int i=0; i<size; i++) {s.writeObject(elementData[i]);}if (modCount != expectedModCount) {throw new ConcurrentModificationException();}}private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {elementData = EMPTY_ELEMENTDATA;s.defaultReadObject();s.readInt(); if (size > 0) {int capacity = calculateCapacity(elementData, size);SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);ensureCapacityInternal(size);Object[] a = elementData;for (int i=0; i<size; i++) {a[i] = s.readObject();}}}
迭代器
ArrayList的listIterator和iterator实际上是返回ListItr和Itr对象,他是ArrayList通过内部类的方式来实现的。listIterator有previous方法支持前后遍历、迭代过程添加元素等操作。for-each就是Java提供的语法糖,实际上也是通过iterator遍历元素。
在用 for 遍历集合的时候是不可以对集合进行 remove操作的,因为 remove 操作会改变集合的大小。从而容易造成结果不准确甚至数组下标越界,更严重者还会抛出 ConcurrentModificationException。因为在创建迭代器时候会有这么一句代码:
int expectedModCount = modCount;
每当操作集合时,modCount都会自增,而每次iterator调用next方法时都会调用一下代码:
final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}
判断expectedModCount和modCount是否相等,不相等所以抛出ConcurrentModificationException异常。而iterator自带的remove方法每次都会将modCount重新赋给expectedModCount,所以遍历时删除元素需要使用集合的iterator的remove,以下是ListItr 和Itr的源码:
public ListIterator<E> listIterator(int index) {if (index < 0 || index > size)throw new IndexOutOfBoundsException("Index: "+index);return new ListItr(index);}public ListIterator<E> listIterator() {return new ListItr(0);}public Iterator<E> iterator() {return new Itr();}private class Itr implements Iterator<E> {int cursor; int lastRet = -1; int expectedModCount = modCount;Itr() {}public boolean hasNext() {return cursor != size;}@SuppressWarnings("unchecked")public E next() {checkForComodification();int i = cursor;if (i >= size)throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;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;lastRet = -1;expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}@Override@SuppressWarnings("unchecked")public void forEachRemaining(Consumer<? super E> consumer) {Objects.requireNonNull(consumer);final int size = ArrayList.this.size;int i = cursor;if (i >= size) {return;}final Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length) {throw new ConcurrentModificationException();}while (i != size && modCount == expectedModCount) {consumer.accept((E) elementData[i++]);}// update once at end of iteration to reduce heap write trafficcursor = i;lastRet = i - 1;checkForComodification();}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}}private class ListItr extends Itr implements ListIterator<E> {ListItr(int index) {super();cursor = index;}public boolean hasPrevious() {return cursor != 0;}public int nextIndex() {return cursor;}public int previousIndex() {return cursor - 1;}@SuppressWarnings("unchecked")public E previous() {checkForComodification();int i = cursor - 1;if (i < 0)throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length)throw new ConcurrentModificationException();cursor = i;return (E) elementData[lastRet = i];}public void set(E e) {if (lastRet < 0)throw new IllegalStateException();checkForComodification();try {ArrayList.this.set(lastRet, e);} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}public void add(E e) {checkForComodification();try {int i = cursor;ArrayList.this.add(i, e);cursor = i + 1;lastRet = -1;expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}}
4、总结
- ArrayList 底层基于数组实现的,所以插入或删除元素时,需要移动插入位置之后的所有元素,开销较大,所以适合随机查询,不适合增删多的操作。
- ArrayList初始容量默认为10。扩容机制为首先扩容为原始容量的 1.5 倍再与当前size+1取较大值成为新的数组长度。由于扩容是通过Arrays.copyOf每次创建新的数组,所以创建ArrayList时候尽量传入合适的容量,减小内存开销。
- ArrayList不是线程安全的,所以有modCount的存在,直接修改集合时都会使modCount++,所以遍历ArrayList时删除元素时使用iterator的remove方法,该方法会同步modCount值,避免出现ConcurrentModificationException异常。
这篇关于ArrayList源码解析(空间结构和特性、常用方法、遍历时删除元素注意事项等)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!