JavaSE 数组的定义与使用

1 数组基本用法

数组: 是一块连续的内存,用来存放相同数据类型的集合。一定要注意数组中包含的变量必须是相同类型。在Java当中数组也叫做数组对象。

1.1 定义数组

如何用程序来定义一个数组?
定义数组有三种定义方式,具体写法如下所示:

  1. 数据类型[ ] 数组名称 = { 初始化数据 };
    //这里不仅定义了一个数组,而且对这个数组同时进行了初始化,这种初始化叫做静态初始化。
  2. 数据类型[ ] 数组名称 = new 数据类型 [数据大小] ;
    //这里定义了数组,但是没有初始化,所以这个数组的默认值为0
  3. 数据类型[ ] 数组名称 = new 数据类型 [ ] { 初始化数据 };
    //这种定义方式要注意第二个[]里绝对不能写数字,它会自己判断当前数组有多大,否则会出现编译报错;而且这里不仅定义了一个数组,而且对这个数组同时进行了初始化,这种初始化叫做动态初始化。

在这里等号右面这一堆都叫做对象,数组的对象;int[]叫做所定义的是数组的类型;
array、array2、array3都叫做引用。引用就是一个变量,只不过存在它里面的值是地址。引用指向对象。
具体代码示例如下:

public class TestDemo {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6,7};//这里不仅定义了一个数组,而且对这个数组同时进行了初始化,这种初始化叫做静态初始化。
        int[] array2 = new int[10];//这里定义了数组,但是没有初始化,所以这个数组的默认值为0
        int[] array3 = new int[]{1,2,3,4,5,6,7};//这种定义方式要注意第二个[]里绝对不能写数字,它会自己判断当前数组有多大,否则会出现编译报错;而且这里不仅定义了一个数组,而且对这个数组同时进行了初始化,这种初始化叫做动态初始化。
    }
    }

对于当前这个函数来说,我们的array、array2、array3属于3个变量(引用),这3个变量在main()函数当中,那么这3个变量都是局部变量,它们存储在栈上,分别会占用一块内存,并且每一个引用都会指向一个对象。但是这三个变量的对象都存储在堆上,尽管可能像array和array3 的对象都是数字1到7,但他们所指向的对象还是两个不同的对象,引用就是用来存地址的,这两个对象的地址当然也就不同。可借助下图理解引用与对象之间的关系(引用就是用来存地址的)。
在这里插入图片描述
注意: 引用不一定存储在栈上,但是等号右面的只要是对象,则一定是在堆上的。上面所讲解的引用存储在栈上,是因为上面的引用是一个局部变量。如果此时的引用是一个实例变量,不是局部变量,此刻它就不能放在栈上。例如下面代码中的tmp变量就是一个实例变量,不是局部变量,此刻它就不能放在栈上。

public class TestDemo {
    int[] tmp =  {1,2,3,4,5,6,7};
    public static void main(String[] args){
        int[] array = {1,2,3,4,5,6,7};
        int[] array2 = new int[10];
        int[] array3 = new int[]{1,2,3,4,5,6};
        }
    }

1.2 打印数组

如何对数组进行打印?
对数组进行打印有三种打印方式,具体写法如下所示:

1. 第一种是采用for()循环中的fori()的方式进行打印数组;
for (int i = 0; i < 数组名.length ; i++) {
System.out.print(array[ i ] + " ");
}
2. 第二种则是采用for()循环中的foreach()的方式进行打印数组;
for ( int x: 数组名 ) {
System.out.print( x + " ");
}
3. 第三种是利用Arrays.toString();方式进行打印数组。
Arrays是一个工具类,专门用来操作数组的工具类。
String 变量名 = Arrays.toString(数组名);
System.out.println(变量名);

具体代码示例如下:

public class TestDemo {
    public static void main(String[] args){
        int[] array = {1,2,3,4,5,6,7};
        int[] array2 = new int[10];//下标可以取到0到9
        int[] array3 = new int[]{1,2,3,4,5,6};
        //方法1
        //这个length就属于这个数组的属性,表示获取数组的长度
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]+" ");
        }
        System.out.println();
        System.out.println("=====================");
        //方法2
        for (int x:array3) {//array中的每一个引用是一个数字
            System.out.print(x+" ");
        }
         System.out.println();
        System.out.println("=====================");
        /*
        * 这种打印方式是将数组转化为字符串进行输出
        * */
        String str = Arrays.toString(array3);
        System.out.println(str);
        }
    }

