Java数组从基础定义到栈堆内存解析

前言❤️❤️

hello hello💕,这里是洋不写bug~😄,欢迎大家点赞👍👍,关注😍😍,收藏🌹🌹
我们在C语言中已经学习过数组,但是Java中的数组跟C语言中的语法,内存机制差别还是很大的,这篇文章从多个角度解析Java数组与C中的区别,也初步介绍了引用类型是什么,同时对数组的内存存储进行了画图解析,帮助大家快速入门💪💪💪
请添加图片描述
🎇个人主页:洋不写bug的博客
🎇所属专栏:Java学习之旅,从入门到进阶
🎇铁汁们对于Java的各种常用核心语法(不太常用的也有😆),都可以在上面的Java专栏学习,专栏正在持续更新中🐵🐵,有问题可以写在评论区或者私信我哦~

1,数组简介

在编程时有时需要定义多个变量,管理多组数据,那就要使用数组。
对于Java和C语言,它们的数组都要求里面的元素必须是相同的类型

在C中数组的写法下面这样,这种写法其实是不科学的:

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

例如,在C中定义一个int array[3],那么数组名就是array,变量名就是int [3]
在C中再定义一个int array2[5],那么数组名就是array2,变量名就是int[5]
在C中,这两个数组是不同的两种类型,array的类型是int[3],array2的类型是int[5]。在Java这样写也不会报错,但是这种写法不科学,因此就不建议使用




下面是Java两种定义数组的方法:

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

