JAVA编程讲义数组

网友投稿 766 2022-05-29

在程序开发过程中,有时候需要存储大量的同类型数据。例如,存储一个班级50名学生的姓名,这时需要定义50个变量来保存姓名数据,但这种做法太繁琐了。那么,如何解决这类问题呢?Java语言提供了数组结构,它类似于一个容器,可以批量存储相同数据类型的元素。因此,对于前述学生成绩统计问题,我们只需要定义一个长度为50的字符串数组就可以解决。本章将对数组的基本概念、定义方式、初始化以及使用等内容展开讲解。

4.1 一维数组

一维数组的逻辑结构是线性表,它是最简单的数组。使用一维数组时,要先定义,然后做初始化,最后才能使用。本节为大家详细讲解一维数组的具体用法。

4.1.1 一维数组的创建

在Java中使用数组,一般需要3个步骤:声明数组、创建数组对象空间、为数组元素赋值。其中,声明数组和创建数组对象空间的语法如下:

数据类型[] 数组名; // 声明一维数组,这种是推荐的写法

数据类型 数组名[]; // 声明一维数组的第二种写法

数组名 = new 数据类型[数组元素个数]; // 为数组对象分配内存空间

在上面一维数组的声明语句中,“数据类型”可以是Java语言中的任意数据类型,包括简单类型和对象类型。“数组名”是用来访问数组的标识,作用与变量名类似,在程序中可以通过“数组名”访问数组中的元素,“数组名”的命名规则与变量的命名规则相同。另外,“[ ]”是定义数组类型的符号,声明数组时必须要有这个符号,这个符号可以放在数据类型后面,也可以放在数组名后面。

数组声明之后,就需要为数组对象分配一个固定长度的存储空间。在为数组分配内存空间的时候,需要使用new运算符。通过new运算符可以告诉编译器声明的数组存储什么类型的数据,以及数组要存储的元素个数。数组一旦创建长度就固定了,不能再次改变。

针对数组的声明和内存分配,举例说明如下:

int[] nums; // 声明数组名为nums的整形数组

nums = new int[10]; // 为nums数组分配10个存储整数的内存空间

在这例子中,第1行代码只是声明了一个整数类型的数组变量nums,但是这个变量没有指向任何一个数组对象空间,声明的数组变量是在JVM的栈内存中分配空间。第2行代码先通过new运算符创建了一个长度为10的整型数组空间,创建的数组空间是在JVM的堆内存中分配的。接着把数组对象赋值给nums,也就是将数组对象的地址存储到了nums变量中。此时,nums变量就指了这个数据对象上,因此通过nums可以访问到数组对象空间中的每一个元素。对于数组声明和数组对象内存分配,可以参考图4.1。

(a)只声明数组变量但未指向数组对象空间 (b)声明数组并指向数组对象空间

图4.1 数组声明和内存分配

图4.1中,左图声明了一个整型数组变量nums,这个变量对应一个栈内存的空间。因为nums没有指向数组对象,所以nums存储的内容是null。右图中在堆内存中创建了一个连续的长度为10的整型数组对象空间,数组对象的首地址是0x8A21。右图中声明的数组变量nums存储了数组对象的首地址。因此nums变量指向了这个数组对象,与这个数组对象有了引用关联。通过nums变量可以用下标索引的方式,访问数组中的每个元素。

另外,也可以使用一条语句完成数组的声明和内存分配,语法说明如下:

数据类型[] 数组名 = new 数据类型[数组元素个数]

使用这种方式会在定义数组的同时为数组分配内存空间,举例说明如下:

int[] nums = new int[10]; // 声明数组的同时为数组分配内存空间

在这行代码中,等号左边的nums是声明的数组变量,它指向了右边使用new运算符创建的数组对象。

知识点拨:在Java中,数组对象被创建之后,数组中的元素都具有一个初始的默认值。整型数组的元素默认值是0。浮点类型数组的元素默认值是0.0。布尔类型数组的元素默认值是false。字符串和对象等引用类型数组的元素默认值都是null。

4.1.2 数组元素的分配

在创建数组对象之后,就可以存储数据到数组元素空间中,进行数组元素的分配,也就是为数组元素赋值。为数组元素赋值的方式有3种:静态初始化、动态初始化、通过数组下标为数组赋值。

1.静态初始化

静态初始化就是在声明数组时,由开发者显式指定每个数组元素的初始值,初始值的类型要和定义数组的类型一致。根据这些初始值,系统会自动创建对应长度的数组空间,用于存储每个数组元素的数据。静态初始化语法如下:

数据类型[] 数组名 = {数据1,数据2,数据3,…,数据n}; // 静态初始化,第1种方式

数据类型[] 数组名;

数组名 = new 数据类型[]{数据1,数据2,数据3,…,数据n}; // 静态初始化,第2种方式

第1种静态初始化的方式,需要在声明数组的同时进行数据初始化,初始化的数据要写在大括号中,并以逗号分隔。第2种静态初始化的方式可以先声明数组变量,然后使用new运算符进行数组元素的初始化。另外,第2种方式中,右边表达式中的“[ ]”不允许写数组长度,否则会发生语法错误。

静态初始化示例代码如下:

int[] nums = {10,20,30,40,50}; // 声明数组并进行数组静态初始化

String[] names; // 声明数组变量

