聊聊 Java 集合框架中的 ArrayList

其实 Java 集合框架也叫做容器,主要由两大接口派生而来,一个是 collection,主要存放对象的集合。另外一个是Map, 存储着键值对(两个对象)的映射表。

下面就来说说 List接口,List存储的元素是有序、可重复的。其下有三个子接口,ArrayList、LinkedList 和 vector。

一、 ArrayList概述

ArrayList 底层数据结构是基于 Object 数组来实现的,我们看看它的底层接口源码:

1. ArrayList 实现的接口

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

其中继承的接口中的 RandomAccessCloneableSerializable 只是标记接口,他们的接口内部没有具体的方法和参数:

public interface RandomAccess {} 
public interface Cloneable {}    //覆盖clone(),能被克隆
public interface Serializable {} //支持序列化,能通过序列化传输

标记接口是计算机科学中的一种设计思路。编程语言本身不支持为类维护元数据。而标记接口则弥补了这个功能上的缺失——一个类实现某个没有任何方法的标记接口,实际上标记接口从某种意义上说就成为了这个类的元数据之一。运行时,通过编程语言的反射机制,我们就可以在代码里拿到这种元数据。

以Serializable接口为例。一个类实现了这个接口,说明它可以被序列化。因此,我们实际上通过Serializable这个接口,给该类标记了“可被序列化”的元数据,打上了“可被序列化”的标签。这也是标记/标签接口名字的由来。

此外AbstractList 继承AbstractCollection 抽象类,实现List接口。它实现了 List 的一些基本操作如(get,set,add,remove),是第一实现随机访问方法的集合类,但是不支持添加和替换。

2.ArrayList 的成员属性

private static final int DEFAULT_CAPACITY = 10; //默认初始容量为10 
private static final Object[] EMPTY_ELEMENTDATA = {}; //空数组,用于空实例
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//用于默认大小空实例的共享空数组实例。
transient Object[] elementData; //保存ArrayList数据的数组,transient修饰表示数组默认不会被序列化
private int size; //ArrayList中数组的个数

二、ArrayList 中的方法

Java ArrayList 中常用的方法:

方法描述
add()将元素插入到指定位置的 arraylist 中
addAll()添加集合中的所有元素到 arraylist 中
clear()删除 arraylist 中的所有元素
clone()复制一份 arraylist
contains()判断元素是否在 arraylist
get()通过索引值获取 arraylist 中的元素
indexOf()返回 arraylist 中元素的索引值
removeAll()删除存在于指定集合中的 arraylist 里的所有元素
remove()删除 arraylist 里的单个元素
size()返回 arraylist 里元素数量
isEmpty()判断 arraylist 是否为空
subList()截取部分 arraylist 的元素
set()替换 arraylist 中指定索引的元素
sort()对 arraylist 元素进行排序
toArray()将 arraylist 转换为数组
toString()将 arraylist 转换为字符串
ensureCapacity()设置指定容量大小的 arraylist
lastIndexOf()返回指定元素在 arraylist 中最后一次出现的位置
retainAll()保留 arraylist 中在指定集合中也存在的那些元素
containsAll()查看 arraylist 是否包含指定集合中的所有元素
trimToSize()将 arraylist 中的容量调整为数组中的元素个数
removeRange()删除 arraylist 中指定索引之间存在的元素
replaceAll()将给定的操作内容替换掉数组中每一个元素
removeIf()删除所有满足特定条件的 arraylist 元素
forEach()遍历 arraylist 中每一个元素并执行特定操作

具体的方法细节可以看这里

三、ArrayList 中的扩容机制

在初始化时,ArrayList 有三种方式来进行初始化,以无参构造方法创建 ArrayList 时,实际上赋值的是一个空数组。当真正对数组进行添加元素时,才真正的给 ArrayList 分配容量,即数组容量扩为10。


