ArrayList源码解析与相关知识点

news2024/11/30 6:48:14

ArrayList源码解析于相关知识点(超级详细)

文章目录

  • ArrayList源码解析于相关知识点(超级详细)
    • ArrayList的继承关系
      • Serializable标记接口
      • Cloneable标记接口
      • RandomAccess标记接口
      • AbstractList类
    • 属性
    • 构造函数
      • 无参构造函数
      • 指定初始容量的构造函数
      • 传入集合
    • 添加方法
      • public boolean add(E e) 方法
      • public void add(int index, E element)方法:
      • public boolean addAll(Collection<? extends E> c)方法:
      • public boolean addAll(int index, Collection<? extends E> c)方法:
    • 修改方法
      • public E set(int index, E element)方法:
    • 获取方法
      • public E get(int index)方法:
      • public int indexOf(Object o)方法:
      • public int lastIndexOf(Object o)方法:
    • 转换方法
      • public String toString()方法:
      • 迭代器Iterator
    • 删除方法
      • public E remove(int index)方法:
      • public boolean remove(Object o)方法:
      • public void clear()方法:
      • public boolean removeAll(Collection<?> c)方法:
    • 包含方法
      • public boolean contains(Object o)方法:
    • 判断是否为空方法
  • 常见问题

首先ArrayList是List接口的数组实现类。查询、修改效率高、新增、删除效率低,线程不安全。

ArrayList的继承关系

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

由以上代码可以看出ArrayList继承了AbstractList,实现了List、RandomAccess、Cloneable、Serializable接口。

ArrayList类的继承结构图如下:

在这里插入图片描述

可以看出ArrayList继承了AbstractList,AbstractList已经实现了List接口,为什么还有让ArrayList继续实现List接口?

Serializable标记接口

实现Serializable接口是为了实现序列化和反序列化。序列化:将对象的数据写到本地文件;反序列化:将本地相关文件中的对象数据读取出来。

Cloneable标记接口

是一个标记性接口

实现Cloneable接口是为了使用clone()方法。实现了Cloneable接口后,调用clone()方法可以对于该类的实例进行字段的复制。

使用clone()方法的前提条件:

  • 实现Cloneable接口;
  • 重写clone()方法。
public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();//super.clone()调用的是Object中的clone()方法。
            //调用Arrays类的copyOf方法。实现克隆
            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);
        }
    }
/*copyof方法*/
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
    //不管三元运算结果如何都会创建一个数组。
    //新数组的长度等于集合的长度一样。
        T[] copy = ((Object)newType == (Object)Object[].class)//判断俩个类型是否一致
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    //Array.newInstance()创建一个具有指定组件类型和长度的新数组,返回的是Object类型
    //数组的拷贝
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
    //arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));是将原来的original数组 拷贝到copy新数组中
        return copy;//返回新数组
    }

由上面的代码可知:克隆返回的是新的地址。

