深入理解数组
# 深入理解数组
# 引言
数组是计算机科学中最基础、最常用的数据结构之一。在 Java 中,数组提供了一种有序、连续的存储方式,它不仅在内存中占据一块连续的空间,还支
持通过索引快速访问和操作元素。本文将深入探讨 Java 中数组的基本概念、常见用法和后端开发中的实际应用。
最近写代码时发现自己对于数组的知识掌握得不是很好,所以特地梳理了这么一篇文章,希望对您有所帮助。
# 数组的基本概念
# 什么是数组?
数组是一种线性数据结构,由相同类型的元素构成,这些元素在内存中按照一定的顺序依次存储。每个元素可以通过数组的下标(索引)来访问,而下标从零开始。
# 数组的特点
- **相同类型:**数组中的元素必须是相同的数据类型,可以是基本数据类型或对象。
- **连续存储:**数组的元素在内存中占据一块连续的空间,这使得通过索引快速访问成为可能。
- **固定长度:**数组的长度是固定的,一旦创建就无法改变。
# 数组的声明和初始化⭐️
// 声明数组
int[] intArray;
// 初始化数组 -- 初始化长度为5的整型数组
intArray = new int[5];
// 同时声明和初始化
int[] intArray = new int[]{1, 2, 3, 4, 5};
2
3
4
5
6
7
8
# 数组的常见操作
# 1、访问和修改元素
// 获取下标为2的元素
int value = intArray[2];
// 修改下标为2的元素的值为10
intArray[2] = 10;
2
3
4
5
# 2、遍历数组
// 使用普通for循环
for (int i = 0; i < intArray.length; i++) {
System.out.println(intArray[i]);
}
// 使用增强for循环
for (int num : intArray) {
System.out.println(num);
}
2
3
4
5
6
7
8
9
# 3、多维数组的创建
// 3行4列的二维数组
int[][] matrix = new int[3][4];
// 访问和修改元素
matrix[1][2] = 5;
2
3
4
5
# 4、查找最大/最小值
int[] numbers = {1, 3, 5, 2, 4};
int max = numbers[0];
int min = numbers[0];
for (int i = 1; i < numbers.length; i++) {
if (numbers[i] > max) {
max = numbers[i]; // 更新最大值
}
if (numbers[i] < min) {
min = numbers[i]; // 更新最小值
}
}
System.out.println("最大值:" + max);
System.out.println("最小值:" + min);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 5、数组过滤/筛选
int[] numbers = {1, 2, 3, 4, 5};
List<Integer> evenNumbers = new ArrayList<>();
for (int number : numbers) {
if (number % 2 == 0) {
evenNumbers.add(number); // 将偶数筛选出来并添加到新的列表中
}
}
System.out.println(evenNumbers);
2
3
4
5
6
7
8
9
10
# 6、数组转换
int[] numbers = {1, 2, 3, 4, 5};
String[] strings = new String[numbers.length];
for (int i = 0; i < numbers.length; i++) {
strings[i] = String.valueOf(numbers[i]); // 将整数数组转换为字符串数组
}
System.out.println(Arrays.toString(strings));
2
3
4
5
6
7
8
# 7、数组的工具类
Java 提供了 Arrays
类,其中包含许多有用的数组操作方法:
// 复制数组 -- copyOf
int[] newArray = Arrays.copyOf(intArray, 10);
// 对数组进行排序 -- sort
Arrays.sort(intArray);
// 二分查找 -- binarySearch
int index = Arrays.binarySearch(intArray, 3);
2
3
4
5
6
7
8
# 8、数组合并
int[] numbers1 = {1, 2, 3};
int[] numbers2 = {4, 5, 6};
int[] mergedNumbers = new int[numbers1.length + numbers2.length];
// 复制numbers1到mergedNumbers
System.arraycopy(numbers1, 0, mergedNumbers, 0, numbers1.length);
// 复制numbers2到mergedNumbers
System.arraycopy(numbers2, 0, mergedNumbers, numbers1.length, numbers2.length);
System.out.println(Arrays.toString(mergedNumbers));
2
3
4
5
6
7
8
9
10
11
# 应用场景
# 数据库查询结果
将数据库查询结果存储在数组中进行处理。
# 文件处理
存储文件信息或处理文件数据。
# 缓存
使用数组作为缓存结构,例如缓存最近的请求记录等。
# 排序与搜索
在需要有序数据时,数组的排序和搜索功能很常见。
# 数组的常见问题与技巧⭐️
# 越界问题
**避免数组越界:**在访问数组元素时,确保索引值在数组的有效范围内,以防止数组越界异常。访问不存在的下标会导致 ArrayIndexOutOfBoundsException
异常。
# 动态扩展
**使用动态数组或集合:**数组长度通常是固定的,如果需要动态扩展,考虑使用动态数组(ArrayList
)等集合类。
# 空指针异常处理
**注意空指针异常:**在操作数组时,特别是在从其他方法或函数返回数组时,要注意空指针异常的处理。
以下是一个示例代码,展示了如何处理可能引发空指针异常的情况:
public class ArrayExample {
public static void main(String[] args) {
int[] numbers = getNumbers();
// 判断是否为空
if (numbers != null) {
// 数组不为空,可以进行操作
for (int number : numbers) {
System.out.println(number);
}
} else {
System.out.println("数组为空");
}
}
public static int[] getNumbers() {
// 模拟从其他方法或函数返回数组的情况
int[] numbers = null; // 假设获取数组的过程出现问题,返回null
return numbers;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 数组拷贝
使用 System.arraycopy
或 Arrays.copyOf
进行数组的复制。
# 数组与集合的转换
使用 Arrays.asList
将数组转换为 List,或使用 List.toArray
将 List 转换为数组。
# 性能问题
以下是一些与数组性能相关的问题:
- 访问时间:数组的访问时间是常数时间(O(1)),因为可以通过索引直接访问数组中的元素。这是数组的一个重要优势,因为它允许快速访问和修改元素。
- 内存占用:数组在内存中占用连续的一块空间。这种紧凑的存储方式可以提高访问效率,但也意味着数组的大小是固定的。如果需要动态调整数组的大小,可能需要重新分配内存并复制元素,这可能会导致性能损失。
- 插入和删除元素:在数组中插入或删除元素通常涉及到元素的移动操作。如果插入或删除的位置靠近数组的开头,需要移动的元素较多,可能会导致较大的性能开销。在这种情况下,使用链表等数据结构可能更有效。(不适合频繁插入删除的场景)
- 遍历和搜索:遍历数组的性能是线性时间(O(n)),其中 n 是数组的大小。这意味着随着数组的增长,遍历所需的时间也线性增加。对于大型数组,遍历和搜索可能会显著影响性能。
- 多维数组:多维数组的性能可以受到访问模式的影响。例如,对于二维数组,按行访问通常比按列访问更高效,因为二维数组的内存布局是按行存储的。了解数组的内存布局和访问模式可以帮助优化性能。
为了提高数组的性能,可以考虑以下几点:
- 避免不必要的数组复制和内存重新分配。 -- 尽量在初始化时明确数组大小
- 尽量使用原始数据类型(如int、double)而不是包装类(如Integer、Double),以减少对象创建和垃圾回收的开销。
- 考虑使用其他数据结构,如链表、树或哈希表,根据具体需求选择更适合的数据结构。 -- 根据实际场景需求
- 对于大型数组,可以采用分块、分段或分布式存储等技术来减少单个数组的大小,提高访问效率。
- 如果需要频繁插入或删除元素,可以考虑使用其他数据结构,如链表或动态数组,以避免元素移动的开销。
- 对于多维数组,了解内存布局并根据访问模式进行优化,可以提高访问性能。
- 在遍历或搜索数组时,注意使用合适的算法和数据结构来优化性能,如二分查找、哈希表等。
# 多维数组的内存布局
笔者遇到的不多,简单了解一下
多维数组在内存中的布局方式取决于编程语言和具体实现。在大多数编程语言中,多维数组实际上是通过一维数组来表示的,使用某种索引计算方式来映射多维索引到一维数组的索引。
常见的多维数组内存布局方式有两种:行主序(Row-major order)和列主序(Column-major order)。这两种布局方式在访问多维数组的时候会有所不同。
# 行主序布局(Row-major order)
在行主序布局中,多维数组的内存存储按照行的顺序进行排列。也就是说,相邻的元素在内存中是连续存储的,而行之间是按照顺序排列的。这种布局方式在大多数编程语言中被广泛采用,包括 C
、C++
和 Java
等。
下面是一个二维数组的行主序内存布局示例:
// 二维数组
int[][] matrix1 = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
2
3
4
5
6
内存布局示意图:
1 2 3 4 5 6 7 8 9
通过行主序布局,数组中的元素在内存中是按照从左到右、从上到下的顺序排列的。
# 列主序布局(Column-major order)
在列主序布局中,多维数组的内存存储按照列的顺序进行排列。也就是说,相邻的元素在内存中是连续存储的,而列之间是按照顺序排列的。这种布局方式在一些科学计算领域中常见,例如 Fortran
语言。
下面是一个二维数组的列主序内存布局示例:
// 二维数组
int[][] matrix2 = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
2
3
4
5
6
内存布局示意图:
1 4 7 2 5 8 3 6 9
通过列主序布局,数组中的元素在内存中是按照从上到下、从左到右的顺序排列的。
# 总结
- 具体的内存布局方式会受到编译器、操作系统和硬件架构的影响。
- 在大多数情况下,行主序布局是默认的,并且在常见的编程语言中使用得更广泛。