前言

数组是Java编程中最基本也是最重要的数据结构之一。它提供了一种存储同类型多个元素的有效方式,是学习Java集合框架的基础。无论是简单的数据存储,还是复杂的算法实现,数组都扮演着重要角色。本文将全面介绍Java数组的各个方面,从基础概念到高级应用,帮助您掌握数组的所有核心知识点。

数组基础概念

什么是数组

数组是一种容器对象,用于存储相同类型的多个值。在Java中,数组是引用类型,它具有以下特点:

  • 同质性:数组中的所有元素必须是相同类型
  • 连续性:数组元素在内存中是连续存储的
  • 索引访问:通过索引(下标)访问数组元素,索引从0开始
  • 固定长度:一旦创建,数组的长度就不能改变
graph TD
    A[数组的特点] --> B[同质性]
    A --> C[连续性]
    A --> D[索引访问]
    A --> E[固定长度]
    
    B --> B1[所有元素相同类型]
    C --> C1[内存连续存储]
    D --> D1[下标从0开始]
    E --> E1[长度不可变]
    
    F[数组优势] --> F1[访问效率高O1]
    F --> F2[内存使用效率高]
    F --> F3[缓存友好]
    
    G[数组劣势] --> G1[插入删除成本高]
    G --> G2[长度固定]
    G --> G3[空间浪费]

数组的内存结构

graph LR
    subgraph "堆内存"
        A[数组对象]
        B[0: element1]
        C[1: element2]
        D[2: element3]
        E[3: element4]
        F[length: 4]
        
        A --> B
        B --> C
        C --> D
        D --> E
        A --> F
    end
    
    subgraph "栈内存"
        G[数组引用变量]
    end
    
    G --> A
    
    subgraph "内存地址"
        H[0x1000]
        I[0x1004]
        J[0x1008]
        K[0x100C]
    end
    
    B --> H
    C --> I
    D --> J
    E --> K

一维数组

数组的声明

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

1
2
3
4
5
6
7
8
9
10
// 格式1:推荐使用
数据类型[] 数组名;

// 格式2:C风格
数据类型 数组名[];

// 示例
int[] numbers; // 声明整型数组
String[] names; // 声明字符串数组
double[] scores; // 声明双精度数组

数组的创建

声明数组后,需要使用new关键字创建数组对象:

1
2
3
4
5
6
7
8
9
10
11
// 静态初始化:声明时指定元素值
int[] arr1 = {1, 2, 3, 4, 5};
String[] arr2 = {"Java", "Python", "C++"};

// 动态初始化:先指定长度,后赋值
int[] arr3 = new int[5];
arr3[0] = 10;
arr3[1] = 20;

// 完整形式的静态初始化
int[] arr4 = new int[]{1, 2, 3, 4, 5};

数组的访问和遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ArrayDemo {
public static void main(String[] args) {
int[] numbers = {10, 20, 30, 40, 50};

// 访问数组元素
System.out.println("第一个元素: " + numbers[0]);
System.out.println("数组长度: " + numbers.length);

// 普通for循环遍历
System.out.println("普通for循环:");
for (int i = 0; i < numbers.length; i++) {
System.out.println("numbers[" + i + "] = " + numbers[i]);
}

// 增强for循环遍历
System.out.println("增强for循环:");
for (int num : numbers) {
System.out.println("元素值: " + num);
}
}
}

多维数组

二维数组

二维数组可以看作是”数组的数组”,常用于表示表格、矩阵等二维结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 二维数组的声明和创建
int[][] matrix = new int[3][4]; // 3行4列的二维数组

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

// 动态初始化
int[][] dynamicArr = new int[3][];
dynamicArr[0] = new int[2];
dynamicArr[1] = new int[3];
dynamicArr[2] = new int[4]; // 锯齿数组

多维数组遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MultiArrayDemo {
public static void main(String[] args) {
int[][] matrix = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};

// 嵌套循环遍历
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();
}

// 增强for循环遍历
for (int[] row : matrix) {
for (int element : row) {
System.out.print(element + "\t");
}
System.out.println();
}
}
}

数组的常见操作

数组的查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class ArraySearch {
// 线性查找
public static int linearSearch(int[] arr, int target) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) {
return i;
}
}
return -1; // 未找到
}

// 二分查找(要求数组有序)
public static int binarySearch(int[] arr, int target) {
int left = 0, 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;
}
}

数组的排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class ArraySort {
// 冒泡排序
public static void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
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;
}
}
}
}

// 选择排序
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;
}
}
// 交换
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}

数组的复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ArrayCopy {
public static void main(String[] args) {
int[] original = {1, 2, 3, 4, 5};

// 方法1:手动复制
int[] copy1 = new int[original.length];
for (int i = 0; i < original.length; i++) {
copy1[i] = original[i];
}

// 方法2:使用System.arraycopy()
int[] copy2 = new int[original.length];
System.arraycopy(original, 0, copy2, 0, original.length);

// 方法3:使用Arrays.copyOf()
int[] copy3 = Arrays.copyOf(original, original.length);

// 方法4:使用clone()方法
int[] copy4 = original.clone();
}
}

Arrays工具类

Java提供了java.util.Arrays工具类,包含了许多操作数组的静态方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.Arrays;

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

// 排序
Arrays.sort(arr);
System.out.println("排序后: " + Arrays.toString(arr));

// 二分查找
int index = Arrays.binarySearch(arr, 5);
System.out.println("元素5的位置: " + index);

// 数组转字符串
System.out.println("数组内容: " + Arrays.toString(arr));

