📦 08 数组详解

更新日期:2026年5月
版权声明:本文为原创文章,转载请注明出处。© 2026 Java入门到精通系列



一、什么是数组

1.1 数组的概念

数组(Array)是Java中最基本的数据结构之一,它是相同类型元素的有序集合。数组在内存中占据一块连续的空间,可以通过**下标(索引)**快速访问任意元素。

┌──────────────────────────────────────────────┐
│              数组内存结构示意                    │
├──────────────────────────────────────────────┤
│                                              │
│   int[] arr = {10, 20, 30, 40, 50};          │
│                                              │
│   ┌─────┬─────┬─────┬─────┬─────┐           │
│   │  10 │  20 │  30 │  40 │  50 │           │
│   └──┬──┴──┬──┴──┬──┴──┬──┴──┬──┘           │
│      │     │     │     │     │               │
│   arr[0] arr[1] arr[2] arr[3] arr[4]         │
│                                              │
│   内存地址:                                    │
│   0x100  0x104  0x108  0x10C  0x110          │
│   (每个int占4字节,地址连续)                      │
│                                              │
└──────────────────────────────────────────────┘

1.2 为什么需要数组

// 没有数组:存储5个学生的成绩
int score1 = 85;
int score2 = 90;
int score3 = 78;
int score4 = 92;
int score5 = 88;
// 需要5个变量,如果100个学生呢?难以管理和操作

// 使用数组:存储5个学生的成绩
int[] scores = {85, 90, 78, 92, 88};
// 一个变量搞定,可以通过循环轻松操作

1.3 数组的特点

特点 说明
类型统一 数组中所有元素必须是相同类型
长度固定 一旦创建,长度不可改变
连续内存 元素在内存中连续存储,访问速度快
下标访问 通过索引访问元素,时间复杂度O(1)
下标从0开始 第一个元素索引为0,最后一个为length-1

二、一维数组的声明与创建

2.1 数组的声明

Java中声明数组有两种语法格式:

// 方式一:推荐写法(类型[] 变量名)
int[] arr;
String[] names;
double[] prices;

// 方式二:C语言风格(了解即可)
int arr[];
String names[];

⚠️ 注意:声明数组时不会分配内存空间,只是声明了一个引用变量。

2.2 数组的创建

// 方式一:使用new关键字指定长度
int[] arr1 = new int[5];        // 创建长度为5的int数组,默认值为0
String[] arr2 = new String[3];  // 创建长度为3的String数组,默认值为null
double[] arr3 = new double[4];  // 创建长度为4的double数组,默认值为0.0

// 方式二:声明的同时创建
int[] arr4;
arr4 = new int[5];  // 先声明,后创建

// 方式三:声明并初始化(直接赋值)
int[] arr5 = {1, 2, 3, 4, 5};        // 最简洁的方式
int[] arr6 = new int[]{1, 2, 3, 4};  // new 类型[]{值},注意不能指定长度

2.3 数组的默认值

当使用 new 创建数组但未赋值时,数组元素会有默认值:

数据类型 默认值
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0
char '\u0000'(空字符)
boolean false
引用类型 null
public class ArrayDefault {
    public static void main(String[] args) {
        int[] ints = new int[3];
        double[] doubles = new double[3];
        boolean[] booleans = new boolean[3];
        String[] strings = new String[3];

        System.out.println("int默认值:" + ints[0]);       // 0
        System.out.println("double默认值:" + doubles[0]); // 0.0
        System.out.println("boolean默认值:" + booleans[0]); // false
        System.out.println("String默认值:" + strings[0]); // null
    }
}

三、一维数组的初始化

3.1 静态初始化

在定义数组时直接为每个元素赋值,数组长度由值的个数决定。

// 完整写法
int[] arr1 = new int[]{10, 20, 30, 40, 50};

// 简写方式(推荐)
int[] arr2 = {10, 20, 30, 40, 50};

// 字符串数组
String[] fruits = {"苹果", "香蕉", "橘子", "西瓜"};

// 字符数组
char[] grades = {'A', 'B', 'C', 'D', 'F'};

3.2 动态初始化

先创建指定长度的数组,然后逐个赋值。

// 动态初始化
int[] scores = new int[5];

// 逐个赋值
scores[0] = 85;
scores[1] = 90;
scores[2] = 78;
scores[3] = 92;
scores[4] = 88;

