API源码学习之集合(1)--ArrayList

 

近期杂事较多,学习进度依旧缓慢。惭愧....

ArrayList源码学习

  1、类有两个主要成员变量:

 1  /**
 2      * The array buffer into which the elements of the ArrayList are stored.
 3      * The capacity of the ArrayList is the length of this array buffer. Any
 4      * empty ArrayList with elementData == EMPTY_ELEMENTDATA will be expanded to
 5      * DEFAULT_CAPACITY when the first element is added.
 6      */
 7     private transient Object[] elementData;
 8 
 9     /**
10      * The size of the ArrayList (the number of elements it contains).
11      *
12      * @serial
13      */
14     private int size;

  elementData为数组,用于真正存储ArrayList实例中的数据,size标志ArrayList元素个数。

  2、构造函数

 

 1  /**
 2      * Constructs an empty list with the specified initial capacity.
 3      *
 4      * @param  initialCapacity  the initial capacity of the list
 5      * @throws IllegalArgumentException if the specified initial capacity
 6      *         is negative
 7      */
 8     public ArrayList(int initialCapacity) {
 9         super();
10         if (initialCapacity < 0)
11             throw new IllegalArgumentException("Illegal Capacity: "+
12                                                initialCapacity);
13         this.elementData = new Object[initialCapacity];
14     }
15 
16     /**
17      * Constructs an empty list with an initial capacity of ten.
18      */
19     public ArrayList() {
20         super();
21         this.elementData = EMPTY_ELEMENTDATA;
22     }
23 
24     /**
25      * Constructs a list containing the elements of the specified
26      * collection, in the order they are returned by the collection's
27      * iterator.
28      *
29      * @param c the collection whose elements are to be placed into this list
30      * @throws NullPointerException if the specified collection is null
31      */
32     public ArrayList(Collection<? extends E> c) {
33         elementData = c.toArray();
34         size = elementData.length;
35         // c.toArray might (incorrectly) not return Object[] (see 6260652)
36         if (elementData.getClass() != Object[].class)
37             elementData = Arrays.copyOf(elementData, size, Object[].class);
38     }

  可创建一个空ArrayList,也可根据需要创建一个长度为非负数的ArrayList,也可由一个规定泛型的子类来创建一个ArrayList,如:

    public class  MyClass {}

    class SubClass extends MyClass {}

    List<SubClass>  s  = new List<SubClass>();

    ArrayList<MyClass> c = new ArrayList<MyClass>(s);

  即凡是MyClass或者MyClass的子类的Collection均可以构造成ArrayList<MyClass>,也算是体现了向上造型的思想吧。

   还有一个小实验:

  

  由上可见,当创建一个大小为10的arrlist的时候,size仍未变化,只有当对数组进行增删操作时size才会变化,可见size确实代表ArrayList中的元素个数。

  3、动态数组的实现

  

1 public boolean add(E e) {
2         ensureCapacityInternal(size + 1);  // Increments modCount!!
3         elementData[size++] = e;
4         return true;
5     }

  

  add()方法会调用ensureCapacityInternal()确定最小容量minCapacity,当elementData为空数组时,最小容量minCapacity为10,否则为size+1。

 随后调用ensureExplicitCapacity()确定与当前elementData数组长度的关系,若minCapacity大于实际数组的长度,则调用grow()对数组进行动态扩容。

 1  private void ensureCapacityInternal(int minCapacity) {
 2         if (elementData == EMPTY_ELEMENTDATA) {
 3             minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
 4         }
 5 
 6         ensureExplicitCapacity(minCapacity);
 7     }
 8 
 9     private void ensureExplicitCapacity(int minCapacity) {
10         modCount++;
11 
12         // overflow-conscious code
13         if (minCapacity - elementData.length > 0)
14             grow(minCapacity);
15     }

   grow()获取扩容前容量oldCapacity,随后设置新容量newCapacity,大小为旧容量的1.5倍,此时再进行两次判断:

    1、若扩容后容量仍比所需最小容量minCapacity的话,则直接将新容量设置为最小容量minCapacity;

    2、若扩容后容量比数组所设定最大容量MAX_ARRAY_SIZE大的话,则将newCapacity设置为最大容量。

