第5章 字符串和数组
1883
2012-6-20

第五章 字符串和数组

本周提要

一维数组

常用算法

多维数组

经典算法

增强for循环与数组

Java数组是有序的元素集合。数组元素可以是基本类型,也可以是对对象的引用或对其它数组的引用。数组本身也是扩展自Object的对象。本章节将详细介绍一维数组、多维数组及与数组相关算法等。

在面向过程的编程语言中,有这样一个公式:程序=数据结构+算法。虽然Java属于面向对象的编程语言,与面向过程的编程语言有很大的区别,但也有很多联系。例如数组和算法是各门编程语言必不可少的组成部分。要实现数据的高效存储或查询等,就要掌握数组和一些常用的算法,以提高编程效率。

5.1  一 维 数 组

数组的使用过程通常是先定义一个数组类型的变量,然后初始化各个元素,下面将分别介绍与数组相关的内容。

5.1.1  为什么要使用数组

一次Java内部测试结束后,老师给小华分配了一项任务,计算全班(30人)考试成绩的平均分,按照以前我们学习的方法可以定义30个变量:

int stu1 = 95;int stu2 = 89;int stu3 = 79;int stu4 = 64;int stu5 = 76;

int stu6 = 88;…

然后将它们全部加起来,再除以30就得到了平均分,即avg = (stu1+stu2+stu3+stu4+ stu5+…+stu30)/30。如果要统计全系的平均成绩,还这样把每个变量一一相加,那就太烦琐了。生活中也有类似的案例,某个收藏家收藏了很多的古董,为了便于管理,它总是把这些古董分类存放,如图5.1所示。

图5.1  收藏分类

再比如小明是一个比较懒散的人,他特别喜欢听歌,于是买了很多刘德华、张学友等歌手的CD碟。平常听腻了就随处放,当需要找的时候却不知道从何处找起。如果小明是一个会打理生活的人,他可能会去买一个CD盒,为这个CD盒贴上标签,分出哪一部分是刘德华的CD,哪一部分是张学友的CD等。这样在后期查找CD时就比较快捷方便。

在编程时也存在类似问题,有很多不同类型的数据,如int、float、char型等,也需要进行分类存放,这样便于查找。分类存放的具体做法如下。

l   格子提供了存储空间。

l   每一类别都有一个名字。

l   每件物品都有个标号。

与此类似,在Java数据存放中,可不可以把数据归类存放呢?分类存放不同类型的数据举例如图5.2所示。这种数据分类在编程过程中是通过数组来完成的,它可以提高数据的查找效率。

图5.2  数据的分类

5.1.2  什么是数组

前面介绍了变量,一个变量就是一个用来存储数值的命名区域。同样,一个数组就是一个用来存储一系列变量值的命名区域,因此,可以使用数组组织变量。

int i=80;

int[] a={100,98,67,57,78};

变量i和a在内存中的存储情况如图5.3所示。

图5.3  数组在内存中的存储

数组也是一个变量,它存储的是相同数据类型的一组数据。如存储了五个整数的整形数组int[] score={67,64,79,89,95};用图形来描述数组score中的相关概念如图5.4所示。

图5.4  数组的相关概念

数组中的元素应为相同数据类型的数据,除了特殊情况,如“int[ ] a=new int[ ]{3,'a',4,8};”外,其他不同数据类型的数据是不能放到数组中的。

5.1.3  如何使用数组

使用数组需要按以下4个步骤进行。

第1步  声明数组。如:“int[ ] a;”。

第2步  分配数组内存空间。如:“a = new int[5];”。

第3步  给数组元素赋值。如:“a [0] = 80;”。

第4步  处理数据。如:“a [0] = a[0] * 10;”。

为数组元素赋值的示例如图5.4所示。

图5.4  为数组元素赋值

下面主要介绍各个步骤的具体工作该做什么。

1. 声明数组

声明数组主要是告诉数组中存放的数据的类型是什么。

声明数组的语法为:

数据类型    数组名[ ] ;

数据类型[ ]  数组名 ;

以上两种声明方式都可以,更为形象的表示方式是第二种方式,其他语言也经常采用类似的声明方式。通过数组类型,定义此数组类型的变量,例如:

int[ ] score;      //Java成绩

int age[ ];         //年龄

String[ ] name;     //学生姓名

1、Java语言中声明数组时不能指定其长度(数组中元素的个数),因为数组中元素的个数是在使用new创建数组对象时决定的,而不是在声明数组变量时决定的。

2、数组属于引用类型的数据,它在声明时,默认的初始化值为“null”(表示此时没有数据,不可用状态)。

2. 分配空间

分配空间主要是告诉计算机分配几个内存空间给这个数组。

分配空间的语法为:

数组名 = new 数据类型[大小];

示例代码如下:

score = new int[30];

//包含了30个整形变量,它的初始值为0。

sage = new int[6];

//包含了6个整形变量,它的初始值为0。

name = new String[30];

//包含了30个字符串变量,它的初始值为null。

为score数组分配空间示意图如图4.6所示。

图4.6  为数组分配空间示意图

3. 访问

用户可以根据元素在数组中的位置去访问它。比如第一个元素的下标值是0,最后一个元素的下标值是数组的长度减1。可以通过使用数组名加上被方括号括起来的元素下标值来访问数组中的元素。

比如要显示score数组中第五个元素的值即为:System.out.println(score[5]);

4. 赋值

赋值主要是向分配的内存空间里放数据。

示例代码如下:

score[0] = 89;

score[1] = 79;

score[2] = 76;

M

为数组元素赋值的示意图如图4.7所示。

图4.7  为数组元素赋值示意图

这样分别给每一个数组元素赋值的方式比较麻烦,能不能用一段代码给所有的数组元素一起赋值呢?Java中提供了如下几种解决办法。

方法1:边声明边赋值。如:

//静态初始值

