摘要:注意,迭代器的快速失败行为无法得到保证,快速失败迭代器会尽最大努力抛出。迭代器的快速失败行为应该仅用于检测。
几个重要接口
首先看方法声明:
public class ArrayListextends AbstractList implements List , RandomAccess, Cloneable, java.io.Serializable
RandomAccess:
public interface RandomAccess { }
RandomAccess接口都是给 List所使用的,用来表明其支持快速(通常是固定时间)随机访问,为其提供良好的性能。实际经验证明,如果是下列情况,则 List 实现应该实现此接口,即对于典型的类实例而言,此循环:
for (int i=0, n=list.size(); i < n; i++) list.get(i);
的运行速度要快于以下循环:
for (Iterator i=list.iterator(); i.hasNext(); ) i.next();
Cloneable:
public interface Cloneable { }
实现了此接口的类就可以通过重写 Object.clone()方法来定制对其进行复制的细节,如果在没有实现 Cloneable 接口的实例上调用 Object 的 clone 方法,则会导致抛出 CloneNotSupportedException 异常。
两个变量/** * ArrayList中元素存储的地方,数组的长度就是它的容量 */ private transient Object[] elementData; /** *ArrayList所包含的元素的大小 */ private int size;构造方法
提供了3种构造方法:
public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; } public ArrayList() { this(10); } public ArrayList(Collection extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); }
可以看到:第一种方法需要一个默认的容量大小,第二个是默认的构造方法,会默认创建一个容量为10的 ArrayList,第三个则传给一个 Collection,注意,不管 Collection里面是什么类型,最后放进 ArrayList都会上转为 Object
重要方法 addpublic boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
add方法中使用了 ensureCapacityInternal来控制容量:
private void ensureCapacityInternal(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
其中 modCount是用来记录 list被结构修改的次数,所谓结构上的修改是 指任何添加或删除一个或多个元素的操作,或者显式调整底层数组的大小;仅仅设置元素的值不是结构上的修改;在上面的方法中,如果 minCapacity 大于现有数组长度,则执行 grow方法:
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; /*newCapacity 扩展为旧容量的1.5倍左右*/ int newCapacity = oldCapacity + (oldCapacity >> 1); /*如果这时新容量还小于minCapacity,则新容量为minCapacity*/ if (newCapacity - minCapacity < 0) newCapacity = minCapacity; /*新容量大于 Integer.MAX_VALUE - 8*/ if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 最后将原数组放进新数组,改变长度 elementData = Arrays.copyOf(elementData, newCapacity); }
如果 newCapacity大于 Integer.MAX_VALUE - 8,则 newCapacity为 Integer.MAX_VALUE,这也是能够扩充的最大容量
再来看第二种 add方法:
public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
这种方法是在指定位置插入元素,主要使用了 System.arraycopy()方法将 index之后的元素向后移动一个位置,将 index位空出来放入新元素
clearclear方法比较简单,但是注意调用 clear也会使 modCount加1:
public void clear() { modCount++; // Let gc do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }clone
public Object clone() { try { @SuppressWarnings("unchecked") ArrayListv = (ArrayList ) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn"t happen, since we are Cloneable throw new InternalError(); } }
clone方法只能进行浅复制,并不复制元素本身
removepublic 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方法中,如果要移除的元素为 null,则删除数组中第一个为 null的元素。如果数组中有超过一个匹配的元素,仅移除第一个
toArraypublic Object[] toArray() { return Arrays.copyOf(elementData, size); }
这个方法被很多方法使用,它调用了 Arrays工具类中的方法 copyOf:
public staticT[] copyOf(T[] original, int newLength) { return (T[]) copyOf(original, newLength, original.getClass()); }
public staticT[] copyOf(U[] original, int newLength, Class extends T[]> newType) { 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; }
最终调用了方法:
public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)
它的参数列表如下:
src - 源数组。
srcPos - 源数组中的起始位置。
dest - 目标数组。
destPos - 目标数据中的起始位置。
length - 要复制的数组元素的数量。
从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。这个方法是一个 native方法,并且经测试,如果是引用数组类型,它不会真正复制对象,只是复制引用(浅复制)
trimToSize将此 ArrayList 实例的容量调整为列表的当前大小。应用程序可以使用此操作来最小化 ArrayList 实例的存储量。
public void trimToSize() { modCount++; int oldCapacity = elementData.length; if (size < oldCapacity) { elementData = Arrays.copyOf(elementData, size); } }
由此可知:在 ArrayList容量确定下来以后,可以调用这个方法最小化存储空间
Fast-Fail快速失败机制此类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:在创建迭代器之后,除了通过迭代器自身的 remove 或 add 方法从结构上对列表进行修改,否则在任何时间以任何方式对列表进行修改,迭代器都会抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。
注意,迭代器的快速失败行为无法得到保证,快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。迭代器的快速失败行为应该仅用于检测 bug。
前面说到 ArrayList中定义了一个 modCount来记录对容器进行结构修改的次数,在 add、 addAll、 remove、 clear、 clone方法中都会引起 modCount变化,而在创建迭代器时,会使用局部变量保存当前的 modCount值:
private class Itr implements Iterator{ int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; ...
在进行迭代的过程中,会先检查 modCount 有没有发生变化,以此来判定是否有外部操作改变了容器:
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
最后,因为 ArrayList是非同步的,因此,在多线程环境下,如果有对容器进行结构修改的操作,则必须使用外部同步。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/64428.html
摘要:同步众所周知,是同步的而不是,在一些必要的方法上都加了关键字,但是这也会加大系统开销。中有一个方法用来返回一个,以匿名内部类的方式实现的接口和类似,都用作于对集合进行迭代,不过没有删除功能,已经被取代。还有是的,但不是,这一点很重要。 在上篇文章ArrayList源码浅析中分析了一下 ArrayList的源码和一些重要方法,现在对比 ArrayList,总结一下 Vector和 Arr...
摘要:重要方法在链尾添加元素除了这个方法以外,还提供了等一些方法,都是为实现和方法服务的,因为双向链表的原因,这些实现都很简单。 类声明 LinkedList类声明如下: public class LinkedList extends AbstractSequentialList implements List, Deque, Cloneable, java.io.Seria...
摘要:它主要做了件事初始化容器,并将元素添加到容器里维护这样我们再调用的方法直接就返回了,不需要再次遍历和统计的过程。维护实时的维护,及时删除总结整体上是对底层的二次封装,很好的处理了各种细节,比如子容器的判空处理,的计算效率,的维护等。 在日常开发中我们通常有需要对 List 容器进行分组的情况,比如对下面的list数据根据name字段来进行分组: [ { date...
摘要:是非,而是,这意味着是线程安全的,多个线程可以共享一个而如果没有正确的同步的话,多个线程是不能共享的。另一个区别是的迭代器是迭代器,而的迭代器不 这里只解析一些常用的、比较重要的一些集合类,并且作者水平有限,有些地方可能解析不到位或者解析错误,还望各位读者指出错误。 Collection List ArrayList LinkedLis...
摘要:泛型类在类的申明时指定参数,即构成了泛型类。换句话说,泛型类可以看成普通类的工厂。的作用就是指明泛型的具体类型,而类型的变量,可以用来创建泛型类的对象。只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。 什么是泛型? 泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,...
阅读 2451·2023-04-26 02:47
阅读 2966·2023-04-26 00:42
阅读 846·2021-10-12 10:12
阅读 1344·2021-09-29 09:35
阅读 1653·2021-09-26 09:55
阅读 425·2019-08-30 14:00
阅读 1511·2019-08-29 12:57
阅读 2333·2019-08-28 18:00