(Ps: 若minCapacity<0,则抛OutOfMemoryError();minCapacity>MAX_ARRAY_SIZE,则返回Integer.MAX_VALUE,否则返回MAX_ARRAY_SIZE,大小为Integer.MAX_VALUE-8。该逻辑判断在hugeCapacity()方法中)

  随后将旧数组copy到新数组中来。

  此时,ensureCapacityInternal()执行完毕,数组动态扩容完毕,add方法下一步将E e对象添加进数组的size++位置中即可,此时ArrayList的size也随之更新了

 1  private void grow(int minCapacity) {
 2         // overflow-conscious code
 3         int oldCapacity = elementData.length;
 4         int newCapacity = oldCapacity + (oldCapacity >> 1);
 5         if (newCapacity - minCapacity < 0)
 6             newCapacity = minCapacity;
 7         if (newCapacity - MAX_ARRAY_SIZE > 0)
 8             newCapacity = hugeCapacity(minCapacity);
 9         // minCapacity is usually close to size, so this is a win:
10         elementData = Arrays.copyOf(elementData, newCapacity);
11     }

   以上,就完成了一次ArrayList的add(E e)的添加操作。

  remove方法如下:

 1 public E remove(int index) {
 2         rangeCheck(index);
 3 
 4         modCount++;
 5         E oldValue = elementData(index);
 6 
 7         int numMoved = size - index - 1;
 8         if (numMoved > 0)
 9             System.arraycopy(elementData, index+1, elementData, index,
10                              numMoved);
11         elementData[--size] = null; // clear to let GC do its work
12 
13         return oldValue;
14     }

  remove方法调用rangeCheck()判断index下标的范围合法性,若index>size||index<0,则抛IndexOutOfBoundsException。

  随后计算需要重新移动的元素下标的起始值,若下标大于0(即不是第一个元素),则利用System.arraycopy()将被删元素的后面所有元素往前移一位。并将数组最后一位元素置null,及时清出空间让GC处理。

  随后返回被删除元素对象。

 1 public boolean remove(Object o) {
 2         if (o == null) {
 3             for (int index = 0; index < size; index++)
 4                 if (elementData[index] == null) {
 5                     fastRemove(index);
 6                     return true;
 7                 }
 8         } else {
 9             for (int index = 0; index < size; index++)
10                 if (o.equals(elementData[index])) {
11                     fastRemove(index);
12                     return true;
13                 }
14         }
15         return false;
16     }

   remove()方法的重载,若想直接删掉数组中的某个元素,除了利用下标,还可以直接传入对象本身作为参数进行删除操作。

  原理是遍历并匹配数组中元素,并将与其equals的第一个元素删除并返回true。

    若数组中无此元素,返回false。

   其中调用的fastRemove()方法如下:

1 private void fastRemove(int index) {
2         modCount++;
3         int numMoved = size - index - 1;
4         if (numMoved > 0)
5             System.arraycopy(elementData, index+1, elementData, index,
6                              numMoved);
7         elementData[--size] = null; // clear to let GC do its work
8     }

  fastRemove()与remove()方法的区别是在官方注释中说明不返回被删元素且不进行元素检查。

  (remove()方法调用了elementData(index)方法,这个方法会将对应元素转型后返回个调用者,同时由于通过elementData[index]方法引用元素,故会抛出IndexOutOfBoundsException)

 

  以上就是ArrayList主要的add/remove方法,其中还有些工具方法:

  

  1、trimToSize()方法:

1  public void trimToSize() {
2         modCount++;
3         if (size < elementData.length) {
4             elementData = Arrays.copyOf(elementData, size);
5         }
6     }

  由于ArrayList的grow()方法的动态扩展数组长度,导致数组真实大小一般比ArrayList中的元素个数size要大,这个方法是将数组中空闲的长度去除,精简ArrayList对象大小。

  一般用于内存情况紧张的情况下,用于提升运行效率和减缓运行压力

  2、size(),isEmpty(),contains()方法

 1  /**
 2      * Returns the number of elements in this list.
 3      *
 4      * @return the number of elements in this list
 5      */
 6     public int size() {
 7         return size;
 8     }
 9 
10     /**
11      * Returns <tt>true</tt> if this list contains no elements.
12      *
13      * @return <tt>true</tt> if this list contains no elements
14      */
15     public boolean isEmpty() {
16         return size == 0;
17     }
18 
19     /**
20      * Returns <tt>true</tt> if this list contains the specified element.
21      * More formally, returns <tt>true</tt> if and only if this list contains
22      * at least one element <tt>e</tt> such that
23      * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
24      *
25      * @param o element whose presence in this list is to be tested
26      * @return <tt>true</tt> if this list contains the specified element
27      */
28     public boolean contains(Object o) {
29         return indexOf(o) >= 0;
30     }

  size()返回size数值,表示ArrayList元素个数

  isEmpty()返回size==0的判断结果,表示ArrayList是否为空

  contains()调用indexOf(o)方法,若数量大于0则说明包含该元素,用于判断ArrayList中是否包含某个对象

 3、indexOf()方法

 1  public int indexOf(Object o) {
 2         if (o == null) {
 3             for (int i = 0; i < size; i++)
 4                 if (elementData[i]==null)
 5                     return i;
 6         } else {
 7             for (int i = 0; i < size; i++)
 8                 if (o.equals(elementData[i]))
 9                     return i;
10         }
11         return -1;
12     }

  遍历并判断数组中元素,并返回第一个与传入对象equals的数组元素下标。若未找到,则返回-1。

  lastIndexOf()与上类似:

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

  与index()从数组第一个元素开始遍历相反,lastIndexOf()从数组最后一个元素开始遍历。

 4、clone()方法

 1 public Object clone() {
 2         try {
 3             @SuppressWarnings("unchecked")
 4                 ArrayList<E> v = (ArrayList<E>) super.clone();
 5             v.elementData = Arrays.copyOf(elementData, size);
 6             v.modCount = 0;
 7             return v;
 8         } catch (CloneNotSupportedException e) {
 9             // this shouldn't happen, since we are Cloneable
10             throw new InternalError();
11         }
12     }

  首先其用在try catch的catch块中捕获 无法支持克隆 异常,并抛出一个虚拟机内部异常。原因为:

    正如其注释中所说的,“this shouldn't happen, since we are Cloneable”。

    因为ArrayList实现了Cloneable接口,所以不会是因为被克隆对象未实现Cloneable接口的原因,

    故若克隆失败,必然是虚拟机出现了问题。

    所以捕获了此异常后,为了程序的健壮性而抛出虚拟机内部异常,提醒程序员去修复或重新配置相关环境。

  随后引用了 Object的clone()方法返回的对象,并在后续代码中实现深克隆,返回一个全新的、但内部对象与数据完全一致的克隆对象(操作记录数modCount = 0、深克隆)。

  Ps:

    Object的clone()方法能实现克隆效果,若是内部成员变量均为基本类型,则该方法则是完美的,因为直接复制相关值即可。即浅克隆。

    但若是有引用类型成员变量,则会复制相关变量的引用,这就会引发一系列问题,因为如果你的克隆对象若仍然是引用着原对象的成员变量的地址,

    那么当克隆对象有变化时,原对象也会受影响。那么为了实现深克隆,即完美的克隆出一个与之前对象一致,但又无关联的对象,

    就必须在堆中开拓一个新的空间,

    将其中的引用类型变量重新创建一个新对象并将原引用类型变量中相关的值写入其中。

    故

      v.elementData = Arrays.copyOf(elementData, size);

    这句代码则将ArrayList中唯一的引用类型成员变量利用Arrays.copyOf()方法将克隆对象的数组变量指向了一个新的、内部元素一致的对象,

    从而实现了深克隆。