这三种打印方式的区别是:

  1. 对于fori()的方式进行打印数组可以拿到数组的下标,即 System.out.print()语句中的array[i]。
  2. 而对于foreach()的方式进行打印数组只能拿到值,即System.out.print()语句中的x。
  3. 而相较于前两种打印方式,利用Arrays.toString();方式进行打印数组,是将数组转化为字符串进行输出,前面两种方式打印出来的数据没有[ ],直接是1 2 3 4 5 6这样的;而这种打印方式打印出来的数据是含有[ ]的,是[1,2,3,4,5,6]这样的。

1.3 数组的使用

获取数组长度: array.length。
使用 array.length 能够获取到数组的长度,这个操作为成员访问操作符.,在面向对象中会经常用到。
访问数组元素: 使用 [ ] 按下标获取数组元素即可。
我们要注意,使用 [ ] 按下标取数组元素时,下标是从 0 开始计数;并且使用 [ ] 操作既能读取数据, 也能修改数据;下标访问操作不能超出有效范围 [0, length - 1] , 如果超出有效范围, 会出现下标越界异常。
上面两个关于数组使用的具体代码示例如下:

public class TestDemo {
    public static void main(String[] args) {
        int[] array = {1, 2, 3};
        // 获取数组长度
        System.out.println("length: " + array.length); // 执行结果: 3
        // 访问数组中的元素
        System.out.println(array[1]); // 执行结果: 2
        System.out.println(array[0]); // 执行结果: 1
        array[2] = 100;
        System.out.println(array[2]); // 执行结果: 100
    }
    }

下标越界: 下标访问操作不能超出有效范围 [0, length - 1] , 如果超出有效范围, 会出现下标越界异常。具体代码示例如下:

public class TestDemo {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6,7};
        System.out.println(array[7]);
    }
    }

发生下标越界时,虽然代码在编译过程中不会报错,但是在运行过程中会发生报错,抛出异常。这里抛出的异常为 java.lang.ArrayIndexOutOfBoundsException ,叫做数组下标超出范围异常,也称为数组越界异常。具体运行结果如下图所示:
在这里插入图片描述
黄色框里面的表示是异常名,紫色圆圈里面的表示此时你访问了这个数组的7下标,这是你就要明白你的数组长度一定小于等于7,使用数组一定要下标谨防越界。
认识null: null 在 Java 中表示 “空引用” , 也就是一个无效的引用。
如下面代码中的 int a = 0;这个a是一个整型类型的数据,我们初始化时直接使用0就可以。 而如果当你写一个array = 0;此时你的array它是一个引用[ ],如果此时给它赋值一个0,则会发生编译报错,因为此时0是一个整型,等号左边的array是一个引用,两边类型不匹配。 array是一个引用,当你不知道引用指向谁的时候,就把array的值赋为初值,也可以把它叫做null,这个null就是我们引用的零值,此时array这个引用,不指向任何对象。具体代码示例如下:

public class TestDemo {
    public static void main(String[] args) {
        int a = 0;//这个a是一个整型类型的数据,我们初始化时直接使用0就可以。
        int[] array = {1,2,3,4,5,6,7};
        //array = 0;此时你的array它是一个引用[],如果此时给它赋值一个0,则会发生编译报错,因为此时0是一个整型,等号左边的array是一个引用,两边类型不匹配
        //array是一个引用,当你不知道引用指向谁的时候,就把array的值付为初值,也可以把它叫做null,这个null就是我们引用的零值
        array = null;//此时array这个引用,不指向任何对象
        System.out.println(array[2]);
        //或System.out.println(array.length);
    }
    }

运行此代码,发现在运行过程中发生报错,抛出异常。这里抛出的异常为 java.lang.NullPointerException,叫做空指针异常。原因是因为 array = null不指向任何的对象,所以编译器自然也不知道array[2]是哪个对象的2下标了,那它就会发生一个空指针异常;同理,array.length中的array也就不知道指向的是哪个对象了,也自然就求不出这个数组的长度了,那它也就会发生一个空指针异常。具体运行结果如下图所示:在这里插入图片描述
如果你的代码出现了空指针异常,一定要去检查引用是否为null。
但此时针对上面的这个代码你可能又会提出,那array原来指向的对象{1,2,3,4,5,6,7},在array指向空以后又去哪了?
答:原来指向的对象{1,2,3,4,5,6,7}被JVM垃圾回收器回收了。即当堆中的对象没有引用再引用的时候,那么这个对象就会被垃圾回收器回收掉。
数组作为方法的参数: 数组作为参数传递,传过去的一定是当前这个引用的值(地址),也就是这个引用所指向的这个对象的值(地址)。
以打印数组内容为例,具体代码示例如下:

public class TestDemo {
    public static void print(int[] array2){
        for (int i = 0; i < array2.length; i++) {
            System.out.println(array2[i]+" ");
           // System.out.println(2 * array2[i]+" ");//将数组扩大2倍输出
        }
        System.out.println();
        //array2[2]=2 * array2[2];
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6};
        print(array);
       // System.out.println(array[2]);
    }
    }

对于此段代码来说,array和array2都同时指向了{1,2,3,4,5,6}这同一个对象。在这个代码中int[ ] array2是函数的形参, int[ ] array 是函数实参。如果需要获取到数组长度, 同样可以使用array2.length。具体可借助下图进行理解。
在这里插入图片描述
所以,两个引用同时指向了一个对象,不管你通过哪个引用修改对象的值,另一个引用去访问的时候,都会受到影响。
数组作为方法的返回值1: 在Java中数组不仅可以作为方法得参数,还可以作为方法的返回值。
具体代码示例如下:

public class TestDemo {
    public static void main(String[] args) {
        int[] ret = func1();
        String str = Arrays.toString(ret);
        System.out.println(str);
    }
    public static int[] func1(){
        int[] ret = {1,2};
        return ret;
        //对于上面两句代码,我们有一个更直接的写法
        //直接 return new int[]{1,2};
        }
    }

在注释里的int[ ]{1,2},我们把这个数组叫做匿名数组,代表当前这个数组是没有名字的数组。匿名数组的不好处就是你每次用都得重新new一次,得随用随取。
总结: 引用可以把它看作是一个指针,就是用来存地址的。
补充: 下面的这个代码并不会报错,它是合理的。虽然引用是不能指向引用的,但是array2这个引用,并没有指向array这个引用,而指向了array这个引用所指向的对象。

public class TestDemo {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6};
        int[] array2 = array;
    }
    }

可借助下图进行理解:
在这里插入图片描述引用只能同时指向1个对象: 就如下面代码中的array所指向的前两个对象{1,2,3,4,5,6}和{4,5,6,7,8,9},在被array指向第三个对象{14,15,16,17,18,19}时,前两个对象就会被垃圾回收器回收掉,所以此时array这个引用还是只指向了{14,15,16,17,18,19}这一个对象。具体代码示例如下:

public class TestDemo {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6};
        array = new int[]{4,5,6,7,8,9};
        array = new int[]{14,15,16,17,18,19};
    }
    }

所以,上面所说如下图所示:
在这里插入图片描述
之前我们提到过,JVM对我们的内存进行了划分,JVM中内存共划分为5块,分别为:Java虚拟机栈(JVM Stack)、本地方法栈、堆、程序计数器和方法区。我们每一个线程(Thread)就拥有一个独立的程序计数器(PC Register)、Java虚拟机栈(JVM Stack)和本地方法栈(Native Method Stack),但你会发现并不是每一个线程(Thread)就拥有一个独立的堆(Heap)和方法区(Method Area),所以堆和方法区属于线程共享的。并且运行时常量池(Runtime Constant Pool)在JDK1.7以后已经从方法区移到堆中去了,具体如下图所示:
在这里插入图片描述
当数组作为方法的返回值2: 写一个方法,将数组中的每个元素都 * 2,具体代码示例如下所示:

public class TestDemo {
    int[] tmp =  {1,2,3,4,5,6,7};
    public static int[] func2(int[] array) {
        for (int i = 0; i < array.length; i++) {
            array[i] = array[i] * 2;
        }
        return array;
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6};
        int[] ret = func2(array);
        System.out.println(Arrays.toString(ret));
    }
    }