克隆又分为浅克隆深克隆

  • 浅克隆

    /*创建Student类实现Cloneable接口,重写clone()方法*/
    public class Student implements Cloneable {
        private  String name ;
        private int age ;
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    	//重写时访问权限不能比父级低,返回值类型不能比父级高(jdk7、jdk8之后)
        @Override
        public Student clone() throws CloneNotSupportedException {
            return (Student) super.clone();
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    
    /*测试类:
    		先创建一个Student对象stu1 在通过克隆创建stu2,可以看见两个对象的地址不同,内容相同,
    		然后修改stu1的名字,stu2是不会改变的。
    */
    public class Test {
        public static void main(String[] args) throws CloneNotSupportedException {
            Student stu1 = new Student("张三", 20);
            Student stu2 = stu1.clone();
            System.out.println(stu1==stu2);//地址比较
            System.out.println(stu1);
            System.out.println(stu2);
            stu1.setName("王五");
            System.out.println("==============");
            System.out.println(stu1);
            System.out.println(stu2);
        }
    }
    /*false
    Student{name='张三', age=20}
    Student{name='张三', age=20}
    ==============
    Student{name='王五', age=20}
    Student{name='张三', age=20}*/
    

    当类中包含引用类型时:

    //在上面的基础上添加年级grade 成员变量 Grade未实现Cloneable接口
    public class Student implements Cloneable {
        private  String name ;
        private int age ;
        private  Grade grade;
        public Student(String name, int age , Grade grade) {
            this.name = name;
            this.age = age;
            this.grade = grade;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public Grade getGrade() {
            return grade;
        }
    
        public void setGrade(Grade grade) {
            this.grade = grade;
        }
    
        @Override
        public Student clone() throws CloneNotSupportedException {
            return (Student) super.clone();
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", grade=" + grade +
                    '}';
        }
    }
    
    public class Test {
        public static void main(String[] args) throws CloneNotSupportedException {
            Grade grade = new Grade("大一");
            Student stu1 = new Student("张三", 20,grade);
            Student stu2 = stu1.clone();
            System.out.println(stu1==stu2);
            System.out.println(stu1);
            System.out.println(stu2);
            //修改stu1的年级
            grade.setName("大二");
            stu1.setGrade(grade);
            System.out.println("==============");
            System.out.println(stu1);
            System.out.println(stu2);
        }
    }
    /*false
    Student{name='张三', age=20, grade=Grade{name='大一'}}
    Student{name='张三', age=20, grade=Grade{name='大一'}}
    ==============
    Student{name='张三', age=20, grade=Grade{name='大二'}}
    Student{name='张三', age=20, grade=Grade{name='大二'}}*/
    

    发现stu2的年级也改变了。

    浅克隆存在的问题:基本数据类型可以完全克隆,引用类型不行。

    原因是:引用类型的变量在克隆时,仅仅只是拷贝了一份引用,当该引用变量内容改变时,所有使用此引用的变量地方的 值都会改变。

  • 深克隆:

    解决浅克隆的问题。让Grade类实现Cloneable接口,重写clone()方法。然后改变原来Student类当中的clone()方法。

    /*Student类中修改clone方法*/
    @Override
        public Student clone() throws CloneNotSupportedException {
           // return (Student) super.clone();
            // 1.克隆student对象
            Student stu = (Student) super.clone();
            // 2.克隆Grade对象
            Grade grade =(Grade) this.grade.clone();
            // 3.将grade 赋值给stu
            stu.setGrade(grade);
            //返回
            return stu;
        }
    

RandomAccess标记接口

此标记接口由List实现使用,表示支持快速随机访问。实现此接口的主要目的是允许通用算法更改其行为,以便在应用于随机访问列表或顺序访问列表时提供良好的性能。

随机访问:

//随机访问就是随机给一个索引就可以访问
for (int i = 0; i < list.size(); i++) {
            list.get(i);//这就是随机访问,随机访问第i个值;
        }

顺序访问:

//顺序访问是从头到尾的
Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()){
            iterator.next();
        }

AbstractList类

是一个抽象类,在api中这样介绍:此类提供的骨干实现的List接口以最小化来实现该接口有一个"随机访问"数据存储备份所需的工作对于顺序存取的数据,AbstractSequentialList应;优先使用此类。要实现一个不可修改的列表,程序员只需要扩展这个类并提供get(int) 和size()方法的实现。要实现一个可以修改的列表,程序员必须覆盖set(int ,E) 方法,否则会抛出异常信息,如果列表大小可变,则程序员需要覆盖add(int ,E)和remove(int) 方法。

属性

/*实现Serializable接口后给的唯一标识*/
private static final long serialVersionUID = 8683452581122892189L;
/*设置默认容量=10*/
private static final int DEFAULT_CAPACITY = 10;
/*声明一个空数组,用于空实例的共享空数组*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/*
其注释的翻译为:
	“用于默认大小的共享空数组实例。将其和EMPTY_ELEMENTDATA区别开来是为了知道第一次加入数据时扩容的大小”.
	简单来说就是用来区分是从哪个构造函数来初始化ArrayList的。*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/*集合真正存放元素的数组的容器,transient阻止被序列化的*/
transient Object[] elementData; // non-private to simplify nested class access
/*集合的大小*/
private int size;

声明:数组的elementData的大小称为容量capacity;集合的大小称为size,表示实际存储元素的个数。

构造函数

ArrayList共有三个构造函数

  • 无参构造函数
  • 指定初始容量的构造函数
  • 传入集合的构造函数

无参构造函数

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

无参构造中是将DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组赋值给elementData存放元素的数组,此时并没有对数组容量进行分配。通过一下分析可知第一次添加元素时才分配容量。

指定初始容量的构造函数

 /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //elementData直接赋值为一个initialCapacity大小的数组对象
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //EMPTY_ELEMENTDATA赋值给elementData,,
            //值得注意的的是,此时与无参构造相同都不分配容量,但是赋值的变量不同,目的就是为了区分是哪个构造器来初始化的;
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            //initialCapacity小于0时报出容量不合法的消息;
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

传入集合

创建一个包好指定集合的元素的列表,按照他们由集合的迭代器返回的顺序。

/**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        Object[] a = c.toArray();//将传入的集合改为数组;
        if ((size = a.length) != 0) {//判断集合的大小,并把大小传给size属性
            if (c.getClass() == ArrayList.class) {//判断c集合的类型是不是ArrayList.class
                elementData = a;//如果是直接传
            } else {
                //如果不是这先将其转为Object[].class
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // replace with empty array.
            elementData = EMPTY_ELEMENTDATA;
        }
    }

添加方法

有四种:

方法方法说明
public boolean add(E e)添加指定元素
public void add(int index, E element)在列表的指定位置添加指定元素
public boolean addAll(Collection<? extends E> c)将指定集合的所有元素按顺序追加到列表的尾部
public boolean addAll(int index, Collection<? extends E> c)从指定的位置开始将指定集合的所有元素添加到列表中

public boolean add(E e) 方法

/**
     * Appends the specified element to the end of this list.
     *在list的末尾添加上指定的元素
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
public boolean add(E e) {
    	//将添加一个元素后的集合长度传入,在其内部判断是否需要扩容,
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;//正式添加值e到size位置,size自增
        return true;//返回true
    }
private void ensureCapacityInternal(int minCapacity) {
    	//先将实际存放数组的容器和添加一个元素后的长度传入calculateCapacity方法中
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    	//判断elementData是不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA默认的空数组,如果是选择无参构造,在构造列表的时候是不赋予实际容量的,在此时判断是否是第一添加元素;
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //是,就在添加一个元素后的长度minCapacity和默认长度10中返回一个大的值
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
    	//不满足,就返回minCapacity添加一个元素后的长度
        return minCapacity;
    }
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//实际修改集合的次数加加

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)//判断是否需要扩容 ,传过来的容量要求如果比实际存储容器elementData的容量大就需要扩容
            grow(minCapacity);//扩容方法
    }
/**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     * //minCapacity渴望的最小容量
     */

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;//原本容量
        //新容量 = 原本容量 + (原本容量/2):这里是经典的总结扩容到原来的容量的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            //如果是第一次添加元素,可能原本的容量就是0,0的1.5倍还是零,
            //此处判断新容量是否小于渴望容量minCapacity,
            //如果小于就将新容量的值改为渴望容量
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)//这里是做一个保障用的 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);//将原来容器中的元素复制到新容量容器中
    }

