Java数组进阶,null,引用和本体修改,元素交换,方法模拟实现,排序,逆序

前言❤️❤️

hello hello💕,这里是洋不写bug~😄,欢迎大家点赞👍👍,关注😍😍,收藏🌹🌹
上篇博客包括数组的一些基础知识和内存存储的解析,因此在看这篇博客前,建议铁汁们先把上篇博客过一下(链接就放在下面),这篇博客的内容包括数组常见的各种操作,引用,元素交换,数组复制,数组元素的查找和排序,帮助大家快速入门💪💪💪Java,内容比较多,铁汁们可以分两次来看🐵
请添加图片描述
🎇上篇博客:数组基础
🎇个人主页:洋不写bug的博客
🎇所属专栏:Java学习之旅,从入门到进阶
🎇铁汁们对于Java的各种常用核心语法(不太常用的也有😆),都可以在上面的Java专栏学习,专栏正在持续更新中🐵🐵,有问题可以写在评论区或者私信我哦~

1,认识null(空引用)

null这个单词要读对,如果发音不对的话,后面面试时可能面试官听不懂我们在讲什么,这个拼音的话读的是nao.

在这里插入图片描述




在Java中,空引用定义为“无效的引用”,至于里面的地址是啥,其实是不知道的,这个取决的JVM的具体实现。
下面这段代码就会报错,提示空指针异常,但是Java中把指针这个概念淡化掉了,因此我们一般称这个为空引用异常。

public class exercise {
    public static void main(String[] args) {
       int []arr = null;
       System.out.println(arr[0]);
    }
}

在这里插入图片描述

可以这样理解“空引用(null)”,那就是一个不指向任何对象的引用
上篇数组基础博客中的第5部分有这部分内容的解析,这部分想再了解一下可以看下:
数组基础

null(空引用)其实与C语言中的空指针比较类似,都表示一个无效的内存位置,不能对这个内存位置进行读取,修改等操作,一旦尝试操作,就会报出“空引用异常”的错误

2,方法中数组内部引用修改的解析

1,修改引用

铁汁们现在可以先猜一下这段代码的执行结果,认为会打印出1,2,3的在评论区扣1,其他的在评论区扣2

import java.util.Arrays;

public class exercise {
    public static void main(String[] args) {
       int []arr = {1,2,3};
       func(arr);
       System.out.println(Arrays.toString(arr));
    }

    public static void func(int[] a){
        a = new int[]{100,2,3};
    }
}



上篇博客提到,形参是实参的一份拷贝,那么刚执行到方法时,形参就在栈上拷贝了一份,存储的地址和实参存储的地址相同。

在这里插入图片描述




接下来执行到 a = new int[ ]{100,2,3}; 这一步是重点,这里的new int[]其实是新在堆上创建了一片空间,然后用形参a指向这一块新的空间。

在这里插入图片描述

这里一定要注意的一点,那就是形参在栈中存储的地址更新了,指向了新创建的这部分空间,相当于跟原来的实参就没有任何关系了。

我们前面说过,只要是new,都是在堆内存中创建了一块空间,a = new int[ ]{100,2,3}
代码大家不要从左往右看,那样容易误解,其实是从右往左看的,先在堆中创建了一个空间,再让a这个引用指向这个空间。



所以,最后的结果就是没有改变

在这里插入图片描述

2,修改本体

铁汁们再看一下面这段代码,猜一下这个的打印结果。

import java.util.Arrays;

public class exercise {
    public static void main(String[] args) {
       int []arr = {1,2,3};
       func(arr);
       System.out.println(Arrays.toString(arr));
    }

    public static void func(int[] a){
        a[0] = 100;
    }
}

这里再画一个内存图,这里的修改,其实是直接修改到了本体,因为并没有重新在堆内存中重新new出一片空间:

在这里插入图片描述

运行结果如下:

在这里插入图片描述