names = new String[]{"唐僧","孙悟空","猪八戒","沙和尚"}; // 进行静态初始化

2.动态初始化

进行数组动态初始化时,开发者只需要指定数组长度,然后由系统自动为数组元素分配初始值。动态初始化的语法格式如下:

数据类型[] 数组名 = new 数据类型[数组长度];

在进行动态初始化后,程序会根据指定的数组长度,创建对应长度的数组元素空间,并为每个数组元素空间设置初始值。

动态初始化的示例代码如下:

int[] nums = new int[5]; // 创建长度为5的整型数组,数组元素的初始值都是0

String[] names = new String[3]; // 创建长度为3的字符串数组,数组元素的初始值都为null

3.通过数组下标为数组赋值

在数组创建之后,可以使用数组名结合数组下标的方式,为数组空间中的每个元素赋值。使用数组下标赋值的语法格式如下:

数据类型[] 数组名 = new 数据类型[数组长度];

数组名[下标1] = 数值1;

数组名[下标2] = 数值2;

数组名[数组长度-1] = 数值n;

在通过数组下标为数组元素赋值的时候,数组下标的取值范围从0到数组长度减1为止。下标超出这个范围,会发生“ArrayIndexOutOfBoundsException”数组下标越界的异常。

通过数组下标为元素赋值的代码示例如下:

String[] names = new String[4]; // 声明长度为4的字符串数组

names[0] = "唐僧"; // 通过下标为每个数组元素赋值

names[1] = "孙悟空";

names[2] = "猪八戒";

names[3] = "沙和尚";

4.1.3 数组元素的访问

数组创建之后,最常用的操作就是访问数组元素,这包含为数组元素赋值和输出数组元素中的值。访问数组元素的方式是通过数组名结合数组下标的方式完成的。

接下来,通过实例来演示如何访问数组元素,如例4-1所示。

例4-1 Demo0401.java

1  package com.aaa.p040103;

2

3  public class Demo0401 {

4   public static void main(String[] args) {

5   int[] nums = new int[3]; // 定义长度为3的整型数组

6   nums[0] = 10; // 为数组元素赋值

7   nums[1] = 20;

8   nums[2] = 30;

9   System.out.println(nums[0]); // 输出数组元素中的值

10   System.out.println(nums[1]);

11   System.out.println(nums[2]);

12   }

13  }

程序运行结果如下:

10

20

30

在例4-1中,先创建了一个长度为3的整型数组,然后使用数组名结合下标的方式分别为3个数组元素赋值。注意,数组的下标必须写在中括号内。最后,使用打印输出语句输出每个数组元素内的数据。

4.1.4 length的使用

要获取数组的长度,可以通过数组对象的length属性得到。每个数组都会有length属性,当通过length属性获取了数组长度之后,就可以通过循环的方式,使用下标逐一遍历数组中的每个元素。

接下来,我们使用length获取数组长度,并通过循环遍历数组元素,如例4-2所示。

例4-2 Demo0402.java

1  package com.aaa.p040104;

2

3  public class Demo0402 {

4   public static void main(String[] args) {

5   int[] nums = new int[3]; // 创建长度为3的数组对象

6   nums[0] = 10; // 为数组元素赋值

7   nums[1] = 20;

8   nums[2] = 30;

9   for(int i = 0;i < nums.length;i++) { // 使用length获取数组长度,作为循环条件

10   System.out.println(nums[i]); // 循环输出每个元素

11   }

12   }

13  }

程序运行结果如下:

10

20

30

在例4-2中,先定义了长度为3的整型数组,然后使用数组名结合下标的方式为每个数组元素赋值,接着使用数组的length属性获取数组的长度,作为for循环的循环条件,最后通过for循环逐一遍历数组元素,并打印输出。

4.1.5 使用foreach遍历数组

除了使用for循环遍历数组外,Java中还有另外一种很简洁的遍历方法:foreach循环遍历。这种方式也称为增强for循环,它的功能比较强大,遍历时不需要依赖数组的长度和下标,即可实现数组遍历。foreach循环的语法格式如下:

for (数组中元素类型 临时变量 : 数组对象变量){

程序语句;

}

通过上面的语法结构可以看出,foreach遍历数组的时候不需要获取数组长度,也不需要用索引去访问数组中的元素,这是与for循环不同的地方。foreach循环会自动将数组中的元素逐一取出,存入一个临时变量中,然后使用临时变量进行数据处理,从而完成数组元素的遍历。

接下来,通过案例来演示foreach循环遍历数组,如例4-3所示。

例4-3 Demo0403.java

1  package com.aaa.p040105;

2

3  public class Demo0403 {

4   public static void main(String[] args) {

5   String[] names = {"唐僧","孙悟空","猪八戒","沙和尚"}; // 声明数组并进行初始化

6   for(String name : names) { // 使用foreach循环逐一取出数组元素并存入临时变量

7   System.out.println(name); // 输出临时变量存储的数据

8   }

9   }

10  }

程序运行结果如下:

唐僧

孙悟空

猪八戒

沙和尚

例4-3中,首先定义了一个数组对象,并初始化了4个字符串数据。然后使用foreach循环遍历数组,每次循环时foreach都通过临时变量存储当前遍历到的元素,并将元素打印显示。