// 使用循环赋值
int[] arr = new int[10];
for (int i = 0; i < arr.length; i++) {
    arr[i] = (i + 1) * 10;  // 10, 20, 30, ..., 100
}

3.3 静态初始化 vs 动态初始化

对比项 静态初始化 动态初始化
语法 int[] arr = {1,2,3} int[] arr = new int[3]
适用场景 已知所有元素的值 只知道数组长度,值后续计算
长度 由值的个数决定 由程序员指定
默认值 无默认值,直接赋值 元素有默认值

四、数组的访问与遍历

4.1 通过下标访问

int[] arr = {10, 20, 30, 40, 50};

// 获取元素
System.out.println(arr[0]);  // 10(第一个元素)
System.out.println(arr[2]);  // 30(第三个元素)
System.out.println(arr[4]);  // 50(最后一个元素)

// 修改元素
arr[0] = 100;
System.out.println(arr[0]);  // 100

// 获取数组长度
System.out.println("数组长度:" + arr.length);  // 5

// 访问最后一个元素
System.out.println(arr[arr.length - 1]);  // 50

⚠️ 数组越界:访问不存在的索引会抛出 ArrayIndexOutOfBoundsException 异常。

int[] arr = {1, 2, 3};
// System.out.println(arr[5]);  // ❌ 运行时异常:ArrayIndexOutOfBoundsException

4.2 for循环遍历

int[] arr = {10, 20, 30, 40, 50};

// 标准for循环
for (int i = 0; i < arr.length; i++) {
    System.out.println("arr[" + i + "] = " + arr[i]);
}

4.3 增强for循环(for-each)

int[] arr = {10, 20, 30, 40, 50};

// for-each循环(只能读取,不能修改)
for (int num : arr) {
    System.out.println(num);
}

4.4 数组遍历实战

public class ArrayDemo {
    public static void main(String[] args) {
        int[] scores = {85, 92, 78, 96, 88, 73, 90};

        // 求总分
        int sum = 0;
        for (int score : scores) {
            sum += score;
        }
        System.out.println("总分:" + sum);

        // 求平均分
        double avg = (double) sum / scores.length;
        System.out.println("平均分:" + String.format("%.2f", avg));

        // 求最大值和最小值
        int max = scores[0];
        int min = scores[0];
        for (int score : scores) {
            if (score > max) max = score;
            if (score < min) min = score;
        }
        System.out.println("最高分:" + max);
        System.out.println("最低分:" + min);
    }
}

输出结果

总分:602
平均分:86.00
最高分:96
最低分:73

五、二维数组

5.1 二维数组的概念

二维数组可以理解为"数组的数组"——每个元素本身也是一个数组。可以把它想象成一个表格(行和列)。

┌──────────────────────────────────┐
│        二维数组内存结构             │
├──────────────────────────────────┤
│                                  │
│  int[][] matrix = {              │
│      {1, 2, 3},   // 第0行       │
│      {4, 5, 6},   // 第1行       │
│      {7, 8, 9}    // 第2行       │
│  };                              │
│                                  │
│  matrix[0] → [1, 2, 3]          │
│  matrix[1] → [4, 5, 6]          │
│  matrix[2] → [7, 8, 9]          │
│                                  │
│  matrix[1][2] → 6(第1行第2列)    │
│                                  │
└──────────────────────────────────┘

5.2 二维数组的声明与创建

// 声明
int[][] arr1;
String[][] arr2;

// 创建(指定行数和列数)
int[][] arr3 = new int[3][4];  // 3行4列

// 静态初始化
int[][] arr4 = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// 动态初始化
int[][] arr5 = new int[2][3];
arr5[0][0] = 1;
arr5[0][1] = 2;
// ...

5.3 二维数组的遍历

public class TwoDArrayDemo {
    public static void main(String[] args) {
        int[][] matrix = {
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}
        };

        // 双重for循环遍历
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                System.out.print(matrix[i][j] + "\t");
            }
            System.out.println();
        }
    }
}

输出结果

1	2	3
4	5	6
7	8	9

5.4 不规则数组(锯齿数组)

Java的二维数组允许每行的列数不同:

// 不规则数组:每行的列数不同
int[][] jagged = new int[3][];
jagged[0] = new int[]{1, 2};
jagged[1] = new int[]{3, 4, 5, 6};
jagged[2] = new int[]{7};

// 遍历
for (int i = 0; i < jagged.length; i++) {
    for (int j = 0; j < jagged[i].length; j++) {
        System.out.print(jagged[i][j] + " ");
    }
    System.out.println();
}
// 输出:
// 1 2
// 3 4 5 6
// 7

