【JavaSE】ArrayList源码深入研究

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第12天,点击查看活动详情

ArrayList集合介绍:

ArrayList 是 List 接口的可调整大小的数组实现。 数组:一旦初始化长度就不可以发生改变

数组结构特点介绍:

增删慢:每次删除元素,都需要更改数组长度、拷贝以及移动元素位置。 查询快:由于数组在内存中是一块连续空间,因此可以根据地址+索引的方式快速获取对应位置上的元素。

一句话总结: List接口的可调整大小的阵列实现,底层数据结构是数组,查询快,增删慢

1. ArrayList继承关系

image.png

1.1 Serializable标记性接口

介绍:

类的序列化由实现java.io.Serializable接口的类启用。 不实现此接口的类将不会使任何状态序列化或反 序列化。可序列化类的所有子类型都是可序列化的。 序列化接口Serializable没有方法或字段,仅用于标识某个实现了它的类能序列化的语义。 一句话:类想要实现序列化必须实现该接口(只是一个标志没有任何的实现方法)

序列化:将对象的数据写入到文件(写对象) 反序列化:将文件中对象的数据读取出来(读对象)

Serializable源码介绍:

public interface Serializable {
}

举例: 将List集合序列化存入文件中,然后读取文件再反序列化获取集合

@Data
@AllArgsConstructor
@NoArgsConstructor
class Student implements Serializable{
    private static final long serialVersionUID = 1014100089306623762L;
    //姓名
    private String name;
    //年龄
    private Integer age;
}
 
public class Test01 {
    public static void main(String[] args) throws Exception {
        Student s = new Student();
        System.out.println(s);
        //创建对象操作流 --> 序列化
        ObjectOutputStream oos = new ObjectOutputStream(new 
FileOutputStream("MyTest\\obj.txt"));
        //创建集合,且添加学生对象
        ArrayList<Student>  list = new ArrayList<Student>();
        list.add(new Student("悔创阿里杰克马",51));
        list.add(new Student("会点一点长几颗",26));
        list.add(new Student("容颜老去蒋青青",32));
        list.add(new Student("将里最丑刘一飞",27));
        //将集合写入到文件
        oos.writeObject(list);
 
        //创建对象输入流 --> 反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("MyTest\\obj.txt"));
        //读取数据
        Object o = ois.readObject();
        //向下转型
        ArrayList<Student> al = (ArrayList<Student>) o;
        //遍历集合
        for (int i = 0; i < al.size(); i++) {
            //根据索引取出集合的每一个元素
            Student stu = al.get(i);
            System.out.println(stu);
        }
    }
}

1.2 Cloneable 标记性接口

介绍:

一个类实现 Cloneable 接口来指示 Object.clone() 方法,该方法对于该类的实例进行字段的复制是合法的。 在不实现 Cloneable 接口的实例上调用对象的克隆方法会导致异常 CloneNotSupportedException 被抛出。 简言之:克隆就是依据已经有的数据,创造一份新的完全一样的数据拷贝

Cloneable接口介绍

public interface Cloneable {
}

克隆的前提条件

  • 被克隆对象所在的类必须实现 Cloneable 接口
  • 必须重写 clone 方法

clone的基本使用

/**
 * 克隆的基本使用:
 *         将ArrayList集合的数据clone到另外一个集合
 */
public class ArrayList_Clone {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        list.add("人生就是旅途");
        list.add("也许终点和起点会重合");
        list.add("但是一开始就站在起点等待终点");
        list.add("那么其中就没有美丽的沿途风景和令人难忘的过往");
        //调用方法进行克隆
        Object o = list.clone();
        System.out.println(o == list);
        System.out.println(o);
        System.out.println(list);
    }
}

clone源码分析

public class ArrayList<E> {
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e);
        }
    }
}

案例: 已知 A 对象的姓名为豹子头林冲,年龄30 。由于项目特殊要求需要将该对象的数据复制另外一个对象B 中,并且此后 A 和 B 两个对象的数据不会相互影响

案例: 已知 A 对象的姓名为鲁智深,年龄30,技能为倒拔垂杨柳 (技能为一个引用数据类型 Skill ),由于项目特殊要求需要将该对象的数据复制另外一个对象 B 中,并且此后 A 和 B 两个对象的数据不会相互影响

方式1:创建两个对象模拟 1.准备学生类

1.3 RandomAccess标记接口

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

官方还说,实现该接口的集合,使用for循环的方式获取数据for (int i=0, n=list.size(); i < n; i++)会优于用迭代器获取数据for (Iterator i=list.iterator(); i.hasNext();)

可以在应用于顺序访问列表时产生二次行为(如LinkedList )。鼓励通用列表算法在应用如果将其应用于顺序访问列表之前提供较差性能的算法时,检查给定列表是否为 instanceof ,并在必要时更改其行为以保证可接受的性能。 人们认识到,随机访问和顺序访问之间的区别通常是模糊的。 例如,一些 List 实现提供渐近的线性访问时间,如果它们在实践中获得巨大但是恒定的访问时间。 这样的一个 List 实现应该通常实现这个接口。 根据经验, List 实现应实现此接口,如果对于类的典型实例,此循环:

for (int i=0, n=list.size(); i < n; i++)
            list.get(i);

比这个循环运行得更快:

for (Iterator i=list.iterator(); i.hasNext(); )
            i.next();

案例演示1

public class Test01 {
    public static void main(String[] args) {
        //创建ArrayList集合
        List<String> list = new ArrayList<>();
        //添加10W条数据
 
        for (int i = 0; i < 100000; i++) {
            list.add(i+"a");
        }
 
        System.out.println("----通过索引(随机访问:)----");
 
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < list.size(); i++) {
            //仅仅为了演示取出数据的时间,因此不对取出的数据进行打印
            list.get(i);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("随机访问: "+(endTime-startTime));
 
        System.out.println("----通过迭代器(顺序访问:)----");
        startTime = System.currentTimeMillis();
        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            //仅仅为了演示取出数据的时间,因此不对取出的数据进行打印
            it.next();
        }
        endTime = System.currentTimeMillis();
        System.out.println("顺序访问: "+(endTime-startTime));
    }
}

控制台效果

----通过索引(随机访问:)---- 随机访问: 2 ----通过迭代器(顺序访问:)---- 顺序访问: 3