注意:foreach循环代码简洁,编写方便,但是有其局限性,当使用foreach遍历数组时,只能访问其中的元素,不能对元素进行修改。

接下来,通过案例进一步演示在使用foreach循环的过程中,对元素进行修改会有什么结果,如例4-4所示。

例4-4 Demo0404.java

1  package com.aaa.p040105;

1

1  public class Demo0404 {

1   public static void main(String[] args) {

1   String[] strs = new String[3]; // 创建一个长度为3的数组

1   int i = 0;

1   for(String str : strs) { // 循环遍历数组

1   str = new String("字符串:" + i ); // 修改每个遍历到的值

1   i++;

1   }

1   for(String str : strs) {

1   System.out.println(str); // 打印数组中的值

1   }

1   }

1  }

程序运行结果如下:

null

null

null

例4-4中,先定义一个长度为3的字符串数组。然后通过第1个foreach循环,将遍历到的每个数组元素的数据都进行了修改。但在第2个foreach循环中,遍历输出的每个元素依旧是null。这说明在使用foreach循环遍历时,遍历的元素并没有真正被修改。原因是第8行中只是将临时变量str指向了一个新字符串,变量str和数组中的元素实际上没有任何联系。所以,foreach循环的过程中无法修改所遍历的数据。因此,foreach并不能替代for循环,仅仅是让遍历的方法变得更简洁。

4.1.6 基本类型数组的初始化

按照数据类型的不同,数组可分为基本类型数组和引用类型数组。基本类型数组的特点是,数组元素的值是直接存储在数组元素中的。所以,定义基本类型数组并初始化时,会先为数组分配空间,然后将数据直接存入对应的数组元素中。基本类型数组的初始化示例如图4.2所示。

(a)定义值类型数组并由系统自动初始化 (b)为值类型数组设置数据

图4.2 值类型数组的初始化和设值

在图4.2中,左图定义了一个值类型数组,也就是整型数组nums。该数组长度为5,初始化后每个数组元素的值都是0。右图是为nums数组中的元素都设置一个整数值。从图4.2中可以看出,值类型数组的数据都是直接存储在数组元素中的。

接下来,通过案例来演示值类型数组的初始化和设值,如例4-5所示。

例4-5 Demo0405.java

1  package com.aaa.p040106;

2

3  public class Demo0405 {

4   public static void main(String[] args) {

5   int[] nums = new int[5]; // 定义长度为5的数组,并动态初始化

6   for(int n : nums) { // 使用foreach输出每个元素值

7   System.out.println(n);

8   }

9

10   System.out.println("=========="); // 输出分隔符

11

12   nums[0] = 34; // 为每个数组元素设置特定值

13   nums[1] = 21;

14   nums[2] = 15;

15   nums[3] = 56;

16   nums[4] = 71;

17

18   for(int i = 0;i < nums.length;i++) { // 使用for循环输出每个元素

19   System.out.println(nums[i]);

20   }

21   }

22  }

程序运行结果如下:

0

0

0

0

0

==========

34

21

15

56

71

在例4-5中,先定义了一个长度为5的整型数组。接着,使用foreach循环输出数组元素中的数据,此时数组中的数据都为0。然后,通过数组名结合下标的方式,为每个数组元素设置特定数据,最后使用for循环将数组元素的数据逐一打印出来。

4.1.7 引用类型数组的初始化

引用类型数组中存储的是数据的引用地址。通过引用地址指向了实际存储数据的内存区域。下面通过定义一个Student类型的数组来演示引用类型数组的使用,如例4-6所示。

例4-6 Demo0406.java

1  package com.aaa.p040107;

2

3  class Student{ // 学生类

4   String name; // 姓名

5   int age; // 年龄

6  }

7

8  public class Demo0406 {

9   public static void main(String[] args) {

10   Student[] stus = new Student[2]; // 创建长度为2的学生数组

11   stus[0] = new Student(); // 为第一个数组元素存储学生对象

12   stus[0].name = "张三"; // 设置学生对象的属性

13   stus[0].age = 20;

14

15   stus[1] = new Student(); // 为第二个数组元素存储学生对象

16   stus[1].name = "李四"; // 设置学生对象的属性

17   stus[1].age = 18;

18

19   for(Student s : stus) { // 使用foreach循环输出学生对象数据

20   System.out.println(s.name + " " + s.age);

21   }

22   }

23  }

程序运行结果如下:

张三 20

李四 18

在例4-6中,先定义了一个长度为2的Student类型的数组。接着,为每个数组元素存储一个学生类型的对象,并为存储的学生对象设置属性。然后,使用foreach循环输出数组元素中的学生对象,将学生的姓名和年龄打印出来。

为了便于大家更好地理解引用类型数组存储数据的特点。下面通过一个图例对引用类型数组的使用进行说明,如图4.3所示。

(a)引用类型数组的初始化 (b)引用类型数组存储数据

图4.3 引用类型数组的数据存储方式

图4.3中,左图是引用类型数组定义并初始化的情况,引用类型数组的元素默认值都为null,不指向任何数据。右图中,定义了学生类型的数组,数组长度为2,数组包含两个元素。每个数组元素都存储了一个地址,分别指向不同的学生对象。

4.2 二维数组