5、toArray()

 1 public Object[] toArray() {
 2         return Arrays.copyOf(elementData, size);
 3 }
 4 @SuppressWarnings("unchecked")
 5 public <T> T[] toArray(T[] a) {
 6         if (a.length < size)
 7             // Make a new array of a's runtime type, but my contents:
 8             return (T[]) Arrays.copyOf(elementData, size, a.getClass());
 9         System.arraycopy(elementData, 0, a, 0, size);
10         if (a.length > size)
11             a[size] = null;
12         return a;
13}

  toArray()转化为数组,第一个方法没什么好说的,直接返回了elementData中的一个copy。

  第二个方法:

  传入一个规定类型的数组的引用,若长度不够,则返回一个长度大于等于size的数组,此数组根据传入数组的类型来实例化

  当传入的数组长度比ArrayList中元素多时,将传入数组第size个成员设为null。

6、elementData(int index)

1 E elementData(int index) {
2         return (E) elementData[index];
3     }

  返回下标为入参的元素

7、get(int index)

1 public E get(int index) {
2         rangeCheck(index);
3 
4         return elementData(index);
5     }

检查入参,若大于元素个数,则抛出IndexOutOfBoundsException

1  private void rangeCheck(int index) {
2         if (index >= size)
3             throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
4     }

随后返回数组中对应成员

8、E set(int index, E element)

public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

检查入参是否合法,随后将旧元素取出,并将新元素覆盖至对应位置中,返回被替代的旧元素。

 9、clear()

1 public void clear() {
2         modCount++;
3 
4         // clear to let GC do its work
5         for (int i = 0; i < size; i++)
6             elementData[i] = null;
7 
8         size = 0;
9     }

遍历数组中每个元素,将其置null,便于让GC及时清理无用对象。

并将元素个数size设为0。

10、addAll()

1  public boolean addAll(Collection<? extends E> c) {
2         Object[] a = c.toArray();
3         int numNew = a.length;
4         ensureCapacityInternal(size + numNew);  // Increments modCount
5         System.arraycopy(a, 0, elementData, size, numNew);
6         size += numNew;
7         return numNew != 0;
8     }

该方法将传入的集合(泛型为ArrayList实例泛型的类或其泛型类型的子类),

将该集合转化为数组后调用ensureCapacityInternal()进行动态扩容。

若传入集合元素个数为0,那么返回false;否则返回true。

另一个addAll()方法是将传入的集合传入到elementData的指定下标开始的位置。代码如下:

 1  public boolean addAll(int index, Collection<? extends E> c) {
 2         rangeCheckForAdd(index);
 3 
 4         Object[] a = c.toArray();
 5         int numNew = a.length;
 6         ensureCapacityInternal(size + numNew);  // Increments modCount
 7 
 8         int numMoved = size - index;
 9         if (numMoved > 0)
10             System.arraycopy(elementData, index, elementData, index + numNew,
11                              numMoved);
12 
13         System.arraycopy(a, 0, elementData, index, numNew);
14         size += numNew;
15         return numNew != 0;
16     }

若插入下标index>size或<0则会抛出IndexOutOfBoundsException。

若插入下标index>=size,那么中间过程就不再对elementData中的元素进行移位操作了。

 

若传入的集合元素数为0,那么返回false;否则返回true。

