Java入门到精通-08 数组详解
目录
📦 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字段),不是方法。而 String 的 length() 是方法。
十二、总结与预告
本文小结
| 知识点 | 要点 |
|---|---|
| 数组声明 | int[] arr 或 int arr[] |
| 创建方式 | new int[5] 或 {1,2,3} |
| 访问元素 | arr[index],下标从0开始 |
| 数组长度 | arr.length(属性) |
| 二维数组 | int[][] arr = new int[3][4] |
| Arrays工具类 | sort、binarySearch、copyOf、toString等 |
| 排序算法 | 冒泡排序、选择排序 |
| 查找算法 | 线性查找、二分查找 |
💬 互动问题
- 冒泡排序的优化策略是什么?为什么要设置
swapped标志? - 为什么二分查找要求数组必须是有序的?
- 数组作为方法参数时,是值传递还是引用传递?为什么在方法内可以修改数组元素?
📢 下篇预告
下一篇:09 方法函数详解 — 我们将学习Java中最重要的代码组织单元——方法(函数),包括方法的定义与调用、参数传递机制、方法重载以及递归等核心概念。掌握了方法,你的代码将更加模块化和可维护!
参考资料
更多推荐




所有评论(0)