【Java】剖析数组的使用


数组(Array)是 相同类型的变量的集合,若将该集合命名,那么这个名称为 数组名。组成数组的各个变量称为数组的元素。

(本文画的图里的地址均为杜撰,了解其含义即可)

一、创建数组

✒️基本语法形式:

// 静态初始化
数据类型[] 数组名称 = { 初始化数据 };
// 动态初始化
数据类型[] 数组名称 = new int[元素个数];
数据类型[] 数组名称 = new 数据类型 [] { 初始化数据 };

📑代码示例:

public class TestDemo {
    public static void main(String[] args) {
        int[] array1 = {1,2,3,4,5,6}; //方法一
        int[] array2 = new int[10];   //方法二 初始化的数据默认为0
        int[] array3 = new int[]{1,2,3,4,5,6,7}; //方法三
    }
}

💬代码解释:

  • array1、array2、array3 都是局部变量,需要在Java虚拟机栈上开辟空间进行存储。
  • 同时它们也都是引用变量,该变量中存放着其指向的对象的地址

二. 引用变量是什么?

  • 引用变量又叫引用,可以理解成一个指针。
  • 创建一个引用就相当于创建了一个保存了地址的变量,这个地址就是这个变量的值。
  • 如果进行数组参数传参,其实就相当于将数组的地址传入到所对应函数的形参中,避免整个数组的拷贝(万一数组比较长,空间会浪费很多)。

三. 对象是什么?

  • 对象的一个实例,人,狗,书本等都可以叫做对象。
  • array1指向的对象就是1到6这六个数字。
  • 对象需要在上开辟空间进行存储。

在这里插入图片描述

注意:

  • 在初始化数组的时候,数据类型后面的 [ ] 中不可以添加任何数字。初始化数组后,编译器会自动知晓数组元素的个数。
  • 习惯将 [ ] 放在数组名称的前面,因为 [ ] 也是数据类型的一部分。尽管也可以写成类似 int array[] = {1,2,3};这样的代码形式。
  • 在静态初始化的时候,数组元素个数和初始化数据的格式应当保持一致
  • 日常的数组使用以方法一为主

二、打印数组内容

方法一:利用循环结构打印

📑代码示例:

public class TestDemo {
    public static void main(String[] args) {
        int[] array1 = {1,2,3,4,5,6};
        System.out.println("数组元素个数为:" + array1.length);
        for (int i = 0; i < array1.length; i++) {
            System.out.print(array1[i] + " ");
        }
    }
}

🏸 代码结果:

在这里插入图片描述

💬代码解释:

  • array.length能够获取到数组的长度
  • 使用 [ ] 来访问到各个下标的数组元素,下标有效范围 [0 , array.length - 1]
  • 不能超出有效范围,否则会出现数组越界异常(ArrayIndexOutOfBoundsException)

方法二:增强for循环(for-each 循环)

📑代码示例:

public class TestDemo {
    public static void print(int[] array) {
        for(int x : array) {
            System.out.print(x + " ");
        }
    }
    public static void main(String[] args) {
        int[] array1 = {1,2,3,4,5,6};
        print(array1);
    }
}

🏸 代码结果

在这里插入图片描述

💬代码解释:

  • for( : ) 括号里冒号的右侧是需要遍历的数组名称,左侧是数组里面存放的元素的类型定义的变量。向变量x中存放一个,接着打印一个,从而实现遍历数组内容的功能。
  • 和方法一相比,方法二没有办法利用下标拿到各个元素的内容,从而进行更多的操作,但是能够更方便的完成对数组的遍历. 可以避免循环条件和更新语句写错。

在这里插入图片描述

  • 利用 print 函数打印数组,传参时,将 array1的值(即其指向的对象的地址)传给了 print 函数,用数组 array 接收,此时 array 数组也指向了地址为 0x888 的那个对象,便可以将其打印。

  • 这意味着,array 函数拥有改变地址为 0x888 的那个对象的内容

方法三:利用toString()方法

📑代码示例:

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

🏸 代码结果

在这里插入图片描述

💬代码解释:

一. 这里调用 Arrays 这个操作数组的工具类里面的 toString() 方法,该方法可以将当前的数组转换为字符串的形式进行输出

