详解ArrayList的扩容机制

下面是讲解ArrayList的扩容机制的完整攻略:

标准版答案

概述

ArrayList 是基于数组实现的,其内部有一个数组用于存放数据。它的扩容机制就是在插入数据时,判断数组已满,此时将数组扩容为原数组长度的1.5倍。

具体实现

ArrayList 的核心代码如下:

private Object[] elementData;
private int size;

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 判断数组是否需要扩容
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

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

    // 如果当前数组不足以容纳数据,则需要扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);  // 新数组长度为原数组的1.5倍
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 复制原数组
    elementData = Arrays.copyOf(elementData, newCapacity);
}

上面的代码中,add() 方法是向 ArrayList 中插入元素的方法,它先通过 ensureCapacityInternal() 方法来判断数组是否需要扩容,如果需要则调用 grow() 方法来进行扩容。

在 grow() 方法中,先根据原数组长度计算出新数组长度 newCapacity,然后将原数组中的数据复制到新数组中,最后将新数组赋给 elementData 属性。

示例说明

示例一:添加三个元素

假设 ArrayList 的初始容量为 5,首先添加三个元素:

ArrayList<Integer> list = new ArrayList<>(5); // 指定初始容量为 5
list.add(1);
list.add(2);
list.add(3);

此时,ArrayList 的 elementData 数组长度已经为 5,其中存放了三个元素。

当再次添加元素时(如添加数字 4),由于数组已满,需要进行扩容操作:

list.add(4);

此时,在执行 grow() 方法时,oldCapacity = 5,newCapacity = 7,elementData 数组被复制到了一个长度为 7 的新数组中。

示例二:手动设置容量大小并添加元素

假设想要手动指定 ArrayList 的容量大小,然后向其中添加元素。可以通过手动设置容量大小来触发扩容操作。

ArrayList<Integer> list = new ArrayList<>(5); // 指定初始容量为 5
list.ensureCapacity(10); // 手动将容量设置为 10
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6); // 此时 elementData 数组长度已满,需要进行扩容操作

在这个示例中,手动设置了 ArrayList 的容量大小为 10。向其中添加元素时,前 5 个元素不会触发扩容操作。当添加第 6 个元素时(数字 6),由于数组已满,需要进行扩容操作。

进阶版答案

概述

ArrayList 是一种基于数组实现的动态数组,如果内部数组大小不够,它会通过创建一个新数组来增加容量。该新数组是旧数组大小的1.5倍。在插入大量数据时,大量的数组复制操作可能会拖慢 ArrayList 的速度。当面对大量数据集合时,如果可以提前预测容量大小,那么在之后的数据插入中就可以减少扩容的机会,也降低了数组扩容操作的影响。

具体实现

ArrayList 内部数组的大小是其最基本的属性之一,通过调用以下构造函数可以在创建 ArrayList 时制定容量大小:

ArrayList(int initialCapacity);

在运行期间,还可以调用以下 ensureCapacity() 方法来增加 ArrayList 的容量大小:

public void ensureCapacity(int minCapacity);

如果你能够提前预测需要存储的数据量,我们可以使用该方法来避免大量数据复制操作,这样我们就可以减少在之后的插入操作中创建新数组的机会。

ArrayList<Integer> list = new ArrayList<>(); // 默认容量大小为 10
list.ensureCapacity(20); // 预先调整数组大小为 20
for (int i = 0; i < 20; ++i) {
    list.add(i);
}

在上面的代码中,我们调用 ensureCapacity() 方法预先调整 ArrayList 的大小为 20,然后我们向其中插入了 20 个整数。因为我们预先调整了大小,所以不会触发数组扩容操作。

示例说明

示例一:使用默认构造器创建 ArrayList

ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 20; ++i) {
    list.add(i);
}

在这个示例中,使用默认构造器创建 ArrayList,它的容量大小默认为 10。向其中插入 20 个整数后,当添加第 11 个元素时(数字 10),由于数组已满,需要进行扩容操作。接下来数组大小被扩大到 15,元素添加到了数组中。

示例二:利用 ensureCapacity() 方法提前设置容量大小

ArrayList<Integer> list = new ArrayList<>();
list.ensureCapacity(20);
for (int i = 0; i < 20; ++i) {
    list.add(i);
}