一维数组主要用于存储线性数据,比如存储某校某年级所有学生一门课程的成绩,这种数据存储的结构是单个维度的。但是在实际应用中,一维数组并不能满足所有需求,比如存储某校某年级所有学生两门课程的成绩。所以,Java中提供了多维数组,但是Java中并没有真正的多维数组结构,它的多维数组的本质是让数组元素再存储一个数组,从而构成多维数组的结构。本节将以二维数组为例讲解多维数组的用法。二维数组的数据结构类似于一张表,包含行和列,下面展开详细讲解。

4.2.1 二维数组的创建

二维数组的结构可以看作是一张表,其中包含行和列,分别对应第一维度和第二维度。如图4.4所示,是一个3行2列的二维数组的存储结构示意图。

图4.4 3行2列二维数组结构

在Java中,声明二维数组时,需要使用两个中括号进行定义,在分配二维数组元素空间时,需要指明两个维度的数组长度。二维数组的创建和一维数组类似,也需要进行数组声明和数组空间分配,创建二维数组的语法如下:

数据类型[][] 数组名; // 声明二维数组变量

数组名 = new 数据类型[第一维长度][第二维长度]; // 为二维数组分配空间

当然也可以将数组声明和数组的内存分配用一行代码定义:

数据类型[][] 数组名 = new 数据类型[第一维长度][第二维长度]; // 声明二维数组并分配空间

根据二维数组的语法,创建一个3行2列的整型二维数组的示例代码如下:

int[][] scores; // 声明二维数组变量scores

scores = new int[3][2]; // 为二维数组分配3行2列的内存空间

在上述示例代码中,第1行声明了一个整型二维数组变量scores,这个变量未指向任何数组空间。接着,第2行代码先通过new运算符创建了一个3行2列的整型二维数组空间。然后,将二维数组赋值给scores变量,因此 scores变量存储了整型二维数组的地址。通过scores变量可以访问二维数组对象空间的每个元素。对于二维数组的声明和内存分配,如图4.5所示。

(a)声明整型二维数组变量但未指向数组对象空间 (b)声明整型二维数组变量并指向二维数组对象空间

图4.5 二维数组声明和内存分配

图4.4中,左图声明了一个整型二维数组变量scores,因为没有指向任何数组对象,所以scores变量存储的内容是null。右图在堆内存中创建了一个3行2列的整型二维数组。右图的scores变量存储了这个二维数组的首地址。二维数组的第一维是一个长度为3的一维数组,这个一维数组的每个空间都指向到另外一个长度为2的一维数组,由此构成了二维数组结构。通过scores变量使用两个维度的下标可以访问到二维数组中的每个数组元素。

4.2.2 二维数组的内存分配

二维数组创建之后,就可以存储数据到二维数组的元素空间中。二维数组元素的赋值方式与一维数组类似,也是3种方式:静态初始化、动态初始化、通过数组下标为数组赋值。

1.静态初始化

二维数组的静态初始化就是在声明数组时,由开发者显示指定二维数组元素的初始值。因为二维数组有两个维度,所以在设置数据的时候,要使用两层大括号来体现两个维度的结构。如下是两种二维数组静态初始化的语法:

// 声明数组同时初始化,外面的大括号表示第一维元素,里面的大括号表示第二维元素

数据类型[][] 数组名 = {{数据1,数据2,…},{数据1,数据2,…},…};

// 先声明二维数组,然后进行初始化

数据类型[][] 数组名 ;

数组名 = new 数据类型[][]{{数据1,数据2,…},{数据1,数据2,…},…};

上述语法中,二维数组静态初始化时,初始化的数据要写在两层大括号中,这是与一维数组不同的地方。第1层大括号中定义的是第一维的数组元素,这些数组元素本身又是一个数组,元素之间以逗号分隔。第2层大括号中存储的是实际的数据内容,多个数据之间也以逗号分隔。另外,在第2种静态初始的语法中,右边表达式中的“[ ][ ]”内,也不允许写数组长度,否则会发生语法错误。

二维数组静态初始化示例代码如下:

int[][] scores = {{78,65},{98,79},{87,89}}; // 声明数组并进行数组静态初始化

int[][] scores; // 先声明数组变量

scores = new int[][]{{78,65},{98,79},{87,89}}; // 再进行静态初始化

2.动态初始化

二维数组进行动态初始化时,开发者需要指定两个维度的数组长度,然后由系统自动为数组元素分配初始值。二维数组动态初始化的语法格式如下:

数据类型[][] 数组名 = new 数据类型[第一维数组长度][第二维数组长度];

二维数组在进行动态初始化后,程序会根据指定的两个维度的数组长度,创建对应的数组元素空间,并为每个数组元素空间设置初始值。

二维数组动态初始化的代码示例如下:

int[][] scores = new int[3][2]; // 创建3行2列的整形二维数组,元素的初始值都是0

String[][] names = new String[3][2];// 创建3行2列的字符串二维数组,元素的初始值都为null

3.通过数组下标为数组赋值

在二维数组创建之后,可以使用数组名结合二维数组行列下标的方式,为二维数组空间中的每个元素赋值。使用二维数组下标赋值的语法格式如下:

数据类型[][] 数组名 = new 数据类型[第一维数组长度][第二维数组长度];

数组名[第一维度下标][第二维度下标] = 数值;