二. 想要使用这个类,首要做的就需要导入包(就相当于C语言当中的#include < >),这里需要导入Java的实用工具类库java.util包,具体导入代码为 import java.util.Arrays;

三. 什么是包?
程序员进行开发并不是从最底层一步一步的完完全全的由自己实现,为了减少代码量,提高效率,我们需要站在巨人的肩膀上进行开发,包里面有着大量的编译好生成字节码文件,拿来就可以在jdk中运行。导包实际上就是导入这个包底下的类。

  • import java.util.*表示把 util 包下的所有类都导入到程序当中,但是实际上并不会导入所有东西到文件里,因为在Java中用到了哪个类才会加载哪个类。
  • import java.util.Arrays表示将util包下特定的类Arrays导入到程序当中。
自我实现toString()方法
public class TestDemo {
    public static String myToString(int[] array) {
        if(array == null) {
            return null;
        }
        if(array.length == 0) {
            return "";
        }
        String ret = "[";
        for (int i = 0; i < array.length  ; i++) {
            ret += array[i];
            if(i < array.length - 1) {
                ret += ",";
            }
        }
        ret += "]";
        return ret;
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6};
        String ret = myToString(array);
        System.out.println(ret);
    }
}

💬代码解释:

  • 将数组转换成字符串的形式输出,通过 ‘+’ 将字符进行拼接,需要注意的是,数组最后一个元素(下标为array.length - 1)后面是没有逗号的

  • 还需要注意实参是 null实参是一个不包含任何元素的数组这两种情况。考虑完善,使方法更加完善。

拓展:交换整形变量

写一个方法使其具有交换两个整形变量的功能

思路一:

📑代码示例:

public class TestDemo {
    public static void swap(int a,int b) {
        int tmp = a;
        a = b;
        b = tmp;
    }
    public static void main(String[] args) {
        int x = 10;
        int y = 20;
        swap(x,y);
        System.out.println("x = " + x + " y = " + y);
    }
}

🏸 代码结果:

在这里插入图片描述

💬代码解释:

结果显而易见,并没有实现数据的交换,思路一的思路是有问题的。

因为对于 int 这样的基础类型,形参 a 和 b 就是实参 x 和 y 的临时拷贝,即传值调用。

就是说,你a b交换了和我x y有啥关系!

思路二:

利用数组进行交换

📑代码示例:

public class TestDemo {
    public static void swap(int[] array) {
        int tmp = array[0];
        array[0] = array[1];
        array[1] = tmp;
    }
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        int[] array1 = {a,b};
        System.out.println("交换前: " + array1[0]  + " " + array1[1]);
        swap(array1);
        System.out.println("交换后: " + array1[0] + " "  + array1[1]);
    }
}

🏸 代码结果:

在这里插入图片描述

💬代码解释:

在这里插入图片描述

三、了解null

📑代码示例:

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

💬代码解释:

  • 在此处,array2 这个引用指向 array1 这个引用所指向的对象
  • array3 这个引用赋值一个null,代表着不指向任何对象
  • 一个引用类型如果不知道将来指向哪个对象,就可以将其赋值为 null
  • 当引用类型赋值为 null 时,如果通过 null 尝试访问属性的时候,例如想要求数组的长度时(array3.length),将会报错,显示空指针异常(NullPointerException)

四、牛刀小试

练习一:元素 * 2

题目:写一个方法, 将数组中的每个元素都 * 2(要求不改变原来的数组)

📑代码示例:

public class TestDemo {
    public static int[] change(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[] array1 = {1,2,3,4,5};
        System.out.println("改变前array1数组:" + Arrays.toString(array1));
        int[] ans = change(array1);
        System.out.println("改变后array1数组:" + Arrays.toString(array1));
        System.out.println("改变后返回的数组:" + Arrays.toString(ans));
    }
}

🏸 代码结果:

在这里插入图片描述

💬代码解释:

  • 将 array1作为参数传给change()函数,由于数组是引用类型,返回的时候只是将 array1指向的对象的地址递给了实参 array ,并没有拷贝数组的内容,从而比较高效。
  • 在 change() 函数中重新创建了一个数组 ret 来接收array数组中每个数乘以2后的结果,返回数组 ret,就不会破坏原有的数组。
  • change() 函数结束后,数组 array 和 数组 ret 随之被销毁,但是其指向的对象在堆上并不会销毁。

在这里插入图片描述

练习二:数组拷贝

方法一:循环拷贝

📑代码示例:

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

🏸 代码结果:

在这里插入图片描述

💬代码解释:

在 arraysCopy 这个拷贝的方法中定义一个新的数组 newArray 通过 for 循环的方式来进行拷贝,最后将拷贝的数组返回

方法二:Arrays.copyOf 和 Arrays.copyOfRange

