zoukankan      html  css  js  c++  java
  • Java集合框架分析(List)——ArrayList类详解

    ArrayList类中的方法与实现原理

    目录


    一.数据结构

    ArrayList的底层数据结构就是一个数组,数组元素的类型为Object类型,对ArrayList的所有操作底层都是就与数组的。
    对ArrayList进行添加元素的操作的时候是分两个步骤进行的,即第一步先将Object[size]的位置上存放需要添加的元素;第二步将size的值加1。由于这个过程在多线程的环境下是不能保证具有原子性的,因此ArrayList在多线程的环境下是线程不安全的。
    如果非要在多线程的环境下使用ArrayList,就需要保证它的线程安全性,通常有两种解决办法:第一,使用synchronized关键字;第二,可以用Collections类中的静态方法synchronizedList();对ArrayList进行调用。

    二.类标题

    ArrayList类的标题如下:

    public class ArrayList extends AbstractList implements List,Cloneable,java.io.Serializable

    这个标题说明ArrayList类是AbstractList类的子类,并且实现了三个接口:List、Cloneable和Serializable。如下图所示:

    [Q1]为什么ArrayList继承了AbstractList还要实现List接口

    Serializable接口源码:

    package java.io;
    
    
    public interface Serializable
    {
    }
    

    凡是实现Serializable接口(java.io)的类都可以进行序列化和反序列化操作

    [Q2]:Serializable为什么要定义一个空接口来进行标识

    [Q3]:如何理解: implements Cloneable表示该对象能被克隆,能使用Object.clone()方法。如果没有implements Cloneable的类调用Object.clone()方法就会抛出CloneNotSupportedException。


    三.字段

    private transient Object elementData[];
    private int size;//数组中元素的个数
    protected transient int modCount = 0; //继承自AbstractList

    【注】:成员变量modCount记录着集合的修改次数,也就是每次add或者remove它的值都会加1


    四.构造函数

    1.public ArrayList(int initialCapacity);
    作用:ArrayList类的构造方法,根据传入的initialCapacity值创建一个initialCapacity大小的数组。 源码如下:
        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);
            }
        }
    

    源码解析:
    传入initialCapacity为0,创建空数组,否则,创建intialCapacity大小的数组elementData

    2.public ArrayList();

    作用:ArrayList类的构造方法,创建一个空的数组。在第一次添加元素时容量扩大到10
    源码如下:

        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    

    源码分析:创建一个空数组

    3.public ArrayList(Collection c);

    作用:使用指定Collection来构造ArrayList的构造函数
    源码如下:

        public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();//将传入的集合转换为一个数组,并将数组的应用赋给elementData
            if ((size = elementData.length) != 0) {//数组不为空
                // defend against c.toArray (incorrectly) not returning Object[]
                if (elementData.getClass() != Object[].class)//数组不为Object[],将其复制成为Object
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {//数组为空的情况
                // replace with empty array.
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }
    

    分析:
    1.将传入的集合转换为数组
    2.判断转换后的数组是否为空
    若为空:创建一个空的elementData数组
    若不为空,且转换后的数组类型不为Object[]类型,将其复制为符合Object[]数组类型的elementData数组


    五.其他方法

    1.public boolean add(Object o);
    作用:将指定元素追加到列表的末尾。返回值是true
    源码如下:
        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  //判断是否需要扩容
            elementData[size++] = e;    //将元素插入到已有元素后一个位置
            return true;
        }
        
        //将预计插入元素后所需最小容量传入EnsureCapacityInternal
        private void ensureCapacityInternal(int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//如果调用的无参构造方法(创建空数组),minCapacity=10
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
    
            ensureExplicitCapacity(minCapacity);
        }
    
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // 如果传入的预计插入后的最小容量大于当前数组的容量,用grow进行扩容
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    
        //对elementData[]进行扩容,每次增加1/2 elementData数组长度。
        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:
            // 调用 Arrays.copyOf 方法将 elementData 数组指向新的内存空间 newCapacity 的连续空间
             // 并将 elementData 的数据复制到新的内存空间
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    

    源码分析:
    ArrayList集合add()过程如下图所示:

    [Q4]add(E e)方法为什么总是返回true?
    不理解:[此方法总是返回true,是因为在Collection接口中对应的方法返回的是一个boolean值:如果调用对象所属类允许重复,或者被添加的元素尚不再调用对象中,就返回true,否则返回false.有些Collections接口的实现就不允许重复]

    【注】Max_ARRAY_SIZE定义为private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE-8 其中,Integer.MAX_VALUE是Integer类中的一个int类型的常量MAX_VALUE,它代表int所能表示的最大值Ox7FFFFFFF

    2.public void add(int index,Object element);

    作用:将指定元素插入此列表的指定位置。将当前位置的元素(如果有)和任意后续元素向右移动。

    源码如下:

    //elementData数组在索引index处插入元素
    public void add(int index, E element) {
        rangeCheckForAdd(index);//检查index索引是否越界
        ensureCapacityInternal(size + 1);  //扩容,源码在1.public boolean add(Object o)c处
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        elementData[index] = element;
        size++;
    }
    
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
             throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
    //java.lang.System
    /*
        @Function:复制数组,以插入元素,但是要将index之后的元素都往后移一位。然后就是插入元素,增加sized的值
        @src:源数组  srcPos:源数组要复制的起始位置   dest:目的数组  destPos:目的数组放置的起始位置  length:复制的长度
    */
    public static native void arraycopy(Object src,int srcPos,Object dest, int destPosint length);
    

    源码分析:

    1.边界检查。检查传入的index索引是否越界。

    2.容量判断。判断数组是否有足够空间接收元素,不够的话进行扩容操作。

    3.右移。对索引后的元素进行右移一位操作。

    4.插入元素。将新元素插入到数组索引处。

    5.记录数组元素个数的size+1.

    System.arraycopy(elementData, index, elementData, index + 1, size - index);

    复制源数组elementData从index处开始size-index长度(复制index后面的元素)到elementData数组的index+1位置。图示如下:

    [Q5]native关键字的使用

    3.public boolean addAll(Collection c);

    作用:将指定集合中的所有元素插入此列表末尾

    源码如下:

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // 同4.public boolean add(Object o)分析,确保数组容量足够
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
    

    源码分析:

    1.将集合转换为数组。

    2.确保加入新数组元素后,数组容量足够

    3.将转换后的数组添加到已有数组末尾

    4.记录数组元素个数的size+numNew

    System.arraycopy(a, 0, elementData, size, numNew);

    复制a数组从索引0-numNew的元素到elementData数组的size位置。源码及分析在2.public void add(int index,Object element)处;

    4.public boolean addAll(int index,Collection c);

    作用:从指定位置开始,将指定集合中的所有元素插入此列表。将当前位置的元素元素往后移。参数列表中两个参数,第一个参数,填写数组下标,第二个参数,直接填写要被插入的数组名即可。

    源码如下:

     public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);//索引边界检查,源码在);
    
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // 确保容量足够,源码在1.public boolean add(Object o);
    
        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                                 numMoved);//源码在2.public void add(int index,Object element)
    
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }
    

    源码分析:

    1.索引边界检查

    2.将要插入的集合转换为数组

    3.确保加入新数组元素后,数组容量足够

    4.计算已有数组在index后的元素个数

    5.将已有elementData数组index后的numMoved位元素右移numNew位

    6.将常茹为numNew位的新数组插入到index的位置

    7.记录elementData数组元素个数的size+numNew

    5.public void clear();

    作用:从此列表中删除所有元素。

    源码如下:

    public void clear() {
        modCount++;
    
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
    
        size = 0;
    }
    

    源码分析:
    成员变量modCount记录集合的修改次数。clear()方法原理即将elementData数组的所有元素赋空值。

    6.public Object clone();

    作用:返回此ArrayList实例的浅表副本。若要赋值给一个对象,需要强制转型。
    源码如下:

    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 Cloneable
            throw new InternalError(e);
        }
    }
    
    //Arrays.copyOf().Arrays的copyOf()方法传回的数组是新的数组对象,改变传回数组中的元素值,不会影响原来的数组。copyOf()的第二个自变量指定要建立的新数组长度,如果新数组的长度超过原数组的长度,则保留数组默认值
    public static byte[] copyOf(byte[] original, int newLength) {//original 需要复制的源数组
        byte[] copy = new byte[newLength];
        System.arraycopy(original, 0, copy, 0,
                             Math.min(original.length, newLength));
        return copy;
    }
    
    //from java.lang.Object
    protected native Object clone() throws CloneNotSupportedException;
    

    浅克隆:将新集合每个元素的引用指向原集合对应对象在栈空间的内存地址,所以,原集合修改或删除,克隆的集合随之改变;新集合修改添加会改变引用重新指向其他堆内存地址,删除就直接删除引用。

    深克隆:将新集合的元素对象复制,在堆内存中重新开辟空间存一样的内容,一般要对集合中的对象重写clone(),在clone()中返回new的新对象,再add到新集合中,所以新旧集合操作互不影响。

    源码分析:

    ArrayList中的clone方法继承自Object类。ArrayList实现的clone()为浅克隆,即复制对象的引用。

    1.利用Object类中的clone(),复制elementData数组的引用v

    2.将已有数组elementData数组元素复制给v指向的elementData数组

    [Q6]super.clone()进行了什么操作,v.elementData表示什么?

    7.public boolean contains(Object elem);

    作用:如果此列表中包含此元素,则返回true
    源码如下:

    public boolean contains(Object o) {
        return indexOf(o) >= 0;  //indexOf(Object o)方法同15.public int indexOf(Object elem);
    }
    

    源码分析:
    查找elementData数组中是否包含o元素。
    若包含,返回元素所在数组单元的索引值;
    若不包含,返回-1.

    8.public boolean containsAll(Collection c);

    作用:方法用于检测 arraylist 是否包含指定集合中的所有元素。
    源码如下:

    public boolean containsAll(Collection<?> c) {
        for (Object e : c)
            if (!contains(e))//源码在7.public boolean contains(Object elem);
                return false;
        return true;
    }
    

    源码分析:
    ArrayList类的containsAll(Collection c)继承自AbstractCollection类。
    该方法用for-each遍历c集合,同时查看c集合中是否包含e元素。
    若包含,则返回ture.否则返回false.

    9.public boolean equals(Object o);

    作用:这里调用的equals方法并不是Object类中的。ArrayList继承了AbstractList< E>,它的equals方法应该从这个抽象类中来的。发现该类确实重写了equals方法,就是使用迭代器遍历两个List每个元素是否相等。 那么ArrayList调用equals方法就是元素进行比较了。
    源码如下:

    //inheried from java.util.AbstractList
    public boolean equals(Object o) {
        if (o == this)//如果o为调用此方法的对象,则返回true
            return true;
        if (!(o instanceof List))//若对象o不是List对象,则返回false
            return false;
    
        //定义两个List迭代器用来遍历元素
        ListIterator<E> e1 = listIterator();
        ListIterator<?> e2 = ((List<?>) o).listIterator();
        while (e1.hasNext() && e2.hasNext()) {//当两个迭代器均有下一个元素
            E o1 = e1.next();
            Object o2 = e2.next();
            if (!(o1==null ? o2==null : o1.equals(o2)))
                return false;
        }
        return !(e1.hasNext() || e2.hasNext());
    }
    

    源码分析:

        if (!(o1==null ? o2==null : o1.equals(o2)))
    
            return false;
    

    若o1为空,返回o2 == null.则变为if(!(o2==null));此时,若o2不为null,则直接返回false.

    若o1非空,返回o1.equals(o2).则变为if(!(o1.equals(o2)))【注】此处的equals()方法应为Object类中的equals()方法.若o1与o2不等,则返回false.

    return !(e1.hasNext() || e2.hasNext());

    若e1和e2都遍历完了,未返回false,则认定equals()返回true.

    10.public Object get(int index);

    作用:返回此列表中指定位置的元素
    源码如下:

    public E get(int index) {
        rangeCheck(index);//检查索引是否越界,源码在2.public void add(int index,Object element)处
    
        return elementData(index);
    }
    
    E elementData(int index) {
        return (E) elementData[index];
    }
    

    源码分析:
    1.检查索引是否越界
    2.返回elementData数组指定索引index位置的元素。

    11.public int hashCode();

    作用:求ArrayList的哈希值,这个方法是ArrayList的抽象父类AbstractList中的方法
    源码如下:

    //inherited from java.util.AbstractList
    public int hashCode() {
        int hashCode = 1;
        for (E e : this)
            hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
        return hashCode;
    }
    

    源码分析:
    在JAVA语言中,判断两个对象是否相等,一般有两种方法,一种是hashcode(),另一种是equals(),这两个方法在判断准确性和效率上有很大的区别。hashCode()方法和equal()方法的作用其实一样,在Java里都是用来对比两个对象是否相等一致,那么equal()既然已经能实现对比的功能了,为什么还要hashCode()呢?
    因为重写的equal()里一般比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高,那么hashCode()既然效率这么高为什么还要equal()呢?
    因为hashCode()并不是完全可靠,有时候不同的对象他们生成的hashcode也会一样(生成hash值得公式可能存在的问题),所以hashCode()只能说是大部分时候可靠,并不是绝对可靠。

    12.public int indexOf(Object elem);

    作用:返回此列表中元素第一次出现的位置下标,如果没有此元素则返回-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;
    }
    

    源码分析:
    ArrayList类中indexOf(Object elem)重写了AbstractList类中的方法。

    1.判断传入元素是否为空

    若为空,遍历数组元素,用“==”找出数组单元元素为空所在的索引

    若不为空,遍历数组元素,用“equals()”找出数组中与传入元素内容相同的单元所在的索引

    否则,返回-1。

    13.public boolean isEmpty();

    作用:判断此列表中是否为空,空则返回true,否则返回false
    源码如下:

    public boolean isEmpty() {
        return size == 0;
    }
    

    源码分析:
    如果记录数组元素个数的size==0,表示数组中没有元素,所以返回true.否则,返回false.

    14.public Iterator iterator();

    作用:返回当前集合的双向迭代器
    每个实现Iterable接口的类必须提供一个iterator方法,返回一个Iterator对象,ArrayList也不例外
    源码如下:

    public Iterator<E> iterator() {
            return new Itr();
    }
    

    返回的是一个Itr类的对象,接下来我们来看它的源码

    private class Itr implements Iterator<E> {
        int cursor = 0;//指向下一个要被迭代的元素
        int lastRet = -1;//指向当前元素
        int expectedModCount = modCount;//在迭代器初始化过程中会将modCount赋给迭代器的expectedModCount。在迭代过程中,判断 modCount 跟 expectedModCount 是否相等,如果不相等就表示通过其他方法修改了 ArrayList的结构
    
        public boolean hasNext() {
            return cursor != size();
        }
    
        public E next() {//将cursor指向下一个被迭代的元素,lastRet指向当前元素
            checkForComodification();//检查是否有线程在修改数组结构
            try {
                int i = cursor;
                E next = get(i);
                lastRet = i;
                cursor = i + 1;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }
    
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
    
            try {
                AbstractList.this.remove(lastRet);//删除lastRet指向的数组单元
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }
        //有两个线程(线程A,线程B),其中线程A负责遍历list、线程B修改list。
    -线程A在遍历list过程的某个时候(此时expectedModCount = modCount=N),线程启动,
    同时线程B增加一个元素,这是modCount的值发生改变(modCount + 1 = N + 1)。 线程A继续遍历执行next方法时,
    通告checkForComodification方法发现expectedModCount = N , 而modCount = N + 1,两者不等
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
    

    源码分析:

    每个实现Iterable接口的类必须提供一个iterator方法,返回一个Iterator对象,ArrayList也不例外

    cursor--;
    expectedModCount = modCount;

    如图所示,Cursor初始状态是指向5,即若调用ArrayList的迭代器使用next()遍历数组,在遍历途中使用ArrayList.remove(1),则会跳原本应该遍历的5,直接遍历到6.采用上述代码后,它在每一次删除之后都会将cursor(下一项)的位置设置为当前位置,也就是将cursor往前移动了一位,之后再将modCount赋值给expectedModCount使它们保持相等。

    15.public int lastindexOf(Object elem);

    作用:返回此列表中元素最后一次出现的位置下标,如果没有则返回-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;
    }
    

    源码分析:

    若查找数组中指定元素最后一次出现的位置下标,从后往前遍历elementData数组,找到该元素第一次出现位置的索引值。

    16.public ListIterator listIterator();

    作用:从0开始,按当前集合中现有的元素进行迭代(双向迭代)
    源码如下:

    
    //inherited from java.util.AbstractList
    public ListIterator<E> listIterator() {
        return listIterator(0);
    }
    
    public ListIterator<E> listIterator(final int index) {
        rangeCheckForAdd(index);//检查索引是否越界
    
        return new ListItr(index);//返回ListIter对象
    }
    
    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            cursor = index;//cursor表示当前元素索引
        }
    
        public boolean hasPrevious() {
            return cursor != 0;
        }
    
        public E previous() {//返回当前元素的前一个元素
            checkForComodification();
            try {
                int i = cursor - 1;
                E previous = get(i);
                lastRet = cursor = i;
                return previous;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }
    
        public int nextIndex() {//返回当前元素后一个元素的索引值
            return cursor;
        }
    
        public int previousIndex() {//返回当前元素前一个元素的索引值
            return cursor-1;
        }
    
        public void set(E e) {//修改当前对象的属性
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
    
            try {
                AbstractList.this.set(lastRet, e);
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    
        public void add(E e) {//向数组中添加对象
            checkForComodification();
    
            try {
                int i = cursor;
                AbstractList.this.add(i, e);
                lastRet = -1;
                cursor = i + 1; //分析类似17.public Iterator iterator();
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }
    

    源码分析:

    Iterator与ListIterator代码结构大同小异。其主要区别体现在以下:

    (1)ListIterator有add()方法,可以向List中添加对象,而Iterator不能

    (2)ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。

    (3)ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。

    (4)都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。

    17.public ListIterator listIterator(final int index);

    作用:返回指定下标(包含该下标)后的值
    源码如下:

     public ListIterator<E> listIterator(final int index) {
        checkForComodification();//判断是否有线程修改数组
        rangeCheckForAdd(index);//判断索引是否越界
        final int offset = this.offset;
    
        return new ListIterator<E>() {
            int cursor = index;
            int lastRet = -1;
            int expectedModCount = ArrayList.this.modCount;
    
            public boolean hasNext() {//是否存在下一元素
                return cursor != SubList.this.size;
            }
    
            @SuppressWarnings("unchecked")
            public E next() {
                checkForComodification();
                int i = cursor;
                if (i >= SubList.this.size)
                    throw new NoSuchElementException();
                Object[] elementData = ArrayList.this.elementData;
                if (offset + i >= elementData.length)
                    throw new ConcurrentModificationException();
                cursor = i + 1;
                return (E) elementData[offset + (lastRet = i)];
            }
    
            public boolean hasPrevious() {
                return cursor != 0;
            }
    
            @SuppressWarnings("unchecked")
            public E previous() {
                checkForComodification();
                int i = cursor - 1;
                if (i < 0)
                    throw new NoSuchElementException();
                Object[] elementData = ArrayList.this.elementData;
                if (offset + i >= elementData.length)
                    throw new ConcurrentModificationException();
                cursor = i;
                return (E) elementData[offset + (lastRet = i)];
            }
    
            @SuppressWarnings("unchecked")
            public void forEachRemaining(Consumer<? super E> consumer) {
                Objects.requireNonNull(consumer);
                final int size = SubList.this.size;
                int i = cursor;
                if (i >= size) {
                    return;
                }
                final Object[] elementData = ArrayList.this.elementData;
                if (offset + i >= elementData.length) {
                    throw new ConcurrentModificationException();
                }
                while (i != size && modCount == expectedModCount) {
                    consumer.accept((E) elementData[offset + (i++)]);
                }
                // update once at end of iteration to reduce heap write traffic
                lastRet = cursor = i;
                checkForComodification();
            }
    
            public int nextIndex() {
                return cursor;
            }
    
            public int previousIndex() {
                return cursor - 1;
            }
    
            public void remove() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                checkForComodification();
    
                try {
                    SubList.this.remove(lastRet);
                    cursor = lastRet;
                    lastRet = -1;
                    expectedModCount = ArrayList.this.modCount;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
    
            public void set(E e) {
                if (lastRet < 0)
                    throw new IllegalStateException();
                checkForComodification();
    
                try {
                    ArrayList.this.set(offset + lastRet, e);
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
    
            public void add(E e) {
                checkForComodification();
    
                try {
                    int i = cursor;
                    SubList.this.add(i, e);
                    cursor = i + 1;
                    lastRet = -1;
                    expectedModCount = ArrayList.this.modCount;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
    
            final void checkForComodification() {
                if (expectedModCount != ArrayList.this.modCount)
                    throw new ConcurrentModificationException();
            }
        };
    }
    

    源码分析:

    18.public boolean remove(Object o);

    作用:移除指定元素
    源码如下:

        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;
        }
        
          //快速删除es数组的第i个元素
         private void fastRemove(Object[] es, int i) {
            modCount++;
            final int newSize;
            if ((newSize = size - 1) > i)
                System.arraycopy(es, i + 1, es, i, newSize - i);
            es[size = newSize] = null;
        }
    

    19.public Object remove(int index);

    作用:删除指定位置的元素,后续元素往前补
    源码如下:

        public E remove(int index) {
            Objects.checkIndex(index, size);
            final Object[] es = elementData;
    
            @SuppressWarnings("unchecked") E oldValue = (E) es[index];
            fastRemove(es, index);
    
            return oldValue;
        }
    

    20.public boolean removeAll(Collection c);

    作用:删除指定集合在此列表中的所有元素
    源码如下:

    //inherited from AbstractCollection
        public boolean removeAll(Collection<?> c) {
            Objects.requireNonNull(c);//判断要移除的集合是否为空
            return batchRemove(c, false);
        }
    
        /*
            @parameter:Collection c:目标集合
                        boolean complement:当前集合是否需要包含目标集合
                        false,则删除目标集合在当前集合中所存在的元素
                        true,则删除目标集合在当前集合中不纯在的元素
        */
        private boolean batchRemove(Collection<?> c, boolean complement)//
            final Object[] elementData = this.elementData;//定义当前数组对象
            int r = 0, w = 0;
            // r 在外层定义for循环所用下标,目的是为了判断有没有报错,如果报错了,r就!=size,如果没有报错r==size
            // w 进行操作的次数
            boolean modified = false;//定义成功标识
            try {
                for (; r < size; r++)
                    if (c.contains(elementData[r]) == complement)//目标集合中元素存在或不存在  在当前集合中    
                        elementData[w++] = elementData[r];//将不需要移除的元素放入elementData数组中
            } finally {
                // Preserve behavioral compatibility with AbstractCollection,
                // even if c.contains() throws.
                if (r != size) {//表示报错了,与ArrayList的父类保持一致,当c.contains()这个方法抛出异常才会r != size
                    System.arraycopy(elementData, r,
                                     elementData, w,
                                     size - r);//复制数组,从报错下标开始复制到 w下标(最后被修改的下标),复制长度是未成功循环的长度
                    w += size - r;//因为复制后数组长度就变了,所以需要求出目前数组的长度,w+ 复制的长度  
                }
                if (w != size) {// 表示原数组有删除了数据    
                    // clear to let GC do its work
                    for (int i = w; i < size; i++)
                        //从w开始循环,循环到size,这些数据是要删除的数据,设为null
                        elementData[i] = null;
                    modCount += size - w;//修改操作次数计数 size-w表示删除的数量
                    size = w; //将长度设为新的长度
                    modified = true;
                }
            }
            return modified;
        }
    

    源码分析:
    1.判断要移除的集合是否为null,如果为空则抛出null异常

    2.如果不为null,则调用batchRemove方法即可移除成功

    2.1. 通过contains方法,将所有不需要移除的元素放入elementData数组中,然后标记最后一个放入元素的位置w

    2.2. 将所有需要移除的元素设置为null

    2.3. 将size的大小设置为w

    2.4. 修改modCount字段(该阻断主要是为了防止多线程模式下安全问题)

    2.5. 返回移除成功的标识

    21.public boolean retainAll(Collection c);

    作用:原数组和参数数组做交集,保留相同的部分。
    源码如下:

    //inherited from AbstractCollection
    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);//源码在20.public boolean removeAll(Collection c)处
    }
    
    

    源码分析:
    ArrayList类中的retainAll(Collection c)重写了AbstractCollection中的此方法
    private boolean batchRemove(Collection<?> c, boolean complement)部分源码:

    for (; r < size; r++)
    if (c.contains(elementData[r]) == complement)//目标集合中元素存在或不存在 在当前集合中
    elementData[w++] = elementData[r];//将不需要移除的元素放入elementData数组中

    removeAll()传入batchRemove中complement为false,表示将不存在的元素放入elementData数组
    retainAll()传入batchRemove中complement为true,表示将存在的元素放入elementData数组中

    22.public Object set(int index,Object element);

    作用:用指定元素替换指定位置的元素。
    源码如下:

    public E set(int index, E element) {
        rangeCheck(index);//检查索引是否越界
    
        E oldValue = elementData(index);//将需要修改位置的值赋给oldValue
        elementData[index] = element;//将element覆盖初值
        return oldValue;
    }
    

    23.public int size();

    作用:返回此列表中的元素数
    源码如下:

    public int size() {
        return size;
    }
    

    24.public List subList(int fromIndex,int toIndex);

    作用:只返回从fromIndex到toIndex之间的数据元素。
    源码如下:

        public List<E> subList(int fromIndex, int toIndex) {
            subListRangeCheck(fromIndex, toIndex, size);//索引边界检查
            return new SubList(this, 0, fromIndex, toIndex);
        }
    
        static void subListRangeCheck(int fromIndex, int toIndex, int size) {
            if (fromIndex < 0)
                throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
            if (toIndex > size)
                throw new IndexOutOfBoundsException("toIndex = " + toIndex);
            if (fromIndex > toIndex)
                throw new IllegalArgumentException("fromIndex(" + fromIndex +
                                                   ") > toIndex(" + toIndex + ")");
        }
    
        //内部类SubList的构造函数:
            SubList(AbstractList<E> parent,
                    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;
            }
    

    源码分析:

    25.public Object[] toArray();

    作用:将当前集合中的所有元素转化成顶级Object类型对象数组返回
    源码如下:

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

    源码分析:
    ArrayList的数据结构本来就是一个数组,利用Object [] toArray()复制了一个对象

    26.public Object[] toArray(Object []a);

    作用:将集合中的元素依次存入参数数组中并返回。
    源码如下:

        public <T> T[] toArray(T[] a) {
            if (a.length < size)//如果传入数组的长度小于size,返回一个新的数组,大小为size,类型与传入数组相同
                // Make a new array of a's runtime type, but my contents:
                return (T[]) Arrays.copyOf(elementData, size, a.getClass());
            System.arraycopy(elementData, 0, a, 0, size);//所传入数组长度与size相等,则将elementData复制到传入数组中并返回传入的数组。
            if (a.length > size)// 若传入数组长度大于size,除了复制elementData外,还将把返回数组的第size个元素置为空。
    
                a[size] = null;
            return a;
        }
    

    27.public String toString();

    作用:一个arraylist转换为字符串
    源码如下:

    //inherited from AbstractCollection
        public String toString() {
            Iterator<E> it = iterator();//集合本身调用迭代器方法(this.iterator),得到集合迭代器
            if (! it.hasNext())
                return "[]";
    
            StringBuilder sb = new StringBuilder();
            sb.append('[');
            for (;;) {
                E e = it.next();
                sb.append(e == this ? "(this Collection)" : e);
                if (! it.hasNext())
                    return sb.append(']').toString();
                sb.append(',').append(' ');
            }
        }
    

    源码分析:
    若集合没有元素->返回String类型"[]"
    若集合有元素->先拼接'['->进入for死循环用append拼接e元素+','+' '->没有元素使用return跳出死循环并拼接']'

    28.public void trimToSize();

    作用:去除集合两端的null
    源码如下:

    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {//如果时间大小小于缓冲区容量的长度,则进行数组复制。
            elementData = (size == 0)
                ? EMPTY_ELEMENTDATA
                : Arrays.copyOf(elementData, size);
        }
    }
    

    源码分析:底层数组的容量调整为当前列表保存的实际元素的大小的功能

    六.参考资料

    ArrayList中的Iterator详解

    Java 集合中常见 checkForComodification()方法的作用? modCount和expectedModCount作用?

    java面试(ArrayList去重;hashcode与equals)

    Java 集合中关于Iterator 和ListIterator的详解
    ArrayList之removeAll底层原理实现详解
    ArrayList中remove、removeAll、retainAll方法源码解析
    用大白话告诉你ArrayList的底层原理

  • 相关阅读:
    chrome安装vue-devtools
    企业网络设计
    nginx的安装
    Typora 的使用
    如何学习dubbo?
    关于原码反码和补码以及byte&0xff
    HttpClient以及连接池的使用
    计算机网络读书笔记:tcp面试常见问题都在里面了
    接口类是否继承自Object?
    数据挖掘会议记录
  • 原文地址:https://www.cnblogs.com/miaowulj/p/14521287.html
Copyright © 2011-2022 走看看