在通过数组下标为数组元素赋值的时候,每个维度的数组下标的取值范围从0到对应维度的数组长度减1为止。下标超出这个范围,会发生“ArrayIndexOutOfBoundsException”数组下标越界的异常。

通过数组下标为元素赋值的示例代码如下:

String[][] names = new String[3][2]; // 声明3行2列字符串二维数组

names[0][0] = "张三"; // 通过2个维度的下标为每个数组元素赋值

names[0][1] = "李四";

names[1][0] = "王五";

names[1][1] = "赵六";

names[2][0] = "孙七";

names[2][1] = "钱八";

4.2.3 嵌套循环存取二维数组

二维数组有两个维度,在访问数组元素的时候要通过两个下标来访问。因为数组的下标具有连续性的特点,所以可以通过循环嵌套的方式来访问二维数组的每个元素。

接下来,通过循环嵌套的方式为一个3行2列的二维数组赋值,并用循环嵌套的方式输出存入的数据,如例4-7所示。

例4-7 Demo0407.java

1  package com.aaa.p040203;

2  import java.util.Scanner;

3

4  public class Demo0407 {

5   public static void main(String[] args) {

6   Scanner sc = new Scanner(System.in); // 定义输入扫描器对象,用于接受键盘输入

7   // 定义3行2列的二维数组,存储3名学生的2门成绩

8   int[][] scores = new int[3][2];

9   // 使用嵌套循环的方式为数组赋值, scores.length获取第一维数组的长度

10   for(int i = 0; i < scores.length; i++) {

11   // scores[i].length获取第二维数组的长度

12   for(int j = 0; j < scores[i].length; j++) {

13   System.out.println("请输入第" + ( i + 1 ) + "个学生的第" + ( j + 1 ) +

14   "门课成绩:");

15   scores[i][j] = sc.nextInt(); // 将输入的数据存入对应的数组位置

16   }

17   }

18

19   System.out.println("===================="); // 输出分隔符

20

21   // 使用嵌套循环的方式打印二维数组元素, scores.length获取第一维数组的长度

22   for(int i = 0; i < scores.length; i++) {

23   // scores[i].length获取第二维数组的长度

24   for(int j = 0; j < scores[i].length; j++) {

25   int score = scores[i][j]; // 获取当前遍历到的二维数组元素

26   // 输出分数

27   System.out.println("第" + (i + 1) + "个学生的第" + (j + 1) +

28   "门课成绩是:" + score);

29   }

30   }

31

32   }

33  }

程序运行结果如下:

请输入第1个学生的第1门课成绩:

23

请输入第1个学生的第2门课成绩:

34

JAVA编程讲义之数组

请输入第2个学生的第1门课成绩:

45

请输入第2个学生的第2门课成绩:

56

请输入第3个学生的第1门课成绩:

67

请输入第3个学生的第2门课成绩:

87

====================

第1个学生的第1门课成绩是:23

第1个学生的第2门课成绩是:34

第2个学生的第1门课成绩是:45

第2个学生的第2门课成绩是:56

第3个学生的第1门课成绩是:67

第3个学生的第2门课成绩是:87

在例4-7中,先定义了一个输入扫描器对象(Scanner),用于接受从键盘输入的数据。接着,定义了一个3行2列的整型二维数组,用于存储3个学生两门课程的成绩。然后使用嵌套循环实现二维数组元素的赋值。scores.length是第1层循环的终止条件,它表示的是第一维度数组的长度。scores[i].length是第二层循环的终止条件,它表示的是第二维度数组的长度。在第2层循环内部,使用scores[i][j]表示访问到的某个二维数组元素,用于存储用户通过键盘输入的数据。嵌套循环结束后,这个二维数组的所有元素都会被赋值。接下来通过另外一个嵌套循环,将二维数组内的所有元素逐一打印输出。

知识点拨:Java中的多维数组本质上是在数组中存储数组,是在一维数组的基础上衍生出来的,因此理论上可以定义任何维度的数组。定义二维数组的时候需要使用两个中括号"[ ][ ]",以此类推,定义三维、四维数组只要定义对应个数的中括号即可。

4.2.4 非对称型数组

前面讲的二维数组用的都是矩阵数组,也就是数组的第二维度的长度都是一样的,是等行等列的对称结构。但是,Java中还有一种非对称的数组结构,被称为非对称型数组。对称型数组和非对称型数组的结构如图4.6所示。

(a)对称型数组(矩形数组) (b)非对称型数组(不规则数组)

图4.6 对称型数组和非对称型数组

对称型的3行4列数组有12个元素,但是非对称型数组的元素个数是不确定的。这里,我们定义一个非对称型数组,并进行静态初始化,然后输出一维数组元素指向的数组中的最后一个元素,示例代码如下:

int[][] scores = {{1,2,3},{5,6,7,8},{9,10}}; // 用静态初始化的方式定义非对称型数组

System.out.println(scores[0][2]); // 输出第一维下标为0的数组的最后一个元素

System.out.println(scores[1][3]); // 输出第一维下标为1的数组的最后一个元素

System.out.println(scores[2][1]); // 输出第一维下标为2的数组的最后一个元素

在上述示例代码中,声明的二维数组,第一维长度是3。但是,第二维长度各不相同,长度分别是:3,4,2。因此,第一维数组元素关联的数组的最后一个元素的下标是各不相同的。