案例演示2

public class Test02 {
    public static void main(String[] args) {
        //创建LinkedList集合
        List<String> list = new LinkedList<>();
        //添加10W条数据
        for (int i = 0; i < 100000; i++) {
            list.add(i+"a");
        }
        System.out.println("----通过索引(随机访问:)----");
 
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < list.size(); i++) {
            //仅仅为了演示取出数据的时间,因此不对取出的数据进行打印
            list.get(i);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("随机访问: "+(endTime-startTime));
 
        System.out.println("----通过迭代器(顺序访问:)----");
        startTime = System.currentTimeMillis();
        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            //仅仅为了演示取出数据的时间,因此不对取出的数据进行打印
            it.next();
        }
        endTime = System.currentTimeMillis();
        System.out.println("顺序访问: "+(endTime-startTime));
    }
}

控制台效果

----通过索引(随机访问:)---- 随机访问: 33759 ----通过迭代器(顺序访问:)---- 顺序访问: 9

为什么LinkedList随机访问比顺序访问要慢这么多? 源码分析 随机访问

//每次LinkedList对象调用get方法获取元素,都会执行以下代码
list.get(i);
 
public class LinkedList<E> {
    public E get(int index) {
        //检验是否有效
        checkElementIndex(index);
        //调用node(index)
        return node(index).item;
    }
    