public void add(int index, E element)方法:

/**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     * 在指定的位置插入指定的元素,并且将该位置之后的元素后移
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * index 指定的位置 
     * element 指定的元素
     */
public void add(int index, E element) {
        rangeCheckForAdd(index);//判断索引是否超界
    	//与上面的添加方法一样
        ensureCapacityInternal(size + 1);  // Increments modCount!!
    	/*这里是此方法的一个重点:做法是先将index及index以后的元素后移,空出index位置方便添加元素*/
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;//在index位置添加元素
        size++;//size自增
    }
private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)//判断索引是否在0-size中
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

public boolean addAll(Collection<? extends E> c)方法:

/*
将指定集合的所有元素按顺序追加到列表的尾部
*/
//这个方法看起来和构造方法中,传入集合构造类似
public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();//将传入集合转为Object数组
        int numNew = a.length;//传入集合的长度
    	// 是否进行扩容
        ensureCapacityInternal(size + numNew);  // Increments modCount
    	// 附加到集合中:将数组a从0索引开始复制到elementData容器中从size位置开始,复制的长度为numNew
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

public boolean addAll(int index, Collection<? extends E> c)方法:

/*
  从指定的位置开始将指定集合的所有元素添加到列表中
*/
public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);//判断索引是否出界

    Object[] a = c.toArray();//将集合转为数组
    int numNew = a.length;传入集合的长度
    // 是否进行扩容
    ensureCapacityInternal(size + numNew);  // Increments modCount
    //计算需要移动元素有多少位
    int numMoved = size - index;
    if (numMoved > 0)//如果移动的元素个数大于0,就将elementData中index之后的元素后移numNew位,长度为numMoved
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);
	//如果需要移动的元素个数不大于0,肯定==0,这就如同上一个方法一样直接在末尾添加
    //从index位置添加元素
    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}

