当前位置:首页 > 单片机 > 架构师社区
[导读]前言ArrayList是Java集合框架中比较常用的数据结构了。继承自AbstractList,实现了List接口。底层基于数组实现容量大小动态变化。一看就是一个比较重要的模块,所以我们今天就来学习一下ArrayList相关知识。ArrayList的数据结构和作用ArrayLis...

前言

ArrayList是Java集合框架中比较常用的数据结构了。继承自AbstractList,实现了List接口。底层基于数组实现容量大小动态变化。一看就是一个比较重要的模块,所以我们今天就来学习一下ArrayList相关知识。

ArrayList的数据结构和作用

ArrayList数据结构是数组,用来装载数据。
相对于LinkedList,查询效率高,因为底层是数组,分配的是连续的内存空间,CPU在读取时可以缓存连续的内存空间,大幅度降低读取的性能开销;增删效率低,相对于Vector来说是线程不安全。
虽然ArrayList是线程不安全的,但在我们实际的应用过程中,一般都是用来查询,涉及到增删的操作比较少,如果涉及到的增删操作比较频繁的场景,我们可以选择LinkedList,如果想保证线程安全,可以使用Vector、CopyOrWriteArray。

如何实现存放任意数量的对象

ArrayList构造器有无参构造和有参构造。在有参构造器中,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);
}
}
由代码可知,我们可以很明确地得出结论:不会初始化数组大小。不信的话我们可以测试一下:
public static void main(String[] args){
//此处使用有参构造,大小为10
ArrayListarrayList = new ArrayList<>(10);
System.out.println("size:" arrayList);
arrayList.set(1, "A");
}
关于ArrayList的几大问题,看完还不懂来打我!看到这里是不是已经懵圈了?不要慌,我们慢慢来分析。我们的参数是 initialCapacity,这里是将参数基于 elementData 设置的,并不是直接设置的数组大小(值得注意的是,ensureCapacity();方法也是这种原理)。我们也可以理解为这个数组现在理论上最大可以装10个数据,但是他现在还是空的。
在无参构造器中,初始化出一个默认空的数组,数组容量为 0,当我们调用add();方法是,默认分配【DEFAULT_CAPACITY = 10】的初始容量。下面会具体介绍新增过程,此处不再赘述。
/**
* 数组默认初始容量
*/

private static final int DEFAULT_CAPACITY = 10;
/**
* 用于默认大小的空实例的共享空数组实例。
* 与 EMPTY_ELEMENTDATA 区分开来,以了解何时膨胀多少添加第一个元素。
*/

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 构造一个初始空列表。
*/

public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
但是,对于无参构造和有参构造,数组都是有长度限制的,ArrayList是通过什么方式去实现可以存放任意数量的对象,长度没有限制的呢?不要慌,原来这个地方也是用到了数组的扩容。

数组的扩容

当前我们有一个初始容器为10的数组,且每个位置已经插满数据,但是现在又要新增一条数据,这个时候当前数组已经不能满足我们的要求了,那我们就需要进行扩容;
关于ArrayList的几大问题,看完还不懂来打我!然后我们就需要进行扩容,扩大到原来的1.5倍,即【10 10 / 2】;
关于ArrayList的几大问题,看完还不懂来打我!最后将原数组的数据原封不动地移动到新数组,再返回新数组的地址,这样ArrayList中数据就是新的数组了。
关于ArrayList的几大问题,看完还不懂来打我!接下来,我们就看一下源码中的具体实现吧
//扩容前置判断
private void ensureExplicitCapacity(int minCapacity) {
modCount ;
//minCapacity: 插入数据后容器大小或者默认容器大小
//当minCapacity比当前数组大时,说明需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//扩容具体过程
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//此处java 8采用了位运算,提升效率
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);
}

ArrayList的的新增

在ArrayList中,新增有三个方法分别是以下三种:
//1\. 将指定的元素附加到此列表的末尾
public boolean add(E e) {
ensureCapacityInternal(size 1); // Increments modCount!!
elementData[size ] = e;
return true;
}
//2\. 在此指定位置插入指定元素列表。
public void add(int index, E element) {
//判断指定参数是否超过范围
rangeCheckForAdd(index);
ensureCapacityInternal(size 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index 1,
size - index);
elementData[index] = element;
size ;
}
//3\. 将指定集合中的所有元素追加到末尾这个列表,
//   按照它们返回的顺序指定集合的迭代器。
public boolean addAll(Collection c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size   numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size  = numNew;
return numNew != 0;
}
由代码可知,不管是哪种插入,我们都需要通过调用 ensureCapacityInternal(); 方法来校验数组长度,如果长度不够,就进行扩容,前面我们已经了解过。对于指定位置新增时,我们在校验完成之后通过调用 arraycopy(); 方法来实现数组的复制。下面我们就来了解一下指定位置插入的过程。
当前我们有一个长度为10,还有一个空位的数组;
关于ArrayList的几大问题,看完还不懂来打我!现在我们需要插入一个【a】,目标位置是【5】,则先复制一个数组,指定位置之前的数据不变,从【5】开始把后面的数据从【5 1】的位置开始复制,新数组空出位置【5】;
关于ArrayList的几大问题,看完还不懂来打我!上一步我们已经把【5】这个位置空出来了,然后将数据【a】插入空位,这样就完成了指定位置插入的操作了。
关于ArrayList的几大问题,看完还不懂来打我!由上可知,ArrayList在新增的需要把数据复制一份,这个操作如果是针对大数据量List,再加上扩容的操作,那效率就慢了。

ArrayList的删除

话不多说,我们先来看一下代码:
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);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
由代码可知,如果是删除末位,则直接删除就完成了操作;如果是将中间数据删除,则此过程中也是类似于插入操作,将数组进行了复制,调用arraycopy();方法。下面我们就来了解一下指定位置删除的过程。
当前我们有一个长度为10;
关于ArrayList的几大问题,看完还不懂来打我!现在我们需要删除目标位置为【5】的数据,则先复制一个数组,指定位置之前的数据不变,从【5 1】之后的数据进行复制到新数组;
关于ArrayList的几大问题,看完还不懂来打我!得到新的数组,就是删除了指定位置【5】数据的数组了。
关于ArrayList的几大问题,看完还不懂来打我!同理,ArrayList的删除和新增一样效率比较低。对于数据量大的数组需要复制和移动的位置就比较大了。

ArrayList适合做队列吗

一般的队列是先进先出队列(FIFO),从尾部出入,头部删除。
对于数组是十分适合做队列的,比如 ArrayBlockingQueue内部的实现就是通过一个定长数组来实现一个环形定长队列,使用两个偏移量来标记数组的读位置和写位置,如果超过长度就折回到数组开头。但是前提必须是一个定长的数组。
因为在ArrayList中,底层虽然是数组,但是数组长度是不确定的。这样我们就需要进行大量的增加和删除操作,就算是指定位置的删除和新增,它也是需要经过数组复制,这样的话,会比较消耗性能。
因此,定长数组适合做队列,ArrayList不适合做队列


本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