总结
方法内部对引用的修改,也不总是能影响到外面的,关键是看对引用进行的修改是哪种操作

  1. 如果修改的是指向对象(堆内存中)的本体,那么这里的修改是能被方法外感知到的。
  2. 如果修改的引用本身,那么方法中的引用只是栈内存中的一份拷贝,这时的修改方法外面是感知不到的。

    其实这里大家不需要死记硬背,只需要结合内存图,多写几次代码,理解一下即可

    3,交换

    1,常量的交换

    如果这样交换的话,能否进行有效交换?

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

    其实是不能的,因为形参只是一份拷贝,交换后并不会影响原来的实参

    在这里插入图片描述



    虽然形参这里的拷贝交换了,但是它只是一份复制,方法调用完后就会自动销毁,因此就不会影响原来的。

    在这里插入图片描述

    那我们怎么进行常量的交换呢,其实说来也简单,那就是把变量搞在数组中,数组存在堆内存中,这样是比较好交换的

    2,数组的交换

    这个就是直接修改堆内存中的数据,能够修改成功(也就是前面提到的在方法中本体的修改)

    import java.util.Arrays;
    
    public class exercise {
        public static void main(String[] args) {
           int [] arr = {1,2};
           swap(arr,0,1);
           System.out.println(Arrays.toString(arr));
        }
    
        public static void swap(int []arr,int i,int j){
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    
    

    在这里插入图片描述

    4,C和Java数组内存存储和释放机制

    铁汁们可以回忆一下,在C语言中这个代码写的有没有问题

    int *func(){
        int [] a = {1,2,3};
        return a;
    }
    

    其实是有问题的
    a这个数组是局部变量,当函数执行完以后,这个局部变量就释放内存了,当函数外面拿着这个内存再去使用(比如说读取什么的),就是访问非法的内存空间。
    (就好比出去旅游住酒店,已经把房退了,还拿着私自配的房卡,去开这个门)




    在Java中写这样一个代码,运行后并不会报错。

    import java.util.Arrays;
    
    public class exercise {
        public static void main(String[] args) {
            int [] arr = func4();
            System.out.println(Arrays.toString(arr));
        }
    
        public static int[] func4(){
            int[] a = {1,2,3};
            return a;
        }
    }
    

    在这里插入图片描述
    原因就是:

    在C语言中,数组是存到栈中的,一出方法就会被释放掉。
    在Java中,数组的核心数据是存储在堆中的,堆上的内存,释放实际上并不是跟当前的代码相关的。




    那么Java中这些new出来的在堆上的内存到底什么时候才会释放呢?
    当我们的代码中没有任何地方使用这些对象/数组时,就会被JVM自动释放掉,这个释放内存的过程是我们感知不到的,称为GC(garbage collection 垃圾回收机制),堆上的内存,都是通过GC来自动管理的。

    而在C++中,它们堆上的内存是需要手动去释放的,要是忘记释放,或者代码出现bug没有运行到,那么就会就会引起一个大问题———内存泄漏。
    内存泄露是C++程序员职业生涯的最大杀手,一旦爆发,系统就会崩溃,卡顿,但是我们学习的Java中,通过垃圾回收,就不用考虑这一问题
    当然,垃圾回收也需要一定的代价,比如消耗更多的系统资源,使程序在运行中需要更多的时间等。

    5,模拟实现toString方法

    1,目的

    上篇博客在数组的打印部分提到了用Arrays类的toString方法来打印,代码如下:

    import java.util.Arrays;
    
    public class exercise {
        public static void main(String[] args) {
            int[] arr = {1,2,3};
            System.out.println(Arrays.toString(arr));
        }
    
    }
    

    在这里插入图片描述

    那既然Arrays工具类为我们提供了这个方法,为什么还要自己再写一个呢
    这样的练习,一方面能增强我们对这个方法的理解,另外也能增强我们的写代码的能力
    有时候面试也会考让我们自己实现一个工具类中存在的方法,就是为了考验我们的理解能力(这里注意,我们只是练习一下,后序在打印时直接用工具类Arrays的就行)

    2,字符串知识补充和代码实现

    这里要用到字符串(String),但是字符串是在后面博客中才会提到的,这里先补充一些。
    字符串是可以拼接字符串的,代码如下,打印出来就是helloworld

    public class exercise2 {
        public static void main(String[] args) {
            String s = "hello";
            String a = "world";
            System.out.println(s + a);
        }
    }
    




    这里是我们自己写的toString方法,用的不是Arrays这个工具类中的方法,而是自己写的,因此就不用写Arrays.toString,直接写toString就可以了。

    这里就是让这个toString字符串不断的拼接新的字符串
    每次拼接完一个元素,需要判断是不是数组中最后一个元素,进而决定加不加逗号,如果是数组中的最后一个元素,就不需要加逗号了

    public class exercise {
        public static void main(String[] args) {
            int[] arr = {1,2,3};
            System.out.println(toString(arr));
        }
    
        public static String toString(int [] arr) {
            String result = "[";
            for (int i = 0; i < arr.length; i++) {
                result += arr[i];
                if (i < arr.length - 1) {
                    result += ",";
                }
            }
            result += "]";
            return result;
        }
    }
    

    打印的结果跟Arrays类中的toString方法效果是相同的

    在这里插入图片描述

    6,Arrays工具类中的数组复制方法

    1,区间复制

    这个Arrays工具类copyOfRange方法就是用于数组的区间复制
    前面是要复制的数组名,后面是复制的区间,是前闭后开的,也就是[1,3),实际上复制的就是arr数组的第一项和第二项,刚好是2和3。

    import java.util.Arrays;
    
    public class exercise2 {
        public static void main(String[] args) {
            int [] arr = {1,2,3,4,5};
            int [] arr2 = Arrays.copyOfRange(arr,1,3);
            System.out.println(Arrays.toString(arr2));
        }
    }
    

    在这里插入图片描述




    这里的copyOfRange中的“Range”就是区间的意思,在IDEA界面会看到这里有个from to

    在这里插入图片描述

    在编程中,只要说到区间,大部分都是前闭后开(极少数情况是前闭后闭)
    这样的设定是为了能够立刻算清楚这个区间中有几个元素,比如说[1,3),我们直接用后一项减去前一项,就能算出区间中元素的个数为2

    2,长度复制

    长度复制,就是直接写出新复制数组的长度,然后从前往后进行复制,长度用完就停止复制了。

    import java.util.Arrays;
    
    public class exercise2 {
        public static void main(String[] args) {
            int [] arr = {1,2,3,4,5};
            int [] arr2 = Arrays.copyOf(arr,5);
            System.out.println(Arrays.toString(arr2));
        }
    }
    

    在这里插入图片描述



    方法后面的参数改成4,就是复制4个元素,是从前往后复制的。

    import java.util.Arrays;
    
    public class exercise2 {
        public static void main(String[] args) {
            int [] arr = {1,2,3,4,5};
            int [] arr2 = Arrays.copyOf(arr,4);
            System.out.println(Arrays.toString(arr2));
        }
    }
    

    在这里插入图片描述





    那如果写的长度超过了原数组的长度,不会报错,多出的部分就是补零

    import java.util.Arrays;
    
    public class exercise2 {
        public static void main(String[] args) {
            int [] arr = {1,2,3,4,5};
            int [] arr2 = Arrays.copyOf(arr,6);
            System.out.println(Arrays.toString(arr2));
        }
    }
    

    在这里插入图片描述

    3,模拟实现长度复制方法

    如果这样写代码实现长度复制,有没有bug?

    import java.util.Arrays;
    
    public class exercise2 {
        public static void main(String[] args) {
            int [] arr = {1,2,3,4,5};
            int [] arr2 = copyOf(arr,3);
            System.out.println(Arrays.toString(arr2));
        }
    
        public static int[] copyOf(int [] arr,int newLength){
            //按照指定长度创建新数组
            int [] result = new int [newLength];
            for(int i = 0;i < arr.length;i++){
                result[i] = arr[i];
            }
            return result;
        }
    }
    

    这里使用的是静态初始化,根据需要创建,上来会把result里面的元素都初始化为0
    但是arr.length是5,result创建的大小为3,后面再赋值时就会出现数组越界异常

    在这里插入图片描述



    在方法中这样先判断一下,就可以了
    如果arr.length大,说明复制的数组长度比较小,就使用newLength来赋值,避免result出现数组越界
    如果newLength大,说明复制的数组长度比较大,就使用arr.lenth来赋值,避免arr出现数组越界异常
    相等的话,那就是一比一复制,用哪个来复制都可以

        public static int[] copyOf(int [] arr,int newLength){
            //按照指定长度创建新数组
            int [] result = new int [newLength];
            int len = arr.length > newLength ? newLength : arr.length;
            for(int i = 0;i < len;i++){
                result[i] = arr[i];
            }
            return result;
        }
    



    IDEA这里会提醒有更好的写法,我们把鼠标放上去,alt + enter一下

    在这里插入图片描述

    会发现替换成了这样,这个就是使用Math类中的求最值的方法
    那以后就没有必要再去使用三元表达式了,直接用这个函数就可以了

    在这里插入图片描述

    7,Java标准库

    前面使用的Arrays.toString()方法,还有Arrays.copy方法,包括刚刚IDEA帮我们自动补充道的Math.min,都是Java标准库中的方法。

    在C中也有标准库,但是C的标准库近似于没有,功能非常少。
    而Java的标准库就牛逼许多了,那些开发Java的大佬已经把各种常用功能准备好了,我们只需调用即可,

    除了Java官方公司(Oracle)搞得标准库之外,还有很多第三方库,是其他非官方的人搞的库,这些库的数量,超级超级多

    8,编写方法查找数组中指定的元素

    1,顺序查找

    这是最简单的一种查找,找到的话返回所在下标,找不到就返回-1

        public static int find(int[] arr,int num){
            int f = 1;
            for(int i = 0;i < arr.length;i++){
                if (arr[i] == num)
                    return i;
        }
            return -1;
        }
    

    2,二分查找(效率高)

    二分查找是有要求的,只能用于有序的数组

    public class exercise {
        public static void main(String[] args) {
            int []arr = {1,2,3,4,5,6,7,8,9,10};
            int index = binarySearch(arr,7);
            if(index == -1){
                System.out.println("数组中没有这个元素");
            }else{
                System.out.println("这个元素在数组中的下标为:" + index);
            }
        }
        //二分查找函数(返回查找到的数的下表)
        public static int binarySearch(int [] arr,int target){
            int left = 0;
            int right = arr.length - 1;
            while(left <= right){
                int mid = (left + right) / 2;
                if(target == arr[mid]) {
                    return mid;
                }
                    else if(target < arr[mid]){//target在mid的左边
                        right = mid - 1;
                    }
                else if(target > arr[mid]){//target在mid的右边
                    left = mid + 1;
                }
            }
            return -1;
        }
    }
    

    二分查找的思路:
    ①用left表示最左侧元素的下标,用right表示右侧元素的下标。
    ②找出数组的中间元素,如果有两个中间元素的话因为这里的int的舍弃小数,因此我们取得是前一个元素。
    ③对比中间元素跟比较值的大小关系,再根据这个不断的缩小比较范围。
    ④那这时候就需要在最外层搞一个while循环,判断什么情况是没有查找失败,可以继续查找,这里就是left <= right,那么为什么这样写的,分析如下:

    ①首先确定能不能写等号,这里的区间是前闭后闭,[left,right]区间,这个区间中还是存在一个数的,因此还可以再比较一次

    ②当最后这个数还是不相等,无论是比目标值大还是小,只有两种情况
    要不就是执行right = mid - 1;right跑到left左边
    要不就是执行left = mid + 1;left跑到right右边
    那这时候我们就知道没有判断的必要了,left > right时,不符合while循环的条件,会直接跳出来,也就是没有找到对应元素

    大家可以自己在本子上画一下left和right移动的过程,有不懂的地方也可以在评论留言或者发私信

    9,改进版冒泡排序(bubbleSort)

    冒泡排序是数组非常经典的一种排序方式,在C语言中已经学过,下面就是改进版的冒泡排序的代码:

    import java.util.Arrays;
    
    public class exercise {
        public static void main(String[] args) {
            int [] arr = {9,5,2,7,3,6};
            bubblesort(arr);
            System.out.println(Arrays.toString(arr));
        }
        //冒泡排序(升序)
        public static void bubblesort(int [] arr){
            for(int i = 0;i < arr.length - 1;i++){
                boolean f = true;
                for(int j = 0;j < arr.length - 1 - i;j++){
                    if(arr[j] > arr[j + 1])
                    {
                        f = false;
                        int temp = arr[j];
                        arr[j] = arr[j + 1];
                        arr[j + 1] = temp;
                    }
                }
                if(f)
                    break;
            }
        }
    }
    

    ①外面的for循环是每循环一次,就让最大的一个数到最后,一个数组有n个数,只需要循环n - 1次即可
    因为还剩两个数时,只需要把较大的数放后面,那么这两个数就都是有序的的了

    ②内层循环就是判断相邻的两个数谁大,把大的放在后面,这样循环一轮,就会把最大的一个数放在最后
    那么如何看到底需要循环几次呢,
    可以看下面这个图,两两循环,有n个数,是循环n - 1次,那么每次循环完以后,需要排序的数都会减一个,第一次相对于所有数少1个,第二次相对于所有数少两个,因此再减去i就可以了。因此就是循环n - 1 - i次

    在这里插入图片描述

    代码优化: 当数组是比较有序的时候,可能外部for循环搞个几次就有序了,那么这时候再循环到底,不断地判断,就有点麻烦了
    当完全有序的时候,内部的for循环里面if里的内容就不会执行;
    因此,如果内部for循环走了一圈还没有触发if的话,那这个数组就已经有序了,就可以直接跳出排序了。
    这里引入了布尔类型的变量f,用来判断内层每一轮for循环结束后,是否数组已经有序了

    10,标准库排序(有手就能排)

    import java.util.Arrays;
    
    public class exercise {
        public static void main(String[] args) {
            int [] arr = {9,5,2,7,3,6};
            Arrays.sort(arr);
            System.out.println(Arrays.toString(arr));
        }
    }
    

    就是这么简单,而且标准库排序是大佬们研究的高度优化的排序,比我们自己写的排序效率是要高的。

    那既然有这么牛逼的排序,我们为什么还要自己写各种排序呢,原因就是面试,笔试的时候会用到😄,用来考察我们的思维和代码能力

    11,数组逆序(双下标法)

    要求:把一个数组里面的元素进行逆序处理,原来是[1,2,3],逆序处理后就是[3,2,1]
    一些铁汁可能会想到再创建一个新的数组,老数组逆序赋值给新数组,再用新数组把老数组给替换掉,但是这样就需要额外的一块存储数组的空间

    这里我们使用“双指针法”,由于Java中弱化了指针的概念,因此称之为双下标法,双下标法是无需额外的数组空间的
    最左侧元素的下标left,最右侧元素的下标right,交换left和right的元素,并将left和right都往中间移动,移动一次,交换一次,直到left和right重合,或者left在right右边,right在left左边。

    import java.util.Arrays;
    
    public class exercise {
        public static void main(String[] args) {
            int [] arr = {1,2,3,4,5,6,7};
            reverseArray(arr);
            System.out.println(Arrays.toString(arr));
        }
        //    //数组倒序函数
        public static void reverseArray(int [] arr){
            int left = 0;
            int right = arr.length - 1;
            while(left <= right){
                swap(arr,left,right);
                left++;
                right--;
            }
        }
        public static void swap(int []arr,int i,int j){
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    



    当数组元素个数是奇数时,最后left和right会移到中间
    这时候自己跟自己交换也没什么问题,因此写不写等号无所谓
    下一步right向左移,left右移,这时候已经全部交换完了,就不用再交换了,left > right,跳出循环。

    在这里插入图片描述


    当个数是偶数个时,这时候最后一次交换对应数是在中间两个数中,接下来left就跑到了right的右边,交换完成,不符合while条件,直接退出。

    在这里插入图片描述

    注:上面只是简单的演示一下,实际的数字交换博主并没有做。

    12,二维数组

    二维数组,本质上是一个一维数组,只不过每个数组的元素都是一个一维数组
    三维数组,本质上也是一个一维数组,只不过每个数组的元素都是一个二维数组
    维数的升高,就是在套娃😄

    下面这段代码可以理解为定义了一个长度为3的一维数组,每个一维数组里有4个元素

    public class exercise {
        public static void main(String[] args) {
            //定义一个3行4列的arr数组
            int[][] arr = new int[3][4];
        }
    }
    




    这段代码,打印的结果是2,并不是4,在Java中,是不要求所有的行数,列数一致的

    上篇博客中提到,在C中,数组的类型不是int[ ],而是int[4],因此数组要求每个元素的类型一致,二维数组中的每个元素都得是int[4],这是不科学的。

    public class exercise {
        public static void main(String[] args) {
            //定义一个3行4列的arr数组
            int[][] arr = {{1,2,3,4},{1,2,3,4},{1,2}};
            System.out.println(arr[2].length);
        }
    }
    

    在这里插入图片描述

    结语💕💕

    这篇文章中代码量很大,强烈建议铁汁们有空动手敲一下,刚开始学习感觉绕是很正常的,,经过不断的深入学习,几个月后再看这些东西就会感觉超级简单👍👍👍
    🥳🥳大家都学废了吗?完结撒花~ 🎉🎉🎉

    在这里插入图片描述

    评论
    成就一亿技术人!
    拼手气红包6.0元
    还能输入1000个字符
     
    红包 添加红包
    表情包 插入表情
     条评论被折叠 查看
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值