<Java八股文面试>ArrayList源码 | Iterator源码 | LinkedList和ArrayList对比(下)

简介: <Java八股文面试>ArrayList源码 | Iterator源码 | LinkedList和ArrayList对比

2.2 fail-fast和fail-safe源码剖析

fail-fast源码分析

当使用增强for迭代list集合时,会先创建一个Itr对象(属于Iterator类),为属性expectedModCount初始化.


expectedModCount的初始值为list中的modCount (modCount 是list的成员变量,记录list被修改的次数)


之后每次迭代list就是用Itr对象.先判断hasNext()是否有值,如果则调用NEXT()方法进行获取.


NEXT()方法执行时,会先判断是否存在并发修改.如果存在则抛出异常,不存在则返回需要的元素.


分析演示场景: 迭代过程中为list增加了一个元素,导致modCount变成了5,但是expectedModCount还为4.所以Next()中验证是否存在修改时就会报异常.

private class Itr implements Iterator<E> {
    int cursor;       // 返回下一个元素的下标
    int lastRet = -1; // 返回最后一个元素的下标,如果没有则返回-1
    int expectedModCount = modCount;//迭代器对象刚进行迭代时修改的初始次数(也就是循环开始时的修改次数)
    Itr() {}//构造方法
    public boolean hasNext() {//判断是否还有下一个元素
        return cursor != size;//如果5个元素 size:5 当遍历完最后一个元素后再次迭代时,cursor=5,返回false.
    }
    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];//返回需要的元素
    }
    //检查是否在迭代过程中进行了修改
    final void checkForComodification() {
        if (modCount != expectedModCount)//如果进行了修改modCount就会增加,导致这俩属性值不相等,抛出并发修改异常
            throw new ConcurrentModificationException();
    }
}

fail-safe源码分析


当使用增强for迭代list集合时,会先创建一个COWIterator对象,将需要迭代的集合赋值给 snapshot,cursor初始化为0.


之后每次迭代list就是使用COWIterator对象,先hasNext()判断是否有值.然后调用Next()获取值.


**分析演示场景:**当进行for遍历时,会把需要遍历的集合在COWIterator中存一份.之后每次遍历都是使用COWIterator中的数组对象. 当在遍历过程中进行add操作时,会创建一个新的数组,将老的元素和新的元素放入,再赋值给list集合中的数组对象.这个过程也被称为 写时复制(读写分离)


所以最后输出结果是:遍历出的没有新元素E,list直接输出有新元素E


//COWIterator
static final class COWIterator<E> implements ListIterator<E> {
    /** 数组的快照 */
    private final Object[] snapshot;
    /** 返回元素的下一个坐标.  */
    private int cursor;
    //构造器
    COWIterator(Object[] es, int initialCursor) {
        cursor = initialCursor;
        snapshot = es;
    }
    //判断是否有下一个袁旭
    public boolean hasNext() {
        return cursor < snapshot.length;
    }
    //获取下一个元素的值.
    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];//cursor指针后移
    }
}
//CopyOnWriteArrayList类
public class CopyOnWriteArrayList<E> implements List<E>{
    final transient Object lock = new Object();
    //数组对象用于存放元素
    private transient volatile Object[] array;
    /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }
    //添加元素
    public boolean add(E e) {
        synchronized (lock) {
            Object[] es = getArray();//获取之前的数组对象
            int len = es.length;//获取长度
            es = Arrays.copyOf(es, len + 1);//创建一个 len + 1 的新数组
            es[len] = e;//将新的值放在最后一个位置上.
            setArray(es);//将新数组对象赋值给list中的array
            return true;
        }
    }
}

2.3 补充:Vector底层使用的是哪种迭代机制呢?

Vector其实就比ArrayList多了个同步机制,也就是每个方法加上了synchronized.

经过Debug发现,Vector使用的也是Itr作为迭代器,其迭代类型属于:Fail-Fast.

代码演示