    //node方法
     Node<E> node(int index) {
         //node方法每次被调用的时候都会根据集合size进行折半动作
         //判断get方法中的index是小于集合长度的一半还是大于
        if (index < (size >> 1)) {
            //如果小于就从链表的头部一个个的往后找
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
            } else {
            Node<E> x = last;
            //如果大于就从链表的尾部一个个的往前找
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
}

顺序访问

//获取迭代器的时候,会执行以下代码
Iterator<String> it = list.iterator();
 
//AbstractList为LinkedList父类的父类
public abstract class AbstractList<E> {
    public ListIterator<E> listIterator() {
        //返回一个列表迭代器,且指定参数为0
        return listIterator(0);
    }
}
 
public class LinkedList<E>{
    public ListIterator<E> listIterator(int index) {
        //检查索引位置
        checkPositionIndex(index);
        //返回ListItr对象
        return new ListItr(index);
    }
    
    //LinkedList迭代器实现类
    private class ListItr implements ListIterator<E> {
        private Node<E> lastReturned;
        private Node<E> next;
        private int nextIndex;
        //将实际修改集合次数赋值给预期修改次数
        private int expectedModCount = modCount;
        
        ListItr(int index) {
            //判断 0 == size,实际上就是调用 node(index)方法
            next = (index == size) ? null : node(index);
            //将index的值赋值给 nextIndex,便于下次查找
            nextIndex = index;
        }
    }
    
    Node<E> node(int index) {
        //在获取迭代器的时候也会进行折半的动作
        //但是在获取迭代器的时候 index 一定是0,因此 if的条件成立
        if (index < (size >> 1)) {
            Node<E> x = first;
            //由于循环条件不成立,不会执行 x.next;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;   //返回第一个元素
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
}
 
//迭代器调用 hasNext()方法的时候,会执行以下代码
private class ListItr implements ListIterator<E> {
    public boolean hasNext() {
        //如果nextIndex < 集合的长度,就说明还有元素,可以进行next
        return nextIndex < size;
    }
}
 
//当迭代器调用it.next();方法的时候会执行以下代码
it.next();
public class LinkedList<E>{
    private class ListItr implements ListIterator<E> {
        public E next() {
            checkForComodification();   //检查集合实际修改次数和预期次数是否一样
            //再次判断是否有元素
            if (!hasNext())
                throw new NoSuchElementException();
            //将链表的第一个元素赋值给lastReturned
            lastReturned = next;
            //将下一个元素赋值给next
            next = next.next;
            //nextIndex++
            nextIndex++;
            //返回第一个元素
            return lastReturned.item;
        }
    }
}

**小结: **由于随机访问的时候源码底层每次都需要进行折半的动作,再经过判断是从头还是从尾部一个个寻找。 而顺序访问只会在获取迭代器的时候进行一次折半的动作,以后每次都是在上一次的基础上获取下一个元素。 因此顺序访问要比随机访问快得多 实际开发应用场景

public class Test01 {
    //创建JdbcTemplate对象
    JdbcTemplate jt = new JdbcTemplate(JDBCUtils.getDataSource());
 
    //查询出基础班在读男学员的所有信息,且按成绩的降序输出到控制台上(利用JDBC)
    @Test
    public void fun2() throws Exception {
        //拼写SQL
        String sql = "select * from stutb where sex = ? and type like ? order by score 
desc";
        //调用方法查询  将结果集的每一行都封装成一个Stutb对象,并把每一个对象都添加到集合
        List<Stutb> list = jt.query(sql, new BeanPropertyRowMapper<Stutb>(Stutb.class), 
"男", "%基础班%");
        //在遍历集合取出结果集之前面临一个问题,使用普通for遍历好 还是使用迭代器(增强for)?
        //特别是数据量特别大的时候一定要考虑!
        //对返回的集合进行判断,如果返回的集合实现了 RandomAccess 就使用 普通for
        //否则使用迭代器(增强for)
        if(list instanceof RandomAccess){
            for (int i = 0; i < list.size(); i++) {
                System.out.println(list.get(i));
            }
        }else {
            for (Stutb stutb : list) {
                System.out.println(stutb);
            }
        }
    }
}

1.4 AbstractList抽象类

image.png

2. 字段和方法

ArrayList的重要字段和方法

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    @java.io.Serial
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * 初始化默认容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 指定该ArrayList容量为0时,返回该空数组。
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 当调用无参构造方法,返回的是该数组。刚创建一个ArrayList 时,其数据量为0。
 * 	它与EMPTY_ELEMENTDATA的区别就是:该数组是默认返回的,而后者是在用户指定容量为0时返回。
     当第一次添加元素时 可以看到数组扩容量
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * 集合中元素个数
     */
    private int size;

    /**
     * list的指定容量的构造方法
     *
     * @param  initialCapacity  指定list的初始化容量
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    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);
        }
    }

    /**
     * 空构造---初始容量是10
     Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 构造一个包含指定集合元素的列表
     * @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) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // defend against c.toArray (incorrectly) not returning Object[]
            // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
    ...
}

ArrayList实现了List接口,List接口有的方法它也都有,下面ArrayList类的字段和方法:

ArrayList.png

需要注意的是elementData的访问权限是transient(短暂的意思,不会参与序列化) ArrayList自定义了它的序列化(writeObject(java.io.ObjectOutputStream s))和反序列化(readObject(java.io.ObjectOutputStream s))方式。

几个重要辅助方法

System.java

arraycopy 数组拷贝方法

/**
         * Copies an array from the specified source array, beginning at the
         * specified position, to the specified position of the destination array.
         * A subsequence of array components are copied from the source
         * array referenced by {@code src} to the destination array
         * referenced by {@code dest}. The number of components copied is
         * equal to the {@code length} argument. The components at
         * positions {@code srcPos} through
         * {@code srcPos+length-1} in the source array are copied into
         * positions {@code destPos} through
         * {@code destPos+length-1}, respectively, of the destination
         * array.
      ...
         * @param      src      the source array.
         * @param      srcPos   starting position in the source array.
         * @param      dest     the destination array.
         * @param      destPos  starting position in the destination data.
         * @param      length   the number of array elements to be copied.
         * @throws     IndexOutOfBoundsException  if copying would cause
         *             access of data outside array bounds.
         * @throws     ArrayStoreException  if an element in the {@code src}
         *             array could not be stored into the {@code dest} array
         *             because of a type mismatch.
         * @throws     NullPointerException if either {@code src} or
         *             {@code dest} is {@code null}.
         */
@HotSpotIntrinsicCandidate
public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);

3. ArrayList源码分析

3.1 构造方法

Constructor Constructor描述
ArrayList() 构造一个初始容量为10的空列表
ArrayList(int initialCapacity) 构造一个具有指定初始容量的空列表
ArrayList(Collection<? extends E> c) 构造一个包含指定集合元素的列表,按照它们由集合的迭代器返回的顺序

3.2 案例演示

**案例一: **

  1. 空参构造ArrayList()
public class Test01 {
    public static void main(String[] args) {
        //这行代码做了什么?
        //真的构造一个初始容量为10的空列表吗?
        ArrayList<String> list = new ArrayList<String>();
   }
}

2. 源码分析

public class ArrayList<E> {
    /**
     * 默认初始容量
     */
    private static final int DEFAULT_CAPACITY = 10;
     /**
     * 空数组
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};
    /**
     * 默认容量的空数组
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    /**
     * 集合真正存储数组元素的数组
     */
    transient Object[] elementData; 
     /**
     * 集合的大小
     */
    private int size;
   
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
}
  1. 结论

通过空参构造方法创建集合对象并未构造一个初始容量为10的空列表,仅仅将 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的地址赋值给elementData **案例二: **

  1. 指定容量ArrayList(int initialCapacity)
public class Test01 {
    public static void main(String[] args) {
        //这行代码ArrayList底层做了什么?
        ArrayList<String> list = new ArrayList<String>(5);
   }
}
  1. 源码分析
public class ArrayList<E> {
    public ArrayList(int initialCapacity) { 	//initialCapacity = 5
        //判断初始容量initialCapacity是否大于0
        if (initialCapacity > 0) {
            //创建一个数组,且指定长度为initialCapacity
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //如果initialCapacity容量为0,把EMPTY_ELEMENTDATA的地址赋值给elementData
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            //以上两个条件都不满足报错
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
}
  1. 结论

根据 ArrayList 构造方法参数创建指定长度的数组 **案例三: **

  1. ArrayList(Collection<? extends E> c)
public class Test01 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        //这行代码做了什么?
        ArrayList<String> list1 = new ArrayList<>(list);
        for (String s : list1) {
            System.out.println(s);
        }
   }
}

2. 源码分析

public class ArrayList<E> {
    public ArrayList(Collection<? extends E> c) {
        // 将集合构造中的集合对象转成数组,且将数组的地址赋值给elementData
        elementData = c.toArray();
        // 将elementData的长度赋值给 集合长度size,且判断是否不等于 0
        if ((size = elementData.length) != 0) {
            // 判断elementData 和 Object[] 是否为不一样的类型
            if (elementData.getClass() != Object[].class)
                //如果不一样,使用Arrays的copyOf方法进行元素的拷贝
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 用空数组代替
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
    
    //将集合转数组的方法
    public Object[] toArray() {
        //调用数组工具类方法进行拷贝
        return Arrays.copyOf(elementData, size);
    }
    
}
 
//数组工具类
public class Arrays {
    public static <T> T[] copyOf(T[] original, int newLength) {
        //再次调用方法进行拷贝
        return (T[]) copyOf(original, newLength, original.getClass());
    }
    
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> 
newType) {
        //用三元运算符进行判断,不管结果如何都是创建一个新数组
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        //将数组的内容拷贝到 copy 该数组中
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
       //返回拷贝元素成功后的数组
        return copy;
    }
}

3.3 add添加方法

方法名 描述
public boolean add(E e) 将指定的元素追加到此列表的末尾
public void add(int index, E element) 在此列表中的指定位置插入指定的元素
public boolean addAll(Collection<?
extends E> c) 按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾
public boolean addAll(i nt index,
Collection<? extends E> c) 将指定集合中的所有元素插入到此列表中,从指定的位置开始
  • public boolean add(E e) 添加单个元素
public class Test01 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("程序员");
    }
}

源码分析:

public class ArrayList<E> {
    //将添加的数据传入给 e
    public boolean add(E e) {
        //首先调用该方法对内部容量进行校验
        ensureCapacityInternal(size + 1);  
        elementData[size++] = e;
        return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        //判断集合存数据的数组是否等于空容量的数组
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //通过最小容量和默认容量 求出较大值 (用于第一次扩容)
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //将if中计算出来的容量传递给下一个方法,继续校验
        ensureExplicitCapacity(minCapacity);
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        //实际修改集合次数++ (在扩容的过程中没用,主要是用于迭代器中)
        modCount++; 
        //判断最小容量 - 数组长度是否大于 0 
        if (minCapacity - elementData.length > 0)
            //将第一次计算出来的容量传递给 核心扩容方法grow
            grow(minCapacity);
    }
    
    private void grow(int minCapacity) {
        //记录数组的实际长度,此时由于木有存储元素,长度为0
        int oldCapacity = elementData.length;
        //核心扩容算法 原容量的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //判断新容量 - 最小容量 是否小于 0, 如果是第一次调用add方法必然小于
        if (newCapacity - minCapacity < 0)
            //还是将最小容量赋值给新容量
            newCapacity = minCapacity;
        //判断新容量-最大数组大小 是否>0,如果条件满足就计算出一个超大容量
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 调用数组工具类方法,创建一个新数组,将新数组的地址赋值给elementData
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
}
  • public void add(int index, E element) 在指定索引处添加元素
public class Test01 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("程序员");
        list.add("白领");
        list.add("教师");
        list.add(1, "警察");
        System.out.println(list);
    }
}

源码分析:

public class ArrayList<E> {
    public void add(int index, E element) {
        //添加范围检查
        rangeCheckForAdd(index);
        modCount++;
        final int s;
        Object[] elementData;
        //调用方法检验是否要扩容
        if ((s = size) == (elementData = this.elementData).length)
            elementData = grow();
        //数组拷贝(从index+1后面的元素开始)
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size = s + 1;
    }
    