int[] score = {89, 79, 76};

//构造并赋值

int[ ] score = new int[]{89, 79, 76};

下面通过示例Array1.java来演示对数组边声明边赋值的用法。

【示例1】Array1.java

public class Array1 {

    /**

     * 数组的使用:

     * 1.告诉计算机声明一个数组并制定数组中存放的数据类型

     * 2.它会根据后面的元素个数自动开辟空间来装数据

     * 3.通过下标来获取数组中的元素值

     */

    public static void main(String[] args) {

        int[] a ={8,9,10,11,10};

        //求出数组中元素的和

        int sum=a[0]+a[1]+a[2]+a[3]+a[4];

        System.out.println("和为:"+sum);

    }

}

输出结果为:

和为:48

方法2:动态地从键盘输入信息并赋值。代码片段如下:

Scanner input = new Scanner(System.in);

for(int i = 0; i < 30; i ++){

     score[i] = input.nextInt();

}

数组中的每一个元素也可以通过键盘输入来动态赋值。下面的程序代码就是通过动态输入来进行数组元素的赋值。

【示例2】Array2.java

import java.util.*;//导包命令后面的章节会详细讲解

public class Array2 {

    /**

     * 数组的使用:

       1.声明一个数组,告诉计算机数组中存放的数据类型,并告诉计算机开辟几个空间

       2.通过循环输入也就是动态输入给数组中每一个元素赋值

     * 3.使用

     */

    public static void main(String[] args) {

        int[] a=new int[5];

        Scanner input=new Scanner(System.in);

        System.out.println("请您输入一组整型数据的值5个:");

        for(int i=0;i<5;i++){

            a[i]=input.nextInt();//给数组中的元素动态赋值

        }      

        int sum=a[0]+a[1]+a[2]+a[3]+a[4];

        System.out.println("和为:"+sum);       

    }

}

输出结果为:

请您输入一组整型数据的值5个:

69

89

90

70

60

和为:378

方法3:通过引用传递来为数组赋值。代码片段如下:

数组是引用类型,可以通过改变变量的引用对象来达到赋值的目的。下面的程序代码就是通过引用传递来为数组元素赋值。

【示例3】Array3.java

public class Array3 {

/**

     * 通过引用传递为数组赋值

     */

    public static void main(String[] args) {

       int a[]={8,9,10,11,10};

        int b[]=a;

       for(int i=0;i<5;i++){

        System.out.println("数组中第"+i+"号元素的名为:b["+i+"]值为"+b[i]);

        }

    }

输出结果为:

数组中第0号元素的名为:b[0]值为8

数组中第1号元素的名为:b[1]值为9

数组中第2号元素的名为:b[2]值为10

数组中第3号元素的名为:b[3]值为11

数组中第4号元素的名为:b[4]值为10

此时的内存状态如图4.8所示。

图4.8  示例3内存状态图

由于变量a和b引用同一个对象,所以如果在更改b所引用的对象内容时,则显示a所对应的内容时会同步变动。

【示例4】Array4.java

public class Array4 {

    /**

     * 通过引用传递为数组赋值

     */

    public static void main(String[] args) {

        int a[]={8,9,10,11,10};

                int b[]=a;

                //未改动前a的内容

        for(int i=0;i<5;i++){

            System.out.print("\ta["+i+"]="+a[i]);

        }

               //更改b的内容

                for(int i=0;i<5;i++){

            b[i]*=i;

        }

               System.out.print("\n");

               //改动后a的内容

        for(int i=0;i<5;i++){

            System.out.print("\ta["+i+"]="+a[i]);

        }

    }

}

输出结果为:

a[0]=8  a[1]=9  a[2]=10 a[3]=11 a[4]=10

a[0]=0  a[1]=9  a[2]=20 a[3]=33 a[4]=40

如果用户需要处理的数据较多,亦即数组的长度并不是很容易就确定,则需要使用数组的length属性,可以通过此属性求出数组的大小。读者朋友可以用length属性重新练习以上示例,以加深对数组的理解。

       另外,数组的大小是不可改变的。即数组创建之后,就不能再改变它的大小了。但是,可以使用同一个引用变量引用一个全新的数组。

       int[] myArray = new int[6];

myArray = new int[10];

在这个示例中,第一个数组实际上已经丢失,除非它的引用在另一个位置被保存。

5.1.4  经验之谈——数组常见错误

常见错误1:没有明确指明数组的大小

错误代码片段如下:

public class Hello1{

     public static void main(String[ ] args){

          //编译错误,没有指明数组的大小

          int[ ] score = new int[ ];

          score[0] = 89;

          score[1] = 63;

          System.out.println(score[0]);

    }

}

声明数组时不管用哪种方式,都要让计算机知道数组的大小。上面代码中声明数组的语句应该改为“int[ ] score = new int[2];”。

常见错误2:数组越界

错误代码片段如下:

public class Hello2{

     public static void main(String[ ] args){

          int[ ] score = new int[2];

          score[0] = 89;

          score[1] = 63;

          //编译错误,数组越界

          score[2] = 45;

          System.out.println(score[2]);

    }

}

数组要是告诉计算机开辟多少内存空间后就固定了,不能更改,更不能超过。如果超过规定的空间就会造成数组越界的错误。上面的代码应该将语句“score[2] = 45;”去掉或改变数组空间的大小,如“int[ ] score = new int[3];”等。

还有一种常见的数组越界的错误代码片断如下:

     public static void main(String[ ] args){

          int[ ] score = new int[]{12,32,45,56,67,87,98};

          for(int i=0;i<=score.length;i++)

          System.out.println(score[i]);

    }

数组最后一个元素的下标值是数组的长度减1,所以此处下标若取数组的长度值显然会产生ArrayIndexOutOfBoundsException的异常。

常见错误3:数组初始化错误

错误代码片段如下:

public class Hello3{