11、removeAll和retainAll

 1 public boolean removeAll(Collection<?> c) {
 2         return batchRemove(c, false);
 3 }
 4 public boolean retainAll(Collection<?> c) {
 5         return batchRemove(c, true);
 6     }
 7 
 8     private boolean batchRemove(Collection<?> c, boolean complement) {
 9         final Object[] elementData = this.elementData;
10         int r = 0, w = 0;
11         boolean modified = false;
12         try {
13             for (; r < size; r++)
14                 if (c.contains(elementData[r]) == complement)
15                     elementData[w++] = elementData[r];
16         } finally {
17             // Preserve behavioral compatibility with AbstractCollection,
18             // even if c.contains() throws.
19             if (r != size) {
20                 System.arraycopy(elementData, r,
21                                  elementData, w,
22                                  size - r);
23                 w += size - r;
24             }
25             if (w != size) {
26                 // clear to let GC do its work
27                 for (int i = w; i < size; i++)
28                     elementData[i] = null;
29                 modCount += size - w;
30                 size = w;
31                 modified = true;
32             }
33         }
34         return modified;
35     }

removeAll(Collection<?> c)会将所有被传入的集合里的元素从当前ArrayList实例中删除。而retainAll则会将除了这些元素以外的元素删除。

这两个方法都调用了batchRemove这个方法,只不过传入的参数不同,很好地实现了代码的复用性。

batchRemove先是定义了一个不可改变的数组elementData引用,并指向了ArrayList实例本身的elementData数组。

这样的好处是:

  若直接使用ArrayList中的elementData,那么在这个方法中可能会把elementData的引用改变,从而可能会误指向其他的对象。

  (原elementData不是final的,而该方法中没有必要用到改变elementData引用的情况)。

故若以后设计代码时,对于这种方法内使用到类成员变量的,当需要为了防止自己误操作把原成员变量引用改变了的,可以重新定义一个final修饰的引用,并将原成员变量的引用赋值给它。

这样既保证了防止误操作,又不影响原本打算对原成员变量所进行的操作计划,一举两得!果然源码中的细节真是健壮地惊人啊。。学习了。

接下来定义了两个用于for循环的int变量,r和w,一个负责不停向后遍历,另一个等着将符合条件的元素覆盖到前面去。另外还定义了一个boolean类型变量modified,用于返回函数运行结果情况。

 

然后则在try代码块中开始对传入集合和当前ArrayList的元素进行匹配,当不相等(或者相等,根据complement的传入值决定,这种一个参数实现相反功能的代码真是太精辟了)时,

则将当前匹配成功的第r个元素覆盖到第w个元素处。此举相当于原来的第w个元素就被删除掉了。如此遍历下来,所有在传入的集合中的元素都被remove掉了,从而实现了removeAll()方法的需求。

同样的当complement为true时,也只有在集合中的元素会被保留下来,从而实现了retainAll()的功能。

 

那么若循环过程中contains抛出ClassCastExeption(因为有可能传入的集合的泛型是未被规范的,有可能是任意类型,当传给contains的元素不是传入集合的泛型或其子类时,则会抛出该异常),

那么循环就被终止了。这个时候只有0-w处的元素是被匹配后剩余的元素,r-size的元素是由于抛了异常,未被遍历到的元素,w-r处的元素还是原elementData中的未被覆盖到的元素。

这时候,通过判断r != size可得知循环是否循环完,因为循环被打断后r是小于size的某个整数。

当循环被打断时,利用System.arraycopy方法将第r-size的元素赋值到w处,并将w加至w+size-r,这样w之前就是本次过滤操作后剩余的元素末尾下标。

此时再判断w是否等于size,若相等,则说明没有元素被剔除,那么直接返回初始值为false的modified变量,说明此次removeAll或retainAll并未执行任何过滤操作。

若不相等,说明有元素被剔除,这时候不管循环是否被打断过,直接将w个之后的N个元素遍历置null,方便让GC清理空间(这个编码习惯在jdk源码各个地方都很常见,可见即便有GC Java程序员也是需要对内存空间进行妥善管理的)。

并将操作数modCount加上size-w,表示有这么多个元素被操作过了,可见modCount并不是真正的所有操作数,只是代表字面意义上的操作数。

将modified置true后返回。

12、writeObject()和readObject()

 1 private void writeObject(java.io.ObjectOutputStream s)
 2         throws java.io.IOException{
 3         // Write out element count, and any hidden stuff
 4         int expectedModCount = modCount;
 5         s.defaultWriteObject();
 6 
 7         // Write out size as capacity for behavioural compatibility with clone()
 8         s.writeInt(size);
 9 
10         // Write out all elements in the proper order.
11         for (int i=0; i<size; i++) {
12             s.writeObject(elementData[i]);
13         }
14 
15         if (modCount != expectedModCount) {
16             throw new ConcurrentModificationException();
17         }
18     }
19 
20 private void readObject(java.io.ObjectInputStream s)
21         throws java.io.IOException, ClassNotFoundException {
22         elementData = EMPTY_ELEMENTDATA;
23 
24         // Read in size, and any hidden stuff
25         s.defaultReadObject();
26 
27         // Read in capacity
28         s.readInt(); // ignored
29 
30         if (size > 0) {
31             // be like clone(), allocate array based upon size not capacity
32             ensureCapacityInternal(size);
33 
34             Object[] a = elementData;
35             // Read in all elements in the proper order.
36             for (int i=0; i<size; i++) {
37                 a[i] = s.readObject();
38             }
39         }
40     }