如何理解这段代码呢?具体解析如下:

  1. 此程序执行先进入main()函数,我们遇到了第一个array,array是一个局部变量,在你的栈上就需要开辟一块内存给它,此时{1,2,3,4,5,6}就是一个对象,它被放入堆中。假设这个对象的地址是0x999,我们栈上的array里面存放的也是0x999这个地址,说明当前array这个引用 引用 的就是{1,2,3,4,5,6}这个对象。
  2. 下面我们调用了func2()这个函数,把上面array这个实参的值给func2()函数所对应的(int[] array)这个形参了,所以这个时候又有1个array出现了,这个array也会占用栈上的一块内存,此时这个array里面的值是所传过去的上面array这个实参的值,也就是0x999这个地址。这样一来,我们这个array引用也引用了当前堆当中的{1,2,3,4,5,6}这个对象。
  3. 然后就进入func2()这个方法当中了,通过第二个array这个引用遍历{1,2,3,4,5,6}这个数组中的每一个元素,将取出来的元素每一个乘以2,再把它们分别放回这个数组当中去。这样相当于把原来的{1,2,3,4,5,6}乘完以后变成{2,4,6,8,10,12}放回去。然后for()循环走完以后,返回array,返回的是func2()函数所对应的(int[] array)这个形参的array,也就是这里所说的第二个array。这里的第二个array存放的值是0x999。返回的这个值给ret这个引用(变量)了。
  4. 那么此时我们这个ret又是一个引用,也就是局部变量,也就需要在栈上新开辟一块内存给它,它里面放的值是刚才的返回值,也就是第二个array存放的值0x999。那么此时ret也指向了堆中的0x999这个对象,此时这个对象的具体值已经变为{2,4,6,8,10,12}了。
  5. 最后调用Arrays.toString(),把ret扔进去,相当于通过ret这个引用来访问这个引用所指向的对象。那么此时ret这个引用所指向的对象就是{2,4,6,8,10,12}这个对象了,打印出的结果也就为[2,4,6,8,10,12]了。

具体可借助下图进行理解:
在这里插入图片描述
但是这样写有一个不好的地方,你会发现我们把原来的数组值修改了,如果此时要求你不能修改原来对象数组中的值,你需要重新申请一个数组,把那个对象数组里的值扩大2倍,此时这个代码应该怎么修改?具体代码示例如下所示:

public class TestDemo {
    public static int[] func3(int[] array) {
        int[] ret = new int[array.length];
        for (int i = 0; i < array.length; i++) {
           ret[i] = array[i] * 2;
        }
        return ret;
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6};
        int[] ret = func3(array);
        System.out.println(Arrays.toString(ret));
    }
    }

此时这个代码与上面那个代码不同的是,我们在进入fun3()方法之后,重新申请了一个数组ret放在了堆中,这个数组的长度和原来那个数组的长度是一样的。然后把原来那个数组的每一个元素取出来乘以2,结果返回到ret当中。原来araay这个引用所对应的对象数组并没有改变,依旧是{1,2,3,4,5,6}。具体可借助下图进行理解:
在这里插入图片描述

2 数组练习

例1:用Java解决两数之和问题。
此题的详细解答过程见链接:https://blog.csdn.net/weixin_51312723/article/details/113572849
例2:找到主要元素问题。
问题描述: 数组中占比超过一半的元素称之为主要元素。给定一个整数数组,找到它的主要元素。若没有,返回-1。

  1. 示例1:输入:[1,2,5,9,5,9,5,5,5]; 输出:5。
  2. 示例2:输入:[3,2]; 输出:-1。

问题分析:

1.为了方便查找,我们首先需要将数组中的元素进行一个从小到大的排序,如下图所示将示例1的元素进行了一个从小到大的排序:
在这里插入图片描述
这里我们可以通过直接调用Arrays.sort()方法进行数组排序。
2.要注意这里虽然5最多,但是不一定是超过一半的元素,也就不一定是主要元素,数组中占比超过一半的元素才能称之为主要元素。

具体代码示例如下:

public class TestDemo {
    public static int majorityElement(int[] nums){
        int len = nums.length;
        Arrays.sort(nums);//通过调用Arrays.sort()方法进行数组排序
        for (int i = 0; i <= len/2; i++) {
            for (int j = i+len/2; j <len ; j++) {
                if(nums[i] == nums[j]){
                    return nums[i];
                }
            }
        }
        return -1;
    }
    public static void main(String[] args) {
        int[] nums = {1,2,5,9,5,9,5,5,5};
        System.out.println( majorityElement(nums));
      }
    }

例3:数组转字符串。
方法1: 直接通过Arrays.toString();方式将数组转换为字符串,具体代码示例如下:

public class TestDemo {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6};
        System.out.println(Arrays.toString(array));
    }
    }

方法2: 我们通过自己编写代码,实现一个自己版本的数组转字符串,具体代码示例如下:

public class TestDemo {
    int[] tmp =  {1,2,3,4,5,6,7};
    public static String myToString(int[] array){
        if(array == null){
            return null;
        }
        String ret = "[";
        for (int i = 0; i < array.length; i++) {
            ret += array[i];
            if(i != array.length-1){
                ret +=  ",";
            }// 借助 String += 进行拼接字符串
        }// 除了最后一个元素之外, 其他元素后面都要加上 ", "
        ret += "]";
        return ret;
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6};
        //System.out.println(Arrays.toString(array));
        System.out.println(myToString(array));
    }
    }

例4:数组拷贝。
方法1: 通过for循环进行拷贝,具体代码示例如下:

public class TestDemo {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6};
        //方法1 通过for循环进行拷贝
        int[] array2 = new int[array.length];
        for (int i = 0; i <array.length ; i++) {
            array2[i] = array[i];
        }
        System.out.println(Arrays.toString(array));
        System.out.println(Arrays.toString(array2));
    }
    }

此代码可借助下图进行理解:
在这里插入图片描述
方法2: 通过数组的工具类Arrays.copyOf()进行数组的拷贝,这里的第一个参数是你要考虑的数组为哪一个,第二个参数是要决定拷贝多长,即拷贝的长度。如果大于你所要拷贝的这个数组的长度,则此数组拷贝完以后,剩下的长度里面写的是0。具体代码示例如下:

public class TestDemo {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6};
        int[] array2 = Arrays.copyOf(array,array.length);
        System.out.println(Arrays.toString(array));
        System.out.println(Arrays.toString(array2));
    }
    }

此代码可借助下图进行理解:
在这里插入图片描述方法3: 通过调用System.arraycopy()方法进行数组的拷贝,这里的第一个参数表示要拷贝的数组,也就是原来的数组;第二个参数表示原来数组的位置;第三个参数表示目的地的数组,也就是拷贝后的数组;第四个参数表示目的地的位置,也就是拷贝后数组的位置;第五个参数表示拷贝的长度。具体代码示例如下:

public class TestDemo {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6};
        int[] array2 = new int[array.length];
        System.arraycopy(array,0,array2,0,array.length);
        //Object是所有类的父类
        System.out.println(Arrays.toString(array));
        System.out.println(Arrays.toString(array2));
    }
    }

如下图所示,这里的arraycopy()是一个被native所修饰的方法。记住以后如果看到一个方法被native所修饰,说明这个方法是C或C++代码实现的,我们是看不到具体的实现的,native的特点是速度快,所以方法3要比方法2快。在JVM的本地方法栈中运行的方法就是native的。
在这里插入图片描述
方法4: 通过调用数组名.clone()方法进行数组的拷贝,它的原理是产生当前引用的一个副本。具体代码示例如下:

public class TestDemo {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6};
        int[] array2 = array.clone();
        System.out.println(Arrays.toString(array));
        System.out.println(Arrays.toString(array2));
    }
    }

提问:这四种拷贝方式是深拷贝还是浅拷贝?
答:浅拷贝。下面就介绍一下深拷贝和浅拷贝分别是什么:
如下图所示,引用里存放的只是简单的数值。拷贝结束之后通过 比如array2这个引用 修改所指向的对象的时候,不会影响原来array1这个引用所指向的对象,那么这个拷贝过程叫做深拷贝
在这里插入图片描述
如下图所示,引用里存放的是地址。拷贝结束之后通过 比如array2这个引用 修改所指向的对象的时候,会影响原来array1这个引用所指向的对象,那么这个拷贝过程叫做浅拷贝
在这里插入图片描述
那么如何将浅拷贝变为深拷贝?
答:我们就不能拷贝那个引用了,而应该拷贝那个引用所指向的对象,学习完接口后,就可以实现深拷贝,目前可根据下图先进行一个大概的理解。
在这里插入图片描述
数组部分拷贝: 通过数组的工具类Arrays.copyOfRange()进行数组的部分拷贝,,这里的第一个参数是你要考虑的数组为哪一个,第二个参数是要决定拷贝是从哪一个位置开始拷,第三个参数是要决定拷贝到哪一个位置结束此部分数组的拷贝。它是一个左闭右开的定义,就是说如下面代码按理来说是从第2个位置进行开始,第5个位置进行结束,输出结果为[3,4,5,6];但是因为它左闭右开的定义,第5个位置取不到,正确的输出结果应该为[3,4,5]。具体代码示例如下:

public class TestDemo {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6};
        int[] ret = Arrays.copyOfRange(array,2,5);
        System.out.println(Arrays.toString(ret));
    }
    }

例5:找出数组中最大的元素。
类似于 “打擂台” 这样的过程,其中 max 变量作为 擂台,比擂台上的元素大,就替换上去,否则就下一个对手。具体代码示例如下:

public class TestDemo {
    public static int findMax(int[] array){
        int max = array[0];
        for (int i = 0; i < array.length; i++) {
            if(max < array[i]){
                max = array[i];
            }
        }
        return max;
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6};
        System.out.println(findMax(array));
    }
    }

例6:求数组中元素的平均值。
注意元素的平均值可能是小数,所以这里就不能用整型作为返回值了,要用double作为返回值才合适。具体代码示例如下:

public class TestDemo {
    public static double findAvg(int[] array){
        double avg = 0.0;
        int sum = 0;
        for (int i = 0; i < array.length; i++) {
            sum += array[i];
        }
        avg = 1.0*sum/array.length;
        return avg;
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6};
        System.out.println(findAvg(array));
    }
    }

例7:查找数组中指定元素(顺序查找)。
给定一个数组, 再给定一个元素, 找出该元素在数组中的位置。顺序查找就是我们所说的遍历的方法,就是一个一个找,直到找到自己所需要的数字为止。具体代码示例如下:

public class TestDemo {
    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 void main(String[] args) {
        int[] array = {1,2,31,4,15,6,7};
        System.out.println(findNum(array,7));
    }
    }

例8:查找数组中指定元素(二分查找)。
针对有序数组, 可以使用更高效的二分查找。记住一定得是有序的!有序分为 “升序” 和 “降序”,如 1 2 3 4 , 依次递增即为升序;如 4 3 2 1 , 依次递减即为降序。以升序数组为例, 二分查找的思路是先取中间位置的元素, 看要找的值比中间元素大还是小, 如果小, 就去左边找; 否则就去右边找。
方法1: 具体代码示例如下:

public class TestDemo {
    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){
                left = mid +1;
            }else{
                right = mid -1;
            }
        }
        return -1;
    }
    public static void main(String[] args) {
        int[] array = {1,2,31,4,15,6,7};
        Arrays.sort(array);//对数组进行排序
        System.out.println(Arrays.toString(array));
        System.out.println(binarySearch(array,4));
    }
    }

方法2: 在这里我们还可以直接使用数组的工具类Arrays.binarySearch()进行数组的二分查找,这里的第一个参数表示要查找的是哪一个数组;第二个参数表示在这个数组中要查找哪一个元素。具体代码示例如下:

public class TestDemo {
public static void main(String[] args) {
        int[] array = {1,2,31,4,15,6,7};
        Arrays.sort(array);//对数组进行排序
        Arrays.binarySearch(array,4);
    }
    }

我们可以感受一下对于一个有序数组,二分查找的效率比顺序查找的高得多,比如针对一个长度为 10000 个元素的数组查找, 二分查找只需要循环 14 次就能完成对于元素9999的查找,而顺序查找则要循环9999次才能完成对于元素9999的查找。所以,对于有序数组来说,随着数组元素个数越多, 二分的优势就越大。具体代码示例如下:

public class TestDemo {
    public static int count = 0;
    public static int binarySearch(int[] array,int key){
        int left = 0;
        int right = array.length-1;
        while(left <= right){
            count++;
            int mid = (left+right)/2;
            if(array[mid] == key){
                return mid;
            }else if(array[mid] < key){
                left = mid +1;
            }else{
                right = mid -1;
            }
        }
        return -1;
    }
    public static void main(String[] args) {
        int[] array = new int[1_0000];
        for (int i = 0; i <array.length; i++) {
            array[i] = i;
        }
        System.out.println(binarySearch(array,9999));
        System.out.println(count);
        }
    }

例9:检查数组的有序性。
给定一个整型数组, 判断是否该数组是有序的(升序)。这里方法的返回类型是布尔类型,因为只用判断它是否有序,结果只有true或者false两种情况。具体代码示例如下:

public class TestDemo {
    public static boolean isUp(int[] array){
        for (int i = 0; i < array.length-1 ; i++) {
            if(array[i] > array[i+1]){
                return false;
            }
        }
        return true;
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,10,5,6};
        System.out.println(isUp(array));
    }
    }