/**
     *默认构造函数,使用初始容量10构造一个空列表(无参数构造)
     */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
     * 带初始容量参数的构造函数。(用户自己指定容量)
     */
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {//初始容量大于0
        //创建initialCapacity大小的数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {//初始容量等于0
        //创建空数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {//初始容量小于0,抛出异常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}


/**
    *构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回
    *如果指定的集合为null,throws NullPointerException。
    */
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
/**
     * 将指定的元素追加到此数组的末尾。
     */
public boolean add(E e) {
    //在增加元素前,先调用ensureCapacityInternal方法
    ensureCapacityInternal(size + 1);  
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    //比较当前元素个数和默认元素个数,如果小于10,则将最小容量设为默认值10
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    //继续调用 ensureExplicitCapacity()方法
    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    //如果大于当前数组默认长度,则进行扩容,调用grow()方法
    if (minCapacity - elementData.length > 0)
        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;
    //在以前的容量基础上增加旧容量的1/2
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //检查比较新容量与最小容量的大小,取大值
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //更新完新容量后,比较是否大于最大数组大小 Integer.MAX_VALUE - 8
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        //若大于最大数组大小,则调用hugeCapacity()方法
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    //对minCapacity和MAX_ARRAY_SIZE进行比较
    //若minCapacity大,将Integer.MAX_VALUE作为新数组的大小
    //若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小
    //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}

四、ArrayList 相关面试题

1. System.arraycopy() 和 Arrays.copyOf() 的区别

联系:

看两者源代码可以发现 copyOf()内部实际调用了 System.arraycopy() 方法

区别:

arraycopy() 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 copyOf() 是系统自动在内部新建一个数组,并返回该数组。

2. ArrayList 和 LinkedList 的区别

  1. 是否保证线程安全: ArrayListLinkedList 都是不同步的,也就是不保证线程安全;
  2. 底层数据结构: Arraylist 底层使用的是 Object 数组LinkedList 底层使用的是 双向链表 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)
  3. 插入和删除是否受元素位置的影响:ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element))时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② LinkedList 采用链表存储,所以对于add(E e)方法的插入,删除元素时间复杂度不受元素位置的影响,近似 O(1),如果是要在指定位置i插入和删除元素的话((add(int index, E element)) 时间复杂度近似为o(n))因为需要先移动到指定位置再插入。
  4. 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。
  5. 内存空间占用: ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。

3. ArrayList 和 Vector 的区别

  1. ArrayListList 的主要实现类,底层使用 Object[ ]存储,适用于频繁的查找工作,线程不安全 ;
  2. VectorList 的古老实现类,底层使用 Object[ ]存储,线程安全的。如下代码带有 synchronized 同步锁,能够保证线程安全。
public synchronized void ensureCapacity(int minCapacity) {
    if (minCapacity > 0) {
        modCount++;
        ensureCapacityHelper(minCapacity);
    }
}

4. ArrayList 和 CopyOnWriteArrayList 的区别

CopyOnWriteArrayList 和 Vector 一样也是线程安全的 List 。它在 java.util.concurrent 的包下。它的线程安全主要是通过读写分离来实现的。写操作在一个复制的数组上实现(加锁),读操作还是在原数组中进行。

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

final void setArray(Object[] a) {
    array = a;
}

特点:适合读多写少的应用场景

缺点

  • 内存占用:在写操作时需要复制一个新的数组,使得内存占用为原来的两倍左右;
  • 数据不一致:读操作不能读取实时性的数据,因为部分写操作的数据还未同步到读数组中;

五、参考资料

ArrayList 扩容机制分析

Java ArrayList

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/304000.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

配置git服务器

第一步&#xff1a; jdk环境配置 &#xff08;1&#xff09;搜索【高级系统设置】&#xff0c;选择【高级】选项卡&#xff0c;点【环境变量】 &#xff08;2&#xff09;在【系统变量】里面&#xff0c;点击【新建】 &#xff08;3&#xff09;添加JAVA_HOME环境变量JAVA_HO…

