ArrayList底层实现-程序员宅基地

技术标签: java  Collection  数据结构  

一、什么是ArrayList

ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  • ArrayList 继承了 AbstractList ,并实现了 List 接口。
  • ArrayList类实现了java.io.Serializable接口,即采用了Java默认的序列化机制
  • ArrayList类实现了RandomAccess接口,支持随机访问(当一个List拥有快速访问功能时,其遍历方法采用for循环最快速。而没有快速访问功能的List,遍历的时候采用Iterator迭代器最快速)

一、ArrayList的创建

1、ArrayList的主要成员变量

在这里插入图片描述

// 当ArrayList的构造方法中没有显示指出ArrayList的数组长度时,类内部使用默认缺省时对象数组的容量大小,为10。
private static final int DEFAULT_CAPACITY = 10;

// 当ArrayList的构造方法中显示指出ArrayList的数组长度为0时,类内部将EMPTY_ELEMENTDATA 这个空对象数组赋给elemetData数组。
private static final Object[] EMPTY_ELEMENTDATA = {};

//当ArrayList的构造方法中没有显示指出ArrayList的数组长度时,类内部使用默认缺省时对象数组为DEFAULTCAPACITY_EMPTY_ELEMENTDATA。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//实际ArrayList中存放的元素的个数,默认时为0个元素。
private int size;

//ArrayList中的对象数组的最大数组容量为Integer.MAX_VALUE – 8。
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

//ArrayList的底层数据结构,只是一个对象数组,用于存放实际元素,并且被标记为transient,也就意味着在序列化的时候此字段是不会被序列化的。
transient Object[] elementData;

2、ArrayList继承了Serializable,但底层数据结构却使用了transient使得数组不序列化,为什么?

transient用来表示一个域不是该对象序行化的一部分,当一个对象被序行化的时候,transient修饰的变量的值是不包括在序行化的表示中的。但是ArrayList又是可序行化的类,elementData是ArrayList具体存放元素的成员,用transient来修饰elementData,岂不是反序列化后的ArrayList丢失了对象数组?

发现源码下边有两个方法,ArrayList是自己实现了序列化和反序列化的方法。
在这里插入图片描述

private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // Write out size as capacity for behavioural compatibility with clone()
    s.writeInt(size);

    // Write out all elements in the proper order.
    for (int i=0; i<size; i++) {
    
        s.writeObject(elementData[i]);
    }

    if (modCount != expectedModCount) {
    
        throw new ConcurrentModificationException();
    }
}
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    
    elementData = EMPTY_ELEMENTDATA;

    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in capacity
    s.readInt(); // ignored

    if (size > 0) {
    
        // be like clone(), allocate array based upon size not capacity
        int capacity = calculateCapacity(elementData, size);
        SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
        ensureCapacityInternal(size);

        Object[] a = elementData;
        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
    
            a[i] = s.readObject();
        }
    }
}

首先我们要了解ArrayList的机制,即他是一个可以动态拓展的数组,但因为数组的长度是固定的,所以当ArrayList存满的时候再add元素进来就需要进行扩容,所以ArrayList的实际长度并不是数组的长度而是size这个变量存储的。
那么答案就显而易见了,因为ArrayList扩容后通常会预留一些容量,等容量不足时再扩充容量,那么有些空间可能就没有实际存储元素,采用上诉的方式来实现序列化时,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。

3、ArrayList三种构造器

在这里插入图片描述

①int构造器

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);
    }
}
  • 如果initialCapacity大于0,则创建一个大小为initialCapacity的对象数组赋给elementData。
  • 如果initialCapacity等于0,则将EMPTY_ELEMENTDATA赋给elementData。
  • 如果initialCapacity小于0,抛出异常(非法的容量)。

②无参构造器

public ArrayList() {
    
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

对于无参构造方法,将成员变量elementData的值设为DEFAULTCAPACITY_EMPTY_ELEMENTDATA。

③传入一个集合

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;
    }
}
  • 第一步,将参数中的集合转化为数组赋给elementData;
  • 第二步,参数集合是否是空。通过比较size与第一步中的数组长度的大小。
  • 第三步,如果参数集合为空,则设置元素数组为空,即将EMPTY_ELEMENTDATA赋给elementData;
  • 第四步,如果参数集合不为空,接下来判断是否成功将参数集合转化为Object类型的数组,如果转化成Object类型的数组成功,则将数组进行复制,转化为Object类型的数组。

三、ArrayList的方法

1、add()

add方法有两种,一个是直接再数组末尾添加元素,一个是在指定位置插入元素。

public boolean add(E e) {
    
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

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

不难发现在插入元素之前都要进行ensureCapacityInternal(size + 1);操作
主要目的是确保ArrayList的容量能存的下插入的这个元素,(不能存下的话就扩容)

ArrayList扩容机制

minCapacity可以理解为数组至少需要的长度

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    
	//判断元素数组是不是空数组,即是不是刚通过空参构造器构造的数组
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
    	//是的话返回数组的初始默认长度(10)和要插入的位置的最大值
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    //如果不是初始化的空数组,那么
    return minCapacity;
}

private void ensureCapacityInternal(int minCapacity) {
    
	//calculateCapacity主要目的是防止当数组为空数组时,如果扩容数组长度为minCapacity为1导致后续无法扩容,或扩容太慢。
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private void ensureExplicitCapacity(int minCapacity) {
    
    modCount++;
	//如果至少需要的长度比数组目前的长度长的话就执行扩容操作
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {
    
    //记录目前数组长度
    int oldCapacity = elementData.length;
    //扩容数组长度为当前长度的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //如果扩容后的数组长度比至少需要的长度短的话就把扩容后的数组长度设为minCapacity
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //判断扩容后的数组长度是否比定义的最长的长度还长
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    //扩容数组
    elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
    
	//如果数组至少需要的长度超出int数据范围,那么报内存溢出异常
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
    	//如果需要的数组长度比最大数组长度大且没有超出int的范围那么使用 Integer.MAX_VALUE
        Integer.MAX_VALUE :
        //否则使用MAX_ARRAY_SIZE
        MAX_ARRAY_SIZE;
}

第一步:calculateCapacity(elementData, minCapacity)
将对象数组和数组至少需要的长度传入方法中,目的是防止当数组为空数组时,后续操作扩容数组长度为minCapacity为1导致后续扩容太慢。

第二步:private void ensureExplicitCapacity(int minCapacity)
如果至少需要的长度(minCapacity)比对象数组长度小,那么不需要进行扩容,否则进入扩容操作。

第三步:扩容数组grow(int minCapacity)
预期将数组扩容为原数组长度的1.5倍,若扩容后的长度仍小于minCapacity,那么将扩容后的数组长度设为minCapacity。
判断预期扩容的长度是否比允许的最大值大
最后就是执行扩容操作了

四、ArrayList的优缺点

1、ArrayList的优点

  • ArrayList在不需要扩容的情况下顺序添加一个元素非常方便,只是往数组里面添加了一个元素而已。
  • 根据下标遍历元素,效率高。
  • 可以自动扩容,默认为每次扩容为原来的1.5倍。

2、ArrayList的缺点

  • 插入和删除元素的效率不高。
  • 根据元素值查找元素需要遍历整个元素数组,效率不高。
  • 线程不安全。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/tyh1579152915/article/details/118070198

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法