4.3 数组的排序与查找

使用数组处理数据的时候,除了要对数据进行存储,大部分时候需要对数据进行查找和筛选,而查找数据的时候又往往需要对数据进行排序处理。比如,要找一个分数最高的成绩,或者年龄最小的学生,这时就需要在查找的过程中进行数据排序。

4.3.1 数组元素排序

对数组中的数据进行排序的算法有很多种,对于初学者而言,“冒泡排序”是最简单易懂的方法。冒泡排序的核心特点,就是先比较相邻两个元素的大小,然后根据排序规则进行换位。

如图4.7所示,是对一维数组中的5个数字:56,32,8,76,12进行冒泡排序,要求排序之后,数组中的数据按照从小到大的次序排列。

图4.7 冒泡排序

图4.7中,在开始排序时,5个数字没有按从小到大的次序进行排列。接着开始进行第1轮排序,排序的时候依次对相邻的两个数字比较大小,如果前面的数字大于后面的数字,则将这两个数字交换位置,否则就不进行换位。参与排序的5个数字全都比较一遍并做了相应的换位之后,数值最大的76排在了最后的位置上,这个数字不再参与下一轮的比较。第1轮排序一共进行了4次比较和换位。在第2轮排序时,排除了上一轮的最大值76后,只需要对4个数字进行排序。排序的过程依旧是比较和换位。最终经过3次比较,找到了本轮的最大值56。第3轮排序时,将前两轮的最大值72和56排除掉,只需要对剩下的3个数字进行排序。最终经过2次比较和换位,找到了本轮的最大值32。第4轮排序时,将前3轮的最大值72、56、32排除掉,只需要对剩下的2个数字进行排序,最终经过1次比较找到了本轮的最大值12。经过4轮排序,5个数字实现了从小到大的排列。

经过上面的排序过程可以发现,排序的轮数等于参与排序的数字的个数减1。而每一轮排序的次数等于本轮参与排序的数字个数减1。这是因为每一轮排序都会找到本轮的最大值,而在下一轮排序时这个最大值不再参与比较,所以随着排序轮数的增加,参与比较的数字在减少,这是冒泡排序的一个基本规律。

在了解了冒泡排序的基本原理后,下面用代码实现冒泡排序的过程,如例4-8所示:

例4-8 Demo0408.java

1  package com.aaa.p040301;

2

3  public class Demo0408 {

4   public static void main(String[] args) {

5   int[] nums = {56, 32, 8, 76, 12}; // 定义要排序的整形数组

6   System.out.println("=========排序前===========");

7   for(int n : nums) { // 循环输出初始的数组数据

8   System.out.print(n+" ");

9   }

10   System.out.println(); // 换行

11   // 使用双重循环实现冒泡排序,外层循环控制排序比较的轮数

12   for(int i = 1;i < nums.length; i++) {

13   for(int j = 0;j < nums.length - i;j++) { // 内层循环控制每一轮比较的次数

14   if(nums[j] > nums[j + 1]) { // 每次比较相邻的两个元素的大小

15   // 若前元素大于后元素,则交换位置,先将前元素的数据存入临时变量

16   int temp = nums[j];

17   nums[j] = nums[j + 1]; // 将后元素的数据存到前元素中

18   nums[j + 1] = temp; // 将临时变量数据存储到后元素中

19   }

20   }

21   }

22   System.out.println("=========排序后===========");

23   for(int n : nums) { // 循环输出排序后的数组数据

24   System.out.print(n + " ");

25   }

26   }

27  }

程序运行结果如下:

=========排序前===========

56 32 8 76 12

=========排序后===========

8 12 32 56 76

在例4-8中,先定义了一个整型一维数组,存储了要排序的5个数字。接着使用循环将排序前的这5个数字打印输出,用于和排序后的结果做对比。然后,使用嵌套的for循环实现冒泡排序的算法过程。在嵌套for循环中,外层循环用于控制排序比较的轮数。内层循环用于控制每轮排序比较的次数。在内层循环中,每次做比较时,如果参与比较的两个元素,前面的元素比后面的元素大,则要进行换位。换位的时候,先定义一个临时变量,用于存储参与比较的前面的数组元素,接着把后面的元素存入前面的元素中,最后再把临时变量存储的数据存入后面数组元素中,从而完成元素换位。嵌套循环执行完成之后,数组元素就完成了排序过程。最后,使用循环输出排序后的数组数据。

编程技巧:对于初学者而言,冒泡排序的算法代码不太容易掌握。大家只要记住下面四句话,那么冒泡排序就很容易写出来:N元数组冒泡序,两两相比小前移。外层循环从1始,内层循环减i去。

4.3.2 数组元素的查找

通过排序可以将数组元素排出大小顺序,排序的目的是为了更有效地查找数据。而对于数据的查找,也有很多算法,本节主要讲解顺序查找和二分查找。

1.顺序查找

顺序查找法是最简单的查找方法。可以对数组中的元素按照下标依次对比,直到查找到目标元素或者将所有数组元素遍历完毕为止。顺序查找法的效率较低,比如在N个元素中查找目标数据,平均要查找N/2次。所以,顺序查找法一般用于对少量数据进行查找,或者对未排序数据进行查找。