5.5 二维数组实战:矩阵转置

public class MatrixTranspose {
    public static void main(String[] args) {
        int[][] original = {
            {1, 2, 3},
            {4, 5, 6}
        };
        // 2行3列 → 3行2列

        int rows = original.length;
        int cols = original[0].length;
        int[][] transposed = new int[cols][rows];

        // 转置
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                transposed[j][i] = original[i][j];
            }
        }

        // 打印转置后的矩阵
        for (int[] row : transposed) {
            for (int val : row) {
                System.out.print(val + "\t");
            }
            System.out.println();
        }
    }
}

六、Arrays工具类详解

java.util.Arrays 是Java提供的数组操作工具类,包含了大量常用的数组操作方法,是日常开发中使用频率极高的工具。

6.1 常用方法一览

方法 功能 示例
toString(arr) 将数组转为字符串 [1, 2, 3]
sort(arr) 数组排序(升序) 原地排序
binarySearch(arr, key) 二分查找 返回索引
copyOf(arr, newLength) 复制数组 返回新数组
fill(arr, val) 填充数组 所有元素设为val
equals(arr1, arr2) 比较两个数组 true/false
asList(arr) 数组转List 返回List
deepToString(arr) 深度转字符串(二维数组) 嵌套打印
deepEquals(arr1, arr2) 深度比较(二维数组) true/false

6.2 代码示例

import java.util.Arrays;

public class ArraysDemo {
    public static void main(String[] args) {
        int[] arr = {5, 2, 8, 1, 9, 3, 7};

        // 1. toString - 打印数组内容
        System.out.println("原始数组:" + Arrays.toString(arr));
        // 输出:[5, 2, 8, 1, 9, 3, 7]

        // 2. sort - 排序
        Arrays.sort(arr);
        System.out.println("排序后:" + Arrays.toString(arr));
        // 输出:[1, 2, 3, 5, 7, 8, 9]

        // 3. binarySearch - 二分查找(必须先排序!)
        int index = Arrays.binarySearch(arr, 7);
        System.out.println("7的索引:" + index);  // 4

        // 4. copyOf - 复制数组
        int[] copy1 = Arrays.copyOf(arr, 5);      // 只复制前5个
        int[] copy2 = Arrays.copyOf(arr, 10);      // 复制并扩展到10个,多出的为0
        System.out.println("复制前5个:" + Arrays.toString(copy1));
        System.out.println("扩展到10个:" + Arrays.toString(copy2));

        // 5. fill - 填充
        int[] filled = new int[5];
        Arrays.fill(filled, 66);
        System.out.println("填充66:" + Arrays.toString(filled));
        // 输出:[66, 66, 66, 66, 66]

        // 6. equals - 比较
        int[] a = {1, 2, 3};
        int[] b = {1, 2, 3};
        int[] c = {1, 2, 4};
        System.out.println("a==b? " + Arrays.equals(a, b));  // true
        System.out.println("a==c? " + Arrays.equals(a, c));  // false

        // 7. asList - 转为List(注意:只适用于包装类型数组)
        String[] fruits = {"苹果", "香蕉", "橘子"};
        java.util.List<String> list = Arrays.asList(fruits);
        System.out.println("转List:" + list);
    }
}

6.3 Arrays.fill() 部分填充

int[] arr = new int[10];
Arrays.fill(arr, 3, 7, 99);  // 索引3到6(不包含7)填充为99
System.out.println(Arrays.toString(arr));
// 输出:[0, 0, 0, 99, 99, 99, 99, 0, 0, 0]

七、数组常见算法——排序

7.1 冒泡排序(Bubble Sort)

冒泡排序是最基础的排序算法,核心思想是相邻元素两两比较,把大的元素往后"冒泡"。

public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = {64, 34, 25, 12, 22, 11, 90};

        System.out.println("排序前:" + Arrays.toString(arr));
        bubbleSort(arr);
        System.out.println("排序后:" + Arrays.toString(arr));
    }

    public static void bubbleSort(int[] arr) {
        int n = arr.length;
        for (int i = 0; i < n - 1; i++) {         // 外层:n-1轮
            boolean swapped = false;               // 优化:标记是否发生交换
            for (int j = 0; j < n - 1 - i; j++) { // 内层:每轮减少一个比较
                if (arr[j] > arr[j + 1]) {
                    // 交换
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                    swapped = true;
                }
            }
            if (!swapped) break;  // 如果本轮没有交换,说明已有序
        }
    }
}

