一、数组的概念
之前的章节中出现的整数类型、单精度浮点型等都是基本数据类型,通过一个变量表示一个数据。在实际应用中,经常需要处理具有相同性质的一批数据。例如要处理100个学生的考试成绩,如果将它们视为100个独立的浮点型数据,将需要声明100个变量,极不方便。为此,在Java中(当然,不仅仅是Java,所有的编程语言应该都有此考虑),引入了数组,即用一个变量表示一组相同性质的数据。
1、数组
数组是具有相同数据类型且按一定次序排列的一组变量的集合体。即用一个变量名表示一组数据。Java中,数组属于引用数据类型。
2、数组元素
构成一个数组的每一个数据都可以称为数组元素。
3、数组的数据类型
即数组元素的数据类型,一个数组中,所有数组元素的数据类型应该是一致的。
4、数据元素的下标
一个数组中,各元素通过下标来区分。下标表明了数组元素在数组中的位置。在一个数组中,数组下标是用整数表示的,从0开始,依次累加1。
5、数组大小
数组中元素的个数叫做数组的大小,也叫数组的长度。
二、Java中如何使用数组
Java中,数组必须经过声明、内存分配、初始化后才能使用。
1、声明数组
声明数组的语法:
数据类型 数组名[];
// 或
数据类型[] 数组名;
实例:
public class Test {
public static void main(String[] args) {
// 声明数组
int[] nums;
String words[];
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[3];
}
}
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[1] = 3;
words[0] = "hello";
words[1] = "world";
}
}
如果数据元素个数比较多,可以通过for
循环为数组元素初始化。把for
循环的循环变量当作数组的下标来使用即可。
实例:
public class Test {
public static void main(String[] args) {
// 声明数组
int[] nums;
// 为数组元素分配内存空间
nums = new int[5];
// 初始化数组元素
for (int i = 0; i < nums.length; i ++) {
nums[i] = i;
}
}
}
说明:
- 本例
for
循环的条件中,数组nums
调用了length
属性,length
属性用来获取数组的大小。数组的下标应该介于0
和数组大小之间,不在这个之间的下标都是非法的,访问时会抛出数组下标越界异常(ArrayIndexOutOfBoundsException
)。
定义数组、为数组元素分配内存、数组元素初始化,这三步可以合并在一起写。语法格式如下:
数据类型[] 数组名 = {数组元素};
// 或
数据类型[] 数组名 = 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
。
实例:
public class Test {
public static void main(String[] args) {
// 声明八个基本数据类型的数组,并为数组分配内存空间,但是不初始化数组元素
byte[] bytes = new byte[3];
short[] shorts = new short[3];
int[] ints = new int[3];
long[] longs = new long[3];
float[] floats = new float[3];
double[] doubles = new double[3];
char[] chars = new char[3];
boolean[] booleans = new boolean[3];
String[] strings = new String[3];
// 打印数组元素默认初始化的值
System.out.println("bytes[0] = " + bytes[0]);
System.out.println("shorts[0] = " + shorts[0]);
System.out.println("ints[0] = " + ints[0]);
System.out.println("longs[0] = " + longs[0]);
System.out.println("floats[0] = " + floats[0]);
System.out.println("doubles[0] = " + doubles[0]);
System.out.println("chars[0] = " + (int) chars[0]);
System.out.println("booleans[0] = " + booleans[0]);
System.out.println("strings[0] = " + strings[0]);
}
}
4、冒泡排序
冒泡排序(
Bubble Sort
),是一种计算机科学领域的较简单的排序算法。
它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
算法原理:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
3、Java中由数组到内存
1、初步了解Java的内存管理
有些编程语言编写的程序会直接向操作系统请求内存,而Java语言为保证其平台无关性,并不允许程序直接向操作系统请求内存,而是由Java虚拟机来完成这一操作,开发者只需要关心Java虚拟机是如何管理内存空间的,而不用关心某一种操作系统是如何管理内存的。
Java虚拟机在执行Java程序的过程中会把所管理的内存划分为若干个不同的数据区域,大致有:
- 程序计数器,也有称作为PC寄存器,在JVM中用来指示要执行哪条指令,程序计数器是每个线程所私有的。
- 栈,也被称作Java栈或者虚拟机栈,Java栈是Java方法执行的内存模型。存放的是一个个的栈帧,每个栈帧对应一个被调用的方法。虚拟机栈也是每个线程所私有的。
- 本地方法栈,与栈类似,HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。
- 堆,Java中的堆是用来存储对象本身以及数组本身的。堆是被所有线程共享的,在JVM中只有一个堆。
- 方法区,存储类信息、静态变量、常量以及编译器编译后的代码等。方法区同堆一样,也是被线程共享的区域。
在Java程序运行过程中,栈内存和堆内存是最需要关注的内存区域。
2、Java内存中的数组
Java将数组名称存储在栈中,数组元素分配在堆中。
我们举个例子:
public class Test {
public static void main(String[] args) {
int[] nums;
nums = new int[2];
nums[0] = 1;
nums[1] = 5;
}
}
第一步,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;
System.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中的二维数组或者多维数组而言,并没有什么玄妙,以二维数组为例,只需牢记:二维数组只是一个特殊的一维数组,特殊在这个一维数组的每一个元素的值都是一个指向另一个一维数组的引用。
4.1、数组的复制
public class Test {
public static void main(String[] args) {
//定义两个二维数组nums1和nums2
int nums1[][], nums2[][];
//二维数组nums1分配内存并初始化数据
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.print("\n");
}
}
}
说明:
- 本例中声明了两个
int
类型的二维数组nums1
和nums2
,给数组nums1
的元素分配了内存空间并进行了初始化,同时给数组nums2
的元素分配了内存空间但并没有初始化,通过两层for
循环将数组nums1
中的每个元素值拷贝到数组nums2
的对应位置中。
4.2、打印杨辉三角
public class Test {
public static void main(String[] args) {
// 接收键盘输入的数字,作为要生成及打印的杨辉三角的行数
Scanner input = new Scanner(System.in);
System.out.println("请输入行数:");
int rowNum = input.nextInt();
if (rowNum < 3) {
System.out.println("请输入大于2的整数");
} else if (rowNum > 14) {
System.out.println("行数太大了");
} else {
// 声明二维数组,用来存储杨辉三角,并为该二维数组的第一维分配内存空间
int[][] triangle = new int[rowNum][];
for (int i = 0; i < triangle.length; i++) {
// 为二维数组的第二维分配内存空间
triangle[i] = new int[i + 1];
for (int j = 0; j < triangle[i].length; j++) {
if (j == 0 || j == triangle[i].length - 1) {
// 每行的第一个数字和最后一个数字是1
triangle[i][j] = 1;
}else {
// 其他位置的数字是其两肩的数字之和
triangle[i][j] = triangle[i - 1][j - 1] + triangle[i - 1][j];
}
}
}
System.out.println("您要打印的杨辉三角如下:");
for (int[] row : triangle) {
for (int k = 0; k < rowNum - row.length; k ++) {
System.out.print("\t");
}
for (int element : row) {
System.out.print(element + "\t\t");
}
System.out.print("\n");
}
}
}
}