// 数组比较
int[] arr2 = {1, 2, 3, 5, 8, 9};
boolean equal = Arrays.equals(arr, arr2);
System.out.println("数组是否相等: " + equal);

// 填充数组
int[] fillArr = new int[5];
Arrays.fill(fillArr, 100);
System.out.println("填充数组: " + Arrays.toString(fillArr));

// 部分填充
Arrays.fill(fillArr, 1, 4, 200);
System.out.println("部分填充: " + Arrays.toString(fillArr));
}
}
graph TD
    A[Arrays工具类] --> B[排序操作]
    A --> C[查找操作]
    A --> D[比较操作]
    A --> E[复制操作]
    A --> F[填充操作]
    A --> G[转换操作]
    
    B --> B1[sort - 排序]
    B --> B2[parallelSort - 并行排序]
    
    C --> C1[binarySearch - 二分查找]
    
    D --> D1[equals - 内容比较]
    D --> D2[deepEquals - 深度比较]
    
    E --> E1[copyOf - 复制指定长度]
    E --> E2[copyOfRange - 复制指定范围]
    
    F --> F1[fill - 全部填充]
    F --> F2[fill range - 部分填充]
    
    G --> G1[toString - 转字符串]
    G --> G2[deepToString - 深度转换]

数组的高级应用

动态数组实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class DynamicArray {
private int[] array;
private int size;
private int capacity;

public DynamicArray() {
this.capacity = 10;
this.array = new int[capacity];
this.size = 0;
}

// 添加元素
public void add(int element) {
if (size >= capacity) {
resize();
}
array[size++] = element;
}

// 扩容
private void resize() {
capacity *= 2;
int[] newArray = new int[capacity];
System.arraycopy(array, 0, newArray, 0, size);
array = newArray;
}

// 获取元素
public int get(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("索引越界");
}
return array[index];
}

// 删除元素
public void remove(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("索引越界");
}

for (int i = index; i < size - 1; i++) {
array[i] = array[i + 1];
}
size--;
}

public int size() {
return size;
}
}

数组算法应用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class ArrayAlgorithms {
// 找出数组中的最大值和最小值
public static int[] findMinMax(int[] arr) {
if (arr == null || arr.length == 0) {
return null;
}

int min = arr[0], max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] < min) min = arr[i];
if (arr[i] > max) max = arr[i];
}
return new int[]{min, max};
}

// 数组去重
public static int[] removeDuplicates(int[] arr) {
Arrays.sort(arr);
int[] temp = new int[arr.length];
int j = 0;

for (int i = 0; i < arr.length - 1; i++) {
if (arr[i] != arr[i + 1]) {
temp[j++] = arr[i];
}
}
temp[j++] = arr[arr.length - 1];

return Arrays.copyOf(temp, j);
}

// 数组旋转
public static void rotateArray(int[] arr, int k) {
int n = arr.length;
k = k % n;

reverse(arr, 0, n - 1);
reverse(arr, 0, k - 1);
reverse(arr, k, n - 1);
}

private static void reverse(int[] arr, int start, int end) {
while (start < end) {
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
start++;
end--;
}
}
}

数组的性能考虑

时间复杂度分析

  • 访问元素:O(1) - 直接通过索引访问
  • 查找元素:O(n) - 线性查找,O(log n) - 二分查找(有序)
  • 插入元素:O(n) - 需要移动后续元素
  • 删除元素:O(n) - 需要移动后续元素

空间复杂度

数组的空间复杂度为O(n),其中n是数组的长度。

性能优化建议

  1. 预估数组大小:避免频繁扩容
  2. 使用合适的数据类型:节省内存空间
  3. 考虑使用集合类:当需要频繁插入删除时
  4. 缓存友好的访问模式:按顺序访问数组元素

常见错误和注意事项

数组越界异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ArrayBounds {
public static void main(String[] args) {
int[] arr = new int[5];

// 错误:数组越界
try {
arr[5] = 10; // 索引5超出数组范围[0-4]
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组越界: " + e.getMessage());
}

// 正确:检查边界
int index = 5;
if (index >= 0 && index < arr.length) {
arr[index] = 10;
} else {
System.out.println("索引越界,数组长度为: " + arr.length);
}
}
}

空指针异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class NullPointer {
public static void main(String[] args) {
int[] arr = null;

// 错误:空指针异常
try {
System.out.println(arr.length);
} catch (NullPointerException e) {
System.out.println("空指针异常");
}

// 正确:空值检查
if (arr != null) {
System.out.println("数组长度: " + arr.length);
} else {
System.out.println("数组为空");
}
}
}

总结

数组是Java编程的基础数据结构,掌握数组的使用对于Java开发至关重要。本文全面介绍了Java数组的各个方面:

  1. 基础概念:理解数组的特点和内存结构
  2. 声明和创建:掌握多种数组初始化方式
  3. 访问和遍历:学会使用不同的遍历方法
  4. 多维数组:理解和应用二维数组及更高维度
  5. 常见操作:掌握查找、排序、复制等基本操作
  6. Arrays工具类:熟练使用Java提供的数组工具方法
  7. 高级应用:了解动态数组实现和算法应用
  8. 性能考虑:理解数组操作的时间空间复杂度

数组虽然简单,但在实际开发中应用广泛。理解数组的特点和限制,能够帮助我们在合适的场景选择合适的数据结构,为后续学习集合框架打下坚实基础。

参考资料

  1. Oracle Java Documentation - Arrays
  2. Effective Java by Joshua Bloch
  3. Java核心技术卷I - 基础知识
  4. 算法导论 - 数组和基本数据结构