   public static void main(String[ ] args){

       int[ ] score = new int[5];

       score = {60, 80, 90, 70, 85};   //错误  

int[ ] score2 = new int[5] {60, 80, 90, 70, 85};   //错误

       int[ ] score2;

       score2 = {60, 80, 90, 70, 85};

   }

}

编译出错,创建数组并赋值的操作必须在一条语句中完成,如图4.8所示。

应该将上面编译出错的两句代码改为“int[ ] score =new int[ ]{60,80,70,85};”。

常见错误4:没有给数组元素赋值

错误代码片段如下:

public class Hello4{

     public static void main(String[ ] args){

          int[ ] score = new int[3];

          score[0] = 89;

          System.out.println(score[1]);

     }

}

在默认情况下,整型数组中系统会为每一个元素赋一个0值,所以输出为0。

5.2  基 本 算 法

算法就是解决问题的步骤和方法。算法有很多种,不同的算法,它的执行效率也不同。掌握一些常用的编程算法,会对系统性能的提升大有好处。计算机中常用的算法有求最大值、最小值和平均值等。

5.2.1  求平均值、最大值和最小值

1. 求平均值

求平均值的通常做法是将一列数据中的所有元素都相加,然后除以此数列中元素的个数就得到此数列的平均值。根据此思路求解的示意图如图4.9所示。

图4.9  求平均值

【示例6】Array6.java

给定一些数如21、30、10、20、20、20、40、0,请你计算这些数的总和及平均数。代码如下:

import java.util.*;

public class Array6 {

    /**

     *思路:

     *1.定义一个数组并装入各个值

     *2.定义一个装和的变量sum,定义一个装平均数的变量avg

     *3.通过循环累加求和

     *4.通过和除以个数得出平均数

     */

    public static void main(String[ ] args) {

        int a[ ]={20,30,10,20,20,20,40,0};

        double sum=0;

        double avg=0;

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

            sum=sum+a[i];

        }

        avg=sum/a.length;

        System.out.println("和为:"+sum+"平均数为:"+avg);

    }

}

求平均值时可能会含带小数,所以平均数类型设置为double类型。而这些数字的个数肯定为整形。对计算机而言如果和的类型设置为整数,则结果也将为整数。为了得到较精确的平均值,所以需要将和的类型设置为double类型。

输出结果为:

和为:160.0平均数为:20.0

上面的示例给出的是固定值,在实际的应用中,参与计算的值一般是用户从键盘输入的,接下来看看如果要求实现通过键盘输入10个学员的成绩并计算总成绩和平均成绩的功能,该如何编写代码:

【示例7】Array7.java

import java.util.*;

public class Array7 {

    /**

     * 动态给数组赋值并计算总数和平均数

     */

    public static void main(String[ ] args) {

        int a[ ]=new int[10];

        Scanner input=new Scanner(System.in);

        System.out.println("请您输入10个学员的成绩:");

        double sum=0;//装和

        double avg=0;//装平均数

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

            a[i]=input.nextInt();

            sum=sum+a[i];

        }

        System.out.println("您输入的10个学员的成绩总成绩为:"+sum);

        System.out.println("平均成绩为:"+sum/a.length);

    }

}

输出结果为:

请您输入10个学员的成绩:

77

88

77

65

90

100

98

98

89

78

您输入的10个学员的成绩总成绩为:860.0

平均成绩为:86.0

2. 求最大值

在日常生活中,经常需要找出一列数据中最大的那个数。例如,打擂台时,有1个人站在擂台上,第2个人和他比武。如果打赢了他,则留在擂台上。第3个人和擂台上的人比武,谁赢了谁就留在台上。以此类推,最后留在台上的就是擂主,如图5.10所示。

图5.10  打擂台示意图

通过打擂台,可以判断出谁是武功最厉害的人。同样,一列数据中也可以通过一一比较来得出最大值。比如从键盘输入本次Java考试的5个学生成绩,求最高分,如图5.11所示。

图5.11  求最大值比较图

将数组中的第一个元素拿出来放到一个变量中,然后将数组剩下的各个元素依次与这个变量进行比较,哪个元素的值更大就留在这个变量中,如图5.12所示。

图5.12  找最大值示意图

变量中的数据与数组中最后一个元素比较后,最大值就已经装到此变量中了,也就是此数组中的最大值。类似的java代码如下所示:

max = stu[0] ;

if (a[1]>max ){

     max=a[1] ;

}

if (a[2]>max ){

     max=a[2] ;

}

if (a[3]>max ){

     max=a[3] ;

}

但是,这样书写代码比较麻烦,且不够通用,使用循环可以简单地解决这个问题。

【示例8】Max.java

求一列数据中最大值的代码如下:

public class Max{

    /**

     * 求一列数据中的最大值

     */

    public static void main(String[ ] args) {

        int a[ ]={88,99,33};

        //通过循环扫描数组

        //使用max存储擂主初始值:第一个元素为擂主

        int max=0;        //假设擂主的值为0;

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

            if(a[i]>max){       

                max=a[i];        //哪个元素的值更大就赋值给max变量

            }

        }

        System.out.println("本列数据中最大值是:"+max);

    }

}

输出结果为:

本列数据中最大值是:99

3. 求最小值

求最小值的思路和求最大值是类似的。只需要将打输的人留在擂台上就行。Java代码中只需要将比较判断代码稍微改动一下就可以了。下面是代码实现。

【示例9】Min.java

public class Min{

    /**

     * 求一列数据中的最小值

     */

    public static void main(String[ ] args) {

        int a[ ]={88,99,33};

        //通过循环扫描数组

        int min=0;        //假设擂主的值为0;

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

            if(a[i]<min){     

                min=a[i];  //哪个元素的值更小就赋值给min变量

            }

        }

        System.out.println("本列数据中最小值是:"+min);

    }

}

输出结果为:

本列数据中最小值是:33

5.2.2  递归调用

