首页 > 其他分享 >深度解析ArrayList:灵活高效的动态数组实现

深度解析ArrayList:灵活高效的动态数组实现

时间:2023-12-20 12:06:05浏览次数:22  
标签:容量 int ArrayList elementData minCapacity 数组 解析


在 Java 集合框架中,ArrayList 是一个常用而强大的类,它提供了动态数组的实现,允许在运行时动态调整数组的大小。
ArrayList 是 List 接口的实现类,基于动态数组的数据结构。它可以存储任意类型的对象,并提供了丰富的方法,包括添加、删除、遍历等,使其在各种场景下都能发挥重要作用。

深度解析ArrayList:灵活高效的动态数组实现_java

底层数据结构

ArrayList 的底层数据结构是动态数组,其容量是动态调整的。这意味着 ArrayList 可以根据需要自动增长或缩小。

比如有一个ArrayList,size = 3;

深度解析ArrayList:灵活高效的动态数组实现_初始化_02

ArrayList属性源码如下:

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;
    
    // 空数组实例,用于空 ArrayList 的初始值
    private static final Object[] EMPTY_ELEMENTDATA = {};

    // 默认大小的空数组实例,用于默认初始容量的 ArrayList
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    // 存储ArrayList元素的数组。
    // ArrayList的容量就是这个数组的长度。
    // 任何elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList,
    // 在添加第一个元素时会扩展为DEFAULT_CAPACITY。
    transient Object[] elementData; // non-private to simplify nested class access

    // ArrayList 的大小(元素的数量)
    private int size;
    
    //带初始化容量参数的构造方法
    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 ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    .
    .
    .
}

从源码可以看到,elementData数组是实际存储元素的地方。size变量跟踪ArrayList的大小。当你向ArrayList中添加元素时,它们会存储在elementData数组中,并相应地更新size

对于 ArrayList 中的 elementData 字段,它被声明为 transient 的目的是在序列化时避免将数组内容直接序列化到持久存储中。这是因为 ArrayList 的实际元素可能只占用数组的一部分,而不是整个数组。在反序列化时,elementData 会在构造对象时被重新初始化。

在 ArrayList 类中,具体的反序列化过程是通过实现 readObject 方法来完成的。在该方法中,elementData 被重新赋值,以便在反序列化后正确恢复 ArrayList 对象。

以下是 ArrayList 中的关键部分,用于反序列化:

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // 将 elementData 初始化为 EMPTY_ELEMENTDATA
    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();
        }
    }
}

初始化容量

在使用 new ArrayList() 初始化时,ArrayList 的内部数组 elementData 初始化为一个空数组(EMPTY_ELEMENTDATA),并在添加第一个元素时才进行实际的初始化和分配。默认情况下,ArrayList 的初始容量为 10,即创建一个长度为 10 的空数组。当向 ArrayList 添加元素时,如果容量不足,会触发扩容机制,将数组长度扩大为原来的 1.5 倍。

//带初始化容量参数的构造方法
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 ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

扩容机制

ArrayList 的扩容机制是在 ensureCapacityInternal 和 grow 方法中实现的。当元素个数达到当前容量时,会触发扩容,将数组长度扩大为原来的 1.5 倍。

当我们向ArrayList 中添加元素(调用add()或者addAll())时,都调用了一个ensureCapacityInternal()方法

我们来看源码:

add()

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

addAll()