📑代码示例:

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

🏸 代码结果:

在这里插入图片描述

💬代码解释:

在这里插入图片描述

  1. 如果新的数组(newArray)的长度大于需要拷贝的数组(array)的长度,用进行补充。
  2. Java中一般看到 from to 这样的一个范围,一般情况下都会是左闭右开,在这里从打印下标为 1 的元素进行打印,打印到下标为 4 的元素为止(不包括下标为 4 的元素),范围为 [1,4)。

方法三:System.arraycopy

📑代码示例:

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

🏸 代码结果:

在这里插入图片描述

💬代码解释:

在这里插入图片描述

在该代码中从原数组(array)下标为 1 的位置开始拷贝,拷贝到目标数组(newArray)下标为 2 的位置,拷贝的长度为 3,剩余的位置补 0 。

方法四:array.clone

📑代码示例:

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

🏸 代码结果:

在这里插入图片描述

💬代码解释:

在这里插入图片描述

深拷贝与浅拷贝

以上举例的四种拷贝的实现方式都属于深拷贝,那么什么是浅拷贝?两者又有什么区别?

在这里插入图片描述

提问:如果想要将上述的浅拷贝变成深拷贝,应当怎么做?

唯一的办法就是将 array1 数组中每个元素指向的对象在堆中全部复制一份,拷贝之后的数组中的元素指向新复制的对象。

在这里插入图片描述

当面试的时候,面试官问,这几种拷贝方式是深拷贝还是浅拷贝?

应当这么回答:

首先思考拷贝的对象是什么?

  • 对象是基本数据类型,是深拷贝
  • 对象是引用类型
    • 只是单纯的使用这几个方法,是浅拷贝
    • 使用这几个方法,并且代码本身也处理了深拷贝,是深拷贝

练习三:查找数组中指定元素

顺序查找

📑代码示例:

ublic 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 = {23,35,2,31,5,67};
       int ret = findNum(array,31);
       if (ret == -1) {
           System.out.println("查无此数");
       }else {
           System.out.println("该数下标为: " + ret);
       }
    }
}

🏸 代码结果:

在这里插入图片描述

💬代码解释:

顺序查找采用的是最暴力的通过循环一个一个对比进行查找的方法,思想简单。最大的缺点是效率低下,如果该数组中有1000个元素,刚好查找的元素位于该数组的最后,就意味着循环需要进行1000次

二分查找

二分查找针对的是有序数组

📑代码示例:

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) {
                right = mid - 1;
            }else if (array[mid] < key) {
                left = mid + 1;
            }else {
                return mid;
            }
        }
        return -1;
    }
    public static void main(String[] args) {
       int[] array = {23,35,2,31,5,67};
       Arrays.sort(array);
       System.out.println("排序后的数组:" + Arrays.toString(array));
       int ret = binarySearch(array,5);
       if (ret == -1) {
           System.out.println("查无此数");
       }else {
           System.out.println("下标为: " + ret);
       }
    }
}

🏸 代码结果:

在这里插入图片描述

💬代码解释:

首先要用 Array 这个类中的 sort 这个方法对数组进行排序,使之成为有序数组。

在这里插入图片描述

练习四:冒泡排序

📑代码示例:

public class TestDemo {
    public static void bubbleSort(int[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            boolean flag = true;
            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;
                    flag = false;
                }
            }
            if (flag) {
               break;
            }
        }
    }
    public static void main(String[] args) {
        int[] array = {8,3,5,7,2};
        System.out.println("排序之前" + Arrays.toString(array));
        bubbleSort(array);
        System.out.println("排序之后" + Arrays.toString(array));
    }
}

🏸 代码结果:

在这里插入图片描述

💬代码解释:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

冒泡排序是一种简单基础的排序算法,实现原理是重复排序数组序列,并比较每一对相邻的元素。

  1. n个元素时,一共将进行 n - 1 趟冒泡排序。

  2. i (1 ~ n - 1)趟排序需要进行 n - 1 - i 次比较。每当元素顺序不正确时(在这里实现的是升序),就进行交换。

  3. 为了提高效率,在每次进入一趟新的排序时,定义一个 flag 变量,初始化为 true。如果某一趟冒泡排序,有进行交换这一操作,就将 flag 赋值为 false ;如若一整趟冒泡排序下来没有进行交换元素,flag 始终为true ,说明该数组已经排序完成,就可以提前跳出循环,完成一整个排序过程。

完!

  • 32
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 28
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

富春山居_ZYY(已黑化)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值