JAVA学习笔记 04 – 数组

  • Post author:
  • Post category:java


本文是Java基础课程的第四课。本文主要介绍数组的基本概念、Java中如何使用数组、Java中数组的内存分配等内容,并且点明Java中基本数据类型和引用数据类型的核心区别



一、数组的概念

之前的章节中出现的

整数类型



单精度浮点型

等都是

基本数据类型

,通过

一个变量表示一个数据

。在实际应用中,经常需要处理

具有相同性质



一批数据

。例如要处理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、应用案例



4.1、案例1

下面是一个示例:

public class Test {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        // 声明一个数组存储5名新兵的身高
        int[] heights = new int[5];
        // 存储最高身高值
        int max;
        // 存储最低身高
        int min;
        // 存储总身高,用于计算平均身高
        int sum;
        // 接受键盘输入的5名新兵的身高
        System.out.println("请输入五名新兵的身高:");
        for (int i = 0; i < heights.length; i++) {
            System.out.println("请输入第" + (i + 1) + "名新兵的身高");
            heights[i] = input.nextInt();
        }
        // 对身高进行比较
        max = heights[0];
        min = heights[0];
        sum = heights[0];
        for (int i = 1; i < heights.length; i++) {
            if (heights[i] > max) {
                max = heights[i];
            }
            if (heights[i] < min) {
                min = heights[i];
            }
            sum += heights[i];
        }
        System.out.println("最高的身高为:" + max);
        System.out.println("最低的身高为:" + min);
        System.out.println("平均的身高为:" + sum / heights.length);
    }
}

说明:

  • 本例接收键盘输入的五名新兵的身高,并存储到一个

    int

    型数组中,之后遍历数组,找出五名新兵中最高的身高、最低的身高,另外还计算了五名新兵的平均身高。



4.2、案例2

下面是一个案例:

public class Test {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        // 存储五个新兵的身高
        int[] heights = new int[5];
        // 循环输入五个新兵的身高
        for (int i = 0; i < heights.length; i++) {
            System.out.println("请输入第" + (i + 1) + "个新兵的身高:");
            heights[i] = input.nextInt();
        }
        // 定义临时变量
        int temp;
        // 进行冒泡排序
        for (int i = 0; i < heights.length - 1; i++) {       // 控制比较多少轮
            for (int j = 0; j < heights.length - 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.println(height);
        }
    }
}

说明:

  • 本例接收键盘输入的五名新兵的身高,并存储到一个

    int

    型数组中,之后对该数组进行

    冒泡排序

    ,最后按从低到高排序后的顺序打印出五名新兵的身高。
  • 本例中打印5名新兵身高时,使用了

    For-Each


    循环

    (或者称

    加强型循环

    ),它能在不使用下标的情况下

    遍历数组

    (或

    集合

    )。

    For-Each


    循环

    的基本语法如下:

    for(数组元素的数据类型 数组元素的临时变量名 : 数组名称) {
    	// 循环体; 
    }
    

  • 冒泡排序

    是一种

    常用



    排序算法

    ,即通过对

    相邻元素

    的大小进行

    比较

    ,每一次将

    最小或最大

    的数

    放到最后

    面,

    最终实现

    从小到大或从大到小

    排序

    。下面是冒泡排序的例子图示:

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述



三、由Java中的数组到内存



1、初步了解Java的内存管理

有些编程语言编写的程序会直接向操作系统请求内存,而

Java

语言为

保证



平台无关

性,并

不允许程序直接向操作系统请求内存

,而是

由Java虚拟机

来完成这一操作,开发者

只需



关心Java虚拟机



如何管理内存

空间的,而

不用关心

某一种

操作系统



如何管理内存

的。

Java虚拟机在执行Java程序的过程中会把所管理的内存划分为若干个不同的数据区域,大致有:



  1. 程序计数器


    ,也有称作为

    PC寄存器

    ,在JVM中用来指示要执行哪条指令,程序计数器是每个线程所私有的。





  2. ,也被称作

    Java栈

    或者

    虚拟机栈

    ,Java栈是Java方法执行的内存模型。存放的是一个个的栈帧,每个栈帧对应一个被调用的方法。虚拟机栈也是每个线程所私有的。


  3. 本地方法栈


    ,与栈类似,

    HotSopt虚拟机

    中直接就把本地方法栈和Java栈合二为一。





  4. ,Java中的堆是用来存储对象本身以及数组本身的。堆是被所有线程共享的,在JVM中只有一个堆。


  5. 方法区


    ,存储类信息、静态变量、常量以及编译器编译后的代码等。方法区同堆一样,也是被线程共享的区域。

在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;
		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、二维数组

日常工作中涉及的许多数据由若干行若干列组成,例如行列式、矩阵、二维表格等,为了描述和处理其值的某个数据,需要两个下标,即行下标和列下标。有些情况下可能需要3个或多个下标,如描述三维空间中各点的位置就需要3个下标。为了解决这一问题,Java中可以使用

多维数组

对于Java中的二维数组或者多维数组而言,并没有什么玄妙,以二维数组为例,只需牢记:

二维数组只是一个特殊的一维数组,特殊在这个一维数组的每一个元素的值都是一个指向另一个一维数组的引用

一张图示即可说明二维数组在JVM中的内存分配情况:

在这里插入图片描述

多维数组依次类推即可理解。



5、应用案例



5.1、案例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

    的对应位置中。



5.2、案例2

案例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");
			}
		}
	}
}

说明:

  • 杨辉三角的规则是:每行的第一个数字和最后一个数字是1,其他位置的数字是其两肩的数字之和。



版权声明:本文为cgl125167016原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。