这两个方法主要是实现ArrayList的序列化和反序列化的。

 其先调用了defaultWriteObject实现默认的序列化,即将非final和非transient的变量序列化到流中去,然后再额外多往流中写入了一个size。

至于为何要额外写入此size我也不懂了,因为在readObject中也未见对此写入的size有任何操作,且源码后面加了一句注释//ignore,我猜测是jdk1.7改动代码时过于保守所导致的,故多写的这个size作用待定。

然后序列化的时候再根据size元素个数初始化调用ensureCapacityInternal(),确定elementData的大小,

再按原顺序将所有的元素逐个遍历写入流中,反序列化则按顺序读出。(在序列化过程中若写入了多个相同类型的对象,则在反序列化时需要按顺序一个个读出。)

Ps:

if (modCount != expectedModCount) {
           throw new ConcurrentModificationException();
}

 以上这段代码是因为ArrayList并非线程安全,故需要在随时判断modCount是否与之前相等,若不相等说明出现了线程并发问题。

 很多非线程安全的集合都有这一机制,通过ConcurrentModificationException来阻止并发的出现,又称"fail-fast"快速雪崩机制。

  一般发生在非迭代器的集合迭代过程较多。

以下摘抄至百度百科:

  迭代器的快速失败行为无法得到保证,它不能保证一定会出现该错误,但是快速失败操作会尽最大努力抛出ConcurrentModificationException异常,所以因此,为提高此类操作的正确性而编写一个依赖于此异常的程序是错误的做法,正确做法是:ConcurrentModificationException 应该仅用于检测 bug。

13、removeRange()

 1 protected void removeRange(int fromIndex, int toIndex) {
 2         modCount++;
 3         int numMoved = size - toIndex;
 4         System.arraycopy(elementData, toIndex, elementData, fromIndex,
 5                          numMoved);
 6 
 7         // clear to let GC do its work
 8         int newSize = size - (toIndex-fromIndex);
 9         for (int i = newSize; i < size; i++) {
10             elementData[i] = null;
11         }
12         size = newSize;
13     }

  该方法用于将arraylist实例中某一范围的元素批量删除掉。

  有趣的是,无论操作了多少个元素移动了多少的位置,modCount只会+1,modCount并非指真正的元素被操作的次数,而是记录了某个线程某一时刻对arraylist进行的操作次数,若只移动了一次,无论移动了多少元素多少位置,那么都只记做1次。

  可见modCount确实是用于作检测是否发生了多线程并发的情况。

  该方法的原理是将传入删除的元素下标起始值fromIndex和结束值toIndex,然后将toIndex到size范围的元素往前移动到fromIndex处,再将从size-(toIndex-fromIndex)到size范围的元素迭代置null,便于GC清理空间。

14、Itr内部类---迭代器

 1 private class Itr implements Iterator<E> {
 2         int cursor;       // index of next element to return
 3         int lastRet = -1; // index of last element returned; -1 if no such
 4         int expectedModCount = modCount;
 5 
 6         public boolean hasNext() {
 7             return cursor != size;
 8         }
 9 
10         @SuppressWarnings("unchecked")
11         public E next() {
12             checkForComodification();
13             int i = cursor;
14             if (i >= size)
15                 throw new NoSuchElementException();
16             Object[] elementData = ArrayList.this.elementData;
17             if (i >= elementData.length)
18                 throw new ConcurrentModificationException();
19             cursor = i + 1;
20             return (E) elementData[lastRet = i];
21         }
22 
23         public void remove() {
24             if (lastRet < 0)
25                 throw new IllegalStateException();
26             checkForComodification();
27 
28             try {
29                 ArrayList.this.remove(lastRet);
30                 cursor = lastRet;
31                 lastRet = -1;
32                 expectedModCount = modCount;
33             } catch (IndexOutOfBoundsException ex) {
34                 throw new ConcurrentModificationException();
35             }
36         }
37 
38         final void checkForComodification() {
39             if (modCount != expectedModCount)
40                 throw new ConcurrentModificationException();
41         }
42     }

该内部类用于实现ArrayList的迭代,使用迭代器的好处是提供了一种统一的迭代方式,而且能实现其中安全的迭代方式,避免出现如for循环或foreach循环中调用arrayList的remove()后出现循环次序出现问题或者报ConcurrentModificationException。

调用迭代器后就可以通过迭代器Itr的remove()来对元素进行删除操作。

该内部类主要有三个成员变量:cursor--当前指针指向的元素的下标、lastRet--指针的上一个元素的下标、expectedModCount--期望的modCount值,用于与remove后的modCount值比较,用于判断多线程并发。

另外还有三个常用方法:hasNext()--判断指针指向的是否还有下一个元素、next()--返回指针指向的下一个元素、remove()--用于在迭代过程中安全地将元素从集合中剔除。