递归是程序语言中的一个很基础的算法,学习好这个算法,对于理清程序编码的思路非常有帮助。所以在本章节把递归也作为学习的一部分内容。希望读者朋友了解并掌握它的相关用法。

在中学时期都学过数学归纳法,例:求 n!

比如要求5! ,必须先求出4!,要求3!,必须先求2!,要求1!,就必须先求0!,而0!=1,所以1!=0!*1=1,再进而求2!,3!。分别用函数表示,则如图:

图5.13  5!分析示意图

读者可以从上面观察到,除计算1!子程序外,其他的子程序基本相似,可以设计这么一个子程序:

int factorial(int i){

int res=0;

res=factorial(i-1)*i;

return res;

}

那么当执行主程序语句s=factorial(5)时,就会执行factorial(5),但在执行factorial(5),又会调用factorial(4),这时大家要注意,factorial(5)和factorial(4)虽然是同一个代码段,但在内存中它的数据区是两份!而执行factorial(4)时又会调用factorial(3),执行factorial(3)时又会调用factorial(2),每调用一次factorial函数,它就会在内存中新增一个数据区,那么这些复制了多份的函数大家可以把它看成是多个不同名的函数来理解;

但上面这个函数有点问题,在执行factorial(0)时,它又会调用factorial(-1)…造成死循环,也就是说,在factorial函数中,需要在适当的时候保证不再调用该函数,也就是不执行res=factorial(i-1)*i;这条调用语句。把上例补充完整如下所示。

public class Recursion{

    public static void main(String[] args) {

            int i=5;

              int b=getResult(i);

              System.out.println("运算的结果为:"+b);

    }  

        public static int getResult(int n){

           if(n>0)

             return getResult(n-1)*n;

           else

             return 1;

        }

}

裴波那切数列是一个比较经典的数学推理题,这个数列的第一位和第二位值均为1,其它位数的值均为前面两位的和,如图5.15所示。请你用递归算法求出第20位上的数值。

图5.14  裴波那切数列分析示意图

递归调用在明白原理的情况下,操作起来比较容易,用递归来解决裴波那切数列问题的代码如下所示。

public class RecursionArray{

    public static void main(String[] args) {

        int i=20;

                System.out.println(“值是:”+getResult(i));

    }  

        public static int getResult(int n){

           if(n==1||n==2){

             return 1; 

           }else

             return getResult(n-2)+getResult(n-1);

        }

}

输出结果为:

值是:6765

5.2.3  数组排序

在处理数据过程中,经常要将杂乱的数据进行排序,以便更好地显示或操作。例如,循环录入5个学员的成绩,进行升序排列后输出结果。

数组排序可以使用java.util.Arrays类,java.util包提供了许多存储数据的结构和有用的方法。Arrays类提供了许多方法操作数组,如:排序、查找等方法。Arrays类的sort()方法就是用来对数组进行升序排列的方法。如果需要详细的了解Arrays类提供的相关方法,请打开API文档,查找Arrays类,界面如图5.15所示。

图5.15  Arrays类的文档截图

Arrays类暂时不用体会它是什么意思,只要知道它提供了排序数据的方法就可以了。在后续章节的学习过程中将涉及该类的相关概念。

【示例10】Array9Sort.java

通过键盘任意输入一组数并将数据存放到数组中,然后对数组中的元素进行升序排序。

import java.util.*;

public class Array9Sort {

    /**

     * 通过Arrays.sort(数组名);实现数据排序

     */

    public static void main(String[ ] args) {

        Scanner input=new Scanner(System.in);

        System.out.print("请您输入五位学员的成绩:\n");

        int a[ ]=new int[5];

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

            System.out.print("请您输入第"+(i+1)+"位学员的成绩\t");

            a[i]=input.nextInt();

        }

        //循环输入数据

        System.out.println("您输入的分数的数据排序前为:");

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

            System.out.print(a[i]+"\t");

        }

        Arrays.sort(a);    //按升序对数组a排序

        //循环输出数据

        System.out.println("\n您输入的分数的数据排序后为:");

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

            System.out.print(a[i]+"\t");

        }

    }

}

输出结果为:

请您输入五位学员的成绩:

请您输入第1位学员的成绩   76

请您输入第2位学员的成绩   89

请您输入第3位学员的成绩   90

请您输入第4位学员的成绩   100

请您输入第5位学员的成绩   59

您输入的分数的数据排序前为:

76  89  90  100 59

您输入的分数的数据排序后为:

59  76  89  90  100

本小节主要讲解了使用Arrays类的sort()方法进行排序,读者朋友可以学习后面的经典算法,然后编写自己的排序方法。

5.2.4  数组复制

普通的变量只需要通过赋值便可以实现变量的复制,但数组是一列数据,要实现数组的复制可以使用循环进行数组复制,也可以使用arraycopy()方法实现数组复制。

1. 完全复制数组

通过for循环来完成数组复制的功能,下面是代码实现。

【示例11】ArrayCopy.java

public class ArrayCopy{

    /**

     *数组的复制

     */

    public static void main(String[ ] args) {

        //变量的赋值与复制

        int a=8;

        int b;

        b=a;

        System.out.println(b);     

        //数组是否能这么赋值?

        int[ ]  c={89,98,68};

        int[ ]  d;

        d=c;

        System.out.println(d);     

        //数组的复制

        int[ ] m={9,7,5,90,87,56,45};

        int[ ] n=new int[m.length];    

        System.out.println("m数组中对应的每一个元素的输出默认之前为:");

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

            System.out.print(m[i]+"\t");

        }

         //---------------------------复制前数组n为

        System.out.println("\nn数组中对应的每一个元素的输出默认之前为:");

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