public boolean addAll(Collection<? extends E> 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()这个方法,参数是当前list的长度加上要插入的对象个数(单个对象的话为1,对象集合的话是集合的长度),既集合添加元素所需最小的长度

ensureCapacityInternal()

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

ensureExplicitCapacity() 方法的入参是calculateCapacity()(参数是存储实际元素的数组和集合添加元素所需最小的长度) 方法处理后的值。

calculateCapacity()

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

当前方法返回的值是如果源集合是空的,则返回 默认容量(10)和元素集合添加元素所需最小的长度值比较,值大的一个,若不为空则返回minCapacity。

深度解析ArrayList:灵活高效的动态数组实现_ci_03

ensureExplicitCapacity()

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

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

grow()

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

我们来详细解读下这段代码

  • int oldCapacity = elementData.length;

获取当前数组的容量,也就是elementData数组的长度,即当前存储元素的数组长度。

  • int newCapacity = oldCapacity + (oldCapacity >> 1);

计算新的容量。这里使用位运算右移1位(相当于除以2)的方式,将当前容量扩大1.5倍。

  • if (newCapacity - minCapacity < 0)

检查计算得到的新容量是否满足最小容量要求。如果不满足,则将新容量设置为最小容量minCapacity。

  • if (newCapacity - MAX_ARRAY_SIZE > 0)

检查新容量是否超过了最大数组容量限制。MAX_ARRAY_SIZE是ArrayList内部定义的一个常量,表示数组的最大容量。如果新容量超过这个限制,就调用hugeCapacity(minCapacity)方法来获得一个足够大的容量。

深度解析ArrayList:灵活高效的动态数组实现_java_04

深度解析ArrayList:灵活高效的动态数组实现_初始化_05

  • elementData = Arrays.copyOf(elementData, newCapacity);

使用Arrays.copyOf()方法将原有的elementData数组复制到一个新数组中,新数组的容量为计算得到的新容量newCapacity。这实现了实际的数组扩容操作。

特点

随机访问元素效率高,因为底层是数组。

添加、删除元素效率较低,因为可能需要移动元素。

允许存储重复元素。

允许存储 null 元素。

支持动态调整容量。

适用场景

适用于频繁读取元素和随机访问的场景。

不适用于频繁添加和删除元素的大规模操作,因为这可能导致性能下降。

ArrayList不是线程安全的,不适合在多线程环境下使用。

总结:

ArrayList 是 Java 集合框架中一款强大而灵活的动态数组实现,它的设计和性能特点使得它在许多场景下都能发挥重要作用。在实际开发中,根据具体需求选择合适的集合类是至关重要的,而 ArrayList 无疑是一个常用的选择。

通过本文的深度解析,希望读者能更全面地了解 ArrayList,并在实际项目中充分发挥其优势,提高代码的效率和可维护性。


标签:容量,int,ArrayList,elementData,minCapacity,数组,解析
From: https://blog.51cto.com/xiuji/8905218

相关文章

  • js 处理对象数组 + map 筛选出指定字段数据 + filter过滤重复数据/指定数据
    constres=[{id:1,name:'zhangsan',age:16,gender:0},{id:1,name:'zhangsan',age:16,gender:0},{id:2,name:'lisi',age:20,gender:1}];获取res中的id和name/*[{"id&......
  • 两个数组的过滤
    leta1=[{rmName:'王五'},{rmName:'李四'},{rmName:'张三'},{rmName:'赵六'}];letb2=[{name:'王五'},{name:'李四'}];//结果:得到a1中除去b2中值的其他数据//方法一:forEachletfilterA1=a1.filter(item=>......
  • 实践解析HPA各关联组件扭转关系
    本文分享自华为云社区《HPA各关联组件扭转关系以及建议》,作者:可以交个朋友。一、背景应用程序的使用存在波峰波谷现象,在应用流量处于低谷期间,可以释放因过多的Pod而浪费的硬件资源。在应用流量高峰期提供弹性足够的Pod处理流量。二、HPA各个组件扭转关系kubernetes使用者通过......
  • Python 使用getopt解析命令行参数
    ​ 1、getopt模块此模块可协助脚本解析sys.argv中的命令行参数。它支持与Unixgetopt()函数相同的惯例(包括形式如'-'与'--'的参数的特殊含义)。也能通过可选的第三个参数来使用与GNU软件所支持形式相类似的长选项。1)getopt.getopt(args,shortopts,longopts=[])......
  • 算法数组集合
    JDK1.0java.util.Date缺陷:偏移量JDK1.1java.util.Calendar线程不安全缺陷:a.偏移量b.可变性,线程不安全的c.格式化:java.text.DateFormat只适用于Date,不能用于CalendarJDK8.0java.time:时间包LocalDate:只有年月日LocalTime:只有时分秒L......
  • C语言 不定长数组
    #include<stdio.h>#include<malloc.h>structstudent{intage;};structdata{intlen;//不占用空间structstudentstudents[0];};intmain(){structdata*d=malloc(sizeof(structdata)+2*sizeof(structstudent));......
  • [LeetCode Hot 100] LeetCode33. 搜索旋转排序数组
    题目描述思路如果nums[left]<=nums[mid],则[left,mid]有序如果nums[left]>nums[mid],则[mid,right]有序方法一:classSolution{publicintsearch(int[]nums,inttarget){if(nums==null||nums.length==0)return-1;intleft=0,ri......
  • [LeetCode Hot 100] LeetCode34.在排序数组中查找元素的第一个和最后一个位置
    题目描述思路:二分查找之寻找左右侧边界两个关键点:1.数组有序;2.时间复杂度O(logn)方法一:classSolution{publicint[]searchRange(int[]nums,inttarget){if(nums.length==0||nums==null){returnnewint[]{-1,-1};}......
  • SSO单点登录源码解析
    单点登录:用户在单点登录成功后,会访问某一平台模块,1. 向客户端发送一个请求(免密登录接口),请求客户端接口会携带一个ticket,类似于:http//:ip:port:xxx?ticket=xxxx1. 这个请求会被客户端配置的EKPSSOCLIENT的过滤器拦截,读取配置文件sso-config.properties,获取过滤器链(CASURLFilter、Us......
  • 三道初三数学模拟几何综合题的解题思路解析
     ......