一、数组的概念
整数类型、单精度浮点类型等都是基本数据类型 ,通过一个变量表示一个数据。在实际应用中需要经常处理具有相同性质的一批数据, 为此,在Java中引入了数组,即用一个变量表示相同性质的一组数据。
1、数组
- 数组是具有相同数据类型,且按一定次序排列的一组变量的集合体;
- 用一个变量表示一组数据;
- Java中数组属于引用类型。
2、数组元素
- 构成一个数组的每一个数据称为数组元素。
3、数组的数据类型
- 数组的数据类型也是数据元素的数据类型,一个数组中,所有数组元素的数据类型是一致的。
4、数组元素的下标
- 一个数组中,各元素通过下标来区分;下标表明了数组元素在数组中的位置,在一个数组中,数组下标是用整数表示的,从0开始,依次累加1。
5、数组大小
- 数组中元素的个数叫做数组的大小,也叫做数组的长度。
二、Java中如何使用数组
Java中,数组必须经过 声明 、 内存分配、 初始化后才能被使用。
1、声明数组
-
声明一个数组的语法是:
数组类型 数组名[]; 或 数组类型[] 数组名;
-
示例:
public class Test{ public static void main(String args[]){ // 声明数组 int[] nums; String srts[]; char[] chars; } }
-
说明:
- 这里的数据类型既是数组的数据类型,同时也规定了数组元素的数据类型;
- 数据类型可以是 基本数据类型 ,也可以是引用数据类型;
- 数组名遵循标识符的命名规则,建议使用名词的复数形式;
- 数组在声明时无法指定数组大小。
2、分配内存空间
声明一个数组时仅为数组指定了数组名称和元素的类型,并未指定数组元素的个数,系统无法为数组分配存储空间。要让系统为数组元素分配内存空间,必须指定数组元素的个数。通过 new 运算符可以为数组元素分配内存空间。
-
为数组元素分配内存空间的语法结构如下:
数组名 = new 数据类型[数组长度]
-
示例:
public class Test{ public static void main(String args[]){ // 声明数组 int[] nums; String words[]; // 为数组元素分配内存空间 nums = new int[5]; words = new String[10]; } }
-
说明:
- 数组元素的内存分配之后,长度无法改变。
-
定义数组和为数组元素分配内存空间,这两部可以合在一起写。语法格式如下:
数据类型 数组名[] = new 数据类型[数组长度]; // 或 数据类型[] 数组名 = new 数据类型[数组长度];
- 示例:
public class Test{ public static void main(String args[]){ // 声明数组和为数组元素内存空间合并在一起写 char[] chars = new char[5]; } }
3、初始化
-
数组声明并为数组元素分配内存空间后,必须为数组元素初始化,才能使用数组元素,可以通过数组下标确定某一数组元素。
-
示例:
public class Test{ public static void main(String args[]){ // 声明数组 int[] nums; String words[]; // 为数组元素分配内存空间 nums = new int[5]; words = new String[10]; // 初始化数组元素 nums[0] = 1; nums[2] = 5; words[0] = "nihao"; words[2] = "hello"; } }
-
小贴士:
- 数组的length属性用来获取数组的大小;
-
定义数组,为数组元素分配内存、数组元素初始化,这三步可以合并在一起写。语法格式如下:
数组类型[] 数组名 = {数组元素}; // 或者 数组类型[] 数组名 = new 数组类型[]{数组元素};
-
示例:
public class Test { public static void main(String[] args) { // 声明数组、为数组元素分配内存空间、数组元素初始化,可以合并在一起写 int[] nums1 = new int[]{12, 25, 78, 56}; int[] nums2 = {12, 25, 78, 56}; // 省略 new 运算符的写法 String[] worlds = new String[]{"hello", "world", "tom", "jerry", "jack"}; } }
-
说明:
-
省略new运算符时,不可以将数组声明分开,而将数组元素分配内存和数组元素初始化合并在一切写,如下是错误示例:
public class Test { public static void main(String[] args) { int[] nums; // nums = {12, 25, 78, 56}; // 这句代码是不合法的 } }
-
如果没有为数组元素初始化,数组元素则会使用默认值。
byte、short、int、long
类型的数组元素的默认值是 0;float、double
类型的数组元素的默认值是 0.0 ;boolean
类型的数组元素的默认值是 false;char
类型的数组元素的默认值是 \u0000;- 引用类型数组元素的默认值是null;
-
4、综合案例:
4.1、案例一:
import java.util.Scanner;
public class Height {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int[] heights = new int[5]; // 此数组用来存放键盘输入的五名学生的身高。
int sum; // 身高总和
int min; // 最小值
int max; // 最大值
int average; // 平均值
System.out.println("请输入五名学生的身高:");
for (int i = 0; i < heights.length; i++) {
System.out.print("请输入第" + (i + 1) + "名学生的身高:");
heights[i] = input.nextInt();
}
// 获取最大值与最小值
int h = heights.length;
min = heights[0];
max = heights[0];
sum = 0;
for (int i = 0; i < h; i++) {
if (heights[i] < min){
min = heights[i];
}
if (heights[i] > max){
max = heights[i];
}
sum += heights[i];
}
average = sum / h;
// 输出最大值,最小值,平均值
System.out.println("学生中最高的为:" + max);
System.out.println("学生中最低的为:" + min);
System.out.println("学生的平均身高为:" + average);
}
}
4.2、冒泡排序
- 冒泡排序是一种常见的排序算法, 即通过对相邻元素的大小进行比较,每一次将最小或最大的数放到最后面,最终实现从小到大或从大到小的排序。
- 下面是冒泡排序的示意图:
- 具体代码实现如下:
import java.util.Scanner;
public class Maopao {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int[] heights = new int[5];
int h = heights.length;
System.out.println("请依次输入五名同学的身高");
for (int i = 0; i < h; i++) {
System.out.print("请输入第" +(i + 1)+ "名同学的身高:");
heights[i] = input.nextInt();
}
// 定义临时变量
int temp;
// 进行冒泡排序
for (int i = 0; i < h - 1; i++) { // 控制比较多少轮
for (int j = 0; j < h - 1 - i; j++) {
if (heights[j] > heights[j + 1]){
// 如果满足上述条件,对两数进行交换
temp = heights[j];
heights[j] = heights[j + 1];
heights[j + 1] = temp;
}
}
}
// 将排序后的结果进行输出
System.out.println("从低到高依次输出为:");
for (int height:heights) {
System.out.print(height + " ");
}
}
}
- 说明:
-
本例接受键盘输入的五位同学的身高,并存储到一个int类型的数组中,之后对该数组进行冒泡排序,最后按照从低到高的顺序依次打印出五位同学的身高;
-
本例打印五名同学的身高时,使用了
for-each
循环(或称加强型循环),它能在不适用下标的情况下遍历数组(或集合)。For-Each
循环的基本语法如下:for(数组元素的数据类型 数组元素的临时变量名:数组名称){ //循环体; }
-
三、由Java中的数组到内存
1、初步了解Java的内存管理
有些编程语言编写的程序会直接向操作系统请求内存,而Java语言为保证其平台无关性, 并不允许程序直接向操作系统请求内存,而是由Java虚拟机来完成这一工作,开发者只需关心Java虚拟机是如何管理内存空间的,而不需关心某一操作系统是如何管理内存的。
Java虚拟机在执行Java程序的过程当中会把所管理的内存划分为若干个不同的数据区域,大致有:
- 程序计数器:也称为PC寄存器,在JVM中用来指示要执行那条指令,程序计数器是每个线程所私有的;
- 栈:也被称为Java栈或虚拟机栈,Java栈是Java方法执行的内存模型。存放的是一个个栈帧,每个栈帧都对应一个被调用的方法。虚拟机栈也是每个线程所私有的;
- 本地方法栈:与栈类似,
HotSopt虚拟机中
直接把本地方法栈和Java栈合二为一; - 堆:Java中的堆是用来存储对象本身或数组本身的。堆是被所有线程所共享的,在JVM中只有一个堆。
- 方法区:存储类信息、静态变量、常量以及编译器编译后的代码等。方法区同堆一样,也是被线程共享的区域。
在Java程序运行过程中,栈内存和堆内存是最需要关注的内存区域。
2、Java内存中的数组
-
下面通过一个案例说明数组在JVM中的内存分配情况;
public class Test{ public static void main(String args){ int[] nums; nums = new int[2]; nums[0] = 1; nums[1] = 5; } }
-
Java将数组名称存储在栈中,数组元素分配在堆中。下面将用图示浅显的理解上面这段代码执行时JVM中的内存分配过程。
-
第一步:Test类的main方法开始执行;创建该方法对应的栈帧 ,并将创建的栈帧在 栈内存中压栈。
-
第二步:执行
int[] nums;
,在main方法对应栈帧的局部变量表中,为数组名称nums分配一块内存。
-
第三步:执行
nums = new int[2];
时,首先JVM会在堆中分配能够连续存储两个int类型的内存空间,之后,赋值操作会将堆内存中已经分配好的两个连续内存空间的首地址存储到栈内存main方法对应栈帧的局部变量表中;
-
第四步:执行
nums[0] = 1; nums[1] = 5;时,
在堆内存分配好的两个连续内存空间中存入nums[0]
对应的整型值1
和nums[1]
对应的整型值5
;
-
第五步:main方法结束,栈内存中main方法对应的栈帧出栈,栈内存被回收,(事实上,本例中main方法结束意味着整个Java程序结束了,JVM将自己管理的内存交还给操作系统。)
-
3、基本数据类型和引用数据类型
-
Java将数据类型分为两大类,一类是基本数据类型,一类是引用数据类型。这两大类数据类型最核心的区别是:基本数据类型的变量中存储的是真实的数据,引用数据类型的变量中存储的是内存地址编号(即引用了某一内存地址!)。
-
观察下面的代码:
public class Test{ public static void main(String[] args){ // 基本数据类型的变量赋值 int num1, num2; num1 = 3; num2 = num1; num2 = 4; System.out.println("num1 = " + num1) // 引用数据类型的变量赋值 int[] nums1, nums2; nums1 = new int[1]; nums1[0] = 3; nums2 = nums1; nums2[0] = 4; Systemctl.out.println("nums1[0] = " + nums1[0]); } }
说明:
-
System.out.println("num1 = " + num1)
将打印出num1 = 3。因为基本数据类型的变量中存储的是真实的数据,基本数据类型的变量相互赋值时,拷贝的是真实的数据,故改变变量num2中存储的值不会影响变量num1中存储的值。如下图: -
System.out.println("nums1[0] = " + nums1[0])
将打印出nums1[0] = 4。因为引用数据类型的变量中存储的是内存地址编号,引用数据类型的变量相互赋值时,拷贝的是内存地址编号,本例中,变量nums1和变量nums2最终引用了同一内存地址,改变数组nums2中某一数组元素的值即是改变数组nums1中数组元素的值。如下图:
4、二维数组
在日常工作中涉及的许多数据由若干行若干列组成,例如行列式、矩阵、二维表格等,为了描述和处理其值的某个数据,需要两个下标,行下标和列下标。有些情况下可能需要多个下标,为解决这一问题,Java中引入多维数组。
对于Java中的二维数组或多维数组,并没有什么神奇,以二维数组为例,只需牢记:二维数组只是一个特殊的一维数组,特殊在,这个一维数组的每个元素的值都是一个指向另一个一维数组的引用。
- 一张图示即可说明二维数组在JVM中的内存分配情况:
- 多维数组以此类推即可理解;
5、应用案例
5.1、二维数组
public class Array {
public static void main(String[] args) {
// 声明两个二维数组
int[][] nums1, nums2;
// 为二维数组num1分配内存空间并初始化
nums1 = new int[][]{{6, 8},
{3, 9}};
// 为二维数组nums2分配内存空间
nums2 = new int[2][2];
// 数组nums1的值复制到nums2中
for (int i = 0; i < nums1.length; i++) {
for (int j = 0; j < nums1[i].length; j++) {
nums2[i][j] = nums1[i][j];
}
}
System.out.println("复制后nums2的值如下:");
for (int[] nums:nums2) {
for (int num:nums) {
System.out.print(num + "\t");
}
System.out.println();
}
}
}
5.2、使用二维数组打印杨辉三角
public class Arrays {
public static void main(String[] args) {
// 接受键盘输入的数字,它将是要打印的杨辉三角的行数
Scanner scan = new Scanner(System.in);
boolean choose = true;
while (choose) {
System.out.print("请输入您要打印的杨辉三角的行数:");
int rows = scan.nextInt();
if (rows < 3) {
System.out.println("您输入的行数太小了!请重新输入!");
} else {
// 声明二位数组为存储杨辉三角的值,并为其第一维分配内存空间
int[][] nums = new int[rows][];
for (int i = 0; i < nums.length; i++) {
// 为二维数组的第二维声明内存空间
nums[i] = new int[i + 1];
// 初始化二维数组
for (int j = 0; j < nums[i].length; j++) {
if (j == 0 || j == nums[i].length - 1) {
// 杨辉三角每行首尾均为1
nums[i][j] = 1;
} else {
// 其他位置的数是两肩数字之和
nums[i][j] = nums[i - 1][j - 1] + nums[i - 1][j];
}
}
}
System.out.println("您要打印的杨辉三角如下:");
for (int[] rows1:nums) {
for (int i = 0; i < nums.length - rows1.length; i++) {
System.out.print("\t");
}
for (int element:rows1) {
System.out.print(element + "\t\t");
}
System.out.println();
}
choose = false;
}
}
}
}
- 对此二维数组理解不清晰的可参考我的另外一篇博客