还有一个内部使用的功能方法:checkForComodifcation()--用于判断当前的modCount是否与期望值modCount一致,用于判断多线程并发;该方法是final的,不可被子类继承覆盖。

 

hasNext():判断当前指针指向下标是否等于size,若相等说明已到末尾,返回false;否则返回true;

next():先调用checkForComodification()判断modCount是否与期望值expectedModCount相等,若不相等则抛出ConcurrentModificationException,判断此方法调用前是否已经发生了线程并发

 

    随后将cursor的值赋给一个i变量,判断 i 是否>=size,若大于则不符合预期,抛出NoSuchElementException();

    cursor只会在next()方法里自增,于是也只在这个方法里对cursor的增加进行限制,这也是为什么hasNext()仅判断是否等于size,而非大于size的原因;

 

    随后在方法内创建一个引用指向外部Arraylist的成员elementData数组,因为若直接在后续的引用中继续通过默认方式调用外部成员变量elementData的话

    (内部类引用外部成员变量的默认方式是保留外部成员变量的指针从而去调用,即Itr.ArrayList.this.elementData),

    则在后面有两次地方需要用到elementData的引用,又由于用到next()的情景一般是遍历ArrayList的时候用于获取内部元素对象,

    则每次都通过Itr.ArrayList.this.elementData的方式来引用的话,影响每次变量的效率;

 

    接着再次检查 i 是否大于elementData.length,避免在方法开头判断无并发后、获得elementData引用前,发生了线程并发,导致elementData元素被删除后数组长度减少;

    若不判断直接返回获得的elementData数组的当前下标 i 的元素的话,则会报数组下标越界异常了。

 

    

    (一个简单的返回数组元素对象引用的功能,源码进行了3次数据校核,可见源码的编写的严谨度和考虑范围,学习了!)

 

    以上均无异常后,将cursor赋值为i+1(局部变量i保存着方法开头时的cursor值,尽可能减少线程的并发所带来的后果),

    随后return elementData[lastRet = i],此句子也很经典;

 

    Ps: 此方法前加了一个注解@SuppressWarnings,该注解的作用是(摘抄自百度):

      J2SE提供的最后一个批注是 @SuppressWarnings。该批注的作用是给编译器一条指令,告诉它对被批注的代码元素内部的某些警告保持静默。

      @SuppressWarning批注允许您选择性地取消特定代码段(即,类或方法)中的警告。其中的想法是当您看到警告时,您将调查它,如果您确定它不是问题,您就可以添加一个 @SuppressWarnings 批注,以使您不会再看到警告。虽然它听起来似乎会屏蔽潜在的错误,但实际上它将提高代码安全性,因为它将防止您对警告无动于衷 — 您看到的每一个警告都将值得注意

remove():

  方法开头对lastRet的合法性进行了判断--是否<0,因为lastRet初始值及每次remove后的值都为-1,若lastRet<0,说明指针状态要么未进行过第一次移动(即next()),要么在remove后未进行next指针移动,

  无论是第一次未进行next指针移动或是remove完后未移动指针,直接再次remove都是不符合预期要求的,因未下一个元素是否存在还未可知。

 

  方法随后对modCount和期望值进行校核,确定无并发。

  

  随后再写一个try语句块,调用ArrayList的remove()方法将lastRet下标的元素删除,

  再将cursor置为lastRet,将lastRet置-1,将expectedModCount 置 modCount(因为remove()方法此时已经改变了modCount值)。

  

  在catch块中捕获数组下标越界异常IndexOutOfBoundsException,抛出ConcurrentModificationException(因为若下标越界则说明删除过程中有另一个线程对elementData中的元素进行了改动,导致下标可能不存在了)。

 