            System.out.print(n[i]+"\t");

        }  

        //将m数组中的每一个元素赋值到n数组中对应的每一个元素中

        for(int i=0;i<m.length;i++){//-------复制数组

            n[i]=m[i];  //n[0]=m[0];  n[1]=m[1];......

        }  

        //n中的值循环输出-----------------------复制后数组n为

        System.out.println("\n将m数组中的每一个元素赋值给n数组中对应的每一个
            元素后n数组的输出为:");

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

            System.out.print(n[i]+"\t");

        }

    }

}

输出结果为:

8

[I@182f0db

m数组中对应的每一个元素的输出默认之前为:

9   7   5   90  87  56  45 

n数组中对应的每一个元素的输出默认之前为:

0   0   0   0   0   0   0  

将m数组中的每一个元素赋值给n数组中对应的每一个元素后n数组的输出为:

9   7   5   90  87  56  45

只有通过循环才能将数组中的每一个元素赋值到目标数值中,达到数组复制的目的。目标数组的大小不能小于源数组的大小,否则运行时会出现异常。

2.部分复制数组

如果要复制数组中的部分元素,则for循环的操作就有些力不从心了。此时建议使用System类所提供的arraycopy()方法实现数组复制,arraycopy()方法的API文档及分析如图5.16所示。

图5.16  arraycopy方法的文档及应用分析图

arraycopy()方法可以指定要源数组的位置及长度,还可以指定拷贝到目标数组的位置,具体的用法通过示例12来演示。

【示例12】ArrayCopyBySystem.java

import java.util.Arrays;

public class ArrayCopyBySystem{

    /**

     * 用arraycopy()方法实现部分数组元素的复制

     */

    public static void main(String[ ] args) {

                int[] a = {3,6,8,11,9,12,5};

                int[] b= new int[12];

                System.out.println("复制开始之前a中元素为:");

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

                System.out.print(a[i]+"  ");

            }

                System.out.println("");

                System.out.println("复制开始之前b中元素为:");

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

                System.out.print(b[i]+"  ");

            }

                System.arraycopy(a,1,b,4,6);

                System.out.println("");

                System.out.println("复制开始之后b中元素为:");

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

                System.out.print(b[i]+"  ");

            }

    }

}

输出结果为:

复制开始之前a中元素为:

3  6  8  11  9  12  5

复制开始之前b中元素为:

0  0  0  0  0  0  0  0  0  0  0  0

复制开始之后b中元素为:

0  0  0  0  6  8  11  9  12  5  0  0

在JDK 6中,也为Arrays类别新增了数组复制的copyOf()方法,用法与arraycopy方法类似。

5.2.5  代码优化

在前面的代码中,如ArrayCopy.java和ArrayCopyBySystem.java的代码中多处出现了for循环,除了变量名之外其它语句都相同,如何优化这一块儿的代码呢?需要创建一个类似于main的方法。

方法其实就是一块可以重复调用的代码端,现在回顾一下main方法的格式。

public static 方法返回值 方法名([参数类型 参数名]) {

       语句;

    [return 返回值]

}

当然这样写出来的方法是可以直接在main方法中被调用的,方法的定义在Java语言中还有其它形式,在本章节暂不考虑其它形式。

比如要创建一个显示数组内容的方法printArray,它的详细代码如下所示。

public static void printArray(int[] arry)) {

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

                System.out.print(arry[i]+"  ");

            }

}

本小节正是建议用户朋友通过提取公有部分的代码来实现代码优化的效果。下面看看对ArrayCopyBySystem优化后的代码:

import java.util.Arrays;

public class ArrayCopyBySystem{

    /**

     * 优化后ArrayCopyBySystem的代码

     */

    public static void main(String[ ] args) {

                int[] a = {3,6,8,11,9,12,5};

                int[] b= new int[12];

                System.out.println("复制开始之前a中元素为:");

                printArray(a);

                System.out.println("");

                System.out.println("复制开始之前b中元素为:");

                printArray(b);

                System.arraycopy(a,1,b,4,6);

                System.out.println("");

                System.out.println("复制开始之后b中元素为:");

                printArray(b);

    }

    //输出数组元素的公用方法

public static void printArray(int[] arry)) {

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

                System.out.print(arry[i]+"  ");

            }

}

}

1、方法中的参数称之为形式参数,它不需要被初始化。
       2、如果要定义有返回值的方法,除了使用return语句外还需要将void换为相应的类型。

5.3  多 维 数 组

多维数组可以看做是数组的数组,如果将多维数组看做是比较特殊的一维数组,那么数组的元素本身就是数组。在学习多维数组之前,再来回顾一下多重循环的相关内容。

5.3.1  多重循环

所谓的多重循环就是在循环体中再嵌入循环。下面通过两个问题的解决来介绍这方面的知识。

先来看这样一个问题,某次程序大赛,3个班级各有4名学员参赛,计算每个班级参赛学员的平均分。

问题分析:共3个班级,循环3次计算每个班级参赛学员的平均分,每班4名参赛学员,循环4次累加总分,可以通过while、do…while或for循环相互嵌套来解决此问题。下面是代码实现。

【示例13】ForFor.java

import java.util.*;

public class ForFor {

    /**

     * 某次程序大赛,3个班级各有4名学员参赛,计算每个班级参赛学员的平均分

     * 分析: 共3个班级,循环3次计算每个班的平均分,每班4名学员,循环4次累加总分

     */

    public static void main(String[ ] args) {

        Scanner input=new Scanner(System.in);

        //外层循环要循环三次

        for(int i=0;i<3;i++){//控制3个班级

            int sum=0;//内层循环要计算每个班级的参赛学员的总分

            System.out.println("请您输入第"+(i+1)+"个班级的四个学员的分数:");

            //内层循环结束后,才执行外层循环的语句

            //也就是外层循环执行一次循环体,内层循环必须执行完

            for(int j=0;j<4;j++){//控制每一个班级的四个参赛学员的总分

                System.out.println("请您输入第"+(j+1)+"个学员的成绩:");

                int score=input.nextInt();

                sum=sum+score;

            }      

            double avg=sum/4;

            System.out.println("第"+(i+1)+"个班级的平均分是:"+avg);

        }

        System.out.println("计算完毕!!");

    }

}

