【Java】一文解决数组问题,让你轻松掌握

前言:前面两章我们详细讲解了Java基本程序设计结构中的基本知识,,包括:一个简单的Java应用,注释,数据类型,变量与常量,运算符,字符串,输入输出,控制流,大数值。 本篇将介绍最后一环——“数组”,带领大家更全面透彻地了解Java中数组的相关知识。

目录

  • 一.数组的基本概念
  • 二.数组的声明和初始化
  • 2.1数组的声明
  • 2.2数组的初始化【重要】
  • 三.访问数组元素
  • 3.1数组下标/索引
  • 3.2 for循环访问数组元素
  • 3.3 for each循环
  • 四.命令行参数
  • 五.初始JVM的内存分布
  • 六.数组中常见的各类方法
  • 6.1数组拷贝
  • 6.2数组排序
  • 6.3数组查找
  • 6.4数组填充
  • 6.5数组比较
  • 七.多维数组【二维数组】
  • 八.不规则数组
  • 一.数组的基本概念

    数组即存储着相同类型值的序列。
    从定义中我不可以得到以下结论:

    1.数组是一种数据结构,是用来存储同一类型值的集合;
    2.数组中存放的值连续的;
    3.每个空间有自己的编号,其实位置的编号为0,即数组的下标。

    举个例子:在java中,包含6个整形类型元素的数组,就相当于下图中连在一起6个车位,从下图中可以看到:

    那么,我们如何定义,使用一个数组呢?

    二.数组的声明和初始化

    2.1数组的声明

    俗话说:变量先声明后使用。首先我们看看如何声明一个数组。
    通过以下方式我们可以声明一个Java数组,在声明数组变量的时候,需要指出数组类型和数组变量的名字:

    int[] array1;//array:英文单词表示数组
    double[] array2;
    boolean[] array3;
    //...
    

    这时我们就成功声明了一个array数组。看到这里,一些有C语言基础的小伙伴就会有一种“熟悉的陌生人”的感受,因为C语言是这样声明一个数组的:

    int arr[];//C语言实现方式
    

    两者看上去非常相似,仅仅只是“[ ]”的位置不同,实际上,在Java中,这两种形式都可以定义声明一个数组变量。只不过,大多数Java程序员更倾向于第一种方式,因为这样可以清晰地将变量类型和变量名区分开来,显得更加有逻辑性。(相当于我创建了一个名为array的变量,变量的类型是int[ ]).

    2.2数组的初始化【重要】

    在Java中,数组的初始化方式相对比较灵活,接下来我们一一讲解不同的初始化方式:
    方法一:直接法

    //对于一中我们声明的数组我们仅仅声明了变量array,并没有将它初始化为一个真正的数组
    //方法一:直接初始化法
    int[] array={1,2,3,4,5};
    //这种方式直接指明了数组中元素的值
    

    这种方式对于C小伙伴们是最熟悉不过的,优点是简便直接,缺点是如果数组中元素个数过多,就显得比较麻烦了。
    补充:这种初始化语法中不需要使用new(后文讲解),甚至不用指定长度。最后一个值后面允许有逗号,如果你要不断为数组增加值,这很方便:

    String[] names={"彭于晏”,
    "陈冠希",
    "刘德华”,
    //...
    //可以在这之后添加更多的name,这个逗号是被允许的
    }
    
    

    方法二:新创建法

    //方法二:new int[元素个数];
    int[] array=new int[100];
    //等价于var array=new int[100];
    

    new int[n]语句会创建一个长度为n的数组,一旦创建了数组,就不能再改变它的长度(不过,可以改变单个数组元素)。如果程序运行过程中需要经常扩展数组大小,就应当使用另一种数据结构——“数组列表”(array list),以后会讲解。

    方法三:匿名数组法

    //方法三:
    int[] array=new int[]{1,2,3,4,5};
    

    这种方式会分配一个新数组并填入大括号中提供的值。它会统计初始值个数,所以new int[]中不需要指定数组个数。

    补充:在Java中,允许有长度为0的数组。如果在编写一个结果为数组的方法时,这样一个长度为0的数组就很有用。可以如下创建长度为0的数组:
    new elementType[0]

    new elementType[] {}
    注意:长度为0的数组与null并不相同!

    三.访问数组元素

    3.1数组下标/索引

    我们以及成功声明并初始化好了一个数组,即我们准备工作已经完成,那么如何去使用一个数组呢?这时我们就需要学会去访问数组元素。
    访问数组元素就需要我们找到数组元素“下标/索引”。

    array[index];//通过下标/索引找到了对应数组元素
    

    同其它程序设计语言一样,Java数组元素下标也是从0开始的,例如我们创建一个元素个数为100的数组,那么它的下标就是从0到99(而不是1到100)。一旦创建了数组,我们就可以在数组中填入元素:

    int[] arrary=new int[100];
    for (int i = 0; i < 100; i++) {
          arrary[i]=i+1;
          //向数组中填入数组1到100
     }
    

    创建一个数字数组的时候,所有元素均初始化为0。boolean数组元素会初始化为false。对象数组元素则初始化为一个特殊值null,表示这些元素还未存放任何对象。初学者可能不解i,例如:

    String[] names=new String[10];
    

    会创建一个包含10个字符串的数组,所有的字符串均为null。如果希望这个数组包含空串,必须为元素指定空串:

     for (int i = 0; i < 10; i++) {
          	names[i]="";
    }
    

    3.2 for循环访问数组元素

    此外,在访问数组元素的时候如果需要向数组中添加或删除一个值,第一步就是要改变数组内容,第二步在访问新数组元素的时候我们还需要修改数组元素个数。在Java中提供了一种array.length方法可以获取数组中元素个数,再通过for循环访问数组元素,例如:

     int[] a=new int[]{12,3,46,5,5,5,2,879};
     for (int i = 0; i < a.length; i++) {
           System.out.println(a[i]);
    }
    

    最后就是一个老生常谈的bug了——数组不能越界访问!
    例如:我们创建了一个100个元素的数组,并试图访问array100,就会引发如下异常:

    即表明你访问了一个越界的数组元素,这是不被允许的。

    3.3 for each循环

    虽然上一篇中我们已经提到了for each循环,但是由于它在数组中是一种较为新颖的循环,我们再次在这里提及它。
    for each循环是Java中一种功能很强的循环结构,可以用来依次处理数组(或者其它元素集合)中的每个元素,==而不必考虑指定下标值。==这也是它区别与普通for循环最大的不同。
    其格式为下:

    for(变量:集合) statement
    

    它定义一个变量用于暂存集合中的每一个元素,并执行相应语句(或语句块)。集合的表达式必须是一个数组或者是一个实现了Iterable接口的类对象(例如ArrayList,以后会讲解)。例如:

    int[] a=new int[]{1,2,3,4,5};
    for(int element:a)
        System.out.println(element);
        /*1
    	  2
    	  3
    	  4
    	  5*/
    

    打印数组a的每一个元素,一个元素占一行。
    实际上,这个循环应该读作“循环a中的每一个元素”(for each element in a)这也是它名字由来。
    相比于传统for循环for each循环语句显得更加简洁,更不易出错,因为你不必因为下标的起始值和终止而操心。以下是二者对比:

    //二维数组 就是一个特殊的一维数组
    for (int i = 0; i < array.length; i++) {
        for (int j = 0; j < array[i].length; j++) {
              System.out.print(array[i][j]+" ");
    	}
              System.out.println();
    }/**/
    
    System.out.println("=====");
    
    for(int[] tmpArray : array) {
         for(int x : tmpArray) {
               System.out.print(x+" ");
         }
         System.out.println();
    }
    

    注意:for each循环语句的循环变量会遍历数组中的每个元素,而不是下标值。

    此外,如果你仅仅想打印出数组中的所有值,那么Java中有一个更加简单的方式,即利用Array类中的toString方法,调用Array.toString(a),返回一个包含数组元素的字符串,这些元素包含在中括号内,并用逗号分隔开。例如:

    结果如下:

    注意引入包:import java.util.Arrays;

    此外,我们还可以自我实现一个myToString方法:

       public static String myToString(int[] array) {
            String ret = "[";
            for (int i = 0; i < array.length; i++) {
                ret = ret + array[i];
                if(i != array.length-1) {
                    ret += ", ";
                }
            }
            ret += "]";
            return ret;
        }
    
    

    四.命令行参数

    前面已经看到多个使用Java数组的示例。每一个Java应用程序都有一个带String[] args参数的main方法。这个参数表明main方法将接收一个字符串数组,也就是命令行参数。例如,看一看下面这个程序:

    public class Test{
    	public static void main(String[] args){
    		if (args[0]. equals("-h"))
    			System.out. print("Hello, ");
    		else if (args[0]. equals("-g"))
    			System. out.print("Goodbye, ");
    		// print the other command-line arguments
    		for (int i = 1; i < args. length; i++)
    			System.out. print(" "+ args[i]);
    		System.out.println("!");
    	}
    }
    

    如果使用java Test -g cruel world命令调用这个程序,args数组将会包含一下内容:args[0]=“-g” args[1]=“cruel” args[2]=“world”。这个程序就会显示下面这个信息:

    五.初始JVM的内存分布

    在深入了解数组的使用之前,我们现要了解一下JVM的内存分布,可是我们为什么要了解这个东西呢?
    我们知道,Java的程序是跑在虚拟机(即JVM)上,我们要想理解数组这一引用类型是如何引用变量的,就必须了解其它JVM上的内存是如何分配的。
    首先,JVM对所使用的内存按照功能的不同进行了划分:

    解释一下里面的各个部分:

  • 程序计数器 (PC Register): 只是一个很小的空间, 保存下一条执行的指令的地址
  • 虚拟机栈(JVM Stack): 与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。
  • 本地方法栈(Native Method Stack): 本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是Native方法的局部变量. 在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的
  • 堆(Heap): JVM所管理的最大内存区域. 使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2, 3,4,5} ),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销
    毁。
  • 方法区(Method Area): 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. 方法编译出的的字节码就是保存在这个区域
  • 简单解释一下这里为什么会出现两个栈区:我们知道JVM底层是由C/C++实现的,而本地方法栈中就包含了一些由C/C++程序实现的方法。而虚拟机栈才是我们平时所说的栈区。
    现在我们只简单关心堆 和 虚拟机栈这两块空间,后序JVM中还会更详细介绍。

    六.数组中常见的各类方法

    6.1数组拷贝

    在Java中,允许将一个数组变量拷贝到另一个数组变量中去,这时,两个变量将引用同一个数组

            int[] smallPrimes=new int[10]; 
            int[] luckyNumbers=new int[10];
            luckyNumbers[5]=12;
            luckyNumbers=smallPrimes;//smallPrimes[5]==12
    

    正常来说我们自己是可以很轻松实现一个数组拷贝的方法的:

       public static int[] copyArray(int[] array) {
            int[] copy = new int[array.length];
            for (int i = 0; i < array.length; i++) {
                copy[i] = array[i];
            }
            return copy;
        }
    

    但Java中提供了更便捷的Arrays类的copyOf方法:

    luckyNumbers=Arrays.copyOf(luckyNumbers,luckyNumbers.length);
    //第一个参数是要拷贝的原始数组
    //第二个参数是新数组的长度
    

    这个方法通常用来增加数组大小

    luckyNumbers=Arrays.copyOf(luckyNumbers,2*luckyNumbers.length);
    

    如果数组元素是数值型,那么额外的元素将被赋值为0;如果数组元素是布尔型,则将赋值为false。相反,如果长度小于原始数组的长度,则只拷贝前面的值。
    此外,还有类似的Arrays类的copyOfRange方法:

    public static void main(String[] args) {
        int[] array = new int[] { 1,2,3,4 };
        int[] ret = Arrays.copyOfRange(array,2,4);
        //第一个参数:要拷贝的原始数组
        //第二个参数from:表示要拷贝的起始位置
        //第三个参数to:表示要拷贝的终止位置
        System.out.println(Arrays.toString(ret));//[3,4]
    }
    

    Java数组与堆栈上的C++数组有很大的不同,但基本上与在**堆(heap)**上分配的数组指针一样,也就是说:
    int[] a=new int[100];//Java
    不同于
    int[] a[100];//C++
    而等同于
    int* a=new int[100];//C++
    表明Java中数组变量存储的本质上是一个地址,通过这个地址“引用”到堆区上的目标对象。
    同时,Java中的[ ]运算符被预定义为会完成 越界检查,而且没有指针运算,即不能通过a+1得到数组中的下一个元素。

    6.2数组排序

    我们之前对数组常用的排序是冒泡排序,这里我们再次回顾一下:

    public static void bubbleSort(int[] array){
            //i表示循环的趟数
            for (int i = 0; i < array.length-1; i++) {
                boolean flag=false;
                for (int j = 0; j < array.length-1-i; j++) {
                    //从小到大排序
                    if(array[j]>array[j+1]) {
                        int tempArray = array[j];
                        array[j]=array[j+1];
                        array[j+1]=tempArray;
                        flag=true;
                    }
                }
                if(flag==false)
                    return;
            }
        }
        public static void main(String[] args) {
                int[] a=new int[]{1,3,2,6,0,9};
                bubbleSort(a);
            for (int b:a) {
                System.out.print(b+" ");
            }
            //0 1 2 3 6 9
        }
    

    要想对数值型数组进行排序,我们还可以使用Java中Arrays类的sort方法:

    int[] a=new int[10000];
    Arrays.sort(a);
    

    这个方法使用了优化的快速排序算法。快速排序算法对于大多数数据集合来说都是效率比较高的。
    接下来,我们通过一个经典案例练习一下——“抽彩游戏”:

    问题描述:
    抽彩是从多个数字中随机抽取几个不重复的值,进行排序输出且判断是否中奖。若中奖则输出中奖次数,若未中奖则给出提示。

    示例代码如下:

    import java.util.Arrays;
    import java.util.Scanner;
    
    public class LotteryDrawing {
        public static void main(String[] args) {
            int count = 0;
            //定义一个计量数,计算中奖了几次
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入您要抽取数字的最高数:");
            int n = sc.nextInt();
            //设定一个n存放最高位数值
            int n1 = n;
            //定义了一个n1与n的数值相同
            System.out.println("请输入您要抽取几个数字(1~"+n+"):");
            int k = sc.nextInt();
            //设定了抽取数
            if(k>n){
                System.out.println("对不起,您输入的数字不符合规范!");
                //若此时抽取数比最高数要高则报错
            }else{
                int[] numbers1 = new int[n];
                int[] numbers2 = new int[n1];
                //定义了两个数组,其长度分别与n和n1相同
                for (int i = 0; i < numbers1.length; i++) {
                    numbers1[i] = i+1;
                    //对数组numbers进行从1~n的赋值;
                }
                for (int i = 0; i < numbers2.length; i++) {
                    numbers2[i] = i+1;
                    //对数组numbers2进行从1~n1的赋值;
                }
                int[] result = new int[k];//抽取结果
                //定义了result数组使其长度等于抽取数k
                for (int i = 0; i < result.length; i++) {
                    int r =(int) (Math.random()*n);
                    //Math.random()*n会得到一个0到n-1之间的随机数,左闭右开!
                    //抽取随机数的本质是抽取随机下标
                    result[i] = numbers1[r];
                    //选择随机数numbers[r]赋值给result[i]
                    numbers1[r] = numbers1[n - 1];
                    n--;
                    //这里确保了不会再次抽取到同一个值
                }
                int[] winning = new int[k];//中奖结果
                for (int i = 0; i < winning.length; i++) {
                    int w = (int) (Math.random()*n);
                    winning[i] = numbers2[w];
                    numbers2[w] = numbers2[n1 - 1];
                    n1--;
                }
                Arrays.sort(result);
                //对数组进行排序
                System.out.println("抽取完成,您抽取的数为:");
                for(int r :result){
                    System.out.print(r+" ");
                }
                //使用foreach循环对数组进行遍历
                System.out.println("");
                //换行
                Arrays.sort(winning);
                System.out.println("中奖数为:");
                for (int w:winning){
                    System.out.print(w+" ");
                }
                for (int x = 0; x < result.length; x++) {
                    for (int y = 0; y < winning.length; y++) {
                        if(result[x] == winning[y]){
                            count++;
                        }
                    }
                }
                //使用两个for循环且利用if语句对数组的值进行判断
                //若有相同值则count计数值自增
                System.out.println();
                if(count == 0){
                    System.out.println("对不起,您未中奖,请再接再厉!");
                }else{
                    System.out.println("恭喜您,您中奖了"+count+"次");
                }
            }
    
        }
    }
    

    这个案例综合了数组排序的相关内容,仔细研读定会有所收获。

    6.3数组查找

    在数组使用中,有时我们需要在数组中找到目标元素,这时候我们就需要了解一些数组查找的方法:
    方法一:直接暴力查找

      public static int findNum(int[] array,int key) {
            for (int i = 0; i < array.length; i++) {
                if(array[i] == key) {
                    return i;
                }
            }
            return -1;
            //因为直接法是挨个查找,所以效率很低
        }
    

    这种方式是最直接最简单但效率最为低下的方式,仅限数组大小较小时使用。
    方法一:二分查找

     public static int binarySearch(int[] array,int key) {
            int left = 0;
            int right = array.length-1;
            while (left <= right) {
                int mid = (left+right) / 2;
                if(array[mid] == key) {
                    return mid;
                }else if(array[mid] > key) {
                    right = mid - 1;
                }else {
                    left = mid + 1;
                }
            }
            return -1;
        }
    

    这个方法相比于方法一效率还是提升了不少的。
    对于数组查找,当然Java也提供了相应的方法Arrays类的binarySearch方法:

    Arrays.binarySearch(int[] a,key);
    

    具体使用方法就不必多说了,直接用!
    Java当然不止提供了这一种排序方法,借助Java帮助手册我们可以自行查询相应方法和参数,结合实际情况和对应具体功能合理选择。

    6.4数组填充

    有时我们定义完数组后想要对其初始化一般是用for循环解决。
    而Java中提供了Arrays类的fill方法:
    功能:对数组内容进行指定填充。

    public static void main(String[] args) {
                int[] array = new int[10];
                Arrays.fill(array,5);
                Arrays.fill(array,4,6,10);
                System.out.println(Arrays.toString(array));
                //[5, 5, 5, 5, 10, 10, 5, 5, 5, 5]
    }
    

    6.5数组比较

    在上一篇中我们提到了数组之间的比较一定不能使用“ ==”比较 ,如果相等仅仅只能表明两个数组存储在相同的位置,而不能表示两个数组存储的内容是完全等同的。
    而Java中提供的Arrays类的equals方法实现了数组的比较:

    public static void main(String[] args) {
        int[] arr1 = new int[] { 5,4,3,2,1 };
        int[] arr2 = new int[] { 1,2,3,4,5 };
        int[] arr3 = new int[] { 1,2,3,4,5};
        System.out.println(Arrays.equals(arr1, arr2));//false
        System.out.println(Arrays.equals(arr2, arr3));//true
    }
    

    七.多维数组【二维数组】

    多维数组中我们主要围绕最常见的二维数组展开讨论,这里我们就不详细一一解释了,因为它和一维数组大同小异:

          //二维数组的声明和初始化
           //以Int为例,
           //法一:静态初始化
           int[][] list={{1,2,3},{4,5,6},{7,8,9}};
           //法二:动态初始化
           int[][] list=new int[5][10];
           //法三:列数不确定
           int[][] list=new int[5][];
           //此时所有的一维数组都没有开辟内存空间,它们的地址都为 null;
    

    这里举一个案例,观察一下二维数组在JVM中的内存分布:

    实际上,二维数组的本质就是一个存储着一维数组的数组,它的各个一维数组的内存大小可以相同,也可以不相同。(不规则数组)
    对于二维数组的访问我们跟一维数组类似,也有两种实现方式:

    int[][] list={{1,2,3},{4,5,6},{7,8,9}};
    //法一:
     for (int i = 0; i < list.length; i++) {
         for (int j = 0; j < list[0].length; j++) {
             System.out.print(list[i][j]+" ");
         }
             System.out.println();
    }
         System.out.println("==================");
    //法二:
    for (int[] x :list) {
          for (int y:x) {
             System.out.print(y+" ");
          }
             System.out.println();
    }
    /*
    1 2 3 
    4 5 6 
    7 8 9 
    ==================
    1 2 3 
    4 5 6 
    7 8 9 */
       }
    

    八.不规则数组

    刚才我们提及了二维数组可以省略行初始化,即:

    int[][] list=new int[5][];
    

    为什么可以这样呢?我们知道,二维数组本质上是一个存储着一维数组的数组。列数的不确定可以使我们对数组进行更加灵活的处理,即可以方便地构造一个“不规则”数组,即数组的每一行都有不同的长度
    下面我们通过一个杨辉三角案例具体了解一下不规则数组的实现:

    问题描述:

    示例代码如下:

        public static void main(String[] args) {
                int[][] a = new int[10][];
    
                for (int i = 0; i < a.length; i++) {
                    // 给二维数组中每一个一维数组在堆上开辟内存空间
                    a[i] = new int[i + 1];
                    // 遍历每一个一维数组,赋值
                    for (int j = 0; j < a[i].length; j++) {
                        // 每一行的第一个元素和最后一个元素都是1
                        if (j == 0 || j == a[i].length - 1) {
                            a[i][j] = 1;
                        } else {
                            // 每一行非第一个元素和最后一个元素的值 = 上一行的同一列 + 上一行的上一列
                            a[i][j] = a[i - 1][j] + a[i - 1][j - 1];
                        }
                    }
                }
    
                // 输出杨辉三角
                for (int i = 0; i < a.length; i++) {
                    for (int k = 0; k < a[i].length; k++) {
                        System.out.print(a[i][k] + "\t");
                    }
                    System.out.println();
                }
    }
    

    总结:本篇详细讲解了Java中数组的使用,我们不能发现,其实许多功能和方法Java中都有相应的封装好的类方法供你使用,一方面我们要去了解怎么实现,但更重要的是要学会去自行检索各类方法然后快速上手使用。本篇到此结束,看到这里实属不易,您的支持与鼓励是我前进最大的动力!也希望屏幕前的你能有所收获。

    作者:Neteen

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【Java】一文解决数组问题,让你轻松掌握

    发表回复