avatar

Ryan's Blog

The first step is always the hardest.

  • 首页
  • 分类
  • 标签
  • 归档
  • 关于
  • 工具
Home 数组基础详解:概念、存储结构与常用操作
文章

数组基础详解:概念、存储结构与常用操作

Posted 2023-04-24 Updated 6 days ago
By Ryan Chen
30~39 min read

数组基础详解:概念、存储结构与常用操作

前言

数组是最基础、也最常用的数据结构之一。很多复杂结构,例如动态数组、栈、队列、堆、哈希表、矩阵、字符串处理和部分图像数据处理,本质上都离不开数组思想。

学习数组时,重点不是只会写 int[] arr = new int[10],而是要理解:

  • 数组为什么可以通过下标快速访问?
  • 数组为什么插入、删除不方便?
  • Java 中数组是对象还是裸内存?
  • 一维数组、多维数组、不规则数组有什么区别?
  • 什么时候用数组,什么时候用 ArrayList?

本文以 Java 为例,从概念、存储模型、常用操作、复杂度和实际使用场景几个角度系统梳理数组基础。

数组是什么?

数组是一组相同类型元素的有序集合。数组中的每个元素都有一个下标,通常从 0 开始。

例如:

int[] numbers = {10, 20, 30, 40};

对应关系是:

下标 元素
0 10
1 20
2 30
3 40

数组的核心特点:

  1. 元素类型一致:例如 int[] 只能存 int;
  2. 长度固定:Java 数组创建后长度不能改变;
  3. 支持下标访问:可以通过 arr[index] 访问元素;
  4. 访问效率高:按下标访问通常是 O(1);
  5. 插入删除成本高:中间插入或删除通常需要移动元素或复制数组。

数组的存储结构

从底层模型看,数组通常可以理解为一段连续的存储空间。

假设数组起始地址是 base,每个元素占用 size 个字节,那么第 i 个元素的位置可以抽象为:

元素地址 = base + i × size

这也是数组能够 O(1) 按下标访问的原因:只要知道起始位置、元素大小和下标,就可以直接定位元素。

不过在 Java 中,需要注意:

Java 不暴露 C/C++ 意义上的裸指针。数组是对象,数组变量保存的是对数组对象的引用。

也就是说,连续存储是理解数组的底层模型;在 Java 代码层面,我们通过引用和下标操作数组,而不是直接操作内存地址。

Java 中数组是对象

Java 语言规范中,数组是对象。数组可以动态创建,也可以赋值给 Object 类型变量。

int[] arr = {1, 2, 3};
Object obj = arr;

System.out.println(arr.length); // 3

数组变量保存的是引用。如果两个变量指向同一个数组对象,通过其中一个变量修改元素,另一个变量也能看到变化:

int[] a = {1, 2, 3};
int[] b = a;

b[0] = 100;

System.out.println(a[0]); // 100

这里不是“Java 指针操作”,而是两个引用变量指向同一个数组对象。

如何创建和初始化数组

一维数组

创建固定长度数组:

int[] arr = new int[5];
System.out.println(Arrays.toString(arr)); // [0, 0, 0, 0, 0]

创建并初始化:

int[] arr1 = new int[]{1, 2, 3, 4, 5};
int[] arr2 = {1, 2, 3, 4, 5};

Java 会为数组元素提供默认值:

类型 默认值
整数类型 0
浮点类型 0.0
boolean false
引用类型 null

二维数组

二维数组可以用来表示矩阵、表格等数据:

int[][] matrix = new int[2][3];

初始化二维数组:

int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

遍历二维数组:

for (int[] row : matrix) {
    System.out.println(Arrays.toString(row));
}

不规则数组

Java 的二维数组本质上是“数组的数组”,因此每一行长度可以不同。

int[][] jagged = new int[2][];
jagged[0] = new int[]{1, 2, 3};
jagged[1] = new int[]{4, 5};

for (int[] row : jagged) {
    System.out.println(Arrays.toString(row));
}

输出:

[1, 2, 3]
[4, 5]

这种结构也叫 jagged array,不规则数组。

数组的常用操作

1. 访问元素

int[] arr = {10, 20, 30};
System.out.println(arr[1]); // 20

按下标访问的时间复杂度是 O(1)。

2. 修改元素

arr[1] = 200;
System.out.println(Arrays.toString(arr)); // [10, 200, 30]

3. 遍历数组

普通 for 循环:

for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

增强 for 循环:

for (int value : arr) {
    System.out.println(value);
}

如果需要下标,使用普通 for;如果只是读取元素,增强 for 更简洁。

4. 插入元素