    private void rangeCheckForAdd(int index) {
        //超出指定范围就报错
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
  • public boolean addAll(Collection<? extends E> c) 将集合的所有元素一次性添加到集合
public class Test01 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("程序员");
        list.add("白领");
        list.add("教师");
 
        ArrayList<String> list1 = new ArrayList<>();
        list1.addAll(list);
 
        System.out.println(list);
        System.out.println(list1);
    }
}

源码分析:

public boolean addAll(Collection<? extends E> c) {
    //把集合的元素转存到Object类型的数组中
    Object[] a = c.toArray();
    modCount++;
    //记录要添加的数组长度
    int numNew = a.length;
    if (numNew == 0)
        return false;
    Object[] elementData;
    final int s;
    //判断是否需要扩容
    if (numNew > (elementData = this.elementData).length - (s = size))
        elementData = grow(s + numNew);
    //拷贝
    System.arraycopy(a, 0, elementData, s, numNew);
    //ArrayList数组个数增加
    size = s + numNew;
    return true;
}

3.4 remove删除方法

  • public E remove(int index) 根据索引删除元素
public class Test01 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("李逵");
        list.add("宋江");
        list.add("卢俊义");
        list.add("西门大官人");
 
        //根据索引删除元素
        String value = list.remove(3);
        System.out.println("删除的元素为: "+value);
        System.out.println("集合的元素: "+list);
    }
}

/*
删除的元素为: 西门大官人
集合的元素: [李逵, 宋江, 卢俊义]
*/

源码分析:

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

/**
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
private void fastRemove(Object[] es, int i) {
    //修改次数+1
    modCount++;
    final int newSize;
    if ((newSize = size - 1) > i)
        
        System.arraycopy(es, i + 1, es, i, newSize - i);
    es[size = newSize] = null;
}

//Objects.java
/**
     * Checks if the {@code index} is within the bounds of the range from
     * {@code 0} (inclusive) to {@code length} (exclusive).
     *
     * <p>The {@code index} is defined to be out of bounds if any of the
     * following inequalities is true:
     * <ul>
     *  <li>{@code index < 0}</li>
     *  <li>{@code index >= length}</li>
     *  <li>{@code length < 0}, which is implied from the former inequalities</li>
     * </ul>
     *
     * @param index the index
     * @param length the upper-bound (exclusive) of the range
     * @return {@code index} if it is within bounds of the range
     * @throws IndexOutOfBoundsException if the {@code index} is out of bounds
     * @since 9
     */
@ForceInline
public static
    int checkIndex(int index, int length) {
    return Preconditions.checkIndex(index, length, null);
}
  • public boolean remove(Object o) 根据元素删除元素
ArrayList<String> list = new ArrayList<>();
list.add("李逵");
list.add("宋江");
list.add("卢俊义");
list.add("西门大官人");

boolean flag = list.remove("西门大官人");
System.out.println("是否删除成功: "+flag);
System.out.println("集合的元素: "+list);

/*
是否删除成功: true
集合的元素: [李逵, 宋江, 卢俊义]
*/

源码分析:

/**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * {@code i} such that
     * {@code Objects.equals(o, get(i))}
     * (if such an element exists).  Returns {@code true} if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return {@code true} if this list contained the specified element
     */
public boolean remove(Object o) {
    final Object[] es = elementData;
    final int size = this.size;
    int i = 0;
    
    //找到o对应的索引,其实还是通过索引来删除
    found: {
        if (o == null) {
            for (; i < size; i++)
                if (es[i] == null)
                    break found;
        } else {
            for (; i < size; i++)
                if (o.equals(es[i]))
                    break found;
        }
        return false;
    }
    fastRemove(es, i);
    return true;
}

3.5 set修改方法

  • public E set(int index, E element) 根据索引修改集合元素
ArrayList<String> list = new ArrayList<>();
list.add("李逵");
list.add("宋江");
list.add("卢俊义");
list.add("西门大官人");

String s = list.set(3, "西门庆");
System.out.println("set方法返回值: "+s);
System.out.println("集合的元素: "+list);

/*
set方法返回值: 西门大官人
集合的元素: [李逵, 宋江, 卢俊义, 西门庆]
*/

源码分析:

public E set(int index, E element) {
    //校验索引有效性
    Objects.checkIndex(index, size);
    //取出index索引位置的旧值
    E oldValue = elementData(index);
    //赋新值
    elementData[index] = element;
    //返回旧值
    return oldValue;
}

3.6 get获取方法

  • public E get(int index) 根据索引获取元素
ArrayList<String> list = new ArrayList<>();
list.add("李逵");
list.add("宋江");
list.add("卢俊义");
list.add("西门大官人");

String value = list.get(1);
System.out.println("get方法返回值: "+value);
System.out.println("集合的元素: "+list);

/*
get方法返回值: 宋江
集合的元素: [李逵, 宋江, 卢俊义, 西门大官人]
*/

源码分析:

/**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
public E get(int index) {
    Objects.checkIndex(index, size);
    return elementData(index);
}

3.7 toString转换方法

  • public String toString() 把集合所有数据转换成字符串
ArrayList<String> list = new ArrayList<>();
list.add("李逵");
list.add("宋江");
list.add("卢俊义");
list.add("西门大官人");

System.out.println("集合的元素: "+list);

//将集合的元素转换为字符串
String str = list.toString();
System.out.println(str);

/*
集合的元素: [李逵, 宋江, 卢俊义, 西门大官人]
[李逵, 宋江, 卢俊义, 西门大官人]
*/

源码分析:

/**
     * Returns a string representation of this collection.  The string
     * representation consists of a list of the collection's elements in the
     * order they are returned by its iterator, enclosed in square brackets
     * ({@code "[]"}).  Adjacent elements are separated by the characters
     * {@code ", "} (comma and space).  Elements are converted to strings as
     * by {@link String#valueOf(Object)}.
     *
     * @return a string representation of this collection
     */
public String toString() {
    Iterator<E> it = 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(' ');
    }
}

3.8 iterator迭代器

  • public Iterator<E> iterator()普通迭代器

源码同上(在讲toString方法的时候已经讲过基本操作,通过以下两个案例进行一步分析源码) 案例一: 已知集合:List list = new ArrayList(); 里面有三个元素:"hello"、"Java"、"PHP",使用迭代器遍历获取集合的每一个元素

@Test
public void test02(){
    ArrayList<String> list = new ArrayList<>();
    //添加元素
    list.add("hello");
    list.add("Java");
    list.add("PHP");

    //获取迭代器
    Iterator<String> it = list.iterator();
    //遍历集合
    while (it.hasNext()) {
        String s = it.next();
        System.out.println(s);
    }
}

/*
hello
Java
PHP
*/

案例二: 已知集合:List list = new ArrayList();里面有三个元素:"hello"、"Java"、"PHP",使用迭代器遍历集合看有没有"PHP"这个元素,如果有,就使用集合对象删除该元素

@Test
public void test02(){
    ArrayList<String> list = new ArrayList<>();
    //添加元素
    list.add("hello");
    list.add("Java");
    list.add("PHP");

    //获取迭代器
    Iterator<String> it = list.iterator();
    //遍历集合
    while (it.hasNext()) {
        String s = it.next();
        if(s.equals("PHP")) {
            list.remove("PHP");
        }
    }
}

控制台结果:并发修改异常 java.util.ConcurrentModificationException at java.base/java.util.ArrayList I t r . c h e c k F o r C o m o d i f i c a t i o n ( A r r a y L i s t . j a v a : 1012 ) a t j a v a . b a s e / j a v a . u t i l . A r r a y L i s t Itr.checkForComodification(ArrayList.java:1012) at java.base/java.util.ArrayList Itr.next(ArrayList.java:966)

源码分析:(应该从获取迭代器的时候就进入到源代码中)

public class ArrayList<E> {
    //长度为0的空数组
    private static final Object[] EMPTY_ELEMENTDATA = {};

    //默认容量为空的数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    //集合存元素的数组
    Object[] elementData;

    //集合的长度
    private int size;

    //默认的容量
    private static final int DEFAULT_CAPACITY = 10;

    //查看add方法其目的就是为了找到记录集合实际修改次数的变量
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }

    //获取迭代器的方法
    public Iterator<E> iterator() {
        //创建了一个对象
        return new Itr();
    }

    //ArrayList集合的内部类 --> 迭代器的源码
    private class Itr implements Iterator<E> {
        int cursor;       // 光标,默认值就是0
        int lastRet = -1; // 记录-1
        // 将集合实际修改次数赋值给预期修改次数
        // 获取迭代器的时候,那么expectedModCount的值也就是 3
        int expectedModCount = modCount;

        //判断集合是否有元素
        public boolean hasNext() {
            //光标是否不等于集合的size 3
            return cursor != size;
        }

        public E next() {
            checkForComodification();
            //光标赋值给i = 0
            int i = cursor;
            //判断,如果大于集合的size就说明没有元素了
            if (i >= size)
                throw new NoSuchElementException();
            //把集合存储数据数组的地址赋值给该方法的局部变量
            Object[] elementData = ArrayList.this.elementData;
            //进行判断,如果条件满足就会产生并发修改异常
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            //光标自增
            cursor = i + 1;
            //从数组中取出元素且返回
            return (E) elementData[lastRet = i];
        }

        //校验预期修改集合次数是否和实际修改集合次数一样
        final void checkForComodification() {
            if (modCount != expectedModCount)
                //如果不一样,就会产生并发修改异常
                throw new ConcurrentModificationException();
        }
    }

    //集合删除元素的方法
    public boolean remove(Object o) {
        //判断要删除的元素是否为null
        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;
    }

    //真正删除元素的方法
    private void fastRemove(int index) {
        //在删除的方法中集合实际修改次数会自增
        //集合实际修改次数为:4 但是预期修改次数为:3
        modCount++;
        //计算集合要移动元素的个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
            //移动的核心代码
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //就是让删除的元素置为null,就是为了尽快被垃圾回收机制回收
        elementData[--size] = null; // clear to let GC do its work
    }

 }

/*
 结论:
    一,集合每次调用add方法的时候,实际修改次数变量的值都会自增一次
    二,在获取迭代器的时候,集合只会执行一次将实际修改集合的次数赋值给预期修改集合的次数
    三,集合在删除元素的时候也会针对实际修改次数的变量进行自增的操作
*/

案例三:已知集合:List list = new ArrayList();里面有三个元素:"hello"、"PHP"、"Java",使用迭代器遍历集合看有没有"PHP"这个元素,如果有,就使用集合对象删除该元素 (跟案例2相比,元素添加顺序不同了,PHP 在第二个位置)

@Test
    public void test02(){
        ArrayList<String> list = new ArrayList<>();
        //添加元素
        list.add("hello");
        list.add("PHP");
        list.add("Java");

        //获取迭代器
        Iterator<String> it = list.iterator();
        //遍历集合
        while (it.hasNext()) {
            String s = it.next();
            if(s.equals("PHP")) {
                list.remove("PHP");
            }
        }
    }

无标题.png

  • default void remove() ArrayList内部类Itr迭代器中的remove方法,删除集合中元素