输出结果为:

请您输入第1个班级的四个学员的分数:

请您输入第1个学员的成绩:

33

请您输入第2个学员的成绩:

88

请您输入第3个学员的成绩:

90

请您输入第4个学员的成绩:

80

第1个班级的平均分是:72.0

请您输入第2个班级的四个学员的分数:

请您输入第1个学员的成绩:

98

请您输入第2个学员的成绩:

100

请您输入第3个学员的成绩:

90

请您输入第4个学员的成绩:

87

第2个班级的平均分是:93.0

请您输入第3个班级的四个学员的分数:

请您输入第1个学员的成绩:

56

请您输入第2个学员的成绩:

59

请您输入第3个学员的成绩:

98

请您输入第4个学员的成绩:

99

第3个班级的平均分是:78.0

计算完毕!!

再来看另外一个问题,如何用*打印一个直角三角形图案,如图5.17所示。

图5.17  直角三角形图案

问题分析:在二重循环中,外层循环控制行数,内层循环打印每行的 *。图案的特点是每行*的个数与行数相等。下面是代码实现。

【示例14】RightTriangle.java

public class RightTriangle{

    /**

     * 外层控制行数,内层控制*号数

     */

    public static void main(String[ ] args) {

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

         //内层循环的结束条件与外层循环变量有关

            for(int j=0;j<i;j++){

                System.out.print(" *");    //打印*

            }

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

        }

    }

}

5.3.3  二维数组

二维数组使用名称与两个索引来指定存取数组中的元素。如一个整形的二维数组a的声明为int a[][]。对a[][]进行静态初始化:int a[][]={{1,2},{3,4,5,6},{7,8,9}};可以把二维数组理解为一个数组中嵌套了一个数组。如果用i代表行,用j代表列,则用平面图表述数组a的结构如图5.18所示。

图5.18   数组a的平面结构图

根据图形可知,a[0][1]的值为2;a[1][2]的值为5;a[2][1]的值为8;并且还可以通过数组的length属性获取数组中元素的个数。可以通过如下的代码进行验证结果。

public static void main(String[ ] args) {

            int[][] b={{1,2},{3,4,5,6},{7,8,9}};

                   System.out.print(b[0][1]);

                   System.out.print(b[1][2]);

System.out.print(b[2][1]);

System.out.print(“二维数组的长度为:”+b.length);

System.out.print(“二维数组第一行中元素的个数为”+b[0].length);

                }

    }

二维数组中的元素一般都是结合二重循环来赋值或输出的,外循环控制行数,内循环控制列数,如图5.19所示:

图5.19   数组a理解为嵌套数组示意图

        int[][] arr = {{1,2},{3,4,5,6},{7,8,9}};

        //先控制循环的行数

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

        //再控制循环的列数

            for(int j = 0; j < arr[i].length; j++)

                System.out.print(arr[i][j] + " ");

            System.out.println();

        }

二维数组对象:以对象的方式来配置一个二维数组对象,如图5.20所示。例如:

int[ ][ ] arr = new int[2][3];

图5.20  二维数组的内存示意图

【示例17】ArrayArray.java

二维数组也可以先分解为两个一维数组的形式,然后再分别输出数据,代码如下:

public class ArrayArray{

    /**

     *二维数组也可以先分解为两个一维数组的形式,然后再分别输出数据

     */

    public static void main(String[ ] args) {

        int[ ][ ] arr = { { 1, 2, 3 }, { 4, 5, 6 } };

        int[ ] foo = arr[0]; // 将arr[0]对象引用赋值给foo

        print(foo);

        System.out.println();

        foo = arr[1]; // 将arr[1]对象引用赋值给foo

        print(foo);

        System.out.println();

    }

     public static void print(int[] c){

           for(int i=0;i<c.length;i++)

             System.out.print(" "+c[i]);

           System.out.println("");

        }

}

输出结果为:

1 2 3

4 5 6

也可以使用new关键字配置二维数组并同时指定初值,如:

int[ ][ ] arr = new int[ ][ ] {{1, 2, 3},{4, 5, 6}};

类似的三维数组的初始化形式为:

int[ ][ ][ ] arr = { {{1, 2, 3}, {4, 5, 6}}, {{7, 8, 9}, {10, 11, 12}} };

一般三维数组最左边的一个数据一定要告诉计算机准备多少内存存放第一维元素,如:

int[ ][ ][ ] arr1 = new int[2][ ][ ];

也可以三个都指定,如:

int[ ][ ][ ] arr = new int[2][2][3];

【示例18】ArrayArrayArray.java

虽然一般在Java编程过程中很少涉及三维以上的数组,但是Java语言还是支持三维以上的数组定义,如下面的代码所示。

public class ArrayArrayArray {

    /**三维和四维数组:

     * int[ ][ ][ ] arr = {

                 {{1, 2, 3}, {4, 5, 6}},

                 {{7, 8, 9}, {10, 11, 12}}

                };

     */

    public static void main(String[ ] args) {

        int[ ] arr={1,2,3};

        int[ ][ ][ ] arr1={

                        {{3,4,2},{293,44,22}},

                        {{98,22,32},{33,44,22}},

                        {{98,22,32},{33,44,22}}

                       };

        int[ ][ ][ ] arr2=new int[3][ ][ ];//至少规定一个空间大小而且是最前面的

        int[ ][ ][ ][ ] arrr4=new int[3][ ][ ][ ];

    }

}

在多维数组中,最多用到三维数组,三维以上的基本不会使用到。因此就不过多地讨论了,感兴趣的读者可以查阅相关文档。

5.4  经 典 算 法