冒泡排序过程图解

原始:[64, 34, 25, 12, 22, 11, 90]

第1轮:[34, 25, 12, 22, 11, 64, 90]  ← 90冒泡到末尾
第2轮:[25, 12, 22, 11, 34, 64, 90]  ← 64冒泡到倒数第2
第3轮:[12, 22, 11, 25, 34, 64, 90]
第4轮:[12, 11, 22, 25, 34, 64, 90]
第5轮:[11, 12, 22, 25, 34, 64, 90]  ← 排序完成

7.2 选择排序(Selection Sort)

选择排序每次从未排序部分选出最小值,放到已排序部分的末尾。

public static void selectionSort(int[] arr) {
    int n = arr.length;
    for (int i = 0; i < n - 1; i++) {
        int minIndex = i;  // 假设当前位置是最小值
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;  // 找到更小的值
            }
        }
        // 将最小值交换到正确位置
        if (minIndex != i) {
            int temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }
}

7.3 排序算法对比

算法 时间复杂度(平均) 时间复杂度(最坏) 空间复杂度 稳定性
冒泡排序 O(n²) O(n²) O(1) ✅ 稳定
选择排序 O(n²) O(n²) O(1) ❌ 不稳定
插入排序 O(n²) O(n²) O(1) ✅ 稳定
快速排序 O(n log n) O(n²) O(log n) ❌ 不稳定
归并排序 O(n log n) O(n log n) O(n) ✅ 稳定

💡 稳定性指相等元素在排序后是否保持原有相对顺序。


八、数组常见算法——查找

8.1 线性查找(Linear Search)

从头到尾逐个遍历,找到目标即返回。时间复杂度 O(n)。

public static int linearSearch(int[] arr, int target) {
    for (int i = 0; i < arr.length; i++) {
        if (arr[i] == target) {
            return i;  // 找到,返回索引
        }
    }
    return -1;  // 未找到
}

8.2 二分查找(Binary Search)

二分查找要求数组已排序,每次将搜索范围缩小一半。时间复杂度 O(log n)。

public static int binarySearch(int[] arr, int target) {
    int left = 0;
    int right = arr.length - 1;

    while (left <= right) {
        int mid = left + (right - left) / 2;  // 防止溢出

        if (arr[mid] == target) {
            return mid;         // 找到目标
        } else if (arr[mid] < target) {
            left = mid + 1;     // 目标在右半部分
        } else {
            right = mid - 1;    // 目标在左半部分
        }
    }
    return -1;  // 未找到
}

二分查找过程图解(查找22):

数组:[11, 12, 22, 25, 34, 64, 90]

第1步:left=0, right=6, mid=3 → arr[3]=25 > 22 → right=2
       [11, 12, 22]
第2步:left=0, right=2, mid=1 → arr[1]=12 < 22 → left=2
       [22]
第3步:left=2, right=2, mid=2 → arr[2]=22 = 22 → 找到!返回2

8.3 查找算法对比

算法 前提条件 时间复杂度 适用场景
线性查找 O(n) 无序数组、小规模数据
二分查找 必须有序 O(log n) 有序数组、大规模数据

九、数组常见问题与注意事项

9.1 数组越界

int[] arr = {1, 2, 3};
// System.out.println(arr[5]);  // ArrayIndexOutOfBoundsException!
// System.out.println(arr[-1]); // ArrayIndexOutOfBoundsException!

9.2 空指针异常

int[] arr = {1, 2, 3};
arr = null;
// System.out.println(arr.length);  // NullPointerException!

9.3 数组长度不可变

int[] arr = new int[5];
// arr.length = 10;  // ❌ 编译错误!length是final的

// 如果需要"扩容",必须创建新数组
int[] newArr = new int[10];
for (int i = 0; i < arr.length; i++) {
    newArr[i] = arr[i];
}
arr = newArr;  // 让arr指向新数组

9.4 数组作为方法参数和返回值

public class ArrayMethod {
    // 数组作为参数
    public static int sum(int[] arr) {
        int total = 0;
        for (int num : arr) {
            total += num;
        }
        return total;
    }

    // 数组作为返回值
    public static int[] createRange(int start, int end) {
        int[] result = new int[end - start];
        for (int i = 0; i < result.length; i++) {
            result[i] = start + i;
        }
        return result;
    }

    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        System.out.println("总和:" + sum(arr));  // 15

