一、数组的基本特性
1.1 固定长度
int[] arr1 = new int[5];
String[] arr2 = {"A", "B"};
- 长度不可变:数组一旦创建,容量无法直接修改
- 内存连续性:元素在内存中连续存储,通过索引直接访问(时间复杂度 O(1))
1.2 元素可变性
arr1[0] = 10;
- 元素可写:允许通过索引修改元素值
- 长度只读:
length
是 final 属性,编译时禁止赋值操作
二、数组作为对象类型数据的证据
2.1 对象特性验证
int[] arr = new int[3];
System.out.println(arr instanceof Object);
2.2 类名特征
数组类型 | 类名格式 | 示例输出 |
---|
int[] | [I | class [I |
String[] | [Ljava.lang.String; | class [Ljava.lang.String; |
2.3 内存分配机制
int[] arr = new int[5];
- 堆存储:数组作为对象存储在堆内存
- 引用变量:数组变量本质是引用(指针),指向堆中数据
三、动态调整数组大小的方案
3.1 使用集合类
List<Integer> list = new ArrayList<>();
list.add(1);
- 动态扩容:
ArrayList
内部通过 Arrays.copyOf
实现容量翻倍
3.2 手动复制数组
int[] original = {1, 2, 3};
int[] resized = Arrays.copyOf(original, 5);
- 实现原理:创建新数组 + 数据复制
- 性能损耗:时间复杂度 O(n),频繁操作效率低
四、数组长度不可变的设计原因
4.1 内存效率
- 连续内存:数组要求元素在内存中连续存储
- 扩容代价:改变长度需重新分配内存并复制数据
4.2 访问性能
- 直接寻址:通过公式
基地址 + 索引 * 元素大小
快速定位元素 - 稳定性:固定长度确保内存布局不变,避免动态调整带来的性能抖动
4.3 语言设计哲学
- 简单性:保持基本数据结构的简洁性
- 明确性:开发者需提前明确容量需求,避免误用
五、数组底层实现与源码探究
5.1 JVM 字节码分析
public class Test {
public static void main(String[] args) {
int[] arr = new int[5];
int len = arr.length;
}
}
反编译字节码(javap -c Test.class
):
0: iconst_5
1: newarray int // JVM 指令创建数组
3: astore_1
4: aload_1
5: arraylength // JVM 指令获取长度
6: istore_2
5.2 反射分析
int[] arr = new int[3];
Class<?> clazz = arr.getClass();
System.out.println(clazz.getName());
System.out.println(clazz.getSuperclass());
5.3 JVM 规范定义
- 类名规则:通过
[
+ 元素类型描述符定义(参见 JVMS 4.3.2) - 操作指令:
newarray
/anewarray
创建数组,arraylength
获取长度
六、总结与对比
6.1 数组 vs ArrayList
特性 | 数组 (int[] ) | ArrayList |
---|
长度可变性 | ❌ 固定长度 | ✅ 动态扩容 |
内存管理 | 手动分配 | 自动管理 |
访问速度 | O(1)(直接内存访问) | O(1)(通过数组索引) |
线程安全性 | 非线程安全 | 非线程安全(需外部同步) |
适用场景 | 固定长度、高频访问 | 动态增删、复杂操作 |
6.2 开发建议
- 优先选择集合类:在 90% 的场景下,
ArrayList
是更合适的选择 - 明确使用数组的场景:
- 方法内部临时存储已知长度的数据
- 性能敏感场景(如图像处理、科学计算)
- 避免手动扩容:数组扩容应使用
Arrays.copyOf
或直接改用集合类