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


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

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


相关文章
|
1月前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
65 7
|
2月前
|
数据采集 人工智能 Java
Java产科专科电子病历系统源码
产科专科电子病历系统,全结构化设计,实现产科专科电子病历与院内HIS、LIS、PACS信息系统、区域妇幼信息平台的三级互联互通,系统由门诊系统、住院系统、数据统计模块三部分组成,它管理了孕妇从怀孕开始到生产结束42天一系列医院保健服务信息。
43 4
|
20天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
6天前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
30天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
116 13
|
2月前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
61 12
|
1月前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
1月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
57 2
|
1月前
|
人工智能 移动开发 安全
家政上门系统用户端、阿姨端源码,java家政管理平台源码
家政上门系统基于互联网技术,整合大数据分析、AI算法和现代通信技术,提供便捷高效的家政服务。涵盖保洁、月嫂、烹饪等多元化服务,支持多终端访问,具备智能匹配、在线支付、订单管理等功能,确保服务透明、安全,适用于家庭生活的各种需求场景,推动家政市场规范化发展。
|
2月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!