展台搭建的基本要求

一、选址及总平面布置 展厅人流量大&#xff0c;对选址和总平面布置的要求如下:展厅选址应位于市内或郊区交通便利的区域。大型展览馆应有足够的群众活动广场和停车面积 &#xff0c;并应有室外陈列场地。室外场地要考虑环境的绿化和美化。各功能分区之间联系方便又互不干扰。建…

实现LCM在docker之间的通信

目录 1.docker容器互联 新建网络 连接容器 2.设置环境变量 3.在两个docker之间实现通信 1.docker容器互联 新建网络 $ docker network create -d bridge test-net 连接容器 运行一个容器并连接到新建的 test-net 网络: $ docker run -itd --name lcm_1 --network tes…

极智项目 | 实战Paddle戴口罩检测

欢迎关注我的公众号 [极智视界]&#xff0c;获取我的更多项目分享 大家好&#xff0c;我是极智视界&#xff0c;本文来介绍 实战 Paddle 戴口罩检测项目。 本文介绍的 实战 Paddle 戴口罩检测项目&#xff0c;提供完整的可以一键执行的项目工程源码&#xff0c;获取方式有两个…

AI红娘开启约会新时代;网易云音乐Agent实践探索;微软生成式AI课程要点笔记;ComfyUI新手教程;图解RAG进阶技术 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f440; Perplexity 官宣 7360 万美元B轮融资&#xff0c;打造世界上最快最准确的答案平台 https://blog.perplexity.ai/blog/perplexity-rais…

yyds,4 个 Pandas 必备神器!

Pandas是我们日常处理表格数据最常用的包&#xff0c;但是对于数据分析来说&#xff0c;Pandas的DataFrame还不够直观。 今天我们将介绍4个和Pandas相关的Python包&#xff0c;可以将Pandas的DataFrame转换交互式表格&#xff0c;让我们可以直接在上面进行数据分析的操作。 P…

天洑软件与常州大学成立电子信息专业学位硕士研究生联合培养基地

2023年12月27日&#xff0c;天洑软件与常州大学微电子与控制工程学院成立电子信息专业学位硕士研究生联合培养基地&#xff0c;授牌及企业导师聘任仪式于常州大学西太湖校区隆重举办。天洑软件前瞻技术研究院院长张儒受邀与会。 开幕式上&#xff0c;微电子与控制工程学院党委书…

NUXT3学习笔记

1.邂逅SPA、SSR 1.1 单页面应用程序 单页应用程序 (SPA) 全称是&#xff1a;Single-page application&#xff0c;SPA应用是在客户端呈现的&#xff08;术语称&#xff1a;CSR&#xff08;Client Side Render&#xff09;&#xff09; SPA的优点 只需加载一次 SPA应用程序只需…

Docker实战08|Docker管道及环境变量识别

上一篇文章中&#xff0c;讲解了如何通过Go语言实现对Docker Cgroup的资源限制 具体文章可见《Docker就应该这么学-07》 有需要的小伙伴可以回顾一下。 接下来本文会详细介绍一下Docker 管道及环境变量识别 管道及环境变量识别 获取代码 git clone https://gitee.com/mjr…

SEO全自动发布外链工具源码系统:自动增加权重 附带完整的搭建安装教程

SEO全自动发布外链工具是一款基于PHP和MySQL开发的外链发布工具。它通过自动化流程&#xff0c;帮助站长快速、有效地发布外链&#xff0c;提高网站的权重和排名。该工具支持多种外链发布平台&#xff0c;如论坛、博客、分类信息等&#xff0c;可自定义发布内容和格式&#xff…

Plotly.js 热力图与折线结合

上次记录了Echarts热力图与折线图的结合&#xff0c;但其效果不是很自然。后又找到了Plotly.js库&#xff0c;发现其效果不错。在此整理下实现过程。这里面涉及到自定义工具栏、自定义工具图标等等 配置工具栏显示的工具图标 let config {locale: zh-cn, // 设置本地语…