        int[] range = createRange(1, 6);
        System.out.println(Arrays.toString(range));  // [1, 2, 3, 4, 5]
    }
}

9.5 值传递的陷阱

public class ArrayTrap {
    public static void modify(int[] arr) {
        arr[0] = 999;  // ✅ 会修改原数组(数组是引用类型)
    }

    public static void replace(int[] arr) {
        arr = new int[]{100, 200, 300};  // ❌ 不会影响原数组
    }

    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        modify(arr);
        System.out.println(Arrays.toString(arr));  // [999, 2, 3]

        arr = new int[]{1, 2, 3};
        replace(arr);
        System.out.println(Arrays.toString(arr));  // [1, 2, 3] — 没变!
    }
}

十、综合实战练习

练习1:数组反转

public static void reverse(int[] arr) {
    int left = 0;
    int right = arr.length - 1;
    while (left < right) {
        int temp = arr[left];
        arr[left] = arr[right];
        arr[right] = temp;
        left++;
        right--;
    }
}
// 测试
int[] arr = {1, 2, 3, 4, 5};
reverse(arr);
System.out.println(Arrays.toString(arr));  // [5, 4, 3, 2, 1]

练习2:统计字符出现次数

public static void countChars(String str) {
    int[] counts = new int[26];  // 26个字母的计数器
    for (char c : str.toLowerCase().toCharArray()) {
        if (c >= 'a' && c <= 'z') {
            counts[c - 'a']++;
        }
    }
    for (int i = 0; i < 26; i++) {
        if (counts[i] > 0) {
            System.out.println((char) ('a' + i) + ": " + counts[i] + "次");
        }
    }
}

练习3:杨辉三角(二维数组经典案例)

public static void printYangHui(int rows) {
    int[][] triangle = new int[rows][];
    for (int i = 0; i < rows; i++) {
        triangle[i] = new int[i + 1];    // 每行的列数 = 行号 + 1
        triangle[i][0] = 1;              // 每行第一个为1
        triangle[i][i] = 1;              // 每行最后一个为1
        for (int j = 1; j < i; j++) {
            triangle[i][j] = triangle[i-1][j-1] + triangle[i-1][j];
        }
    }
    // 打印
    for (int i = 0; i < rows; i++) {
        // 打印前导空格(居中对齐)
        for (int s = 0; s < rows - i - 1; s++) System.out.print("  ");
        for (int j = 0; j <= i; j++) {
            System.out.printf("%4d", triangle[i][j]);
        }
        System.out.println();
    }
}

输出(6行杨辉三角)

              1
           1     1
        1     2     1
     1     3     3     1
  1     4     6     4     1
1     5    10    10     5     1

十一、常见面试题

Q1:数组和ArrayList的区别?

对比项 数组 ArrayList
长度 固定 动态可变
类型 基本类型和引用类型都支持 只支持引用类型
方法 无内置方法 丰富的API
性能 更快(连续内存) 略慢(对象封装)

Q2:如何判断数组中是否包含某个元素?

// 方式一:for循环
boolean contains = false;
for (int num : arr) {
    if (num == target) { contains = true; break; }
}

// 方式二:Arrays.stream(Java 8+)
boolean found = Arrays.stream(arr).anyMatch(n -> n == target);

Q3:数组的length是方法还是属性?

数组的 length属性(final字段),不是方法。而 Stringlength()方法


十二、总结与预告

本文小结

知识点 要点
数组声明 int[] arrint arr[]
创建方式 new int[5]{1,2,3}
访问元素 arr[index],下标从0开始
数组长度 arr.length(属性)
二维数组 int[][] arr = new int[3][4]
Arrays工具类 sort、binarySearch、copyOf、toString等
排序算法 冒泡排序、选择排序
查找算法 线性查找、二分查找

💬 互动问题

  1. 冒泡排序的优化策略是什么?为什么要设置 swapped 标志?
  2. 为什么二分查找要求数组必须是有序的?
  3. 数组作为方法参数时,是值传递还是引用传递?为什么在方法内可以修改数组元素?

📢 下篇预告

下一篇:09 方法函数详解 — 我们将学习Java中最重要的代码组织单元——方法(函数),包括方法的定义与调用、参数传递机制、方法重载以及递归等核心概念。掌握了方法,你的代码将更加模块化和可维护!


参考资料

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