集合框架之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的底层原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值