@Test
public void test02(){
    ArrayList<String> list = new ArrayList<>();
    //添加元素
    list.add("hello");
    list.add("PHP");
    list.add("Java");

    //获取迭代器
    Iterator<String> it = list.iterator();
    //遍历集合
    while (it.hasNext()) {
        String s = it.next();
        if(s.equals("PHP")) {
            it.remove();
        }
    }
    System.out.println(list);
}

/*
[hello, Java]
*/

源码分析:

private class Itr implements Iterator<E> {
        int cursor;       	// 光标,默认值就是0
        int lastRet = -1; 	// index of last element returned; -1 if no such
        int expectedModCount = modCount;

        // prevent creating a synthetic constructor
        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];	//lastRet = i = 0
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
}

3.9 clear清空方法

  • public void clear() 清空集合所有数据
public class Test01 {
    public static void main(String[] args) {
        //创建集合对象
        List<String> list = new ArrayList<String>();
        //添加元素
        list.add("hello");
        list.add("PHP");
        list.add("Java");
        System.out.println("清空前的集合: "+list);
        //清空集合所有元素
        list.clear();
        System.out.println("清空后的集合: "+list);
    }
}

源码分析:

public class ArrayList<E> {
    public void clear() {
        //实际修改集合次数++
        modCount++;
        //遍历集合,将集合每一个索引对应位置上的元素都置为null,尽早让其释放
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        //集合长度更改为0
        size = 0;
    }
}

4.0 contains包含方法

  • public boolean contains(Object o) 判断集合是否包含指定元素
@Test
public void test03(){
    //创建集合对象
    List<String> list = new ArrayList<String>();
    //添加元素
    list.add("hello");
    list.add("PHP");
    list.add("Java");
    System.out.println("判断之前集合的元素: "+list);

    //需求:如果集合中没有JavaSE该元素,请添加一个JavaSE元素
    //解决方式一:循环遍历集合,判断集合是否包含JavaSE,如果没有包含就调用集合的add方法进行添加操作
    //解决方式二:使用集合contains方法判断,根据判断的结果决定是否要添加元素
    if(!list.contains("JavaSE")){
        list.add("JavaSE");
    }
    System.out.println("判断之后集合的元素: "+list);
}

/*
判断之前集合的元素: [hello, PHP, Java]
判断之后集合的元素: [hello, PHP, Java, JavaSE]
*/

源码分析:

public class ArrayList<E> {
    //源码contains方法
    public boolean contains(Object o) {
        //调用indexOf方法进行查找
        return indexOf(o) >= 0;
    }
    
    public int indexOf(Object o) {
        //如果元素是null,也进行遍历操作
        //因为集合中有可能够会存储null
        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;
        }
        //如果没有走if,也没有走else,那么就说明o该元素在集合中不存在
        return -1;
    }
}

结论:contains(Object o) 方法的底层也是通过循环遍历集合,取出一个个的元素和要找的元素进行比较

4.1 isEmpty判断集合是否为空

  • public boolean isEmpty()
//创建集合对象
List<String> list = new ArrayList<String>();
//添加元素
list.add("hello");
list.add("PHP");
list.add("Java");
boolean b = list.isEmpty();
System.out.println(b);
System.out.println(list);

/*
false
[hello, PHP, Java]
*/

源码分析:

public class ArrayList<E> {
    public boolean isEmpty() {
        return size == 0;
    }
}

4. 面试题

4.1 ArrayList是如何扩容的?

源码分析过程中已经讲解 第一次扩容10 以后每次都是原容量的1.5倍

public class ArrayList<E> {
    //长度为0的空数组
    private static final Object[] EMPTY_ELEMENTDATA = {};

    //默认容量为空的数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    //集合存元素的数组
    Object[] elementData;

    //集合的长度
    private int size;

    //默认的容量
    private static final int DEFAULT_CAPACITY = 10;

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
            modCount++;

        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        // >> : 右移,右移几位就相当于除以2的几次幂
        // << : 左移,左移几位就相当于乘以2的几次幂
        //扩容的核心算法: 原容量的1.5倍
        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:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
}

4.2 ArrayList频繁扩容导致添加性能急剧下降,如何处理?

  • 初始化ArrayList时未指定容量

添加100000条数据耗时21ms

image.png

  • 指定初始容量

image.png

注意:这种优化方式只针对特定的场景,如果添加的元素是少量的、未知的,不推荐使用

4.3 ArrayList插入或删除元素一定比LinkedList慢么?

  • 根据索引删除

案例:ArrayList和LinkedList对比

@Test
public void test05(){
    //创建ArrayList集合对象
    ArrayList<String> arrayList = new ArrayList<String>();
    //添加500W个元素
    for (int i = 0; i < 5000000; i++) {
        arrayList.add(i+"列表");
    }
    //获取开始时间
    long startTime = System.currentTimeMillis();
    //根据索引删除ArrayList集合元素
    //删除索引5000对应的元素
    String value = arrayList.remove(50000);
    System.out.println(value);
    //获取结束时间
    long endTime = System.currentTimeMillis();
    System.out.println("ArrayList集合删除元素的时间: "+(endTime-startTime));

    //创建LinkedList集合对象
    LinkedList<String> linkedList = new LinkedList<String>();
    //添加500W个元素
    for (int i = 0; i < 5000000; i++) {
        linkedList.add(i+"列表");
    }
    //获取开始时间
    startTime = System.currentTimeMillis();
    //根据索引删除LinkedList集合元素
    //删除索引5000对应的元素
    value = arrayList.remove(50000);
    System.out.println(value);
    endTime = System.currentTimeMillis();
    System.out.println("LinkedList集合删除元素的时间: "+(endTime-startTime));
}

/*
50000列表
ArrayList集合删除元素的时间: 42
50001列表
LinkedList集合删除元素的时间: 44
*/

源码分析:

  • ArrayList根据索引删除元素源码
public class ArrayList<E> {
    public E remove(int index) {
        //范围校验
        rangeCheck(index);
       //增量++
        modCount++;
        //将index对应的元素赋值给 oldValue
        E oldValue = elementData(index);
        //计算集合需要移动元素个数
        int numMoved = size - index - 1;
        //如果需要移动元素个数大于0,就使用arrayCopy方法进行拷贝
        //注意:数据源和数据目的就是elementData
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //将源集合最后一个元素置为null,尽早让垃圾回收机制对其进行回收
        elementData[--size] = null; 
       //返回被删除的元素
        return oldValue;
    }
}
  • LinkedList根据索引删除元素源码