修改方法

public E set(int index, E element)方法:

public E set(int index, E element) {
        rangeCheck(index);//判断索引是否合法
    
        E oldValue = elementData(index);//获得指定索引位置的元素
        elementData[index] = element;//将该位置的元素赋值为指定元素element
        return oldValue;//返回被替换的元素
    }
private void rangeCheck(int index) {
    	//此方法不检查索引是否为负:它总是在数组访问之前立即使用,如果索引为负,则抛出ArrayIndexOutOfBoundsException。
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
@SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

获取方法

方法描述
public E get(int index)根据索引获取集合中的元素
public int indexOf(Object o)根据元素获取元素在该集合中第一次出现的索引位置
public int lastIndexOf(Object o)根据元素获取元素在该集合中最后一次出现的索引位置

public E get(int index)方法:

public E get(int index) {
        rangeCheck(index);//判断索引是否合法

        return elementData(index);//返回指定索引位置的元素;
    }

public int indexOf(Object o)方法:

/*根据元素获取元素在该集合中第一次出现的索引位置*/
public int indexOf(Object o) {
        if (o == null) {//判断需要查询的元素是否为null
            //如果是null遍历集合容器寻找是否有等于null的元素,从这里可以看出ArrayList集合是可以存储null值的
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            //如果不为null,同样遍历寻找
            for (int i = 0; i < size; i++)
                //判断相等用的是equals方法比内容
                if (o.equals(elementData[i]))
                    return i;
        }
    	//如果集合中不包含需要查询的元素则返回-1
        return -1;
    }

public int lastIndexOf(Object o)方法:

/*获取指定元素最后一次出现的索引*/
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;
    }

转换方法

public String toString()方法:

在ArrayList集合对象调用toString()方法的时候调用的是AbstractCollection类中的toString方法。由上面的第一张图可以知道,ArrayList继承了AbstractList,而AbstractList类继承了AbstractCollection类所以是可以调用AbstractCollection类里的toString()方法的.

public String toString() {
    	//获取迭代器
        Iterator<E> it = iterator();
        if (! it.hasNext())//判断集合是否为空
            return "[]";//为空返回"[]"

    	//创建StringBuilder类型对象sb以便返回
        StringBuilder sb = new StringBuilder();
        sb.append('[');//先添加'['
        for (;;) {//设置一个无线循环
            E e = it.next();//取出迭代器中光标指向的元素,并将光标移动到下一个元素
            //三元判断,如果取出来的e==this就拼接一个(this Collection),否则拼接e
            sb.append(e == this ? "(this Collection)" : e);
            if (! it.hasNext())//判断是否还有元素,没有时就为sb添加']',将sb转为string类型返回
                return sb.append(']').toString();
            sb.append(',').append(' ');
        }
    }

迭代器Iterator

迭代器是一种取出集合元素的方法。来自于单列集合根接口,所以所有的单列集合都可使用iterator()ArrayList也对其进行了重写。

Java中的集合各式各样,很多应用场景下需要对集合里面的元素进行遍历,有的集合有下标(ArrayList、Vector、LinkedList),有的集合没有下标。有下标的集合可以轻易地通过下标进行遍历,没有下标的集合遍历起来就非常麻烦,因此Java引入了迭代器模式,迭代器可以使得我们能够使用一种通用的方式遍历各种集合元素。

/*在ArrayList中的iterator()方法*/
public Iterator<E> iterator() {
        return new Itr();
    	//Itr是ArrayList的一个私有的内部类
    //private class Itr implements Iterator<E> 
    }
