Day6|Java数组完全指南
今天来聊一聊数组,顾名思义,数组就是一组数据。
无论是刚入门Java,还是正在准备求职面试,数组都是必须精通的基础数据结构。
它既是语言的底层支撑结构之一,又是算法题和系统设计中频繁出现的角色。
本文将融合学习场景和真实开发实践,从数组的基本概念、内存模型、典型用法,到面试遇到的坑与性能优化,详细的讲一下Java数组知识体系。
一、为什么数组那么重要?
应该都打过游戏吧,抖音应该刷过吧,各种排行榜、积分榜、热搜列表,本质上是固定长度的数组存储前N名,快速排序和更新。
如果你在Leetcode上刷过算法题,你就会发现超过一半的题都跟数组操作有关。
如果你还看过OpenJDK的源码,数组相关的使用频率也非常高。
TIP
数组是最简单但最强大的数据结构之一,它用最小的成本提供了最快的访问速度,撑起了操作系统、应用程序和人工智能的半边天。
如果你以后做游戏开发、电商、算法相关等等,数组操作基本是你的日常。
二、数组的本质与内存模型
什么是数组?
数组是一个用来存放固定个数、相同类型数据的容器。 你可以把它想象成一个排好队的格子箱,每个格子都有编号(下标),我们可以通过编号快速找到里面的值。
int num = 10; // 基本类型,栈中存值
int[] arr = new int[3]; // 引用类型,栈中存地址,堆中才是真正存储数据
简单的小结下数组的特性:
特性 | 描述 | 示例 |
---|---|---|
定长 | 创建后长度不可变 | arr.length 是 final 的 |
同质 | 元素类型必须一致 | int[] 不能存 String |
连续内存 | 快速索引、缓存友好 | 适合频繁随机访问 |
这三点是数组的核心优势,同时也带来了限制,比如无法动态扩容、不支持异构数据等。
TIP
数组的下标是从0开始的。 比如一个长度为10的int类型数组arr,能够存10个整数,如果要访问第一个整数,那么应该写成arr[0]。 访问最后一个整数,应该写成arr[9],如果你写成arr[10]就越界了。
三、数组的声明与初始化方式
在Java中,声明数组其实并不复杂,但初学者容易被不同方式搞混。
1. 声明数组
语法**:**数据类型[] 数组名; 或 数据类型 数组名[];(不推荐后者)
int[] arr1; // 推荐写法
String arr2[]; // 合法但不推荐
2. 静态初始化(放哪些值已经明了)
当你已经知道这个数组存的具体数据,可以直接写成:
int[] scores = {90, 85, 70};
String[] names = {"懒惰蜗牛", "骑着蜗牛奔跑"};
这种方式最清晰、最直观,是数组的立等可取方式。
JDK里面也有这种方式:
3. 动态初始化(长度知道了具体值暂时不知道)
当你只知道数组大小,但暂时不知道值,可以这样:
int[] ages = new int[3];
ages[0] = 18;
ages[1] = 20;
注意就算你不赋值,Java也会给数组中的每一项赋默认值(整型为0,引用类型为null)。
4. 匿名数组(多用于方法传参)
printArray(new int[]{1, 2, 3});
// printArray是一个自定义接收int数组的方法
这种写法可以不先声明变量,直接临时创建一个数组。
四、数组的遍历与输出技巧
数组的最大用处是成批管理数据,那就必须会遍历。
for循环
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
可以精确控制索引,适合带下标处理的操作。
增强for-each
for (int val : arr) {
System.out.println(val);
}
不能访问下标,只适合读取所有元素。
Arrays工具类输出
Arrays是JDK提供的一个类,里面提供了很多关于操作数组的方法。
System.out.println(Arrays.toString(arr));
快速输出内容,开发调试时很好用
五、数组操作的6大技能包
Java数组操作其实就几种套路:查找、排序、拷贝、反转、拆分、组合,你掌握越多,代码越得心应手。
1. 元素查找:最常见的需求
// 线性查找(遍历找)
for (int i = 0; i < arr.length; i++) {
if (arr[i] == key) return i;
}
// 二分查找(必须排序)
Arrays.sort(arr);
int index = Arrays.binarySearch(arr, key);
2. 排序方式对比
// 冒泡排序
public static void bubbleSort(int[] arr) {
for (...) {...}
}
// 实际开发:Arrays.sort(效率更高)
Arrays.sort(arr);
TIP
常用排序算法有很多种:冒泡排序、选择排序、插入排序、快速排序、归并排序、堆排序等等
3. 拷贝数组的四种姿势
System.arraycopy(...); // 性能最佳
Arrays.copyOf(...); // 语法简洁
arr.clone(); // 快速复制(浅拷贝)
for (...) // 手动复制最灵活
4. 多维数组(如矩阵)
int[][] matrix = new int[3][4];
每行是一个一维数组,可以等长。
下面其实就是一个二维数组:
TIP
多维数组在某些特定的场景,像矩阵运算、图像处理、游戏开发、机器学习、数据分析等使用比较多 非特定领域还是使用得比较少,有一个多维的概念就行。
5. 数组截取 / 拼接
int[] part = Arrays.copyOfRange(arr, 1, 4); // 从下标1开始,直到下标4(不含)
6. 求和 / 平均值等
int sum = 0;
for (int val : arr) sum += val;
System.out.println("平均值: " + sum / arr.length);
六、使用数组时常见错误
1. 下标越界
ArrayIndexOutOfBoundsException应该是最常见的错误之一。
在C/C++里,数组越界访问是UB行为,可能会引发程序崩溃、数据损坏、安全漏洞等问题。
Java中,有运行时检查,如果访问越界马上就会抛出ArrayIndexOutOfBoundsException强制终止非法操作。
int[] arr = new int[3];
System.out.println(arr[3]); // 会抛出异常,最大索引是 2
2. 空指针异常
int[] a = null;
a[0] = 1; // 没有初始化内存空间就使用,就会抛 NullPointerException
3. 数组协变
数组协变是Java早期为了灵活性做出的设计选择,允许子类型数组向上转型,但牺牲了部分类型安全性。
泛型通过不变性+通配符实现了更安全的类型操作,避免运行时异常。
Object[] objs = new String[3];
objs[0] = 100; // 编译能通过,但是运行会失败 ArrayStoreException
TIP
这部分涉及到Java的多态、泛型等知识点,可以后面再来理解。 在实际开发中,优先选择泛型集合
4. 浅拷贝 vs 深拷贝
数组的浅拷贝和深拷贝是两种不同的复制方式,核心区别在于是否复制对象本身。
浅拷贝只复制数组的引用(地址),不复制数组中的对象。
深拷贝完全复制数组和所有元素对象。
通俗点理解就是:浅拷贝是复制钥匙(共享房间),深拷贝是重建房子(完全独立)。
Student[] a = {new Student("A")};
Student[] b = Arrays.copyOf(a, 1);
a[0].name = "B";
System.out.println(b[0].name); // B!(引用的是同一个对象)
TIP
这里也需要一点关于对象的前置知识,后续还会在实际开发中进行实践,进一步的理解
七、小练一下
下面有一个int类型的数组,是7个同学的分数。
输出一下最高分、最低分、平均分。
int[] scores = {85, 92, 76, 88, 95, 89, 68};
TIP
涉及排序、遍历、统计等数组的操作。 提示:可以充分利用Arrays这个工具类中提供的方法。
结语
数组是编程的第一个数据结构。
Java的数组虽然语法简单,但背后是内存管理、性能优化、数据建模的基础。
对数组的理解深度,决定了你对后续数据结构、集合、算法的理解深度。