JDK1.8中ArrayList集合源码解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: JDK1.8中ArrayList集合源码解析

ArrayList集合源码解析

在这里插入图片描述

  • 所有集合类都位于java.util包下。Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类

在这里插入图片描述


今天我们了解下List 接口

  1. List集合代表一个有序集合,集合中每个元素都有其对应的顺序索引。List集合允许使用重复元素,可以通过索引来访问指定位置的集合元素。

  2. List接口继承于Collection接口,它可以定义一个允许重复的有序集合。因为List中的元素是有序的,所以我们可以通过使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。

  3. List接口为Collection直接接口。List所代表的是有序的Collection,即它用某种特定的插入顺序来维护元素顺序。用户可以对列表中每个元素的插入位置进行精确地控制,同时可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。

  4. 实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。

ArrayList底层实现原理

在这里插入图片描述

ArrayList 底层基于数组实现的

    /**
     * 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

类中的属性:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    /**
     *  版本号
     */
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * 初始化长度
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 空数组
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 空实例的共享空数组
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 元素数组
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * 长度 默认为0
     * @serial
     */
    private int size;

  }

构造解析:

在这里插入图片描述

    /**
     * 构造具有指定初始容量的空列表。
     */
    public ArrayList(int initialCapacity) {
         //初始容量 大于0则 elementData 如上设置成 定长数组
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
         // 如果等于 0 则默认空数组
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
          //如果为负数 抛出异常   java.lang.IllegalArgumentException: Illegal Capacity: -1
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    /**
     * 构造一个初始容量为10的空列表。如何分配10 下面介绍
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 
    }
/**
 * 构造一个包含指定元素的列表
 */
public ArrayList(Collection<? extends E> c) {
    // 是将c直接转为Object[] 数组 赋值给elementData
    elementData = c.toArray();
    //如果 elementData 等于0的情况初始化空数组
    if ((size = elementData.length) != 0) {
        // 每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类型,那么久需要使用ArrayList中的方法去改造一下。
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

核心方法:

在这里插入图片描述

add(E e)

/**
 * 将指定的元素追加到此列表的末尾。
 */
public boolean add(E e) {
        // 确定内部容量的方法
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
// 确定内部容量的方法
private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //因为数组如果是空的话,minCapacity=size+1;其实就是等于1,空的数组没有长度就存放不了,所以就将minCapacity变成10,也就是默认大小,
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

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

        //如上所诉,因为数组如果是空的话,minCapacity=size+1;其实就是等于1,空的数组没有长度就存放不了,所以就将minCapacity变成10,第二次 elementData不是空数组了,也就是minCapacity代表着elementData中增加之后的实际数据个数,拿着它判断elementData的length是否够用,如果length不够用,那么肯定要扩大容量,不然增加的这个元素就会溢出。
        //好比我第一次进来我是需要10哥容量,你给我0个 我需要扩容
        if (minCapacity - elementData.length > 0)
           //arrayList能自动扩展大小的关键方法就在这里了
            grow(minCapacity);
    }

 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length; //获取扩容前elementData的长度
        int newCapacity = oldCapacity + (oldCapacity >> 1); //新数组的大小=老数组大小+老数组大小的一半 相当于1.5倍
       // 判断上面的扩容之后的大小newCapacity是否够装minCapacity个元素,不够就将数组长度设置为需要的长度 
        if (newCapacity - minCapacity < 0) 
            newCapacity = minCapacity;
       //判断新数组容量是否大于最大值  如果新数组容量比最大值(Integer.MAX_VALUE - 8)还大,那么交给hugeCapacity()去处理
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
            //copy数组,扩容机制的核心
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

//判断是否抛异常  取最大值还是最大值-8
 private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
void add(int,E)
/**
 * 在特定位置添加元素,也就是插入元素
 */
public void add(int index, E element) {
        rangeCheckForAdd(index);
        //跟上面的分析一样
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //这个方法就是用来在插入元素之后,要将index之后的元素都往后移一位,
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //在目标位置上存放元素
        elementData[index] = element;
        size++;
    }
//校验下标合理 插入的位置肯定不能大于size 和小于0
//如果是,就报这个越界异常
private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }


断点举例:

无参构造

public static void main(String[] args) {

        List arrayList = new ArrayList();
        arrayList.add("13");
    }

在这里插入图片描述
在这里插入图片描述
第二次

在这里插入图片描述

remove(int index)
// 通过删除指定位置上的元素
public E remove(int index) {
        rangeCheck(index);

        modCount++;
        //通过索引直接找到该元素
        E oldValue = elementData(index);
        //计算要移动的位数。
        int numMoved = size - index - 1;
        if (numMoved > 0)
            // 用来移动元素
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //将--size上的位置赋值为null,让gc(垃圾回收机制)更快的回收它。
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
remove(int index)
// 通过删除指定位置上的元素
public E remove(int index) {
        rangeCheck(index);

        modCount++;
        //通过索引直接找到该元素
        E oldValue = elementData(index);
        //计算要移动的位数。
        int numMoved = size - index - 1;
        if (numMoved > 0)
            // 用来移动元素
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //将--size上的位置赋值为null,让gc(垃圾回收机制)更快的回收它。
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
remove(Object o)
//通过遍历元素删除,通过(o == null) 发现arrayList是可以存放null值得。
public boolean remove(Object o) {
        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;
    }
removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }
//这个方法,用于两处地方,如果complement为false,则用于removeAll如果为true,则给retainAll()用,retainAll()是用来检测两个集合是否有交集的。
   private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData; //将原集合,记名为A
        int r = 0, w = 0;   //r用来控制循环,w是记录有多少个交集
        boolean modified = false;  
        try {
            for (; r < size; r++)
//参数中的集合C一次检测集合A中的元素是否有,
                if (c.contains(elementData[r]) == complement)
//有的话,就给集合A
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
//如果contains方法使用过程报异常
            if (r != size) {
//将剩下的元素都赋值给集合A,
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
//这里有两个用途,在removeAll()时,w一直为0,就直接跟clear一样,全是为null。
//retainAll():没有一个交集返回true,有交集但不全交也返回true,而两个集合相等的时候,返回false,所以不能根据返回值来确认两个集合是否有交集,而是通过原集合的大小是否发生改变来判断,如果原集合中还有元素,则代表有交集,而元集合没有元素了,说明两个集合没有交集。
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }
clear():
//将elementData中每个元素都赋值为null,等待垃圾回收将这个给回收掉,所以叫clear
public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }
get(int index)
public E get(int index) {
        // 检验索引是否合法
        rangeCheck(index);

        return elementData(index);
    }
set()
public E set(int index, E element) {
        // 检验索引是否合法
        rangeCheck(index);
        // 旧值
        E oldValue = elementData(index);
        // 赋新值
        elementData[index] = element;
        // 返回旧值
        return oldValue;
    }
indexOf()方法
public int indexOf(Object o) {
        if (o == null) {  //查找元素为空
            for (int i = 0; i < size; i++)  //循环,找到null返回
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)  // 遍历数组,找到第一个和指定元素相等的元素,返回下标
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

个人博客:http://blog.yanxiaolong.cn/

相关文章
|
3月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
130 2
|
2月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
2月前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
2月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
30天前
|
自然语言处理 数据处理 索引
mindspeed-llm源码解析(一)preprocess_data
mindspeed-llm是昇腾模型套件代码仓,原来叫"modelLink"。这篇文章带大家阅读一下数据处理脚本preprocess_data.py(基于1.0.0分支),数据处理是模型训练的第一步,经常会用到。
52 0
|
3月前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
76 12
|
2月前
|
PyTorch Shell API
Ascend Extension for PyTorch的源码解析
本文介绍了Ascend对PyTorch代码的适配过程,包括源码下载、编译步骤及常见问题,详细解析了torch-npu编译后的文件结构和三种实现昇腾NPU算子调用的方式:通过torch的register方式、定义算子方式和API重定向映射方式。这对于开发者理解和使用Ascend平台上的PyTorch具有重要指导意义。
|
2月前
|
安全 搜索推荐 数据挖掘
陪玩系统源码开发流程解析,成品陪玩系统源码的优点
我们自主开发的多客陪玩系统源码,整合了市面上主流陪玩APP功能,支持二次开发。该系统适用于线上游戏陪玩、语音视频聊天、心理咨询等场景,提供用户注册管理、陪玩者资料库、预约匹配、实时通讯、支付结算、安全隐私保护、客户服务及数据分析等功能,打造综合性社交平台。随着互联网技术发展,陪玩系统正成为游戏爱好者的新宠,改变游戏体验并带来新的商业模式。
|
3月前
|
Java 编译器 API
深入解析:JDK与JVM的区别及联系
在Java开发和运行环境中,JDK(Java Development Kit)和JVM(Java Virtual Machine)是两个核心概念,它们在Java程序的开发、编译和运行过程中扮演着不同的角色。本文将深入解析JDK与JVM的区别及其内在联系,为Java开发者提供清晰的技术干货。
56 1
|
3月前
|
安全 Java 开发者
AOP中的JDK动态代理与CGLIB动态代理:深度解析与实战模拟
【11月更文挑战第21天】面向切面编程(AOP,Aspect-Oriented Programming)是一种编程范式,它通过将横切关注点(cross-cutting concerns)与业务逻辑分离,以提高代码的可维护性和可重用性。在Java开发中,AOP的实现离不开动态代理技术,其中JDK动态代理和CGLIB动态代理是两种常用的方式。本文将从背景、历史、功能点、业务场景、底层逻辑等多个维度,深度解析这两种代理方式的区别,并通过Java示例进行模拟和比较。
168 5

推荐镜像

更多