例10:数组排序(冒泡排序)。
给定一个数组, 让数组升序 (降序) 排序。
算法思路: 每次尝试找到当前待排序区间中最小(或最大)的元素, 放到数组最前面(或最后面)。如下面两张图中的四个数字就经历了三趟冒泡排序,冒泡排序过程如下图所示:
在这里插入图片描述
在这里插入图片描述
所以我们在编写代码时应该先确定冒泡排序所需要的趟数,通过上述例子我们发现冒泡排序所需要的趟数就是数组长度减1。然后我们要知道上图中的 j 所走的级数的问题,我们发现 j 永远小于数组长度减1,再减去此次正在排序的趟数(但要注意,我们这里的趟数 i 是从0开始的,所以此次正在排序的趟数永远比我们正真所数的趟数少一趟)。具体代码示例如下:

public class TestDemo {
    public static void bubbleSort(int[] array){
        //i表示趟数
        //这是已经经过一次优化的代码,如果不优化,j <array.length-1不减i也可以实现此次排序要求
        for (int i = 0; i <array.length-1 ; i++) {
            for (int j = 0; j <array.length-1-i ; j++) {
                if(array[j] > array[j+1]){
                    int tmp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = tmp;
                }
            }
        }
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,10,5,6};
        bubbleSort(array);
        System.out.println(Arrays.toString(array));
    }
}

这是已经经过一次优化的代码,如果不优化,j <array.length-1不减i也可以实现此次排序要求。如果还想进行进一步的优化,我们可以考虑加一个标志位来判断数组是否在某一趟后已经有序,则后面的循环冒泡排序过程则可以不去进行。具体代码示例如下:

public class TestDemo {
    public static void bubbleSort(int[] array){
        boolean flg = false;
        //i表示趟数
        for (int i = 0; i <array.length-1 ; i++) {
            flg = false;
            for (int j = 0; j <array.length-1-i ; j++) {
                if(array[j] > array[j+1]){
                    int tmp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = tmp;
                    flg = true;
                }
            }
            if(flg == false){
                return;
            }
        }
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,10,5,6};
        bubbleSort(array);
        System.out.println(Arrays.toString(array));
    }
    }

例11:数组逆序。
给定一个数组, 将里面的元素逆序排列。
算法思路: 设定两个下标, 分别指向第一个元素和最后一个元素,交换两个位置的元素,然后让前一个下标自增, 后一个下标自减, 循环继续即可,当前一个下标和后一个下标相遇时,则循环结束,逆序排列实现。
具体代码示例如下:

public class TestDemo {
    //就地逆置
    public static void reverse(int[] array){
        if(array == null){
            return;
        }
        int i = 0;
        int j = array.length-1;
        while(i < j){
            int tmp = array[i];
            array[i] = array[j];
            array[j] = tmp;
            i++;
            j--;
        }
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6};
        reverse(array);
        System.out.println(Arrays.toString(array));
    }
    }

例12:数组数字排列。
给定一个整型数组, 将所有的偶数放在前半部分, 将所有的奇数放在数组后半部分。例如:{1, 2, 3, 4},调整后得到{4, 2, 3, 1}。
基本思路: 设定两个下标分别指向第一个元素和最后一个元素,用前一个下标从左往右找到第一个奇数, 用后一个下标从右往左找到第一个偶数, 然后交换两个位置的元素,依次循环即可。当前一个下标和后一个下标相遇时,则循环结束,数组数字排列实现。
具体代码示例如下:

public class TestDemo {
    public static void func4(int[] array){
        int i = 0;
        int j = array.length-1;
        while(i < j){
            while(i < j && array[i] % 2 == 0){
                i++;
            }
            if(i >= j){
                return;
            }else{
                while(i < j && array[j] % 2 != 0){
                    j--;
                }
                if(i >= j){
                    return;
                }else{
                    int tmp = array[i];
                    array[i] = array[j];
                    array[j] = tmp;
                }
            }
        }
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6,7};
        func4(array);
        System.out.println(Arrays.toString(array));
    }
    }

上面这段代码写的太繁琐了,下面我们将这段代码进行一个简化,如果 i < j 了,则 代码里的 i >= j 也进不来,所以可以将 两个if(i >= j) { return; }这段代码都删掉。具体代码示例如下:

public class TestDemo {
    public static void func4(int[] array){
        if(array == null){
            return;
        }
        int i = 0;
        int j = array.length-1;
        while(i < j){
            while(i < j && array[i] % 2 == 0){
                i++;
            }
            while(i < j && array[j] % 2 != 0){
                    j--;
                }
                    int tmp = array[i];
                    array[i] = array[j];
                    array[j] = tmp;
                }
        }
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6,7};
        func4(array);
        System.out.println(Arrays.toString(array));
    }
}

3 二维数组

二维数组: 本质上也就是一维数组, 只不过每个元素又是一个一维数组。可借助下图进行理解:
在这里插入图片描述

3.1 定义二维数组

如何用程序来定义一个二维数组?
定义二维数组有四种定义方式,具体写法如下所示:

1. 数据类型[ ] [ ] 数组名称 = { {初始化数据},{ },{ } };
//定义了一个n行n列的数组
2. 数据类型[ ] [ ] 数组名称 = new 数据类型 [行数] [列数];
//这里没有初始化,默认为0
3. 数据类型[ ] [ ] 数组名称 = new 数据类型 [行数] [ ];
//列是可以省略的,行不可以省略,叫做不规则的二维数组
4. 数据类型[ ] [ ] 数组名称 = new 数据类型 [ ] [ ] { {初始化数据},{ },{ } };
//这里的列和行都得省略,里面不能有数字

具体代码示例如下:

 public static void main(String[] args) {
        int[][] array = {{1,2,3},{4,5,6},{7,8,9}};//定义了一个三行三列的数组
        int[][] array2 = new int[2][3];//这里没有初始化,默认为0
        int[][] array3 = new int[2][];//列是可以省略的,行不可以省略 叫做不规则的二维数组
        int[][] array4 = new int[][]{{1,2,3},{4,5,6},{7,8,9}};//这里的列和行都得省略,里面不能有数字
    }

3.2 打印二维数组

如何对二维数组进行打印?
对二维数组进行打印有三种打印方式,具体写法如下所示:

1. 第一种是采用for()循环中的fori()的方式进行打印数组;
for (int i = 0; i < 数组名.length ; i++) {
for (int j = 0; j < 数组名 [i].length ; j++) {
System.out.print(array[ i ][ j ] +" "); }
System.out.println(); }
2. 第二种则是采用for()循环中的foreach()的方式进行打印数组;
for (int[ ] arr : 二维数组名) {
for (int x:arr) {
System.out.println(x); }
System.out.println(); }
3. 第三种是利用Arrays.deepToString();方式进行打印数组。
Arrays是一个工具类,专门用来操作数组的工具类。
String 变量 = Arrays.deepToString( 数组名);
System.out.println(变量);

具体代码示例如下:

public class TestDemo {
    public static void main(String[] args) {
        int[][] array = {{1,2,3},{4,5,6},{7,8,9}};
        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[] arr:array) {//array中的每一个引用是一个数组arr
            for (int x:arr) {//arr数组中的引用是一个元素
                System.out.println(x);
            }
            System.out.println();
        }
        System.out.println("===================================");
        String ret = Arrays.deepToString(array);
        System.out.println(ret);
    }
    }

如何采用for()循环中的fori()的方式对不规则二维数组进行打印?
当我们采用for()循环中的fori()的方式进行打印数组时,如果采用以下代码进行打印,则会抛出空指针异常,因为此时我们只能确定出数组的行有几行,而不能确定出数组的列有几列,所以代码在运行到 for (int j = 0; j <array[i].length ; j++)这一行时,会判断不出array[i]的长度,所以就会进行报错。具体代码示例如下:

public class TestDemo {
    public static void main(String[] args) {
        int[][] array = new int[2][];
        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();
        }
        }
    }

对于这个数组的理解,可借助下图进行理解。
在这里插入图片描述
代码运行结果如下图所示:
在这里插入图片描述
所以我们可以通过下面两种方法对这个问题进行解决,方法一如下面的代码所示:

public class TestDemo {
    public static void main(String[] args) {
        int[][] array = new int[2][];
        array[0] = new int[10];
        array[1] = new int[3];
        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();
        }
    }
    }

代码运行结果如下图所示:
在这里插入图片描述
方法二如下面的代码所示:

public class TestDemo {
    public static void main(String[] args) {
        int[][] array = new int[2][];
        array[0] = new int[]{1,2,3,4,5};
        array[1] = new int[]{7,8,9};
        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();
        }
        }
    }

代码运行结果如下图所示:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值