接下来,演示从有10个元素的整型数组中,使用顺序查找法查找数字13,找到则输出元素在数组中的下标,找不到则进行提示,如例4-9所示。

例4-9 Demo0409.java

1  package com.aaa.p040302;

2

3  public class Demo0409 {

4   public static void main(String[] args) {

5   // 定义长度为10的整形数组,并初始化

6   int[] nums = {34, 32, 45, 67, 98, 43, 31, 47, 13, 22};

7   int searchNum = 13; // 定义要查找的目标数据

8   int index = -1; // 定义变量记录查找到的目标数据位置

9   for(int i = 0; i < nums.length; i++) { // 循环遍历数组,用顺序查找法查找目标数据

10   if(nums[i] == searchNum) { // 判断遍历的当前元素和目标数据是否相等

11   index = i; // 如果相等则记录目标数据在数组中的位置

12   break; // 结束循环

13   }

14   }

15   if(index == -1) { // 循环结束后,判断记录目标数据位置是否为-1

16   // 如果记录的位置为-1,说明没有找到数据

17   System.out.println("在数组中没有要找到的目标数据");

18   }else {

19   // 如果记录的位置不为-1,则说明找到了目标数据

20   System.out.println("找到了目标数据,位置是:" + index);

21   }

22

23   }

24  }

程序运行结果如下:

找到了目标数据,位置是:8

在例4-9中,先定义了一个整型一维数组,存储了10个数字。接着,定义了一个变量存储要查找的目标数据13。然后,定义index变量用于记录查找结果,index变量的初始值设为-1。接着使用循环逐一获取数组中的元素与目标数据进行比较。如果数组元素与目标数据相等,就说明在数组中找到了目标数据,则用index存储当前数组元素的下标位置,然后退出循环。在循环结束后判断index的值,如果index值不是-1,则说明在数组元素中找到了目标数据,index存储了目标数据在数组中的位置,则将该位置打印输出。否则,说明数组中不存在查找的目标值,则打印输出没有找到的提示。

2.二分查找

二分查找法是查找效率比较高的查找算法,该方法的核心是在数组数据有序的基础上,在数组中标记低位和高位以限定查找范围,并用查找范围内的中间位置的数据和目标数据进行比较,不断调整低位和高位的索引位置从而缩小查找范围,最终找到目标值。如果在N个数据的范围内进行二分查找,则平均会执行log2N+1次查找比较。

如图4.8所示,是在长度为10的整型数组中,使用二分查找法,查找数据47的过程。

图4.8 二分查找法

图4.8中,初始定义了长度为10的整型数组,并存储了10个整数。然后,对数组进行排序。接着,按照二分查找法的算法规则,在数组中标记了低位(low)和高位(high)的索引位置,低位的索引位置初始为0,高位的索引位置初始为9(数组长度减1)。然后,通过低位和高位索引位置计算出一个中间位置,计算方法是:中间位置=(低位+高位)/2。根据这个公式计算得出中间位置的索引是4,此时使用该位置上的数据34和目标数据47做对比,目标数据47大于34,那么根据二分查找法的规则,为了缩小查找范围,要将低位索引变为中间索引加1,改变后低位索引的值就是5。接着使用改变后的低位索引和高位索引,重新计算得出一个新的中间位置(5+9)/2 = 7,此时中间位置7对应的数据是47,这个就是查找的目标数据。到此,二分查找法的整个过程结束。

接下来,使用代码演示的二分查找法的具体过程,如例4-10所示。

例4-10 Demo0410.java

1  package com.aaa.p040302;

2

3  public class Demo0410 {

4   public static void main(String[] args) {

5   // 定义长度为10的整形数组,并初始化

6   int[] nums = {34, 32, 45, 67, 98, 43, 31, 47, 13, 22};

7   System.out.println("=========排序前===========");

8   for(int n : nums) { // 循环输出排序前的数组数据

9   System.out.print(n+" ");

10   }

11   System.out.println(); // 输出换行

12   // 使用双重循环实现冒泡排序,外层循环控制排序比较的轮数

13   for(int i = 1;i < nums.length;i++) { // 内层循环控制每一轮比较的次数

14   for(int j = 0; j < nums.length - i;j++) { // 每次比较相邻的两个元素的大小

15   if(nums[j] > nums[j + 1]) {

16   // 如果前元素大于后元素,则交换位置,先将前元素的数据存入临时变量

17   int temp = nums[j];

18   nums[j] = nums[j + 1]; // 将后元素的数据存到前元素中

19   nums[j + 1] = temp; // 将临时变量数据存储到后元素中

20   }

21   }

22   }

23

24   System.out.println("=========排序后===========");

25   for(int n : nums) { // 循环输出排序后的数组数据

26   System.out.print(n + " ");

27   }

28   System.out.println(); // 输出换行

29   System.out.println("=========使用二分查找法===========");

30   // 使用二分法查找数据

31   int searchNum = 47; // 定义要查找的目标数据

32   int index = -1; // 定义变量记录查找到的目标数据的位置

33   int low = 0; // 定义低位索引变量,初始为0

34   int high = nums.length - 1; // 定义高位索引变量,初始为数组长度减1

35   int middle = -1; // 定义中间位置变量,初始为-1

36   do { // 通过循环实现二分查找过程

37   middle = (low + high) / 2; // 计算中间位置

38   if(nums[middle] == searchNum) {// 使用中间位置对应的数据和目标数据比较

39   index = middle; // 如果两个数据相等,则用index存储中间位置

40   break; // 退出循环

41   }

42   // 如果中间数据大于目标数据,则将高位索引设置为中间位置减1

43   if(nums[middle] > searchNum) {

44   high = middle - 1;

45   }else {

46   low = middle + 1; // 否则,将低位索引设置为中间位置加1

47   }

48   }while(low <= high); // 循环条件是低位索引位置小于高位索引位置

49   // 输出二分查找结果

50   if(index == -1) {

51   // 如果index记录的位置是-1,说明没有找到目标值

52   System.out.println("在数组中没有找到目标值");

53   }else{

54   // 如果index记录的位置不是-1,说明找到了目标值

55   System.out.println("找到了目标值:" + searchNum + "它的索引位置是:" + index);

56   }

57   }

58  }