Java 数组长度固定,不能在原数组上直接扩容。插入元素通常需要创建新数组并复制元素。

在尾部追加:

int[] arr = {1, 2, 3};
int[] newArr = Arrays.copyOf(arr, arr.length + 1);
newArr[newArr.length - 1] = 4;

System.out.println(Arrays.toString(newArr)); // [1, 2, 3, 4]

在中间插入:

int[] arr = {1, 2, 4, 5};
int index = 2;
int value = 3;

int[] newArr = new int[arr.length + 1];
System.arraycopy(arr, 0, newArr, 0, index);
newArr[index] = value;
System.arraycopy(arr, index, newArr, index + 1, arr.length - index);

System.out.println(Arrays.toString(newArr)); // [1, 2, 3, 4, 5]

5. 删除元素

删除元素也需要创建新数组,或者在原数组中移动元素并维护有效长度。

int[] arr = {1, 2, 3, 4, 5};
int index = 1;

int[] newArr = new int[arr.length - 1];
System.arraycopy(arr, 0, newArr, 0, index);
System.arraycopy(arr, index + 1, newArr, index, arr.length - index - 1);

System.out.println(Arrays.toString(newArr)); // [1, 3, 4, 5]

删除中间元素的时间复杂度通常是 O(n),因为需要移动或复制后续元素。

6. 复制数组

常见复制方式:

int[] arr = {1, 2, 3};

int[] copy1 = Arrays.copyOf(arr, arr.length);
int[] copy2 = new int[arr.length];
System.arraycopy(arr, 0, copy2, 0, arr.length);

Arrays.copyOf() 更易读;System.arraycopy() 更底层,适合精确控制复制范围。

数组操作复杂度

操作 时间复杂度 说明
按下标访问 O(1) 直接通过下标定位
修改指定下标 O(1) 不改变数组长度
遍历 O(n) 需要访问全部元素
线性查找 O(n) 无序数组常用
二分查找 O(log n) 要求数组有序
尾部插入 O(n) 固定数组需要扩容复制
中间插入 O(n) 需要移动或复制元素
删除元素 O(n) 需要移动或复制元素
排序 取决于算法 可使用 Arrays.sort()

数组读快、改指定位置快,但插入删除不灵活。这也是为什么 Java 提供了 ArrayList 来封装动态扩容和元素移动。

线性查找

线性查找适用于无序数组。它从头到尾逐个比较元素。

public static int linearSearch(int[] arr, int target) {
    for (int i = 0; i < arr.length; i++) {
        if (arr[i] == target) {
            return i;
        }
    }
    return -1;
}

示例:

int[] arr = {3, 8, 2, 5, 1};
System.out.println(linearSearch(arr, 5)); // 3

时间复杂度:O(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;
}

示例:

int[] arr = {1, 2, 3, 4, 5, 6, 7};
System.out.println(binarySearch(arr, 5)); // 4

时间复杂度:O(log n)。

注意:

二分查找要求数组已经有序。如果数组无序,必须先排序,或者使用线性查找。

排序与 Arrays 工具类

Java 标准库提供了 java.util.Arrays,可以完成很多常用数组操作。

Arrays.toString

int[] arr = {3, 1, 2};
System.out.println(Arrays.toString(arr)); // [3, 1, 2]

Arrays.sort

Arrays.sort(arr);
System.out.println(Arrays.toString(arr)); // [1, 2, 3]

Arrays.binarySearch

int index = Arrays.binarySearch(arr, 2);
System.out.println(index);

使用 Arrays.binarySearch() 前,需要确保数组已经排序。

Arrays.copyOf

int[] copy = Arrays.copyOf(arr, arr.length + 1);

Arrays.equals

int[] a = {1, 2, 3};
int[] b = {1, 2, 3};

System.out.println(Arrays.equals(a, b)); // true

Arrays.fill

int[] arr = new int[5];
Arrays.fill(arr, 7);
System.out.println(Arrays.toString(arr)); // [7, 7, 7, 7, 7]

数组与 ArrayList 的区别

数组和 ArrayList 都可以按顺序保存元素,但它们的使用方式和适用场景不同。

维度 数组 ArrayList
长度 创建后固定 可动态扩容
元素类型 可存基本类型和对象引用 只能存对象,基本类型会装箱
下标访问 O(1) O(1)
插入删除 需要手动移动或复制 内部封装移动和扩容
API 丰富度 较少 丰富
内存和性能 更直接,基本类型数组更紧凑 更易用,但有对象和扩容成本
适用场景 固定长度、性能敏感、基础算法 业务开发、动态增删、集合操作