/**/
private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return 光标,默认值为0;类似于C语言的指针
        int lastRet = -1; // index of last element returned; -1 if no such 返回最后一个元素的索引
    	//将集合实际修改次数赋值给预期修改次数
        int expectedModCount = modCount;
    	//初始化
        Itr() {}
    	//判断集合是否有元素
        public boolean hasNext() {
            //判断光标是否不等于集合的size,Itr是内部类可以访问外部的成员变量
            return cursor != size;//不相等就返回true
        }
		//next方法
        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();//校验,为类的并发修改做准备的 Itr内部方法
            int i = cursor;//将光标赋值给i
            if (i >= size)//判断光标是否大于size
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;//吧集合存储数据的容器,赋值给该方法的局部变量
            
            if (i >= elementData.length)
                //如果发生会抛出并发修改异常,主要针对多线程
                throw new ConcurrentModificationException();
            //光标向下移动
            cursor = i + 1;
            //从数组中取出元素并返回
            return (E) elementData[lastRet = i];
        }
		//迭代器中的删除方法
        public void remove() {
            if (lastRet < 0)//判断最后一个元素的索引是否小于0
                throw new IllegalStateException();
            checkForComodification();//校验,为类的并发修改做准备的 Itr内部方法

            try {
                ArrayList.this.remove(lastRet);//调用ArrayList类中的remove方法,并将最后一个元素的索引传入
                //将光标移动到lastRet位置
                cursor = lastRet;
                lastRet = -1;//lastRet恢复为-1
                //将实际修改次数重新赋值给预期修改次数,此处表名为什么迭代器对象的方法在遍历时删除元素时可以的,因为它重新赋值了预期修改次数
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }
		//校验预期修改此时是否与实际修改次数相同
        final void checkForComodification() {
            if (modCount != expectedModCount)
                /*如,在获得迭代器,正在遍历时对集合进行增、删操作,
                会引起实际修改次数改变,而致使与预期修改次数不同,
                但是不是只要在遍历期间进行了这些操作就一定会抛出并发异常
                
                如果删除的元素时集合中倒数第二个元素,就不会抛出异常,因为在删除时进行了size自减操作,在hasNext()方法中光标==size时返回false就不会调用next()方法;
                */
                throw new ConcurrentModificationException();
        }
    }

删除方法

方法描述
public E remove(int index)根据索引删除元素
public boolean remove(Object o)删除指定元素(第一次出现的)
public void clear()清除集合中所有元素
public boolean removeAll(Collection<?> c)删除集合中包含指定集合的所有元素

public E remove(int index)方法:

/*根据索引删除元素*/
public E remove(int index) {
        rangeCheck(index);//判断索引是否合法

        modCount++;//实际修改集合的次数加加
        E oldValue = elementData(index);//获取指定索引位置的元素;

        int numMoved = size - index - 1;//确定需要向前移动的元素有多少个
        if (numMoved > 0)
            //如果0个以上就将待删除位置以后的所有元素向前移动一位
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null;//将size减1,并将size位置元素置空 // clear to let GC do its work

        return oldValue;//返回被删除的元素
    }

public boolean remove(Object o)方法:

/*删除指定元素(第一次出现的)*/
public boolean remove(Object o) {
        if (o == null) {//判断指定元素是否为null
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);//将指定元素的索引传给快速删除方法
                    return true;//返回true
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);//将指定元素的索引传给快速删除方法
                    return true;//返回true
                }
        }
        return false;//集合中没有指定元素就返回false
    }
private void fastRemove(int index) {
        modCount++;//实际操作数加一
        int numMoved = size - index - 1;
        if (numMoved > 0)
            //将待删除位置以后的所有元素向前移动一位
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null;//将size减1,并将size位置元素置空 // clear to let GC do its work
    }

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;//size归零
    }

public boolean removeAll(Collection<?> c)方法:

/*删除集合中包含指定集合的所有元素*/
public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);//判断指定集合是否为空
        return batchRemove(c, false);//传入指定集合,和false
    }
//Objects类的静态方法
public static <T> T requireNonNull(T obj) {
        if (obj == null)//如果为空,抛出空指针异常
            throw new NullPointerException();
        return obj;
    }
private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;//将容器地址赋给elementData
        int r = 0, w = 0;//r:elementData遍历的索引 w:elementData待插入位置的索引
        boolean modified = false;//返回标识
        try {
            for (; r < size; r++)//遍历原容器
                //判断指定集合中是否包含elementData[r]
                if (c.contains(elementData[r]) == complement)
                    //不包含时:将elementData[r]插入到elementData待插入位置
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {//如果r!=size就将r之后的元素移至w之后
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;//将w移至元素后一位
            }
            if (w != size) {//w != size时将w及其之后的元素置空
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;//更改实际操作次数
                size = w;//更新size
                modified = true;//更改返回标识
            }
        }
        return modified;
    }

包含方法

public boolean contains(Object o)方法:

public boolean contains(Object o) {
        return indexOf(o) >= 0;//调用获取指定元素的索引的方法,返回是否大于等于0就可以
    }

判断是否为空方法

public boolean isEmpty() {
        return size == 0;//返回size是否等于0;
    }

