📦 Java :数组 (Array)

一、 什么是数组?

数组是一种容器,可以用来存储同种数据类型的多个值。

  • 特点
    1. 一旦初始化,长度固定
    2. 存储的数据类型必须一致。

二、 数组的定义与初始化

1. 静态初始化(创建时已知具体内容)

  • 完整格式:数据类型[] 数组名 = new 数据类型[]{元素1, 元素2, ...};
  • 简化格式:数据类型[] 数组名 = {元素1, 元素2, ...};
  • 范例:int[] ages = {18, 20, 22};

2. 动态初始化(只指定长度,稍后赋值)

  • 格式:数据类型[] 数组名 = new 数据类型[数组长度];
  • 默认值:动态初始化后,数组元素会有初始值(如 int 是 0,double 是 0.0,boolean 是 false,引用类型是 null)。

三、 数组的访问与遍历

数组的灵魂在于 索引 (Index)

  • 访问:数组名[索引](索引从 0 开始,最大索引是 length - 1)。

  • 长度获取:数组名.length。

  • 遍历(重点):利用 for 循环依次访问每一个索引。

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

四、 核心:数组的内存模型

Java 的内存主要划分为

  1. 栈 (Stack):存储局部变量(如方法里的变量名 int[] arr)。代码执行完后立即释放。
  2. 堆 (Heap):存储对象或数组实体。通过 new 关键字创建出来的东西都在这里。
  • 引用传递:变量 arr 存储的不是具体的数值,而是数组在堆内存中的地址值
  • 多个变量指向同一个数组:如果你写 int[] arr2 = arr1;,本质上是把地址给了 arr2。修改 arr2 的内容,arr1 也会变。

五、 两个常见小“坑”

  1. 索引越界异常 (ArrayIndexOutOfBoundsException):访问了不存在的索引(如长度为 3,却访问了索引 3)。
  2. 空指针异常 (NullPointerException):数组变量被赋值为 null 后,再去访问其长度或内容。

💡

  • 数组:查询快(通过索引直接定位),增删慢(需要移动大量元素)。
  • 链表:查询慢(需要从头遍历),增删快(只需改变指针指向)。

🧠 Java 内存分配与数组的深度联系

在 Java 中,内存主要分为 栈(Stack)堆(Heap)。数组是体现这两者交互最典型的例子。

1. 内存区域分工

栈内存(Stack):
存储内容:方法中的局部变量(如 int[] arr 中的 arr)。
特点:执行速度快,方法执行结束,内存立即释放。
堆内存(Heap):
存储内容:所有通过 new 关键字创建出来的实体(对象、数组)。
特点:每个实体都有一个唯一的十六进制地址值。

2. 数组在内存中的生命周期(以 int[] arr = new int[3]; 为例)

声明变量(栈):在栈内存中开辟一块空间,名字叫 arr。此时它只是一个“空壳”。
创建实体(堆):看到 new 关键字,立即在堆内存开辟连续的空间。
根据长度 3 划分空间。
进行默认初始化(如 int 默认值是 0)。
生成该实体的地址值(例如 0x0011)。
引用赋值:将堆中的地址值 0x0011 赋值给栈中的变量 arr。
核心点:arr 变量里存的不是数字,而是地址。所以我们称数组为“引用数据类型”。

3. 数组常见操作的底层逻辑

A. 访问元素(arr[0])
过程:程序先去栈内存找 arr 获取地址值 0x0011 \rightarrow 顺着地址找到堆内存中的实体 \rightarrow 根据索引 0 偏移找到具体位置。
B. 多个变量指向同一个数组
代码:int[] arr2 = arr1;
内存变化:并没有在堆里创建新数组,而是直接把 arr1 存的地址值复制给了 arr2。
结果:arr1 和 arr2 指向同一个房间。修改 arr2[0],arr1[0] 也会跟着变。
C. 数组的“空指针”
代码:arr = null;
内存变化:栈内存中的变量 arr 删除了存储的地址值,不再指向堆内存。
后果:此时访问 arr.length 会报错 NullPointerException。堆内存中失去引用的实体会等待 GC(垃圾回收机制) 处理。

📋 Java 二维数组的存储机制与内存管理

在 Java 中,二维数组并不是一个简单的连续矩阵,而是 “数组的数组”

  • 外层数组:存储的是每一个子数组(行)的引用地址
  • 内层数组:存储的是真正的数据元素

二、 动态初始化与内存图解

当你使用 int[][] arr = new int[2][3]; 时,内存中发生了以下变化:

  1. 栈内存 (Stack)
  • 存储变量名 arr,它持有指向堆内存中“行索引数组”的地址。
  1. 堆内存 (Heap)
  • 第一层(索引数组):开辟一个长度为 2 的空间,专门用来存放两个地址。
  • 第二层(数据数组):开辟两个长度为 3 的一维数组,并将它们的起始地址分别存入第一层的空间中。

三、 常见访问操作与含义

结合内存逻辑,以下表达方式的含义完全不同:

  • arr:指向整个二维数组对象的地址。
  • arr[i]:指向第 i 个一维数组对象的地址。
  • arr[i][j]:具体的元素值。
  • arr.length:获取二维数组的行数
  • arr[i].length:获取第 i 行一维数组的列数

🚀 进阶拓展

1. 不规则二维数组 (Jagged Arrays)

由于 Java 二维数组是“数组的数组”,各行的长度可以不相等。这在处理稀疏数据或特定算法(如杨辉三角)时能节省空间。

int[][] jagged = new int[2][]; // 只指定行数
jagged[0] = new int[3];        // 第一行3列
jagged[1] = new int[100];      // 第二行100列

2. 内存中的默认值

二维数组在动态初始化后,元素会自动赋予默认值:

  • int[][] -> 0
  • double[][] -> 0.0
  • boolean[][] -> false
  • 引用类型(如 String[][]) -> null

3. 数组的拷贝问题

  • 浅拷贝:如果你写 int[][] arr2 = arr1;,你只是复制了地址。修改 arr2[0][0],arr1[0][0] 也会变。
  • 深拷贝:在处理竞赛题目时,如果需要保留原地图/矩阵,必须使用循环嵌套或 System.arraycopy() 对每一行进行单独拷贝。

🛠️ 竞赛实战技巧

  • 空间换时间:二维数组常用于存储图的邻接矩阵(Adjacency Matrix)。对于点数小于 5000 的题目,二维数组是首选。

  • 坐标偏移量:在做搜索题(BFS/DFS)时,习惯定义两个一维数组来表示移动方向:

    int[] dx = {-1, 1, 0, 0}; // 上、下
    int[] dy = {0, 0, -1, 1}; // 左、右
    
    
  • 内存溢出风险:Java 默认堆内存有限,如果定义 int[10000][10000] 会直接导致 OutOfMemoryError。

今天的学习就暂时告一段落。各位看官老爷,期待下次与您相见!

如果觉得内容对您有帮助,还请留下一个免费的点赞吧。

技术无止境,我们一起慢慢肝!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