代码随想录刷题第四十三天| 1049. 最后一块石头的重量 II ● 494. 目标和 ● 474.一和零

代码随想录刷题第四十三天 今天为三道0-1背包问题的变种&#xff0c; 分别有三个小问题 给定一个容量为j的背包&#xff0c;尽可能装下物品&#xff0c;找到能装下物品的最大价值 dp[i][j] max(dp[i-1][j], dp[i-1][j-nums[i]]nums[i]) 给定一个容量为j的背包&#xff0c;找…

在docker中搭建部署clickhouse

因需要给网关日志拉取并存储供数据分析师分析&#xff0c;由于几十个项目的网关请求数量很大&#xff0c;放在mysql不合适&#xff0c;MongoDB不适合分析&#xff0c;于是准备存放在clickhouse&#xff0c;clickhouse对于读写支持也比较友好&#xff0c;说干就干 1、在服务器中…

Docker查看镜像的Dockerfile

前言 在使用Docker构建应用程序时&#xff0c;我们可以通过Dockerfile定义应用程序的环境&#xff0c;并将其打包成一个镜像。有时&#xff0c;我们可能需要查看一个已经构建好的镜像的Dockerfile&#xff0c;以了解镜像是如何构建的&#xff0c;或者进行后续的修改和调整。本…

C++中的虚函数

前言 本篇文章讲述C的虚函数 定义 在C语言中&#xff0c;基类将类型相关的函数和派生类不做改变直接继承的函数区分开来。对于有些函数&#xff0c;基类希望派生类各自定义适合自身的版本。那么基类就会将这些函数标记为virtual&#xff0c;这些被标记的函数就是虚函数。 下…

[计算机提升] 创建和管理一般共享文件夹

4.6 创建和管理一般共享文件夹 4.6.1 创建一般共享文件夹 通过创建共享文件夹&#xff0c;可以让多台计算机在同一网络上共享文件和文件夹。这对于团队协作、家庭用户共享文件和资源非常方便。方式如下&#xff1a; 1、选中要共享的文件夹&#xff0c;然后右键属性&#xff0…

软件测试大作业||测试计划+测试用例+性能用例+自动化用例+测试报告

xxx学院 2023—2024 学年度第二学期期末考试 《软件测试》&#xff08;A&#xff09;试题&#xff08;开卷&#xff09; 题目&#xff1a;以某一 web 系统为测试对象&#xff0c;完成以下文档的编写&#xff1a; &#xff08;满分 100 分&#xff09; &#xff08;1&am…

接雨水(Leetcode42)

例题&#xff1a; 题目说了&#xff0c;给定n个宽度为1的柱子&#xff0c;height数组中的每个元素表示每个柱子的高度。 只要柱子之间存在凹槽&#xff0c;就能接住雨水。 在解决这道题目之前&#xff0c;我们先了解一下单调递减栈。&#xff08;由栈底到栈顶逐渐递减&#xff…

项目知识—SSM及之后02

1、resultMap写的Base内容必须保证select都使用上 2、VALUE单个 &#xff0c;VALUES多个 3、一对多&#xff0c;两张表&#xff0c;多的表加外键 比如班级和学生就是一对多&#xff0c;查就是按照学生表去查询 多对多&#xff0c;三张表&#xff0c;关系表加外键 4、数据库…

yolov8实战第五天——yolov8+ffmpeg实时视频流检测并进行实时推流——(推流,保姆教学)

yolov8实战第一天——yolov8部署并训练自己的数据集&#xff08;保姆式教程&#xff09;_yolov8训练自己的数据集-CSDN博客 yolov8实战第三天——yolov8TensorRT部署&#xff08;python推理&#xff09;&#xff08;保姆教学&#xff09;-CSDN博客 今天&#xff0c;我们继续y…