程序运行结果如下:

=========排序前===========

34 32 45 67 98 43 31 47 13 22

=========排序后===========

13 22 31 32 34 43 45 47 67 98

=========使用二分查找法===========

找到了目标值:47 它的索引位置是:7

在例4-10中,先定义了长度为10的整形数组,并存储了10个整数。接着,输出了排序前的数组元素。然后,对数组进行了排序,并输出了排序后的数组元素。接着,定义了二分查找法需要的相关变量。其中,低位索引变量的初始值是0,高位索引变量的初始值是9(数组长度减1)。然后,使用do...while循环实现二分查找法的算法过程。在循环开始的时候,先通过低位索引和高位索引计算出中间位置。接着,使用中间位置上对应的数据和目标数据做对比,如果中间位置上的数据和目标数据相等,那就记录中间位置的索引值,查找到此结束。如果中间位置对应的数据比目标值大,那么根据二分查找法的规则,为了缩小查找范围,要将高位索引设置为中间位置索引减1。如果中间位置对应的数据比目标值小,那么要将低位索引设置为中间位置索引加1。只要低位索引小于等于高位索引,这个循环过程就一直持续,直到找到目标数据为止。最后,在循环结束后,使用记录目标数据索引值的index变量进行判断,如果该变量的值是-1,则提示没有找到目标数据,否则说明找到了目标数据,并将目标值在数组中的索引打印出来。

4.4 本章小结

  Java中使用数组需要3个步骤:声明数组、创建数组对象空间、为数组元素赋值。

  为数组元素赋值可以使用静态初始化、动态初始化、通过数组下标为数组元素赋值。

  访问数组中的元素时,要使用数组名加下标的方式访问。

  通过数组的length属性可以获取数组的长度。

  除了使用for循环结合下标的方式遍历数组,还可以使用foreach方式遍历数组。

  基本类型的数组,其数组元素空间存储的都是数据本身。

  引用类型的数组,其数组元素空间存储的是数据的地址。

  二维数组就是让数组元素再存储一个数组,二维数组有两个维度对应行和列。

  二维数组的内存分配和一维数组类似,也有3种方式:静态初始化、动态初始化、使用数组下标赋值。

  对二维数组而言,非对称型数组是指数组的第二维的长度各不相等。

  冒泡排序的算法核心是相邻两个元素进行比较和换位

  对数组元素查找的常用方法有:顺序查找法和二分查找法

4.5 理论测试与实践练习

1.填空题

1.1 数组的元素通过 来访问,访问数组的长度用 。

1.2 JVM将数组的存储在 (堆或栈)中。

1.3 数组下标访问超出索引范围时抛出 异常。

1.4 数组创建后其大小 (能/不能)改变。

1.5 Java中数组的最小下标是 。

2.选择题

2.1 定义了一维int类型数组a[10]后,下面错误的引用是( )

A.a[0]=1; B.a[10] = 2; C.a[0] = 5*2; D.a[1] = a[2]*a[0];

2.2 下面二维数组初始化语句中 ,正确的是( )

A.float b[2][2]={0.1,0.2,0.3,0.4};

B.int a[][]={{1,2},{3,4}};

C.int a[2][] = {{1,2},{3,4}};

D.float a[2][2]={0};

2.3 数组的第3个元素表示为( )

A.a(3) B.a[3] C.a(2) D.a[2]

2.4 通过哪些方式能够为数组赋值( )

A.静态初始化 B.动态初始化 C.使用数组下标赋值 D.声明数组变量

2.5 关于字符串类型的数组,说话正确的是( )

A.其数组的默认值是’’ B.其数组的默认值是null

C.数组的长度可以任意变化 D.可以存储整形数值

3.思考题

3.1 请简述什么时候为数组分配内存?

3.2 请简述数组一旦被创建,大小能不能改变?

3.3 请简述顺序查找法的过程?

4.编程题

4.1 定义整形数组,存放5个学生的成绩【成绩值自己设定】,将成绩从大到小排序,获得成绩之和,平均成绩,最小成绩,最大成绩。

4.2定义长度为10的整形数组,并存储10个数字。再通过键盘输入一个数字,使用二分查找法在数组中查找输入的数字是否存在。

Java 数据结构

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:GaussDB(DWS)性能调优系列实战篇三:十八般武艺之好味道表定义
下一篇:从零开始—Scrum团队敏捷转型实战案例
相关文章