Skip to content

数组 ​

Java 初级 · 基础语法

数组是定长、同类型元素的连续容器,是 Java 最基础的数据结构。它看似简单,但「数组在 JVM 里是对象」「多维数组是数组的数组」「length 是字段而非方法」这些细节,是后面学集合、理解内存布局的关键。本节把数组的内存本质和常用算法讲透。

一、数组在 JVM 里到底是什么 ​

一个反直觉的事实:Java 的数组是对象。它和 new Object() 一样在堆里分配,只是它是一种「特殊对象」——JVM 为每种数组类型生成对应的类(如 int[] 对应 [I,String[] 对应 [Ljava/lang/String;),有专门的字节码指令操作:

  • newarray:创建基本类型数组(int[]、byte[] 等)。
  • anewarray:创建引用类型数组(String[]、自定义类数组)。
  • multianewarray:创建多维数组。
  • arraylength:读取长度。
  • iaload/aastore 等:按类型读取/存储元素(i=int,a=reference,b=byte……)。
java
int[] a = new int[3];   // 编译为 newarray + arraylength 初始化为 0
int[] b = {1, 2, 3};    // 语法糖:编译器生成 new + 逐元素 aastore

内存布局:连续 + 存值/存地址 ​

  • int[]:堆里是一块连续的 32 位整数,每个格子直接存值。
  • String[]:堆里是一块连续的引用槽,每个格子存的是「指向堆中 String 对象的地址」。

正因为元素连续,arr[i] 的访问是 O(1):JVM 用「首地址 + i × 元素大小」直接定位,无需遍历。这也是数组随机访问快的根本原因。

数组 vs 集合的第一性差异

数组长度在 new 时就固定(length 是只读字段,不是方法),不能再伸缩;而 ArrayList 内部也是数组,但它用「换更大的数组 + 拷贝」来模拟可变长度(详见集合框架一节)。需要变长容器时优先用集合,需要定长/高性能批量传递时用数组。

二、声明、创建与遍历 ​

java
int[] a = new int[3];        // 默认 0,0,0
int[] b = {1, 2, 3};         // 直接初始化(语法糖)
String[] names = new String[]{"Tom", "Lucy"};

// 传统 for:需要索引时用
for (int i = 0; i < b.length; i++) {
    System.out.println(b[i]);
}

// 增强 for:只读遍历更简洁(底层走数组索引,不是迭代器)
for (int v : b) {
    System.out.println(v);
}

length 是数组对象的字段(所以是 arr.length,不加括号);String 是方法 .length();集合是 .size()——三者不要混。

三、多维数组:数组的数组 ​

int[][] 不是「整齐的二维块」,而是「数组的数组」:外层数组的每个元素,是指向一个内层 int[] 的引用。因此每行可以长度不同:

java
int[][] matrix = {
    {1, 2, 3},
    {4, 5}         // 第二行只有 2 个元素,完全合法
};
System.out.println(matrix[1][2]);  // 越界!这一行没有下标 2

new int[3][] 只创建了外层 3 个引用,每行需单独 new int[...] 才真正有数据。这解释了为什么遍历二维数组要先判每行非空。

四、Arrays 工具类与常用算法 ​

java.util.Arrays 提供了排序、查找、填充、转字符串等。理解两个高频算法,能避免「只会调 API 不懂原理」:

排序:Dual-Pivot Quicksort ​

Arrays.sort(int[]) 在 JDK 7+ 使用双轴快速排序(Dual-Pivot Quicksort),平均 O(n log n),比经典快排更少的元素交换。对象数组 sort(Object[]) 则用 TimSort(归并+插入的混合)。注意:排序是**原地(in-place)**修改原数组。

java
int[] arr = {5, 3, 8, 1};
Arrays.sort(arr);                        // 升序,原数组被改为 [1,3,5,8]
System.out.println(Arrays.toString(arr));// [1, 3, 5, 8]

二分查找:必须先排序 ​

Arrays.binarySearch 前提是数组已升序,否则结果未定义:

java
int idx = Arrays.binarySearch(arr, 5);   // 返回下标 2;找不到返回「负数插入点」

二分查找每次取中点比较,把搜索区间砍半,O(log n)。若数组无序,中点逻辑完全失效,可能查不到存在的元素。

冒泡排序(理解「交换」思想) ​

虽生产用 Arrays.sort,但手写一遍能体会「相邻比较 + 交换」:

java
for (int i = 0; i < arr.length - 1; i++) {          // 每轮把最大者推到末尾
    for (int j = 0; j < arr.length - 1 - i; j++) {
        if (arr[j] > arr[j + 1]) {
            int t = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = t;
        }
    }
}

克隆(clone)的语义

int[] c = arr.clone(); 创建新数组并拷贝元素。对基本类型数组,拷贝的是值(两数组独立);对引用类型数组,clone 只拷贝「地址」,新旧数组仍指向同一批对象——改对象内容会互相影响(浅拷贝)。需要深度独立要逐元素 new。

五、常见误区与注意点 ​

WARNING

  • 数组越界:访问 arr[arr.length] 抛 ArrayIndexOutOfBoundsException。循环条件常错写成 <= length。
  • 混淆 length / length() / size():数组是属性 .length,String 是方法 .length(),集合是 .size()。
  • 数组是引用类型:int[] x = y; 只复制引用,两变量指向同一数组,改一个另一个也变。
  • 二维数组每行长度可不同:matrix[i].length 可能不一样,遍历前先确认行非空。
  • 把数组当方法「返回值」:数组可返回,但若返回内部数组引用,外部能改你的私有数据——必要时 clone 或 Arrays.copyOf 防御性拷贝。

六、最佳实践 ​

  1. 遍历用 for-each;需要索引或反向遍历才用传统 for。
  2. 排序、查找优先用 Arrays 工具类,别手写轮子(除非为了学算法)。
  3. 需要动态增删用 ArrayList,而非反复 new 新数组再 System.arraycopy。
  4. 返回数组给外部前,考虑 Arrays.copyOf 做防御性拷贝,防止内部状态被外部篡改。
  5. 多维数组先判每行长度,避免 NullPointerException / 越界。

📚 官方文档与参考资料 ​

✏️ 练习与自测 ​

  1. 创建一个长度为 10 的 int 数组,存入 1~10,用增强 for 打印;再用 javap -c 观察 arraylength 指令。
  2. 写一个方法 double avg(int[] arr) 求平均值,并在方法里演示「返回前用 Arrays.copyOf 防御性拷贝」的必要性(提示:若直接 return arr 内部字段会有什么问题)。
  3. 用 Arrays.sort 对一个 String 数组排序,观察字符串按字典序(实际按 compareTo,即 UTF-16 码元顺序)排列;解释为何 Arrays.binarySearch 前必须先排序。
  4. 构造一个「每行长度不同」的二维数组,遍历时演示 NullPointerException 的触发与规避。

本节小结

Java 数组是堆里的特殊对象([I、[Ljava/lang/String; 等),由 newarray/anewarray 创建、arraylength 取长、元素连续存储故随机访问 O(1)。多维数组是「数组的数组」,每行可不等长。length 是字段(区别于 String.length()、集合 size())。Arrays.sort 用双轴快排/sort 对象用 TimSort,二分查找前必须有序。clone 对引用数组是浅拷贝。需要变长时用集合,定长高性能时用数组。