示例:

int[] arr = {1, 2, 3};

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);

如果数据长度固定、追求简单和性能,可以使用数组。如果数据规模会动态变化,通常使用 ArrayList 更方便。

关于 ArrayList 的底层结构、扩容和线程安全方案,可以参考:Java List 核心数据结构解析:ArrayList、LinkedList 与线程安全。

常见异常

NullPointerException

数组变量为 null 时访问元素或长度,会抛出空指针异常。

int[] arr = null;
// System.out.println(arr.length); // NullPointerException

ArrayIndexOutOfBoundsException

访问不存在的下标,会抛出数组越界异常。

int[] arr = {1, 2, 3};
// System.out.println(arr[3]); // ArrayIndexOutOfBoundsException

有效下标范围是:

0 <= index < arr.length

ArrayStoreException

对象数组在运行时存入不兼容类型时,可能抛出 ArrayStoreException。

Object[] arr = new String[2];
// arr[0] = 123; // ArrayStoreException

这和 Java 数组的协变特性有关。相比之下,泛型集合会在编译期提供更多类型检查。

数组的适用场景

数组适合以下场景:

  1. 固定长度数据:例如一周 7 天、月份、棋盘大小等;
  2. 算法题和基础数据结构实现:排序、查找、栈、队列、堆;
  3. 性能敏感场景:尤其是基本类型数组,避免装箱成本;
  4. 矩阵和表格数据:例如二维数组;
  5. 底层缓冲区:例如字节数组 byte[]。

不太适合:

  • 频繁动态增删;
  • 长度无法提前估计;
  • 需要丰富集合 API;
  • 需要线程安全集合语义。

这些场景通常更适合 ArrayList、LinkedList、Deque 或并发集合。

总结

数组是理解数据结构和算法的基础。它的优势是结构简单、按下标访问快、内存模型清晰;缺点是长度固定,插入和删除不灵活。

核心要点:

  • 数组是一组相同类型元素的有序集合;
  • 数组通常可以抽象理解为连续存储结构;
  • Java 中数组是对象,数组变量保存引用;
  • 按下标访问是 O(1),遍历和线性查找是 O(n);
  • 二分查找要求数组有序,复杂度是 O(log n);
  • 插入和删除通常需要移动或复制元素;
  • java.util.Arrays 提供了排序、查找、复制、填充、比较等工具方法;
  • 长度固定和性能敏感场景适合数组,动态增删场景更适合 ArrayList。

掌握数组后,再理解 ArrayList、栈、队列、堆、哈希表等结构会更容易。

参考资料

  • Java Language Specification:Chapter 10. Arrays
  • Java 官方文档:Arrays
  • Java 官方文档:ArrayList
Java
数组 数据结构 Java 算法 查找 排序 Arrays ArrayList
License:  CC BY 4.0
Share

Further Reading

Sep 12, 2024

RocketMQ 架构设计与应用最佳实践:高可用消息队列核心解析

本文基于 RocketMQ 4.x 经典架构,梳理 NameServer、Broker、Producer、Consumer、Remoting 与 Store 模块,结合消息轨迹、存储模型、FastFail、事务消息和高可用部署,总结高并发场景下的实践要点。

Aug 16, 2023

Java List 核心数据结构解析:ArrayList、LinkedList 与线程安全

系统梳理 Java List 接口、ArrayList 动态数组、LinkedList 双向链表、容量扩容、遍历与 fail-fast 机制,并对比 synchronizedList、CopyOnWriteArrayList、Vector 等线程安全方案的适用场景。

Apr 24, 2023

数组基础详解:概念、存储结构与常用操作

从数组的连续存储、下标访问、Java 数组对象、一维与多维数组、遍历、查找、插入、删除、复制、排序和 Arrays 工具类出发,系统梳理数据结构学习中的数组基础。

OLDER

MySQL Connector/J 负载均衡与故障转移实践

NEWER

Feign 客户端自动化配置详解:从 FeignAutoConfiguration 到实践

Recently Updated

  • Agent 架构设计原则:Router、Runtime 与 Business Script 的职责划分
  • RocketMQ 架构设计与应用最佳实践:高可用消息队列核心解析
  • Redis 核心概念、数据结构与高可用架构详解
  • B+树原理与 MySQL InnoDB 索引机制解析
  • MySQL AUTO_INCREMENT 插入 0 变成自增值的原因与解决方案

Trending Tags

RocketMQ Windows Feign Docker Zipkin SonarQube OkHttp HttpClient API 性能优化

Contents

©2026 Ryan's Blog. Some rights reserved. · 粤ICP备2022031588号