15、ListItr

 ListItr是上面Itr迭代器的子类,相比于Itr的指针只能单向移动,其是Itr的功能扩展版,在ListItr中指针移动更为灵活,不仅可向前后移动,还可随意指定指针的位置;同时增加了元素的增加操作。

 我们来看下源码:

 1 private class ListItr extends Itr implements ListIterator<E> {
 2         ListItr(int index) {
 3             super();
 4             cursor = index;
 5         }
 6 
 7         public boolean hasPrevious() {
 8             return cursor != 0;
 9         }
10 
11         public int nextIndex() {
12             return cursor;
13         }
14 
15         public int previousIndex() {
16             return cursor - 1;
17         }
18 
19         @SuppressWarnings("unchecked")
20         public E previous() {
21             checkForComodification();
22             int i = cursor - 1;
23             if (i < 0)
24                 throw new NoSuchElementException();
25             Object[] elementData = ArrayList.this.elementData;
26             if (i >= elementData.length)
27                 throw new ConcurrentModificationException();
28             cursor = i;
29             return (E) elementData[lastRet = i];
30         }
31 
32         public void set(E e) {
33             if (lastRet < 0)
34                 throw new IllegalStateException();
35             checkForComodification();
36 
37             try {
38                 ArrayList.this.set(lastRet, e);
39             } catch (IndexOutOfBoundsException ex) {
40                 throw new ConcurrentModificationException();
41             }
42         }
43 
44         public void add(E e) {
45             checkForComodification();
46 
47             try {
48                 int i = cursor;
49                 ArrayList.this.add(i, e);
50                 cursor = i + 1;
51                 lastRet = -1;
52                 expectedModCount = modCount;
53             } catch (IndexOutOfBoundsException ex) {
54                 throw new ConcurrentModificationException();
55             }
56         }
57     }

  该内部类相比于Itr,多了以下这些内容:

  构造函数:ListItr(int index),传入一个int型参数,可将指针移至对应下标元素处。

  一般方法:hasPrevious(): 判断是否还有上一个元素。通过判断cursor是否等于0来实现。

         nextIndex():返回下一个元素的下标--cursor

       previousIndex():返回上一个元素的下标--cursor-1,此处不用lastRet的原因是当remove()方法被调用后,lastRet会被置为-1;

         previous():与next()类似,经过校核后,将cursor赋值为cursor-1,返回elementData[lastRet = i]。

             注意,在指针向后移动过程中,lastRet比cursor小1,而在向前移动的过程中,lastRet与cursor相等。

        set():确定lastRet不小于0后,同时确定没有modCount与期待值不同的情况后,调用ArrayList.this.set(lastRet,e)设置值。

       add():判断modCount是否等于期待值,确定后调用ArrayList.this.add(i, e),将入参对象add到集合中,并同时将指针向下移动一位,并同时重置lastRet为-1。

           操作完成后将expectedModCount期待值重新复制为最新的modCount。

16、SubList子类

   subList也是一个ArrayList中比较重要的内部类,它继承了RandomAccess接口,这是一个标记接口(Marker),它没有任何方法,这个接口被List的实现类(子类)使用。如果List子类实现了RandomAccess接口,那就表示它能够快速随机访问存储的元素。RandomAccess接口的意义在于:在对列表进行随机或顺序访问的时候,访问算法能够选择性能最佳方式。

  在sublist中有4个成员变量:parent--指向父集的引用、parentOffset--在上一层父集数组中的截取子集的起始下标索引值,offset--在真正操作的数组elementData中被截取的子集的下标索引值、size--集合元素个数

  比如在一个ArrayList中先截取的一个子集sublist1中再截取了一个子集sublist2,sublist1截取的是ArrayList的10-30部分元素,则sublist1的parentOffset为10,offset为10;此时sublist2再从sublist1中截取10-20,那么sublist2的parentoffset仍为10,

但offset为20。此两项参数用于确认sublist所截取的子集在数组中存放的真正位置。

  subList中包含set()、get()、size()、add()、remove()、迭代器以及subList()等方法及内部类,实现了如同substring一般的截取功能,并将原集合的特性保留了下来。但是有一点需要注意的是sublist对元素的操作本质上还是对其父集的元素数组的操作,

与substring()后返回的一个新的字符串不同,subList返回的是一个对父集元素数组的引用。因此对子集的任何结构性变化操作都会真实地影响的父集,所以在运用ArrayList子集SubList的过程中要谨慎

