广告

Java数组到底怎么用?算法中的数组操作全解析

Java数组基础与声明

数组声明与初始化

在<Java中,数组是一种引用数据类型,用于存放同一类型的元素。要声明一个数组,需要指明元素类型并带上方括号,随后通过 new 关键字分配长度。常见的写法包括:

int[] nums = new int[5];

对于局部变量,未显式初始化的基本类型数组元素将被设为默认初始值,如 0false 等,引用类型为 null

boolean[] flags = new boolean[3]; // 默认 false
String[] names = new String[2]; // 默认 null

数组的索引从 0 开始,访问最大索引为 length-1,若越界将抛出 ArrayIndexOutOfBoundsException,这是在 算法与数据结构中需要特别注意的点。

int first = nums[0];

数组类型与默认值

数组分为两大类:基本数据类型数组引用类型数组基本类型数组的元素会直接存储数值,而 引用类型数组的元素存储对象的引用。理解这一点对于避免内存泄漏和空指针异常至关重要。

int[] primitiveArr = {1, 2, 3};
Object[] objArr = new Object[2];
objArr[0] = new Object();

在实际编程中,固定长度的数组常用于底层算法、哈希表的实现以及性能敏感的场景,而需要可变容量的场景通常会结合 ArrayList 等集合类。

算法中的数组操作全解析

遍历、拷贝与替换

在算法实现中,遍历是最基础的数组操作之一。常见的遍历方式包括传统的 下标遍历 与增强型 for 循环。对数组执行拷贝、替换等操作时,常用的工具类方法有 Arrays.copyOfSystem.arraycopy,它们在性能与简洁性之间提供了良好的权衡。

// 下标遍历
for (int i = 0; i < arr.length; i++) {
    // 处理 arr[i]
}

// 增强型 for 循环
for (int v : arr) {
    // 处理 v
}

数组拷贝的常见用法:

import java.util.Arrays;

int[] copy1 = Arrays.copyOf(arr, arr.length); // 使用 Arrays.copyOf 拷贝
int[] dest = new int[arr.length];
System.arraycopy(arr, 0, dest, 0, arr.length); // 使用 System.arraycopy 拷贝

排序与查找的数组实现

排序是将数据转化为有序结构的关键步骤,常用的 API 为 Arrays.sort,时间复杂度通常为 O(n log n)。查找则有两种常用方式:线性查找与二分查找,二分查找的前提是数组有序,复杂度为 O(log n)

int[] a = {5, 2, 9, 1};
Arrays.sort(a); // [1, 2, 5, 9]
int idx = Arrays.binarySearch(a, 5); // 2

常用模式:滑动窗口、前缀和等

二分查找在数组上的实现

手写实现的二分查找同样高效,核心在于通过中间下标不断缩小查找区间。以下实现给出一个通用版本,时间复杂度为 O(log n),适用于有序数组。

public static int binarySearch(int[] a, int target) {
    int left = 0;
    int right = a.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (a[mid] == target) return mid;
        if (a[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    return -1;
}

滑动窗口与前缀和技巧

滑动窗口通过维护一个动态区间来解决子数组相关问题,常见于长度约束、和、最大值等场景。下列模式演示了一个简单的“保持和不超过 k”的滑动窗口:

int left = 0, sum = 0;
for (int right = 0; right < arr.length; right++) {
    sum += arr[right];
    while (sum > k) {
        sum -= arr[left++];
    }
    // 窗口大小为 right - left + 1
}

前缀和通过构造一个额外的数组 pre,它表示前缀和。利用前缀和可以在 O(1) 时间内计算任意子区间的和:pre[j] - pre[i]

int[] pre = new int[arr.length + 1];
for (int i = 0; i < arr.length; i++) pre[i + 1] = pre[i] + arr[i];
int sumSub = pre[j] - pre[i];

性能与注意事项

内存布局与缓存友好

在 Java 中,数组是连续内存布局的对象,因此在处理大规模数据时,缓存命中率内存带宽 会显著影响性能。对算法来说,尽量减少随机访问、连续访问的模式可以获得更好的缓存局部性,提升吞吐量。

// 访问模式尽量线性扫描,以提高缓存命中率
for (int i = 0; i < arr.length; i++) {
    process(arr[i]);
}

边界检查、空指针与异常处理

在进行数组操作时,必须始终关注边界条件与空指针风险。越界访问空指针和未初始化的数组都可能引发运行时异常,需要在循环、分支和方法参数处进行必要的校验。

if (arr != null && arr.length > 0) {
    for (int i = 0; i < arr.length; i++) {
        // 安全处理
    }
}
广告

后端开发标签