当前位置: 首页 > news >正文

聊聊 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,则将最小容量设为默认值10if (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 codeint oldCapacity = elementData.length;//在以前的容量基础上增加旧容量的1/2int newCapacity = oldCapacity + (oldCapacity >> 1);//检查比较新容量与最小容量的大小,取大值if (newCapacity - minCapacity < 0)newCapacity = minCapacity;//更新完新容量后,比较是否大于最大数组大小 Integer.MAX_VALUE - 8if (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) // overflowthrow 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

相关文章:

  • 全新加密叙事,以Solmash为代表的 LaunchPad 平台如何为用户赋能?
  • uniapp 打包成 apk(原生APP-云打包)免费
  • 软件测试|Python数据可视化神器——pyecharts教程(九)
  • 确保CentOS系统中的静态HTTP服务器的数据安全
  • 深入了解Java多线程编程:JVM内存模型与同步机制
  • Linux学习记录——사십이 高级IO(3)--- Poll型服务器
  • allegro PCB设计心得笔记(二) PCB板框设计心得
  • 【Golang】IEEE754标准二进制字符串转为浮点类型
  • JsonPath
  • RPA财务机器人在厦门市海沧医院财务管理流程优化汇总的应用RPA全球生态 2024-01-05 17:27 发表于河北
  • 三国杀移动版武将台词大全-神
  • three.js 关键帧动画
  • GSTAE
  • CCF认证+蓝桥杯习题训练
  • 学习记录之JVM
  •  D - 粉碎叛乱F - 其他起义
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • Javascript Math对象和Date对象常用方法详解
  • javascript从右向左截取指定位数字符的3种方法
  • JSONP原理
  • laravel 用artisan创建自己的模板
  • oschina
  • Puppeteer:浏览器控制器
  • Redis提升并发能力 | 从0开始构建SpringCloud微服务(2)
  • STAR法则
  • vue 配置sass、scss全局变量
  • weex踩坑之旅第一弹 ~ 搭建具有入口文件的weex脚手架
  • 当SetTimeout遇到了字符串
  • 分布式任务队列Celery
  • 基于Mobx的多页面小程序的全局共享状态管理实践
  • 国内唯一,阿里云入选全球区块链云服务报告,领先AWS、Google ...
  • ​Distil-Whisper:比Whisper快6倍,体积小50%的语音识别模型
  • ​一文看懂数据清洗:缺失值、异常值和重复值的处理
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • $(function(){})与(function($){....})(jQuery)的区别
  • $.ajax()
  • $Django python中使用redis, django中使用(封装了),redis开启事务(管道)
  • (11)MATLAB PCA+SVM 人脸识别
  • (4)事件处理——(6)给.ready()回调函数传递一个参数(Passing an argument to the .ready() callback)...
  • (C#)if (this == null)?你在逗我,this 怎么可能为 null!用 IL 编译和反编译看穿一切
  • (NO.00004)iOS实现打砖块游戏(九):游戏中小球与反弹棒的碰撞
  • (PHP)设置修改 Apache 文件根目录 (Document Root)(转帖)
  • (二) Windows 下 Sublime Text 3 安装离线插件 Anaconda
  • (附源码)springboot家庭装修管理系统 毕业设计 613205
  • (三十五)大数据实战——Superset可视化平台搭建
  • (转)甲方乙方——赵民谈找工作
  • (转)一些感悟
  • .NET Standard、.NET Framework 、.NET Core三者的关系与区别?
  • .NET 设计模式—简单工厂(Simple Factory Pattern)
  • .Net面试题4
  • .Net下的签名与混淆
  • .net专家(高海东的专栏)
  • /*在DataTable中更新、删除数据*/
  • @data注解_一枚 架构师 也不会用的Lombok注解,相见恨晚
  • @ModelAttribute注解使用