跳到内容

元数据卡

  • 前置知识:第6章前文(数组声明、索引与遍历)
  • 预计时间:15 分钟
  • 阅读模式:👣稳步推进
  • 完成标志:会复制数组、用 Arrays 工具类排序和搜索、理解数组固定长度特性

问题一:街区不能扩建

你刚建好一条 3 户人的街道,突然来了第 4 个人想入住。

java
int[] scores = new int[3];
scores[0] = 85;
scores[1] = 92;
scores[2] = 78;

// 想放第四个人?
scores[3] = 95; // ⚠️ ArrayIndexOutOfBoundsException!

语言:Java 为什么会报错:数组的长度一旦创建就不可变。合法索引 0~2,scores[3] 越界了。

你需要更多空间?只能建一条更大的新街,然后把原来的住户搬过去:

java
int[] oldScores = {85, 92, 78};
int[] newScores = new int[5]; // 更大的街道

// 手动搬过去
for (int i = 0; i < oldScores.length; i++) {
    newScores[i] = oldScores[i];
}

// 新住户入住
newScores[3] = 95;
newScores[4] = 88;

这就是 ArrayList 的原理ArrayList 内部维护一个数组,当空间不够时就自动"扩建"——实际上就是建个更大的新数组,旧数据搬过去。后面第 10 章会详细讲。


问题二:怎么复制一条一模一样的街?

你有三条路可以复制数组。

方法一:手动循环(最直观)

java
int[] source = {1, 2, 3, 4, 5};
int[] dest = new int[source.length];

for (int i = 0; i < source.length; i++) {
    dest[i] = source[i];
}

方法二:System.arraycopy()(原生方法,最快)

java
int[] source = {1, 2, 3, 4, 5};
int[] dest = new int[5];

// System.arraycopy(源数组, 源起始位置, 目标数组, 目标起始位置, 要复制的数量)
System.arraycopy(source, 0, dest, 0, source.length);

// 也可以只复制一部分
System.arraycopy(source, 1, dest, 2, 3);
// dest 变成 [0, 0, 2, 3, 4]

语言:Java 关键点System.arraycopy 是 native 方法——直接在内存层面复制,速度最快

方法三:Arrays.copyOf()(Java 6+,最常用)

java
import java.util.Arrays;

int[] source = {1, 2, 3, 4, 5};

// 复制整个数组
int[] copy = Arrays.copyOf(source, source.length);

// 也可以指定新长度——自动截断或补零
int[] longer = Arrays.copyOf(source, 10);  // [1,2,3,4,5,0,0,0,0,0]
int[] shorter = Arrays.copyOf(source, 3);  // [1,2,3]——截断了!

语言:Java,需要 import java.util.Arrays;何时用:日常开发最常用的复制方式。它内部调用了 System.arraycopy


浅拷贝 vs 深拷贝——第一次见

刚才的复制方法对基本类型数组(int[]double[] 等)没问题,因为每个格子只存数值。

但对引用类型数组(比如 String[]Person[]),复制的是引用——两个数组指向同一个对象。修改其中一个元素对象,另一个也会看到。这叫浅拷贝

如果每个元素对象本身也要复制一份,那叫深拷贝。深拷贝的完整实现留到 OOP 和泛型后复访,现在先记住这两个名字。


问题三:排序、搜索、填充——每次都自己写循环?

当然不。java.util.Arrays 工具类帮你搞定一切。

java
import java.util.Arrays;

public class ArraysDemo {
    public static void main(String[] args) {
        int[] numbers = {42, 7, 15, 8, 23, 3};

        // 1. 排序
        Arrays.sort(numbers);
        System.out.println(Arrays.toString(numbers));
        // [3, 7, 8, 15, 23, 42]

        // 2. 二分搜索(必须先排序!)
        int index = Arrays.binarySearch(numbers, 15);
        System.out.println("15 的位置:" + index); // 3

        // 3. 如果找不到,返回负数(表示插入点)
        int notFound = Arrays.binarySearch(numbers, 99);
        System.out.println("99 找不到:" + notFound); // 负数

        // 4. 填充
        int[] filled = new int[5];
        Arrays.fill(filled, 42);
        System.out.println(Arrays.toString(filled));
        // [42, 42, 42, 42, 42]

        // 5. 比较相等
        int[] a = {1, 2, 3};
        int[] b = {1, 2, 3};
        int[] c = {3, 2, 1};
        System.out.println(Arrays.equals(a, b));  // true
        System.out.println(Arrays.equals(a, c));  // false(顺序不同)

        // 6. Arrays.toString —— 打印数组
        System.out.println(Arrays.toString(new int[]{10, 20, 30}));
        // [10, 20, 30]
    }
}

