ArrayList源码解析(基于Java8)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 首先:执行List list1 = new ArrayList();在堆内存开辟了一块空间,既然是new出来的,那我们直接从构造函数入手Object[]数组,也就是说该数组可以放任何对象(所有对象都继承自父类Object)继续,执行list1.

img_7fa5ce715f0436210225b753df4d103f.png

img_5708171da5ab02046a83a45fb6fc0073.png

img_78a3a5e056228ea49a34015e915ed63a.png

首先:执行 List<Person> list1 = new ArrayList<>();
在堆内存开辟了一块空间,既然是new出来的,那我们直接从构造函数入手
img_7e5a9862d2e9f41488837969b190717a.png

img_67e1db21f6da2e819f29a1aa77ba1b6c.png

Object[]数组,也就是说该数组可以放任何对象(所有对象都继承自父类Object)
继续,执行 list1.add(person1)看怎么处理add的
img_42d4be0fb5d38e491cf7ec5c0385fc80.png

先看 ensureCapacityInternal方法,有个参数 size,看一下这个 size从哪来的
img_2aa9b6019c9213ace96cd3ed46f93b01.png

size是int基本数据类型,成员变量初始化的为0

继续往下看


img_1e0955a3bff35b5cf7514895bcdea9af.png

ensureCapacityInternal是在add里面调用的

img_2b6bea95ec02236935909e4eb8d9a9c0.png

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //如果底层数组就是默认的缓存数组,取两个参数的大的一个值继续往下调用,很明显这个大值为10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
 }

 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//记录修改次数

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

扩容

img_f195ba7eb554b4c256853118fc5dd518.png
    private void grow(int minCapacity) {
        // 取到旧数组的长度
        int oldCapacity = elementData.length;
        //计算新数组的长度
        //>>是移位运算符,相当于int newCapacity = oldCapacity + (oldCapacity/2),但性能会好一些
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //保证长度在正常范围内
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 用计算出来的数组长度,往下传继续处理
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

这就是数组的扩容,一般是oldCapacity + (oldCapacity >> 1),相当于扩容1.5倍

跟进到Arrays这个工具类,很简单


img_9043790cfb60dbd0dacf4b31b0c5411d.png

再看copyOf()方法


img_a2c19c4999dc4f1dbc7cb53394afb9a7.png

System.arraycopy()方法用了一个native来修饰

这是一个数组拷贝方法,大家还在写for循环拷贝数组吗?以后多用这个方法吧,简单又方便还能获得得更好的性能

img_16cd023791b85dcdd0bbeb4599cc757f.png

顺便看一看size()方法的源码


img_c6fcd6d4bcea2f5175c8ebb19268c242.png

很简单,就是返回size值,而不是底层数组的长度,就是为何String里叫length()而List里叫size()的原因

ArrayList还提供了其它构造方法,我们顺便来看一下

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

当我们在写代码过程中,如果我们大概知道元素的个数,比如一个班级大概有40-50人,我们优先考虑List<Person> list2 = new ArrayList<>(50)以指定个数的方式去构造,这样可以避免底层数组的多次拷贝,进而提高程序性能。

在长度为n数组中:

直接通过下标去访问元素,时间复杂度为O(1)
需要循环查找元素的时候,时间复杂度为O(n)

删除

删除指定位置的元素

public E remove(int index) {
        rangeCheck(index);

        //维护的一个变量,记录修改次数
        modCount++;
        //根据数组下标拿到底层数组里的元素
        E oldValue = elementData(index);

        //计算数组需要拷贝的元素个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
            //拷贝删除位置(index)+1后面的numMoved个元素并从删除位置(index)开始复制
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //相当于:size = size - 1; elementData[size] == null
        //size减1,数组最后一个位置赋值为null
        elementData[--size] = null; // clear to let GC do its work

        //返回事先拿到的删除元素
        return oldValue;
}

这段代码里还调了一个方法rangeCheck()方法,我们看一下

private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

就是检查底层数组下标是否越界

再看另外一种删除方式

删除指定对象元素

public boolean remove(Object o) {
        //如果要删除的元素为null
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    //循环查找null,找到后执行fastRemove()方法
                    fastRemove(index);
                    //删除成功,返回true
                    return true;
                }
        } else {
            //要删除元素非空
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    //equals循环对比,对比成功则执行fastRemove()方法
                    fastRemove(index);
                    //删除成功,返回true
                    return true;
                }
        }
        //其他情况,返回 false
        return false;
}

再看一下fastRemove()方法

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; // clear to let GC do its work
}

和上面用下标删除方式一致,就是少了某些代码,就不细说了

2 CopyOnWriteArrayList

写时复制的容器

当我们往一个容器添加元素的时候,不是直接往当前容器添加,而是

  • 先将当前容器进行copy一份,复制出一个新的容器
  • 然后对新容器里面操作元素,最后将原容器的引用指向新的容器

所以CopyOnWrite容器是一种读写分离的思想

2.1 应用场景

适合高并发的读操作(读多写少)。若写的操作非常多,会频繁复制容器,从而影响性能

CopyOnWriteArrayList 写时复制的集合,在执行写操作(如:addsetremove等)时,都会将原数组拷贝一份,然后在新数组上做修改操作。最后集合的引用指向新数组

CopyOnWriteArrayList 和Vector都是线程安全的,不同的是:前者使用ReentrantLock类,后者使用synchronized关键字。
ReentrantLock提供了更多的锁机制,在锁竞争的情况下能表现更佳的性能。就是它让JVM能更快的调度线程,才有更多的时间去执行线程。这就是为什么CopyOnWriteArrayList的性能在大并发量的情况下优于Vector的原因。

private E get(Object[] a, int index) {
    return (E) a[index];
}
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}
private boolean remove(Object o, Object[] snapshot, int index) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] current = getArray();
        int len = current.length;
        ......
        Object[] newElements = new Object[len - 1];
        System.arraycopy(current, 0, newElements, 0, index);
        System.arraycopy(current, index + 1, newElements, index, len - index - 1);
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}
目录
相关文章
|
10天前
|
人工智能 自然语言处理 Java
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
FastExcel 是一款基于 Java 的高性能 Excel 处理工具,专注于优化大规模数据处理,提供简洁易用的 API 和流式操作能力,支持从 EasyExcel 无缝迁移。
69 9
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
|
17天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
16天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
16天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
16天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
2天前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
15天前
|
Java 数据库连接 Spring
反射-----浅解析(Java)
在java中,我们可以通过反射机制,知道任何一个类的成员变量(成员属性)和成员方法,也可以堆任何一个对象,调用这个对象的任何属性和方法,更进一步我们还可以修改部分信息和。
|
27天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
111 13
|
17天前
|
安全 搜索推荐 数据挖掘
陪玩系统源码开发流程解析,成品陪玩系统源码的优点
我们自主开发的多客陪玩系统源码,整合了市面上主流陪玩APP功能,支持二次开发。该系统适用于线上游戏陪玩、语音视频聊天、心理咨询等场景,提供用户注册管理、陪玩者资料库、预约匹配、实时通讯、支付结算、安全隐私保护、客户服务及数据分析等功能,打造综合性社交平台。随着互联网技术发展,陪玩系统正成为游戏爱好者的新宠,改变游戏体验并带来新的商业模式。
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
96 2

推荐镜像

更多