private static void testVector(){
    Vector <Student> vector = new Vector <>();
    vector.add(new Student("A"));
    vector.add(new Student("B"));
    vector.add(new Student("C"));
    vector.add(new Student("D"));
    for (Student student : vector) {
        System.out.println(student);
    }
    System.out.println(vector);
}

4494dbb09bea0eb73aa57f43f5381fa5.png

3. LinkedList

3.1 对比LinkedList 和 ArrayList 的区别


基于双向链表,无需连续内存

随机访问慢(要沿着链表遍历)

头尾插入删除性能高

无法很好的利用CPU缓存,无法很好的配合局部性原理,占用内存多,

ArrayList


基于数组,需要连续内存

随机访问快(指根据下标访问)

尾部插入、删除性能可以,其它部分插入、删除都会移动数据,因此性能会低 (但是性能也可以秒杀LinkedList)

可以很好的利用CPU缓存,配合局部性原理,占用内存少,


3.2 代码对比两者性能

3.2.1 对比继承和实现关系

根据继承和实现关系,可以看出 ArrayList比LinkedList多实现一个RandomAccess接口.


public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{}
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{}

RandomAccess是个标记接口, 主要用于标记该类使用下标进行访问的,还是利用指针进行访问的.

 /* 
 *<pre>
 *     for (int i=0, n=list.size(); i &lt; n; i++)
 *         list.get(i);
 * </pre>
 * 上面这种循环比下面这种循环效率更高:
 * <pre>
 *     for (Iterator i=list.iterator(); i.hasNext(); )
 *         i.next();
 * </pre>
 *
 * @since 1.4
 */
public interface RandomAccess {//标记接口,不做任何实现. 
}

3.2.2 对比随机访问性能

public static void main(String[] args) {
    int n = 1000;
    int insertIndex = n;
    //产生1000个随机数,测试对比ArrayList和LinkedList的随机访问性能
    for (int i = 0; i < 5; i++) {//测试五次(JVM要预热一下,次数越多越准确O!)
        int[] array = randomArray(n);
        List<Integer> list1 = Arrays.stream(array).boxed().collect(Collectors.toList());
        LinkedList<Integer> list2 = new LinkedList<>(list1);
        randomAccess(list1, list2, n / 2);//访问中间的数
    }
}
//随机访问方法
static void randomAccess(List<Integer> list1, LinkedList<Integer> list2, int mid) {
    StopWatch sw = new StopWatch();
    sw.start("ArrayList");
    list1.get(mid);
    sw.stop();
    sw.start("LinkedList");
    list2.get(mid);
    sw.stop();
    System.out.println(sw.prettyPrint());
}

测试结果:

3fc8d38407d839809548b0975b821415.png


3.2.3 对比插入功能

  • 头部插入对比
private static void addFirst(List<Integer> list1, LinkedList<Integer> list2) {
    StopWatch sw = new StopWatch();
    sw.start("ArrayList");
    list1.add(0, 100);
    sw.stop();
    sw.start("LinkedList");
    list2.addFirst(100);
    sw.stop();
    System.out.println(sw.prettyPrint());
}

结果:LinkedList远超于ArrayList.

ddbcba632feda4e9d41f46e99ce62146.png

  • 尾部插入对比
private static void addLast(List<Integer> list1, LinkedList<Integer> list2) {
    StopWatch sw = new StopWatch();
    sw.start("ArrayList");
    list1.add(100);
    sw.stop();
    sw.start("LinkedList");
    list2.add(100);
    sw.stop();
    System.out.println(sw.prettyPrint());
}

结果:ArrayList更快一些

6d05d1bf520e3901680ab326d2333e04.png

  • 中间插入
private static void addMiddle(List<Integer> list1, LinkedList<Integer> list2, int mid) {
    StopWatch sw = new StopWatch();
    sw.start("ArrayList");
    list1.add(mid, 100);
    sw.stop();
    sw.start("LinkedList");
    list2.add(mid, 100);
    sw.stop();
    System.out.println(sw.prettyPrint());
}

结果:ArrayList远超于LinkedList (因为LinkedList的迭代过程非常缓慢)


