一. 什么是数组
数组可以简单的看成一个相同元素的集合, 在内存中是一段连续的存储空间.
一个数组简易模型
从上述这个简易模型中, 我们可以得到以下信息:
- 数组中存放的元素都是相同类型的
- 数组的内存空间是相连的
- 每个空间都有一个编号(即常说的数组下标), 从 0 开始
二. 数组的创建
T[ ] 数组名 = new T[N]
T : 表示数组类型
N : 表示数组长度
例如: int [ ] array = new int [ 10 ];
意为创建一个数组名为 array 且 数组元素为 10个 的整形数组
三. 数组的初始化
1.动态初始化 : 在创建数组时, 直接指定数组中的元素个数
int[] array1 = new int[10];
2.静态初始化 : 在创建的时候不指定数组元素个数, 而是直接将具体内容进行指定.
int[] array2 = new int[] {1,2,3,4};
3.当我们不知道初始化为何值时, 应当初始化赋值为 null, 而不能是其他基础类型
并且, 在初始化为 null 后, 不能被引用, 也就是一个不指向对象的引用, 否则会空指针异常
静态初始化有几个特点
- 虽然没有指定具体长度, 但是数组的长度在编译器编译时会根据 { } 括号中的元素进行确定
- { } 括号中的数据类型必须与 [ ] 前的类型一致
- 静态初始化可以省略创建时的 new T [ ]
int[] array3 = {1,2,3,4};
对于数组的初始化, 有以下几个特点 :
1.如果数组中存储的元素类型为基本类型, 未初始化则数组中的数据值均为基本类型对应的默认值
例如为 int 类型
int[] array1 = new int[10]; System.out.println(Arrays.toString(array1));
例如为 double 类型
double[] array1 = new double[10]; System.out.println(Arrays.toString(array1));
2.如果数组中存储的数据类型为引用类型, 则 默认值为 null
String[] array1 = new String[10]; System.out.println(Arrays.toString(array1));
3.变量未初始化时, 不允许被使用
四. 数组是引用类型
在 Java 中, 数组是为引用类型的, 并非由前面 指定的某个类型它则为该类型. 那么, 该如何去理解数组是引用类型这句话呢? 需要从数组在内存中的存储说起.
了解存储前, 需要先了解 JVM (Java Virtual Machine) 是什么? 简单来说 JVM 就是 Java虚拟机
如果对于内存中的数据不加以区分, 内存管理起来就会非常麻烦, 因此在 JVM 中, 将其大致划分为图中几个数据区
方法区 : 用于存储已被虚拟机加载的类信息、常量、静态变量, 即编译器编译后的代码等数据
堆 : JVM所管理的最大内存区域. 使用new 创建的对象都是在堆上保存
虚拟机栈 : 与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了
本地方法栈 : 本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是Native方法的局部变量 (源码中的一些 Native 方法)
程序计数器 : 只是一个很小的空间, 保存下一条执行的指令的地址
大致了解了 JVM 那么, 数组是如何在 JVM 中存储的呢?
int[] array = new int[] {1,2,3,4}; int a = 10; double b = 9.9;
根据上面 JVM 运行时数据区, 数组 array 是 main 方法中的局部变量, a, b 均为局部变量, 因此在 存储在 JVM 的虚拟机栈中(以下简称栈), 而数组的内容属于 new 创建的对象, 位于堆上, 可以画出如下简图 :
可以看出, array数组在栈上存储的是 数组空间的地址
对数组是引用类型有一定了解以后, 看看下面这段代码输出什么?
public static void main(String[] args) { int[] array = new int[] {1,2,3,4}; fun1(array); System.out.println(Arrays.toString(array)); fun2(array); System.out.println(Arrays.toString(array)); } public static void fun1(int[] array) { array = new int[10]; } public static void fun2(int[] array) { array[1] = 99; }
这儿很容易犯错, 都以为fun1(array) 过后 输出 array里的内容为 {0,0,0,0,0,0,0,0,0,0}
你答对了嘛? 为什么呢? 我们通过刚刚的 JVM 运行时数据区来结合理解
, 在栈上面首先创建了 array 这个变量, 并且在堆上开辟了一块儿空间存储 {1,2,3,4}这组数据. 当我们调用 fun1() 方法时, array 作为形参传入, 此时 fun1() 中的 array 也指向同一块空间 即 0x11, 进入fun1() 方法内部后, array 此时开辟了一块儿新的空间, 因此此时 array 指向新的空间, fun1() 中的 array 存储的就是新的地址 0x33
当调用方法结束时, 我们打印的是实参的 array, fun1() 方法中的 array 指向 和实参中的array不一样, 并不影响我们输出, 因此输出结果为 {1,2,3,4}
再来看调用 fun2() 方法时, 传入 array 作为形参 此时 fun2() 中的 形参 array 指向同一块儿空间 {1,2,3,4}, 当我们去修改这块空间中的 array[1]元素时, 此时就变成了{1,99, 3,4}, 当我们调用完 fun2() 以后, 该空间中的 array[1] 已经被修改了 再来打印 array 输出时, 结果就为 {1,99,3,4}
五. 二维数组
1. 二维数组的创建
1 数据类型 [ ] [ ] 数组名 = new 数据类型 [行数] [列数]
2.Java 中二维数组的行数, 列数可用变量来指定
int row = 3; int line = 3; int[][] array = new int[row][line];
2. 数组的初始化
1.给数组分配空间大小, 但不能被修改, 在赋值
T[ ][ ] 数组名= new T[行数][列数];
int[][] array1 = new int[3][3];
2.通过 new 给数组直接赋值, 但不给定空间大小
T[ ][ ] 数组名= new T[行数][列数]{{值1, 值2, 值3}, {值4, 值5, 值6}, {值7, 值8, 值9}};
int[][] array2 = new int[][]{{1,2,3},{4,5,6},{7,8,9}}; // 编译器会自动计算 array 数组的大小
3.直接赋值, 不予分配空间大小
T[ ][ ] 数组名= {{值1, 值2, 值3}, {值4, 值5, 值6}, {值7, 值8, 值9}};
int[][] array3 = {{1,2,3},{4,5,6},{7,8,9}};
4.指定行数但不指定列数
T[ ][ ] 数组名= new T[行数][ ]
int[][] array4 = new int[3][];
3. 二维数组是特殊的一维数组
在一维数组中, 我们可以通过Arrays.toString() 方法来直接打印, 那么二维数组是不是也可以?
int[][] array1 = new int[3][3]; System.out.println(Arrays.deepToString(array1));
打印的结果却像我们在一维数组中直接输出一维数组看到的地址, 由此可以说明二维数组是特殊的一维数组, 借助前面的 JVM 运行时数据分区图可以知道
那么, 在 Java 中我们是如何打印二维数组呢? 需要借助到一个方法
System.out.println(Arrays.deepToString(array1));
4. 打印二维数组
- 常规打印
int[][] array = new int[row][line]; for (int i = 0; i < row; i++) { for (int j = 0; j < line; j++) { System.out.println(array[i][j] + " "); } System.out.println(); }
此处并未进行初始化赋值, 因此每个值都是对应基本类型默认值
对于常规打印循环条件来说, 如果行列数过大, 数是不现实的, 我们可以借助二维数组是特殊的一维数组这一特点, 写出 行, 列之间的关系
for (int i = 0; i < array.length; i++) { for (int j = 0; j < array[i].length; j++) { System.out.print(array[i][j] + " "); } System.out.println(); } System.out.println(Arrays.toString(array));
此时, array.length 描述的是行数, array[i],length 描述的是 每一行有多少列, 即可得出列数.
2.迭代器打印
int row = 3; int line = 3; int[][] array = new int[row][line]; for (int[] arr : array) { for (int x : arr) { System.out.print(x+" "); } System.out.println(); }
由于二维数组是特殊的一维数组, 因此在第一层循环中, 接收 array 的类型为 一个一维数组, 第二层才是这个一维数组的基本类型