常见问题

  1. ArrayList频繁扩容导致性能下降如何解决?
    简单解决方法就是使用含参构造,创建时传如集合的一个指定的容量。

  2. ArrayList是不是一个线程安全的?
    不是线程安全的

  3. 如何将一个ArrayList复制到另一个ArrayList集合中:

    • 使用clone()方法;
    • 使用ArrayList构造方法;
    • 使用addAll方法。
  4. 如何在多线程环境下使用迭代器在读取集合数据的同时添加数据?
    有一个类它实现了读写分类:CopyOnWriteArrayList集合类。
    它是ArrayList的一个变体,且是线程安全的。

  5. ArrayList和LinkedList的区别?
    ArrayList

    • 基于动态数组的数据结构
    • 对于随机访问的get和set,ArrayList要优于LinkedList
    • 对于随机操作add和remove,ArrayList不一定比LinkedList慢。

    LinkedList

    • 基于链表的数据结构
    • 对于顺序操作,LinkedList不一定比ArrayList慢
    • 对于随机操作,LinkedList效率明显低一点

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/87621.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络工程毕业设计 SSM疫情期间医院门诊管理系统(源码+论文)

文章目录1 项目简介2 实现效果2.1 界面展示3 设计方案3.1 概述3.2 系统开发流程3.3 系统结构设计4 项目获取1 项目简介 Hi&#xff0c;各位同学好呀&#xff0c;这里是M学姐&#xff01; 今天向大家分享一个今年(2022)最新完成的毕业设计项目作品&#xff0c;【基于SSM的疫情…

测试人生 | 转行测试开发,4年4“跳”年薪涨3倍,我的目标是星辰大海(附大厂面经)!

image1080500 66.1 KB 编者按&#xff1a;本文来自霍格沃兹测试学院优秀学员 TesterC&#xff0c;**从运营岗位转行外包测试&#xff0c;再到测试开发&#xff0c;从待业在家到4年4“跳”进入 BAT 大厂&#xff0c;年薪涨了3倍&#xff01;**他是如何完成如此励志的华丽转身的…

12.4、后渗透测试--内网主机数据包流量嗅探

攻击主机&#xff1a; Kali 192.168.11.106靶机&#xff1a;windows server 2008 r2 192.168.11.134Metasploitable2-Linux&#xff1a; 192.168.11.105当成功获取目标机器的会话后&#xff0c;可以使用嗅探手段获取更多信息。前提&#xff1a;获得 meterpreter shell1、加载s…

centos7 安装 zsh + fzf(历史命令搜索神器)

文章目录zsh 安装用 oh-my-zsh 配置 zshfzf 安装结语zsh 安装 参考 用 yum 自动下载安装 zsh yum install -y zsh 安装完成后查看系统可以用的 shell cat /etc/shells 将 zsh 设置为系统默认 shell chsh -s /bin/zsh 退出终端重新登录 查看当前使用的shell echo $0 用 oh-my-z…

大二Web课程设计——美食网站设计与实现(HTML+CSS+JavaScript)

&#x1f468;‍&#x1f393;静态网站的编写主要是用HTML DIVCSS JS等来完成页面的排版设计&#x1f469;‍&#x1f393;,常用的网页设计软件有Dreamweaver、EditPlus、HBuilderX、VScode 、Webstorm、Animate等等&#xff0c;用的最多的还是DW&#xff0c;当然不同软件写出的…

ArcGIS:按属性选择要素、按位置选择要素、空间和属性的组合查询;属性表中长度、面积等的量算

目录 01 说明 02 实验目的及要求 03 实验设备及软件平台 04 实验内容与步骤 4.1 由属性选择要素 4.2 由位置选择要素 4.3 查询四川省乐山市范围内的气象站点。 4.4 查询与乐山市相邻的地市州有哪些 4.5 计算四川省各个地市州的面积。 4.6 查询单一栅格或者多个栅格的不同方法。…

HTML+CSS篮球静态网页设计(web前端网页制作课作业)NBA杜兰特篮球运动网页

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

WPF入门第六篇 WPF的Binding

WPF的Binding 在传统的Windows软件中&#xff0c;大部分都是UI驱动程序的模式&#xff0c;也可以说事件驱动程序。WPF作为Winform的升级&#xff0c;它把UI驱动程序彻底改变了&#xff0c;核心回到了数据驱动程序的模式上面&#xff0c;这样&#xff0c;程序就回到了算法和数据…