在这个示例中,我们调用 ensureCapacity() 方法提前设置容量大小为 20,然后再向其中插入 20 个整数。先预设容量大小,使得数组不会在插满 10 个元素时就进行扩容,再插入元素时避免了扩容操作,提升了程序的效率。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解ArrayList的扩容机制 - Python技术站

(0)
上一篇 2023年5月26日
下一篇 2023年5月26日

相关文章

  • Java String类正则操作示例

    Java String类正则操作示例 简介 Java中String类提供了很多方法进行正则表达式的操作。通过使用正则表达式,我们可以在字符串中匹配特定的字符或者模式,进行替换或者搜索等操作。在这篇文章中,我们将学习String类操作正则表达式的方法,并且提供两个实际的示例说明。 String类操作正则表达式的方法 Java String类提供了以下方法来操作…

    Java 2023年5月27日
    00
  • java实现折半排序算法

    Java实现折半排序算法 折半排序(Binary Insertion Sort)是插入排序的一种改进版本,与插入排序相同的是,该算法的平均时间复杂度也为O(n^2),但是折半排序的优势在于其最坏时间复杂度为O(n^2)。 1. 算法原理 折半排序的算法原理如下: 从第2个元素开始,依次将元素插入到已排序的序列中。 每次插入时使用折半查找的方式,找到插入元素应…

    Java 2023年5月19日
    00
  • Java基础知识之Java语言概述

    Java基础知识之Java语言概述 Java语言是一门面向对象的编程语言,由Sun公司开发,后被Oracle公司收购。Java的特点表现在以下三个方面: 简单性 Java摒弃了C++的多重继承、指针、运算符重载等复杂的特性,使得Java更为简单易懂,因此Java语言入门难度较低。 面向对象 Java强调抽象和封装,支持继承和多态等特性,具有良好的扩展性和复用…

    Java 2023年5月23日
    00
  • Java String类的理解及字符串常量池介绍

    Java String类是Java中最重要的类之一,它用于表示字符串类型的数据。在Java程序中,字符串常常用于数据传递、文件操作、网络编程等多个场景中。本文将介绍Java String类的基本概念、使用方法,并讲解Java字符串常量池的概念和使用方法。 Java String类 基本概念 Java String类是一个不可变的、线程安全的类,它用于表示字符…

    Java 2023年5月26日
    00
  • SpringBoot使用Feign调用其他服务接口

    下面是SpringBoot使用Feign调用其他服务接口的完整攻略。 Feign是什么? Feign是一种声明式Web服务客户端,它使得编写Web服务客户端变得更加容易。使用Feign,只需要定义服务接口并注解,Feign就会自动生成实现。提供了多种注解,比如@FeignClient、@RequestMapping等,使得我们可以快速定义和测试Web服务客户…

    Java 2023年5月20日
    00
  • 一文带你你搞懂Java的3种IO模型

    一文带你搞懂Java的3种IO模型 在Java中,输入输出操作是很常见的。Java的IO模型可以分为三种:Blocking IO、Non-blocking IO和异步IO。它们的区别在于处理IO事件的方式不同。 Blocking IO 在Blocking IO模型中,当向Socket写入数据时,线程会阻塞,直到数据被真正写入。而当Socket读取数据时,线程…

    Java 2023年5月31日
    00
  • Java对象的使用过程是什么?

    Java对象的使用过程分为以下几个步骤: 创建对象:使用new关键字创建一个对象并为其分配内存 初始化对象:为对象的属性赋初值 使用对象:调用对象的方法或属性操作对象 销毁对象:当对象不再被使用时,销毁对象并释放内存 以下是两个示例说明: 示例1: // 创建一个Person类 public class Person { private String nam…

    Java 2023年5月11日
    00
  • Java比较对象大小两种常用方法

    Java中比较对象大小的方式主要有两种方法,分别是 Comparable 和 Comparator 接口。 Comparable 接口比较对象大小 Comparable 接口是 Java 自带的一个接口,它定义了对象的自然顺序。如果我们需要对一个类实例进行排序或者比较大小,那么就需要让这个类实现 Comparable 接口,并重写 compareTo 方法。…

    Java 2023年5月26日
    00
合作推广
合作推广
分享本页
返回顶部