具体源码如下:

  1 public List<E> subList(int fromIndex, int toIndex) {
  2         subListRangeCheck(fromIndex, toIndex, size);
  3         return new SubList(this, 0, fromIndex, toIndex);
  4     }
  5 
  6     static void subListRangeCheck(int fromIndex, int toIndex, int size) {
  7         if (fromIndex < 0)
  8             throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
  9         if (toIndex > size)
 10             throw new IndexOutOfBoundsException("toIndex = " + toIndex);
 11         if (fromIndex > toIndex)
 12             throw new IllegalArgumentException("fromIndex(" + fromIndex +
 13                                                ") > toIndex(" + toIndex + ")");
 14     }
 15 
 16     private class SubList extends AbstractList<E> implements RandomAccess {
 17         private final AbstractList<E> parent;
 18         private final int parentOffset;
 19         private final int offset;
 20         int size;
 21 
 22         SubList(AbstractList<E> parent,
 23                 int offset, int fromIndex, int toIndex) {
 24             this.parent = parent;
 25             this.parentOffset = fromIndex;
 26             this.offset = offset + fromIndex;
 27             this.size = toIndex - fromIndex;
 28             this.modCount = ArrayList.this.modCount;
 29         }
 30 
 31         public E set(int index, E e) {
 32             rangeCheck(index);
 33             checkForComodification();
 34             E oldValue = ArrayList.this.elementData(offset + index);
 35             ArrayList.this.elementData[offset + index] = e;
 36             return oldValue;
 37         }
 38 
 39         public E get(int index) {
 40             rangeCheck(index);
 41             checkForComodification();
 42             return ArrayList.this.elementData(offset + index);
 43         }
 44 
 45         public int size() {
 46             checkForComodification();
 47             return this.size;
 48         }
 49 
 50         public void add(int index, E e) {
 51             rangeCheckForAdd(index);
 52             checkForComodification();
 53             parent.add(parentOffset + index, e);
 54             this.modCount = parent.modCount;
 55             this.size++;
 56         }
 57 
 58         public E remove(int index) {
 59             rangeCheck(index);
 60             checkForComodification();
 61             E result = parent.remove(parentOffset + index);
 62             this.modCount = parent.modCount;
 63             this.size--;
 64             return result;
 65         }
 66 
 67         protected void removeRange(int fromIndex, int toIndex) {
 68             checkForComodification();
 69             parent.removeRange(parentOffset + fromIndex,
 70                                parentOffset + toIndex);
 71             this.modCount = parent.modCount;
 72             this.size -= toIndex - fromIndex;
 73         }
 74 
 75         public boolean addAll(Collection<? extends E> c) {
 76             return addAll(this.size, c);
 77         }
 78 
 79         public boolean addAll(int index, Collection<? extends E> c) {
 80             rangeCheckForAdd(index);
 81             int cSize = c.size();
 82             if (cSize==0)
 83                 return false;
 84 
 85             checkForComodification();
 86             parent.addAll(parentOffset + index, c);
 87             this.modCount = parent.modCount;
 88             this.size += cSize;
 89             return true;
 90         }
 91 
 92         public Iterator<E> iterator() {
 93             return listIterator();
 94         }
 95 
 96         public ListIterator<E> listIterator(final int index) {
 97             checkForComodification();
 98             rangeCheckForAdd(index);
 99             final int offset = this.offset;
100 
101             return new ListIterator<E>() {
102                 int cursor = index;
103                 int lastRet = -1;
104                 int expectedModCount = ArrayList.this.modCount;
105 
106                 public boolean hasNext() {
107                     return cursor != SubList.this.size;
108                 }
109 
110                 @SuppressWarnings("unchecked")
111                 public E next() {
112                     checkForComodification();
113                     int i = cursor;
114                     if (i >= SubList.this.size)
115                         throw new NoSuchElementException();
116                     Object[] elementData = ArrayList.this.elementData;
117                     if (offset + i >= elementData.length)
118                         throw new ConcurrentModificationException();
119                     cursor = i + 1;
120                     return (E) elementData[offset + (lastRet = i)];
121                 }
122 
123                 public boolean hasPrevious() {
124                     return cursor != 0;
125                 }
126 
127                 @SuppressWarnings("unchecked")
128                 public E previous() {
129                     checkForComodification();
130                     int i = cursor - 1;
131                     if (i < 0)
132                         throw new NoSuchElementException();
133                     Object[] elementData = ArrayList.this.elementData;
134                     if (offset + i >= elementData.length)
135                         throw new ConcurrentModificationException();
136                     cursor = i;
137                     return (E) elementData[offset + (lastRet = i)];
138                 }
139 
140                 public int nextIndex() {
141                     return cursor;
142                 }
143 
144                 public int previousIndex() {
145                     return cursor - 1;
146                 }
147 
148                 public void remove() {
149                     if (lastRet < 0)
150                         throw new IllegalStateException();
151                     checkForComodification();
152 
153                     try {
154                         SubList.this.remove(lastRet);
155                         cursor = lastRet;
156                         lastRet = -1;
157                         expectedModCount = ArrayList.this.modCount;
158                     } catch (IndexOutOfBoundsException ex) {
159                         throw new ConcurrentModificationException();
160                     }
161                 }
162 
163                 public void set(E e) {
164                     if (lastRet < 0)
165                         throw new IllegalStateException();
166                     checkForComodification();
167 
168                     try {
169                         ArrayList.this.set(offset + lastRet, e);
170                     } catch (IndexOutOfBoundsException ex) {
171                         throw new ConcurrentModificationException();
172                     }
173                 }
174 
175                 public void add(E e) {
176                     checkForComodification();
177 
178                     try {
179                         int i = cursor;
180                         SubList.this.add(i, e);
181                         cursor = i + 1;
182                         lastRet = -1;
183                         expectedModCount = ArrayList.this.modCount;
184                     } catch (IndexOutOfBoundsException ex) {
185                         throw new ConcurrentModificationException();
186                     }
187                 }
188 
189                 final void checkForComodification() {
190                     if (expectedModCount != ArrayList.this.modCount)
191                         throw new ConcurrentModificationException();
192                 }
193             };
194         }
195 
196         public List<E> subList(int fromIndex, int toIndex) {
197             subListRangeCheck(fromIndex, toIndex, size);
198             return new SubList(this, offset, fromIndex, toIndex);
199         }
200 
201         private void rangeCheck(int index) {
202             if (index < 0 || index >= this.size)
203                 throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
204         }
205 
206         private void rangeCheckForAdd(int index) {
207             if (index < 0 || index > this.size)
208                 throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
209         }
210 
211         private String outOfBoundsMsg(int index) {
212             return "Index: "+index+", Size: "+this.size;
213         }
214 
215         private void checkForComodification() {
216             if (ArrayList.this.modCount != this.modCount)
217                 throw new ConcurrentModificationException();
218         }
219     }

 

 

 

ArrayList的源码学习大致就到这里,若有不足还请各位指正!

posted @ 2017-02-28 08:55  je_perds  阅读(508)  评论(0编辑  收藏  举报