语言:Java,需要 import java.util.Arrays重点方法速查

方法作用注意
sort(a)排序(升序)内部用 Dual-Pivot Quicksort,很快
binarySearch(a, key)二分查找数组必须先排序
fill(a, val)全填充为某值也可指定范围 fill(a, from, to, val)
equals(a, b)比较两个数组是否完全相同长度 + 每个元素都得一样
copyOf(a, newLen)复制数组自动截断或补默认值
toString(a)把数组转成可读字符串不然打印出来是 [I@1b6d3586——内存地址

切记System.out.println(numbers) 打印的是 [I@15db9742——内存地址,毫无用处。一定要用 Arrays.toString(numbers)


差异窗口:C++ 和 Python 的排序

C++:

cpp
#include <algorithm>
int numbers[] = {42, 7, 15, 8, 23, 3};
std::sort(std::begin(numbers), std::end(numbers));

C++ 的 std::sort 需要传入起始和结束迭代器(指针),不像 Java 那样直接用数组名。

Python:

python
numbers = [42, 7, 15, 8, 23, 3]
numbers.sort()          # 原地排序,不返回新数组
sorted_numbers = sorted(numbers)  # 返回新列表,原列表不变

Python 的 sorted() 返回新排序列表——这在函数式编程中更常见,不修改原始数据。


(附录)可变参数——收任意多个同类型的东西

你写了一个方法要计算一堆数字的平均值。调用者可能传 2 个,也可能传 10 个。

不用可变参数的话,你只能写一堆重载方法——或者让调用者手动把数字装成数组。Java 5 引入了可变参数(varargs):

java
public class VarargsDemo {
    public static void main(String[] args) {
        System.out.println(average(85, 92, 78));     // 3 个数
        System.out.println(average(90, 95));          // 2 个数
        System.out.println(average(88, 76, 93, 85, 91)); // 5 个数
    }

    // 可变参数用三个点表示
    static double average(int... numbers) {
        int sum = 0;
        for (int n : numbers) {
            sum += n;
        }
        return (double) sum / numbers.length;
    }
}

语言:Java 预期输出

85.0
92.5
86.6

语法int... numbers —— 本质上等价于 int[] numbers,但调用时可以传多个参数而不用手动创建数组 规则

  1. 每个方法最多只能有一个可变参数
  2. 可变参数必须是最后一个参数
  3. 可以传任意数量(包括 0 个)
java
// 正确的声明
void method(String name, int... scores) { }   // ✅ 可变参数在最后

// 支持的调用方式
method("张三");               // 传 0 个可变参数——numbers 长度为 0
method("张三", 85);           // 传 1 个
method("张三", 85, 92, 78);   // 传多个
method("张三", new int[]{85, 92}); // 也可以直接传数组

本质:Java 在编译时把可变参数转换成了数组。你在方法体内看到 int... numbers,它的类型就是 int[]


🧠 大脑缓存

  • 数组长度建了就定了,想要更大只能新建 + 复制
  • 复制三式:手写循环(慢)、System.arraycopy(快)、Arrays.copyOf(日常用)
  • 基本类型数组复制没坑;引用类型数组是浅拷贝
  • Arrays.sortbinarySearchfillequalstoString——别自己造轮子
  • int... 可变参数本质就是数组

下一步

一条街不够用了?有时候你需要的是棋盘、矩阵、图像——二维甚至多维的数组。下一节我们看看街区的街区长什么样。

继续阅读:多维数组

Built with VitePress | Software Systems Atlas