[附源码]Python计算机毕业设计服装销售商城系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

【ESP8266-NodeMCU软硬串口通讯】

1. 前言 SoftwareSerial库允许在Arduino板的其他数字引脚上进行串行通信,使用软件复制功能(因此得名“SoftwareSerial”)。可以有多个软件串行端口,速度高达115200 bps。参数为需要该协议的设备启用反向信令。 1.1 要使用此库 请执行以下操作: #include <SoftwareS…

Prometheus的PromQL语法讲解和使用示例

目录1. PromQL介绍2. 基本用法2.1 查询时间序列2.2 范围查询2.3 时间位移操作2.4 聚合操作2.5 标量和字符串3. 表达式的合法格式4. PromQL操作符4.1 数学运算4.2 布尔运算4.3 集合运算符4.4 操作符优先级4.5 聚合操作1. PromQL介绍 PromQL提供对时间序列数据进行逻辑运算、过滤…

SpringBoot 配置文件

哈喽呀&#xff0c;你好呀&#xff0c;欢迎呀&#xff0c;快来看一下这篇宝藏博客吧~~~ 1. 配置文件的作用 对于Spring Boot项目而言,所有重要的数据都是在配置文件中配置的,比如: 项目的启动端口;数据库的连接信息(包含用户名和密码的设置);用于发现和定位问题的普通日志和异…

第09讲:路由开发

一、使用脚手架创建vue路由项目 项目的创建步骤参考&#xff1a; 在预设中选择Router 这里输入n&#xff0c;表示不使用history模式 选择In dedicated config files表示将配置分开存放 项目已经创建完成&#xff0c;使用VSCode打开&#xff08;部分win10 以上机型请用管理员…

[GO] 图书管理系统API

图书管理系统 1. 创建项目 2. 配置goproxy GOPROXYhttps://goproxy.cn 3. 添加格式化工具 4. 定义目录结构 |---- Readme.md //项目说明 |---- config // 配置文件(mysql配置,ip,端口,用户名,密码等) |---- controller // CLD服务入口,负责处理路由,参数校验,请求转发 |----…

缓存实现方式

为啥需要缓存&#xff1f; mysql关系型数据库&#xff0c;查询时需要磁盘IO&#xff0c;会消耗系统性能并且耗时&#xff0c;当数据变化量较小&#xff0c;并且响应要快的话&#xff0c;可以考虑使用缓存 服务端缓存方式有哪些&#xff1f; 服务端缓存方式&#xff1a; ①可以使…

git push/pull 超时问题解决

使用代理&#xff0c;发现git仓库浏览器可以访问到&#xff0c;但是本地 push/pull 时提示超时&#xff0c;这里提供一个方案供参考&#xff08;亲测有效&#xff09;&#xff1a; 修改系统的DNS为&#xff1a;114.114.114.114

基于java SSM框架的校园二手交易平台设计

一、项目介绍 游客&#xff1a;浏览商品&#xff0c;登录、注册 用户&#xff1a;浏览商品&#xff0c;发布&#xff0c;评论商品&#xff0c;我的订单&#xff0c;收藏&#xff0c;修改个人信息&#xff0c;搜索&#xff0c;回复评论 管理员&#xff1a;商品分类管理&#xff…

基于MATLAB开发AUTOSAR软件应用层模块-part13.AUTOSAR Dictionary-3 编辑AUTOSAR元素-SWC 和PORTS

配置SWC 此处可以配置SWC的名字和类型,类型包含: Application 应用组件 ComplexDiviceDriver 复杂驱动组件 EcuAbstraction ECU 抽象组件 SensorActuator 传感器执行器组件

学会python可以做哪些兼职?

前言 以我差不多四年的 Python 使用经验来看&#xff0c;大概可以按以下这些路子来赚到钱&#xff0c;但编程技能其实只是当中必不可少的一部分&#xff0c;搭配其它技能栈食用风味更佳。 1、爬虫 很多人入门 Python 的必修课之一一定是 Web 开发和爬虫&#xff0c;但这两项…

如何使用Github+picGo搭建图床???

最开始我使用iPic微博图床匿名上传&#xff0c;但是&#xff01;&#xff01;&#xff01;当我不小心上传了隐私文件后&#xff0c;删除特别麻烦(联系新浪客服,提供图片是自己上传的证据才给予处理)&#xff0c;因为白嫖图床&#xff0c;总担心挂掉&#xff0c;而且不利于备份。…