public class LinkedList<E> {
    public E remove(int index) {
        //调用方法校验元素的索引
        checkElementIndex(index);
        //先调用node(index)方法,找到需要删除的索引
        //再调用unlink方法解开链条
        return unlink(node(index));
    }
    
    //校验索引是否在合法范围之内,不再就报错
    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }
    
    //获取要删除的元素
    Node<E> node(int index) {
        //不管索引是多少,在源码底层都会对整个链表上的元素进行折半的动作
        //如果要删除元素的索引小于集合长度的一半,那么就从头节点一个个的往后找
        //如果要删除元素的索引大于集合长度的一半,那么就从尾节点一个个的往后找
        //(注:这个查找的效率相对于ArrayList集合来说较低)
        if (index < (size >> 1)) {
            Node<E> x = first;
            //如果循环条件不满足,那么first就是要删除的元素
            //否则,要删除的元素就是first的下一个
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            //如果循环条件不满足,那么last就是要删除的元素
            //否则,要删除的元素就是last的前一个
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
    
    //解开链表,让前后节点相互记录地址
    E unlink(Node<E> x) {
        //获取要删除的元素
        final E element = x.item;
        //获取被删除节点下一个节点的地址
        final Node<E> next = x.next;
        //获取被删除节点上一个节点的地址
        final Node<E> prev = x.prev;
 
        //如果被删除节点的上一个节点为null,就让被删除节点的下一个节点成为首节点
        if (prev == null) {
            first = next;
        } else {
            //否则,被删除元素上一个节点的 下一个节点 变成 被删除元素的下一个节点
            prev.next = next;
            //被删除元素的上一个节点置为null
            x.prev = null;
        }
        //如果被删除元素的下一个节点为null,最后一个节点就等于被删除元素的上一个节点
        if (next == null) {
            last = prev;
        } else {
            //否则,被删除节点的下一个节点 等于被删除节点的前一个节点
            next.prev = prev;
            //被删除元素的下一个节点置为null
            x.next = null;
        }
 
        //被删除元素的内容置为null
        x.item = null;
        //集合长度--
        size--;
        //实际修改次数++
        modCount++;
        //返回被删除的元素
        return element;
    }
}

结论:

  1. 数组删除元素确实要比链表慢,慢在需要创建新数组,还有比较麻烦的数据拷贝,但是在ArrayList底层不是每次删除元素都需要扩容,因此在这个方面相对于链表来说数组的性能更好
  2. LinkedList删除元素之所以效率并不高,其原理在于底层先需要对整个集合进行折半的动作,然后又需要对集合进行遍历一次,这些操作导致效率变低

4.4 ArrayList是线程安全的么?

ArrayList不是线程安全的

案例演示:多线程情况下向 ArrayList 添加元素会造成

java.util.ConcurrentModificationException

image.png

高并发情况下要想使用集合怎么办呢? 推荐使用Vector

image.png

image.png

Vector集合 的add方法加锁保证了并发

image.png

实际开发场景 案例:使用JdbcTemplate查询数据库返回一个List集合是否需要保证线程安全?

public class Test01 {
    //创建JdbcTemplate对象
    JdbcTemplate jt = new JdbcTemplate(JDBCUtils.getDataSource());
 
    //3.利用JDBC查询出基础班在读的男学员的所有信息按成绩的降序输出到控制台上(利用JDBC)
    @Test
    public void fun1() throws Exception {
        //拼写SQL
        String sql = "select * from stutb where sex = ? and type like ? order by score desc";
        //调用方法查询  将结果集的每一行都封装成一个Stutb对象,并把每一个对象都添加到集合
        //查询的结果是否需要保证线程安全???
        List<Stutb> list = jt.query(sql, new BeanPropertyRowMapper<Stutb>(Stutb.class), "男", "%基础班%");
        //在遍历集合取出结果集之前面临一个问题,使用普通for遍历好 还是使用迭代器(增强for)?
        //特别是数据量特别大的时候一定要考虑!
        //对返回的集合进行判断,如果返回的集合实现了 RandomAccess 就使用 普通for
        //否则使用迭代器(增强for)
        if(list instanceof RandomAccess){
            for (int i = 0; i < list.size(); i++) {
                System.out.println(list.get(i));
            }
        }else {
            for (Stutb stutb : list) {
                System.out.println(stutb);
            }
        }
    }
}

List<Stutb> list = jt.query... 返回的是一个集合 需要考虑线程安全吗? 回答:不需要,因为list是局部变量 每一个线程调用完该方法后都被销毁了(优先考虑性能,方法中的局部变量不需要加锁)

4.5 如何复制某个ArrayList到另一个ArrayList中去?

  • 使用clone()方法
  • 使用ArrayList构造方法
  • 使用addAll方法

以上三种方式都在前面有讲解

4.6 已知成员变量集合存储N多用户名称,在多线程的环境下,使用迭代器在读取集合数据的同时如何保证还可以正常的写入数据到集合?

  • 普通集合 ArrayList
//线程任务类
class CollectionThread implements Runnable{
    private static ArrayList<String> list = new ArrayList<String>();
    static{
        list.add("Jack");
        list.add("Lucy");
        list.add("Jimmy");
    }
 
    @Override
    public void run() {
        for (String value : list) {
            System.out.println(value);
            //在读取数据的同时又向集合写入数据
            list.add("coco");
        }
    }
}
 
//测试类
public class ReadAndWriteTest {
    public static void main(String[] args) {
        //创建线程任务
        CollectionThread ct = new CollectionThread();
 
        //开启10条线程
        for (int i = 0; i < 10; i++) {
            new Thread(ct).start();
        }
    }
}

打印结果: image.png

  • 读写分离集合
//线程任务类
class CollectionThread implements Runnable{
    private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
    static{
        list.add("Jack");
        list.add("Lucy");
        list.add("Jimmy");
    }
 
    @Override
    public void run() {
        for (String value : list) {
            System.out.println(value);
            //在读取数据的同时又向集合写入数据
            list.add("coco");
        }
    }
}
 
//测试类
public class ReadAndWriteTest2 {
    public static void main(String[] args) {
        //创建线程任务
        CollectionThread ct = new CollectionThread();
 
        //开启10条线程
        for (int i = 0; i < 10; i++) {
            new Thread(ct).start();
        }
    }
}

222.gif

4.7 ArrayList 和 LinkList区别?

  • ArrayList
    • 基于动态数组的数据结构
    • 对于随机访问的get和set,ArrayList要优于LinkedList
    • 对于随机操作的add和remove,ArrayList不一定比LinkedList慢 (ArrayList底层由于是动态数组,因此并不是每次add和remove的时候都需要创建新数组)
  • LinkedList
    • 基于链表的数据结构
    • 对于顺序操作,LinkedList不一定比ArrayList慢
    • 对于随机操作,LinkedList效率明显较低

5. 自定义ArrayList

public class MyArrayList<E>{
    //初始容量
    private static final int DEFAULT_CAPACITY = 10;
    //空数组
    private static final Object[] EMPTY_ELEMENTDATA = {};

    //实际存数据的数组
    transient Object[] elementData;

    //数组中元素个数
    private int size;

    //无参构造
    public MyArrayList() {
        this.elementData = EMPTY_ELEMENTDATA;
    }

    public int getSize() {
        return size;
    }

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

    public boolean contains(Object o){
        return indexOf(o) >= 0;
    }

    public int indexOf(Object o){
        return indexOfRange(o,0,size);
    }

    int indexOfRange(Object o,int start,int end){
        Object[] es = elementData;
        if(o == null){
            for (int i = start; i < end; i++) {
                if(es[i] == null){
                    return i;
                }
            }
        }else{
            for (int i = start; i < end; i++) {
                if(o.equals(es[i])){
                    return i;
                }
            }
        }
        return -1;
    }

    //带容量的构造
    public MyArrayList(int initialCapacity) {
        if(initialCapacity>0){
            this.elementData = new Object[initialCapacity];
        }else if(initialCapacity == 0){
            this.elementData = EMPTY_ELEMENTDATA;
        }else{
            throw new IllegalArgumentException("非法参数:"+initialCapacity);
        }
    }

    //带初始集合的构造
    public MyArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if((size = elementData.length) != 0){
            if(elementData.getClass() != Object[].class){
                elementData = Arrays.copyOf(elementData,size,Object[].class);
            }
        }else{
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

    //扩容1.5倍
    private void grow(){
        if(elementData == EMPTY_ELEMENTDATA){
            elementData = new Object[DEFAULT_CAPACITY];
        }
        //当前容器内元素个数等于数组长度
        if(size == elementData.length){
            System.out.println("==================>执行一次扩容");
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);

            Object[] obj = new Object[newCapacity];
            //拷贝元素
            System.arraycopy(elementData,0,obj,0,elementData.length);
            //把新数组的地址赋值给elementData
            elementData = obj;
        }
    }

    //toString方法
    public String toString(){
        if(size == 0){
            return "[]";
        }

        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int i = 0; i < size; i++) {
            if(i == size-1){
                sb.append(elementData[i]).append("]");
            }else{
                sb.append(elementData[i]).append(", ");
            }
        }
        return sb.toString();
    }

    public E set(int index,E element){
        checkIndex(index);
        E value = (E) elementData[index];
        elementData[index] = element;
        //返回原来位置的数据
        return value;
    }

    private void checkIndex(int index){
        if(index <0 || index >= size){
            throw new IndexOutOfBoundsException("索引越界了!");
        }
    }

    public E remove(int index){
        checkIndex(index);
        E value = (E) elementData[index];
        //计算出要移动元素的个数
        int numMoved = size - index - 1;
        if(numMoved > 0){
            System.arraycopy(elementData,index+1,elementData,index,numMoved);
        }
        //把最后一个位置上的元素置为null
        elementData[--size] = null;
        return value;
    }

    //根据索引获取元素
    public E get(int index){
        checkIndex(index);
        return (E) elementData[index];
    }

    public boolean add(E e){
        grow();
        elementData[size++] = e;
        return true;
    }
}