对于初学Java的读者来说,算法虽然不是最重要的部分,但是了解经典的算法有助于提高编程水平,本节主要介绍几个经典的算法:冒泡排序、插入排序和快速排序。

5.4.1  冒泡排序

冒泡排序是一种简单的排序算法。冒泡排序将一个列表中的两个元素进行比较,并将最小的元素交换到顶部。从最底部的元素开始比较,两个元素中较小的会冒到顶部,而较大的会沉到底部,该过程将被重复执行,直到所有元素都被排序。

冒泡排序就好像学生做操进场一样,要在老师的指导下,以某学生为基准,按高矮次序排队。冒泡排序法如图5.21所示。

图5.21  冒泡排序示意

以图5.19所示的冒泡排序为例,每次比较相邻的两个数值,值小的交换到前面,每轮结束后值最大的数交换到了最后。第一轮需要比较4次;第二轮需要比较3次;第三轮需要比较2次;第四轮只需要比较1次。

那么如何用二重循环将5个数字排序呢?5个数字存放在一维数组中,外层循环控制比较多少轮,循环变量为i;内层循环控制每轮比较多少次,循环变量为j,如图5.22所示。

图5.22  冒泡排序解析

【示例19】MathDemo.java

下面是通过二重循环来实现的冒泡排序。实现代码如下。

public class MathDemo{ 

    public static void main(String[ ] args) {

                int a[] ={16,25,9,90,23};

                System.out.print("排序之前的数组:");

                printArray(a);

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

                  for(int j=0;j<a.length-1-i;j++){

                   //判断两个数的大小

                   if(a[j]>a[j+1]){

                       int temp = a[j+1];

                       a[j+1]=a[j];

                       a[j]=temp;

                    }

                  }                 

                }

                System.out.print("\n");

                System.out.print("排序之后的数组:");

                printArray(a);

    }       

    public static void printArray(int[] arry){

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

             System.out.print(arry[i]+"  ");

       }

}

}

输出结果为:

排序之前的数组:16  25  9  90  23

排序之后的数组:9  16  23  25  90

为了更好的理解这个冒泡排序,这里提供了一个记忆口诀(升序)如下:

                     N个数字来排队;

                     两两相比小靠前;

                     外层循环N-1;

                     内层循环N-1-i。

其中,两个变量的值交换位置,可以把两个变量比作成一杯红水和一杯蓝水,要想两个杯中的水互换,可以定义一个临时变量,也就是增加一个空杯子。先将红水倒入临时的空杯子,如:语句“temp=a[j];”;然后将蓝水倒入刚倒空的装红水的杯子中,如:语句“a[j]=a[j+1];”;最后将临时杯子中的红水倒入刚倒空的装蓝水的杯子中,这样就完成了两个变量中值互换的目的。交换后最好是通过程序调试的方法查看各个变量的变化情况。

5.4.2  插入排序

假设有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法——插入排序法,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据。插入排序算法类似于玩扑克时抓牌的过程,玩家每拿到一张牌都要插入到手中已有的牌里,使之从小到大排好序。如图5.23所示(该图出自[算法导论])。

图5.23  扑克牌插入截取

也许你没有意识到,但其实你的思考过程是这样的:现在抓到一张7,把它和手里的牌从右到左依次比较,7比10小,应该再往左插,7比5大,好,就插这里。为什么比较了10和5就可以确定7的位置?为什么不用再比较左边的4和2呢?因为这里有一个重要的前提:手里的牌已经是排好序的。现在我插了7之后,手里的牌仍然是排好序的,下次再抓到的牌还可以用这个方法插入。

编程对一个数组进行插入排序也是同样道理,但和插入扑克牌有一点不同,不可能在两个相邻的存储单元之间再插入一个单元,因此要将插入点之后的数据依次往后移动一个单元。如对一个数组{15,3,56,1,78,12,7,99,123,90,63}运用插入排序进行操作,操作的步骤解析如图5.24所示。

图5.24  插入算法解析

从上图可了解插入算法的过程:比较a[0]和a[1]的值,然后再比较a[2]与a[1]的大小,本例中a[2]的值比a[1]的大,所以直接插入在a[1]之后。在a[3]和a[2]比较时,a[3]比a[2]小,则56向后移动一位,再比较[3]的位置,a[3]

把这个过程用代码来实现,如示例20所示。

【示例20】InsertSort.java

public class InsertSort {

public static void main(String[] args) {

        int[] a = {234,67,88,99,15,3,56,1,78,12,7,99,123,90,63};

                System.out.println("未排序前的数组元素");

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

            System.out.print("  "+a[i]);

        }

        a = insertSort(a);

                 System.out.println("");              

                System.out.println("未排序后的数组元素");

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

            System.out.print("  "+a[i]);

        }

    }

    public static int[] insertSort(int[] a) {

                int j=0;

                int temp =0;

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

                    temp =a[i];

                    //执行插入操作

                    for(j=i-1;j>=0&&a[j]>temp;j--){

                        a[j+1]=a[j];

                    }

                     //将插入值归位

                    a[j+1]=temp;

                }

        return a;

    }

}

输出结果为:

未排序前的数组元素

  234  67  88  99  15  3  56  1  78  12  7  99  123  90  63

未排序后的数组元素

  1  3  7  12  15  56  63  67  78  88  90  99  99  123  234

insertSort方法有返回类型,所以需要使用return语句。

5.4.3  快速排序

快速排序是对冒泡排序的一种改进。它的基本思想是:将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按这方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

快速排序的步骤如下所示。

假设要排序的数组是a[0]……a[n],首先任意选取一个数据(通常选用第一个数据)作为关键数据,然后将所有比小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一躺快速排序。一躺快速排序的算法是:

  1、设置两个变量i、j,排序开始的时候i=1,j=n;

  2、以第一个数组元素作为关键数据,赋值给x,即x=a[0];

  3、从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于x的值,两者交换;

  4、从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于x的值,两者交换;

  5、重复第3、4步,直到i=j;