第一种定义方法就是先设定一个长度为多少的数组,里面的每个元素都初始化为0,后续进行存放,
定义的形式就是 T [] 数组名 = new T[N];
这里的T指的就是数组中存储的变量的类型,N指的就是数组的大小(在定义时必须指出数组的大小,否则就会报错

第二种定义方法就是直接定义,直接指定出数组中的内容,这时数组的长度就是存入数据的数量(也就是创建长度为5的数组,里面的数据初始化为1,2,3,4,5)

在Java中,只要是int类型的数组,它们的类型都是int[],都是类型相同的,这种设定就更加的科学

2,数组的静态初始化和动态初始化

前面介绍的Java数组的两种写法,就是动态初始化和静态初始化。

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

1,动态初始化

int [] array = new int[5];

动态初始化就是在创建数组时,直接指定数组中元素的个数
动态:指的是“运行的过程中”确定的信息
这里系统在编译时仅能确定数组的类型,无法确定数组的长度,等运行到这里的时候,才会读取数组的长度,为数组分配空间,并且将数组中的所有元素初始化为0

这里的new,是Java中的一个关键字,表示创建一个对象。(数组也可以视为一种特殊的对象





不同类型的数组在动态初始化时大部分的数组元素都是直接初始化为0了,特例就是char是空字符,然后boolean类型就是false,如下表所示:

在这里插入图片描述

Java与C不同,C中这样定义一个数组,数组里的元素是随机值,而不像Java中会设定成统一的初始值,因此,C中这里就比较混乱。
另外,在C中,定义数组的大小时,[ ]里面的数只能是数字,宏定义这些,不能是变量

       const int n;
       int a[n];

就算里面写常量,也还是会报错
可能有的铁汁在devC++上发现这样写也不会报错,但是实际上判断标准不一样
现在企业用的是Visual Studio2022用来写C和C++,里面的MSVC编译器 对 C99 及以后标准的 可变长度数组(VLA)特性是不支持的
这基本上也是在笔试时用的判断标准,大家在牛客上刷一些题的时候看题解,会发现有的会多写许多代码,也不使用去变量定义数组大小,就是这个原因

但是在Java中就不是这样,可以用变量来定义数组的大小比如这个变量让用户来自己输入,去确定数组长度的大小)
当然,输入的如果是负数什么的,不符合条件,也会报错。

2,静态初始化

静态初始化就是在创建数组的时候不直接指定数据元素的个数,而是直接写具体的数据
静态:指的是“编译的过程中”确定的信息。
系统在编译的时候可以确定数组中的元素的类型,通过括号内元素的个数也能知道数组的长度,编译时编译器也能获取到数组中的每个值,后序如果不更改,数组中的初始值就是这些

不省略的写法:

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

省略写法:

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

我们一般采用的都是省略的写法

总结一下:
静态初始化虽然没有指定数组的长度,但是编译器在编译时会根据{}中的元素个数来确定数组的长度。
静态初始化时,等号后{}中的数据类型必须与等号前[]前的数据类型一致。

3,数组的打印

1,基础打印(数组长度确定)

打印时数组的下标(索引)跟C一样,也是从0开始的

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




如果我们打印array[6]的话,就会报数组下标越界异常

在这里插入图片描述

而在C中就不是这样,C中会先打印出一个随机值,然后程序崩溃(如下图所示)
之所以C和Java有这样的区别,是因为Java牺牲了一点点效率(在计算机硬件如此发达的今天,这些效率其实不算什么),在打印前,对是否越界就进行了判断

在这里插入图片描述




也可以用循环进行打印,打印出来效果是一样的。

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

在这个for循环中,我们要写i < 5,那么如果数组长度改变了,这个就同样也要改变,就比较麻烦,那我们能不能计算出数组的长度呢?

C中是这样搞的,sizeof(arr)/sizeof(arr[0])
这里的sizeof就是计算出数据的字节数
那就是先计算出数组的总的字节数,然后再计算出数组的第一个元素的字节数(也就是计算出数组每个元素的字节数),这就有点太复杂了…

这时候Java的好处就体现出来了
直接使用arr.length,即可得出数组的长度,这里面的 “.”就是成员访问运算符,访问对象的成员(这个与C语言中的结构体是类似的,一个结构体中有多个成员变量,也是通过“.”来访问的,arr是一个对象,对象可以看作是一个功能更多的复杂结构体,后面的博客会讲到)
这个.length就是数组中元素的个数
在Java中,数组的元素个数就是等于数组长度的,并且数组的长度创建后是不能修改的。

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

2,for-each循环打印

还有简便的遍历方式,就是使用for-each循环😎

这个写法,就是使当前的num,依次来取数组的每个元素
这里是数字写num,当然写别的的也可以,无所谓(比如说写n),num前面写类型 int,后面写数组的名字

public class exercise {
    public static void main(String[] args) {
        int [] array = {1,2,3,4,5};
        for(int num : array){
            System.out.println(num);
        }
    }
}
public class exercise {
    public static void main(String[] args) {
        int [] array = {1,2,3,4,5};
        for(int n : array){
            System.out.println(n);
        }
    }
}

for-each循环,也称为增强for循环,在C++中也是支持这种写法的,但是在C中不支持。

for-each循环有2个特点

  1. 只能读取打印数据,不能修改数据
  2. 使用for-each循环,只能从前往后读取数据,不能倒序

    3,数组名的打印(toString方法)

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

    大家可以猜一下这段代码会打印出来什么?

    在 C 语言中,数组名单独拿出来,会隐式转为指向第0个元素的指针。直接打印数组名时,输出的是数组第一个元素的地址
    这是因为C语言中数组和指针的界限十分的不明确,数组可以转成指针,而指针也可以通过[ ]转为数组,看起来又跟数组很像。
    这种奇怪的设定,在C之后的语言中就没有了,包括Java。

    Java中的运行结果如下:

    在这里插入图片描述

    这就是对象的hsahCode(哈希值),后面博客会具体讲到,但是hashCode并不是内存地址,Java把内存这个概念给隐藏了起来,在Java中,我们是没有办法通过常规手段来拿到内存地址的
    在Java中,直接打印任何一个对象的名字都会得到这个对象的hashCode




    想要打印出数组,就需要使用Arrays这个类中的toString方法来打印
    在使用这个工具类时,上面会自动导入包。

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

    打印的结果就是这样的:
    在这里插入图片描述

    4,Java数据类型

    1,两大数据类型

    Java的数据类型其实分为两大类:

    1,基础类型
    像int,long,double这些,都是基础的数据类型

    2,引用类型
    像Java中的字符串String,数组,还有自己创建的类,都是引用类型(引用类型在Java中是绝对的主力)

    2,Java引用类型相比C指针的升级

    C中的指针是跟内存核心相关的
    在这里插入图片描述

    这是格子就是内存上的“房间”,把这些房间进行编号,这些编号就称为“内存地址”。
    我们创建一个变量,保存一个在具体的空间中表示的内存地址,那么这样的变量就是“指针变量”。
    指针其实就是一类 变量,这样变量里面保存了一个整数,这个整数具有特定的含义,表示内存中的地址

    在C语言中,指针能够支持的操作,太多太多了,太灵活了
    赋值、解引用、取地址、[]、+ -、==、< >、各种类型转换…

    那么铁汁们想一下,这样的灵活真的是好的吗???
    其实在计算机中,灵活是贬义词 难以使用,容易出错
    死板/呆板/单一/固定是褒义词。 简单无脑,不易出错

    大家可能有这样的感受,学指针时特别复杂,有的人用指针写的牛逼的代码,可能我们一点都看不懂,我们有的写的别人也可能看不懂,**而且一旦出错,是很难检查出错误到底在哪一步。



    因此,Java中直接就把指针这个类型给干掉了,使用引用来代替😄

    Java中的引用本质上就是一个“限制了功能的指针” (用法简单了很多,失去了灵活性,但是不容易出错),功能如下:

    1. 引用可以相互赋值(但是通过常规手段,无法看到引用里面保存的地址)
    2. 解引用(都是通过.操作来访问成员)
    3. 比较 ==(判定两个引用是否指向同一个对象)
    4. 使用[ ]仅限于数组,其他的引用类型都无法使用[ ]

      但是,事实证明,少了那些花里胡哨的功能以后,并不影响我们正常的代码开发。
      整个Java的生态,都是围绕着“呆板”这个词来展开的,写Java代码时经常“一板一眼”,看起来就会很啰嗦。

      5,Java内存解析

      1,内存常识

      在理解引用的过程中,学习一些内存知识也是必要的。

      内存是计算机的硬件设备,是计算机存储数据的空间。
      在台式机和笔记本电脑中的内存都是通过内存条提供的(现在笔记本电脑常见的内存就是16G和32G)

      在这里插入图片描述

      在win11系统中,我们右击下面的任务栏,即可打开任务管理器

      在这里插入图片描述

      在左面选择性能,即可查看当前电脑的内存使用量

      在这里插入图片描述

      我们再看一下进程,每个程序的运行都是需要内存的。
      Java程序的运行依赖的是JVM(Java虚拟机),本质上Java代码是跑在JVM上的,Java程序消耗的内存就是JVM消耗的内存

      在这里插入图片描述

      2,引用变量内存机制详解

      JVM拿到这些内存区域后,又进一步进行了区域的划分。

      主要是分为了4个部分:

      1. 程序计数器:很小的区域,记录了下一条要执行的指令在哪里
      2. 栈:我们在调试器看到的“调用栈”就是指这个,保存方法之间的局部变量
      3. 堆:new int [10],数组new出的对象就是保存在堆中
      4. 方法区:方法区中就加载了写好的代码编译出的.class文件,也就是字节码文件

        注:这里的堆、栈跟我们后面数据结构中学到的堆、栈是完全不同的东西。





        方法中定义的变量都是局部变量(后面会讲到),出方法就会被销毁,而局部变量,都是存储在栈中的。
        这个数组是new出来的,储存在堆中,在Java中,凡是new出来的东西都是储存在堆中的。

          public static void func(){
                int a = 10;
                int b = 20;
                int [] array = new int []{1,2,3};
            }
        

        那这里大家可能就比较奇怪,那就是我一会说方法里的数组是局部变量,局部变量储存在栈中,一会又说数组是new出来的,储存在堆中,那到底储存在哪里?
        下面画一个内存图来看下
        :

        在这里插入图片描述

        这里画了内存中的堆空间和栈空间
        这里假设数组存储的地址是0x100(这里实际存储的地址我们是不知道的,因为是系统随机安排的,在Java中,也无法通过常规手段来获取地址,习惯上我们用十六进制来保存地址,因为地址很多,因此地址的前缀就是0x)

        通过这个图,我们就能理解到底什么是引用变量,本质就是就是在栈中创建一个引用,存储变量在堆中存储的地址,实际的数据是在堆中存储的。




        下面这段代码中
        arr1就是在堆中new出了一个数组,在栈中创建一个引用,引用里面存储arr1在堆中存储的地址。
        arr2并没有new数组,只是创建了一个引用,这个引用指向的是跟arr1相同的空间。

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

        在这里插入图片描述

        可以写代码验证一下:
        这里只改变arr2中的元素,但是打印时会发现arr1的元素也发生了改变。

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

        在这里插入图片描述

        这段代码存储内存图如下:
        因为栈空间引用的是同一块空间,因此arr2的更改也会同步到arr1

        在这里插入图片描述

        结语💕💕

        Java 中的数组在初始化方式、内存管理以及具体的使用细节上,和 C 语言里的数组存在不少差异,刚开始学习时不习惯很正常,后面随着深入学习,再回来看这些内容,会发现其实并不难,还是要多去写代码练习不要刻意去背这些概念。
        🥳🥳大家都学废了吗?完结撒花~ 🎉🎉🎉

        在这里插入图片描述

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

        请填写红包祝福语或标题

        红包个数最小为10个

        红包金额最低5元

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

        抵扣说明:

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

        余额充值