6. 根据异常信息分析源码

描述:

一个线程正在迭代List 而另一个线程去修改List(Collection 多态),会抛出ConcurrentModificationException异常

image.png

//异常
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
	at com.xjt.myList.ListDemo.main(ListDemo.java:18)

分析源码:

异常信息要从下往上读

1、at com.xjt.myList.ListDemo.main(ListDemo.java:18),在myList.ListDemo.main函数第18行

2、at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967),ArrayList.java中第967行

image.png

执行itr.next()方法时,会先去执行 checkForComodification``() 方法

3、at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013) 在ArrayList.java文件的1013行

image.png

判断 modCount != expectedModCount ,如果不相等 抛出 ConcurrentModificationException异常

image.png

当执行arrayList.add("python") modCount+1,下次循环进来的时候expectedModCount 和modCount就不相等了。

那么 modCount 和 expectedModCount 又是什么呢? 我们可以发现在 类Itr 里面定义了:

image.png

ArrayList.java 中 Ctrl+F 搜索发现modCount没有定义,是直接调用的,所有我们去它父类中找

image.png

image.png

问题找到了,那么我们怎么在遍历List时改变它的内容呢?

//for循环遍历
for (int i = 0; i < arrayList.size(); i++) {
    if(arrayList.get(i).equals("java")){
        arrayList.add("python");
    }
}

注意:增强for(foreach)内部其实就是Iterator

image.png

通过ArrayList实例的ListIterator实现遍历 ListIterator:列表的迭代器,允许程序员在任一方向上遍历列表,在迭代期间修改列表,并获取迭代器在列表中的当前位置。

ListIterator<String> lit = arrayList.listIterator();
while (lit.hasNext()) {
    String s = lit.next();
    if (s.equals("java")) {
        arrayList.add("python");
    }
}

这么多种遍历List方式我们怎么选择呢?

  • 只是取List每一项值,不需要修改时,使用foreach最方便;
  • 需要获取索引值或需要修改List项的值时,选择for循环;
  • ListIterator 可以往前遍历往后遍历或从某一项索引开始遍历,且能修改List,使用最灵活;
  • Iterator 遍历List可以删除/获取List每一项;

猜你喜欢

转载自juejin.im/post/7113484537757171749