上例中的数组a用快速排序进行操作的示意图如图5.25所示。

图5.25  快速排序算法解析

用快速排序对数组a进行排序的代码如下所示。

public class QuickSort {

    public static void printArray(int[] arry) {

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

            System.out.print(arry[i] + "  ");

        }

        System.out.println("");

    }

    /** 主方法 */

    public static void main(String[] args) {

        // 声明数组

        System.out.println("排序之前的数组:");

        int[] nums = { 67, 88, 99, 15, 3, 56, 1, 78, 63 };

        printArray(nums);

        System.out.println("排序之后的数组:");

        quickSort(nums, 0, nums.length - 1);

        printArray(nums);

    }

    /** 快速排序方法 */

    public static void quickSort(int[] a, int left, int right) {

        int low = left;

        int high = right;

        if (low >= high)

            return;

        // 确定指针方向的逻辑变量

        boolean transfer = true;

        while (low != high) {

            if (a[low] > a[high]) {

                // 交换数字

                int temp = a[low];

                a[low] = a[high];

                a[high] = temp;

                // 决定下标移动,还是上标移动

                transfer = (transfer == true) ? false : true;

            }

            // 将指针向前或者向后移动

            if (transfer)

                high--;

            else

                low++;

            // 显示每一次指针移动的数组数字的变化

            /**

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

                System.out.print(a[i] + ",");

            }

            System.out.print(" (low,high) = " + "(" + low + "," + high + ")");

            System.out.println("");

              */

        }

        // 将数组分开两半,确定每个数字的正确位置

        low--;

        high++;

        quickSort(a, left, low);

        quickSort(a, high, right);

    }

}

读者朋友可以将注释去掉查看快速排序执行的每一个步骤。输出结果为:

排序之前的数组:

67  88  99  15  3  56  1  78  63 

排序之后的数组:

1  3  15  56  63  67  78  88  99 

5.4.4  选择排序

在介绍选择排序法之前先介绍一种把最小的数放在第一个位置上的算法,当然也可以用前面所讲的冒泡排序法,现在我们改用一种新的算法:其指导思想是先并不急于调换位置,先从a[0]开始逐个检查,看哪个数最小就记下该数所在的位置p,等一躺扫描完毕,再把a[p]和a[1]对调,这时a[0]到a[9]最小的数据就换到了最前面的位置。算法的步骤如下:

1、先假设a[0]的数最小,记下此时的位置p;

2、依次把a[p]和a[i](i从2变化到9)进行比较,每次比较时,若a[i]的数比a[p]中的数小,则把i的值赋给p,使p总是指向当前所扫描过的最小数的位置,也就是说a[p]总是等于所有扫描过的数最小的那个数。在依次一一比较后,p就指向10个数中最小的数所在位置,即a[p]就是10个数中最小的那个数;

3、把a[p]和a[0]的数对调,那么最小的数就在a[0]中去了,也就是在最前面了。

如果现在重复此算法,但每重复一次,进行比较的数列范围就向后移动一个位置。即第二遍比较时范围就从第2个数一直到第n个数,在此范围内找最小的数的位置p,然后把a[p]与a[2]对调,这样从第2个数开始到第n个数中最小数就在a[2]中了,第三遍就从第3个数到第n个数中去找最小的数,再把a[p]与a[3]对调……此过程重复n-1次后,就把a数组中n个数按从小到大的顺序排好了。这种排序的方法就是选择排序法。如果用图形来描述选择排序的过程,如图5.26所示。

图5.26  选择排序算法解析

把上述内容用代码实现,如示例22所示。

【示例20】SelectionSort .java

public class SelectionSort {

          //data是目标数组,size是数组的长度

        public static void selectionSort(int[] data,int size)

        {

            //min假设最小值数组元素的下标值

int i=0,j=0,min=0;

            for(i=0;i<size-1;i++)

            {

                min=i;

                for(j=i+1;j<size;j++)

                {

                    if(data[j]<data[min])

                    {

                        min=j;

                    }

                }

                int temp=data[min];

                data[min]=data[i];

                data[i]=temp;

            }

        }

    public static void print(int[] c) {

        for (int i = 0; i < c.length; i++)

            System.out.print(" " + c[i]);

        System.out.println("");

    }

    public static void main(String[] argv) {

        System.out.println("排序之前的数组:");

        int[] pData = { 67, 88, 99, 15, 3, 56, 1, 78, 63 };

        print(pData);

        System.out.println("排序之后的数组:");

        selectionSort(pData, pData.length - 1);

        print(pData);

    }

}

输出结果为:

排序之前的数组:

 67 88 99 15 3 56 1 78 63

排序之后的数组:

 1 3 15 56 67 78 88 99 63

这里主要介绍了几种常用的算法。因为本书的内容并不打算深入讲解算法,所以建议想深入学习算法的读者朋友参看专业书籍或查阅专业文档。

5.5  增强for循环

在Java SE 5.0之前,输出一列数据可以按如下方法:

int[ ] arr = {1, 2, 3, 4, 5};

for(int i = 0; i < arr.length; i++)

    System.out.println(arr[i]);

但在Java SE 5.0之后,提出了一个增强for循环的语句,如:

int[ ] arr = {1, 2, 3, 4, 5};

for(int temp : arr)

    System.out.println(temp);

其中,temp是一个临时变量,用来存放每次遍历到的元素。如果是对象的话也可以进行输出,如:

String[ ] names = {"caterpillar", "momor", "bush"};

for(String name : names)

     System.out.println(name);

二维数组通过增强for循环也可以进行输出,如:

int[ ][ ] arr = {{1, 2, 3},

          {4, 5, 6},

          {7, 8, 9}};

for(int[ ] row : arr) {

    for(int element : row) {

        System.out.println(element);

    }

}