HOWTO · Java

在 Java 中创建动态数组

本指南教授如何在 Java 中创建动态数组。

本页内容

Java 数组创建后长度固定。若要添加超出该长度的元素,可以用一个包装普通数组的 dynamic array 类来管理它:记录当前存储的值的数量,并在空间不足时替换底层数组。下面的示例存储基本类型 int 值,只支持在末尾追加和移除最后一个值,也可以将存储空间缩减至与当前 size 相同。

在 Java 中使用自定义逻辑创建动态数组

该类管理三项状态:底层 int[]、size 和 capacity。size 表示当前存储的值的数量;capacity 是底层数组的长度。将这两个值分开记录,就能预留数组槽位,而不把这些槽位算作已存储的值。

运行此示例:

本节中的代码以及显示的标签或输出仍为英文。

import java.util.Arrays;

public class DynamicArray {
    private int[] values;
    private int size;

    public DynamicArray() {
        values = new int[2];
    }

    public void add(int value) {
        if (size == values.length) {
            int newCapacity = values.length == 0 ? 1 : values.length * 2;
            values = Arrays.copyOf(values, newCapacity);
        }
        values[size] = value;
        size++;
    }

    public void removeLast() {
        if (size == 0) {
            return;
        }
        size--;
        values[size] = 0;
    }

    public void shrinkToSize() {
        values = Arrays.copyOf(values, size);
    }

    public int size() {
        return size;
    }

    public int capacity() {
        return values.length;
    }

    @Override
    public String toString() {
        return Arrays.toString(Arrays.copyOf(values, size));
    }

    public static void main(String[] args) {
        DynamicArray numbers = new DynamicArray();
        numbers.add(10);
        numbers.add(20);
        numbers.add(30);
        numbers.add(40);
        numbers.add(50);
        System.out.println(numbers + " | size=" + numbers.size()
                + " | capacity=" + numbers.capacity());

        numbers.removeLast();
        System.out.println(numbers + " | size=" + numbers.size()
                + " | capacity=" + numbers.capacity());

        numbers.shrinkToSize();
        System.out.println(numbers + " | size=" + numbers.size()
                + " | capacity=" + numbers.capacity());
    }
}

输出:

[10, 20, 30, 40, 50] | size=5 | capacity=8
[10, 20, 30, 40] | size=4 | capacity=8
[10, 20, 30, 40] | size=4 | capacity=4

添加五个值后,数组中的值为 [10, 20, 30, 40, 50],大小为 5,容量为 8。调用 removeLast() 后,数组中的值为 [10, 20, 30, 40],大小为 4,容量仍为 8。调用 shrinkToSize() 后,大小和容量都为 4。

理解大小、容量与扩容

构造函数将容量设为 2,大小设为 0。每次调用 add 时,都会检查大小是否已达到容量。底层数组已满时,Arrays.copyOf 会分配一个长度为原来两倍的新数组,并将已有值复制过去。随后,新值会写入索引 size 处,大小再增加 1。

对于示例中的五次添加,容量从 2 增至 4,再增至 8。扩容时会复制已存储的值,新数组中多出的槽位则可供后续添加使用。容量表示已分配的存储空间,而不是值的数量。toString() 方法只复制数组中位于大小范围内的部分,因此多出的槽位不会显示为元素。

复制所需的时间与被复制的值的数量成正比,而且复制期间两个数组会同时占用空间。将容量加倍可以避免每次追加都要复制,但触发扩容的那次追加仍然需要复制。这个小类用于演示扩容机制;对于一般用途的集合,通常使用 Java 的 ArrayList<Integer> 更合适。

添加与移除元素

add 只支持在末尾追加,不支持在任意索引处插入。removeLast 会将大小减 1,并把腾出的槽位清为 Java 中 int 的默认值 0。int 是基本类型,不是引用,因此清空槽位并不是为了垃圾回收,也不会释放引用;这样做只是让底层数组未使用部分的状态保持可预测。当大小为 0 时调用 removeLast,对象状态不会改变。

移除元素不会自动缩小容量。如果回收多余数组槽位比为后续添加保留空间更重要,可以调用 shrinkToSize()。缩小容量时也会将值复制到替换数组中。如果大小为 0,容量会变为 0;下一次调用 add 时,容量会先增至 1,再存入新值。此实现只存储基本类型 int 值,不支持按索引访问、插入元素或移除中间元素。