集合框架-ArrayList学习和总结
一、ArrayList
贴上关键代码,有些方法做了合并,方便逻辑理解
public class ArrayList extends AbstractList implements List,RandomAccess,Cloneable,java.io.Serializable{ /** * 记录ArrayList的size被改变的次数,在父类AbstractList中定义 * 为了提供快速失败机制:在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加,删除,修改),则会抛出 ConcurrentModification Exception * 典型的遍历一个集合删除某个元素时,应使用Iterator.remove(),避免使用List.remove(int index) */ protected transient int modCount = 0; /** * size的大小和实际数组中的元素个数一致 */ private int size; /** * elementData.length是随着扩容变化的,和size不是一样的 * 弄清楚minCapacity,size,elementData.length之间的关系就好了 */ transient Object[] elementData; /** * 有参构造器但传入的initialCapacity=0时(或addAll()传入的是一个空集合时),共享这个空数组,java8的一个改进 */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 无参构造器初始化时共享这个空数组,保证初始化容量最后使用的是DEFAULT_CAPACITY=10 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 指定初始化容量的有参构造方法 */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { //指定容量为0时,用的是这个空数组 this.elementData = EMPTY_ELEMENTDATA; } } /** * jdk7中无参构造方法,初始化容量是10,对比来看像 饿汉 调用的是 this(10) * jdk8中,初始化容量是0,直到第一次调用add时才初始化为10,就像是懒汉,延迟了数组创建,节省内存 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 添加方法,modCount会增长 * 先处理elementData的长度 * 再给数组元素赋值 */ public boolean add(E e) { //希望的最小容量总是当前的size+1 int minCapacity = size+1; //只有第一次从0开始的时候,这里才是true,第一次扩容后容量变为10 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(10, minCapacity); } modCount++; //overflow-conscious code,这个if判断是为了考虑int类型值溢出的情况,grow方法中也有 int oldCapacity = elementData.length; if (minCapacity - oldCapacity > 0){ //增长到1.5倍,右移后向下取整,依次是10,15,22 int newCapacity = oldCapacity + (oldCapacity >> 1); //如果oldCapacity接近Integer.MAX_VALUE,newCapacity得到的结果可能为负数,下面的if就是负数结果 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 调用Arrays.copyOf,这个方法会调用System.arraycopy() elementData = Arrays.copyOf(elementData, newCapacity); } elementData[size++] = e; return true; } }
总结几个点:
1、建议创建ArrayList时指定初始容量
2、JDK8默认初始化的数组是空数组,容量不是10,第一次add时才第一次扩容;以后按照1.5倍(向下取整)扩容
3、ArrayList实现了RandomAccess接口,说明支持随机访问,建议优先使用一般的for循环遍历,而不要使用迭代器,注意,增强的foreach循环,内部使用的也是迭代器Iterator
subList方法
为了方便理解,原list叫sourceList,返回的叫subList
/** * Returns a view of the portion of this list between the specified ------返回一个当前ArrayList对象的左闭右开的视图 * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. (If * {@code fromIndex} and {@code toIndex} are equal, the returned list is * empty.) The returned list is backed by this list, so non-structural ------返回的list是基于当前父list的,非结构性的修改会彼此影响原 * changes in the returned list are reflected in this list, and vice-versa. * The returned list supports all of the optional list operations. * * <p>This method eliminates the need for explicit range operations (of ------- 应用场景:对原list的某一段元素进行操作时,可以通过subList视图进行操作来代替对整个list进行操作 * the sort that commonly exist for arrays). Any operation that expects * a list can be used as a range operation by passing a subList view * instead of a whole list. For example, the following idiom * removes a range of elements from a list: * <pre> * list.subList(from, to).clear(); * </pre> * Similar idioms may be constructed for {@link #indexOf(Object)} and * {@link #lastIndexOf(Object)}, and all of the algorithms in the * {@link Collections} class can be applied to a subList. * * <p>The semantics of the list returned by this method become undefined if * the backing list (i.e., this list) is <i>structurally modified</i> in -------结构性修改,也就是改变了sourceList的size的修改 * any way other than via the returned list. (Structural modifications are * those that change the size of this list, or otherwise perturb it in such * a fashion that iterations in progress may yield incorrect results.) * * @throws IndexOutOfBoundsException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} */ public List<E> subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList(this, 0, fromIndex, toIndex); }
返回值是一个新的类,SubList,这是ArrayList的一个内部类,和ArrayList不是一个类型
private class SubList extends AbstractList<E> implements RandomAccess { private final AbstractList<E> parent; private final int parentOffset; private final int offset; int size; //这个构造方法可以看出subList没有重新创建一个新的ArrayList SubList(AbstractList<E> parent, ------这个parent就是上面new SubList(this,0,fromIndex,toIndex)中的this,也就是sourceList int offset, int fromIndex, int toIndex) { this.parent = parent; this.parentOffset = fromIndex; this.offset = offset + fromIndex; this.size = toIndex - fromIndex; this.modCount = ArrayList.this.modCount; } public E set(int index, E e) { rangeCheck(index); checkForComodification(); E oldValue = ArrayList.this.elementData(offset + index); ArrayList.this.elementData[offset + index] = e; ----------- 这个地方看出subList在set时,修改的是原ArrayList的elementData return oldValue; } public E get(int index) { rangeCheck(index); checkForComodification(); return ArrayList.this.elementData(offset + index); ------- 这个可以看出get时也是从原ArrayList的elementData获取的 }
..........
}
总结:
1、将这个SubList强转成ArrayList会抛出 java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList 异常,同理也无法强转为其他的List
2、subList不会返回一个新的ArrayList,而是返回一个SubList,它是ArrayList里面的一个内部类,两者指向的同一套数据
3、对sourceList和subList做非结构性修改,影响是同步的
4、可以对subList做结构性修改,修改会反映到sourceList
5、对sourceList做结构性修改,对subList进行后续操作时,会抛出ConcurrentModificationException
贴一个阿里巴巴集合处理的代码规范说明
6、正例
List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,4,5,6)); list.subList(2,4).clear
//截取一部分,创建一个新的ArrayList
List<Integer> subList = new ArrayList<Integer>( sourceList.subList(0,3));
Collections.unmodifiableList
返回一个不可修改的List
public static <T> List<T> unmodifiableList(List<? extends T> list) { return (list instanceof RandomAccess ? new UnmodifiableRandomAccessList<>(list) : new UnmodifiableList<>(list)); }
Arrays.asList
/** * Returns a fixed-size list backed by the specified array. (Changes to * the returned list "write through" to the array.) This method acts * as bridge between array-based and collection-based APIs, in * combination with {@link Collection#toArray}. The returned list is * serializable and implements {@link RandomAccess}. * * <p>This method also provides a convenient way to create a fixed-size * list initialized to contain several elements: * <pre> * List<String> stooges = Arrays.asList("Larry", "Moe", "Curly"); * </pre> * * @param <T> the class of the objects in the array * @param a the array by which the list will be backed * @return a list view of the specified array */ @SafeVarargs @SuppressWarnings("varargs") public static <T> List<T> asList(T... a) { return new ArrayList<>(a); } /** * @serial include */ private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable { private static final long serialVersionUID = -2764017481108945198L; private final E[] a; ArrayList(E[] array) { a = Objects.requireNonNull(array); }
.....
}
以上源码的说明:
1、Arrays.asList返回的是一个固定长度的list,返回类型不是java.util.ArrayList,而是Arrays的一个内部类ArraysList,这个类没有重写父类AbstractList的add,remove方法
2、如果调用上面的2个方法会抛出UnsupportedOperationException异常,因为实际会调用父类的这三个方法,如下:
public void add(int index, E element) { throw new UnsupportedOperationException(); } public E remove(int index) { throw new UnsupportedOperationException(); }
3、在使用上仅适用于对象类型的数组,不适用于基本类型的数组,比如Arrays.asList(new Integer[]{1,2,3}),size是3,而Arrays.asList(new int[]{1,2,3}),size是1,后者会当成一个整体
4、原数组和返回的list是相互关联的,改变list,原数组也会改变
String[] arr = new String[]{"a","b","c"}; List<String> list = Arrays.asList(arr);
list.set(0,"d");------->arr[0]会变为"d"
fail-fast机制
当遍历一个集合对象时,如果集合对象的结构被修改了,会抛出ConcurrentModficationExcetion异常,防止在遍历过程中出现意料之外的修改
源码注释:
/**
* <p><a name="fail-fast"> * The iterators returned by this class's {@link #iterator() iterator} and * {@link #listIterator(int) listIterator} methods are <em>fail-fast</em>:</a> * if the list is structurally modified at any time after the iterator is * created, in any way except through the iterator's own * {@link ListIterator#remove() remove} or * {@link ListIterator#add(Object) add} methods, the iterator will throw a * {@link ConcurrentModificationException}. Thus, in the face of * concurrent modification, the iterator fails quickly and cleanly, rather * than risking arbitrary, non-deterministic behavior at an undetermined * time in the future. * * <p>Note that the fail-fast behavior of an iterator cannot be guaranteed * as it is, generally speaking, impossible to make any hard guarantees in the * presence of unsynchronized concurrent modification. Fail-fast iterators * throw {@code ConcurrentModificationException} on a best-effort basis. * Therefore, it would be wrong to write a program that depended on this * exception for its correctness: <i>the fail-fast behavior of iterators * should be used only to detect bugs.</i> */
iterator()方法返回的new Itr()迭代器满足快速失败机制:
当创建一个迭代器后(调用iterator()或者增强的foreach循环),使用除迭代器的remove,add方法外的任何方法使得list被结构性改变
迭代器都会抛出一个ConcurrentModificationException,就是说宁可失败也不出错
下面的注意是说:快速失败机制是用来检验bug的,而不是让程序员依赖这个机制来设计程序逻辑
实现方式:
迭代器维护了一个expectedModCount,初始值等于ArrayList的modCount,当在迭代器遍历过程中单独调用list的add,remove时,会单方面修改modCount,迭代器
在调用next,remove等方法时都会调用checkModification()方法,改方法会比较两者是否相等
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException();
}
遍历过程中如何增加和删除元素
注意:并不是说遍历过程中不能对list进行结构性修改,而是说要让迭代器来干,而不是list自己直接动手改自己
正例:
Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Integer i = iterator.next(); if (i<0) { iterator.remove(); } }
fail-safe机制
顺便提一嘴安全失败机制,是后面JUC工具类中的一种机制,与fail-fast相对应的
在安全的副本上进行遍历,当前集合修改和其副本在遍历时没有任何关系,但是缺点很明显,就是读取不到最新数据
这就是CAP理论中C(Consistency)和A(Availability)的矛盾,即一致性和可用性的矛盾。
上面的fail-fast发生时,程序会抛出异常,而fail-safe是一个概念,并发容器并发修改不会抛出异常,并发容器都是围绕着快照版本进行的操作,并没有modCount等数值检查
可以并发读取,不会抛出异常,但是不保证你的遍历读取的值和当前集合对象状态是一致的。
所以fail-safe迭代缺点是:首先不能保证返回集合更新后的数据,因为是在集合的克隆上进行的操作,而非集合本身,其次创建集合拷贝需要相应的开销,包括时间和内存。
JUC包中集合的迭代,如ConcurrentHashMap、CopyOnWriteArrayList等默认的都是fail-safe