45aa661f7c8fc862a06ed21a7f140328.png


总结:


传统的 ArrayList:增删慢,查询快 LinkedList:增删快,查询慢 说法是完全错误的.


ArrayList除了头部插入比LinkedList效率低,其他方面都是超于LinkedList. 所以实际项目开发中,建议使用ArrayList.


3.2.4 对比占用内存


没有CPU缓存前:数据的运算,先将数据从硬盘读到CPU,然后CPU执行计算,计算完写到硬盘.但是CPU的计算速度是很快的(1s执行亿次级),例如CPU执行指令<1nm,但是读数据到CPU和写数据到硬盘却需要几百nm,CPU利用率极低.


有了CPU缓存:数据的运算,先将数据从硬盘加载到缓存中,然后CPU从缓存中读数据(可以提高到10nm),接着CPU进行运算,运算完将结果写到缓存,由缓存将数据写入硬盘.CPU的效率可以大大提高.


489b076d06de33039810b78ccf6da194.png


局部性原理: 读取元素时,其相邻元素也有很大概率被访问到.


对于ArrayList,会将其相邻元素也加载进入缓存,由于其元素在内存是是连续的.下次使用就不用再重新加载了,直接从缓存中取.


而对于LinkedList,由于其数据存储并不是连续的.虽然也将即将使用的数据及其相邻内存的数据加入缓存,但是后面需要的数据可能并不在当前的缓存中.而且一般缓存也是很小的,后面加入的数据可能覆盖前面的.所以需要经常将数据加入缓存. 从而无法很好的配合局部性原理.


如果有收获!!! 希望老铁们来个三连,点赞、收藏、转发。

创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客


相关文章
|
21天前
|
Java 程序员
java线程池讲解面试
java线程池讲解面试
38 1
|
11天前
|
存储 安全 Java
面试题:用过ThreadLocal吗?ThreadLocal是在哪个包下的?看过ThreadLocal源码吗?讲一下ThreadLocal的get和put是怎么实现的?
字节面试题:用过ThreadLocal吗?ThreadLocal是在哪个包下的?看过ThreadLocal源码吗?讲一下ThreadLocal的get和put是怎么实现的?
30 0
|
1天前
|
安全 Java
就只说 3 个 Java 面试题 —— 02
就只说 3 个 Java 面试题 —— 02
8 0
|
1天前
|
存储 安全 Java
就只说 3 个 Java 面试题
就只说 3 个 Java 面试题
7 0
|
11天前
|
Java 关系型数据库 MySQL
大厂面试题详解:Java抽象类与接口的概念及区别
字节跳动大厂面试题详解:Java抽象类与接口的概念及区别
33 0
|
20天前
|
存储 缓存 算法
Java入门高频考查基础知识4(字节跳动面试题18题2.5万字参考答案)
最重要的是保持自信和冷静。提前准备,并对自己的知识和经验有自信,这样您就能在面试中展现出最佳的表现。祝您面试顺利!Java 是一种广泛使用的面向对象编程语言,在软件开发领域有着重要的地位。Java 提供了丰富的库和强大的特性,适用于多种应用场景,包括企业应用、移动应用、嵌入式系统等。下是几个面试技巧:复习核心概念、熟悉常见问题、编码实践、项目经验准备、注意优缺点、积极参与互动、准备好问题问对方和知其所以然等,多准备最好轻松能举一反三。
46 0
Java入门高频考查基础知识4(字节跳动面试题18题2.5万字参考答案)
|
24天前
|
Java 程序员 API
java1.8常考面试题
在Java 1.8版本中,引入了很多重要的新特性,这些特性常常成为面试的焦点
42 8
|
29天前
|
NoSQL Java 关系型数据库
整理Java面试题
整理Java面试题
|
30天前
|
安全 算法 Java
Java 并发编程 面试题及答案整理,最新面试题
Java 并发编程 面试题及答案整理,最新面试题
88 0
|
30天前
|
存储 算法 安全
Java 面试题及答案整理,最新面试题
Java 面试题及答案整理,最新面试题
80 1