前言
数组是日常中很常用的一种类型。
在java中,与c语言不同的是,数组被当做一个对象,一种类型,他的定义也和c语言不一样,看代码:
//定义一个数组
int array[10]; //c语言
int[] array = new int[10];//java
从中也可以很明显地看出他们思想的不同,但实际上他们在内存中的实质是差不多的。不同的是java还可以使用引用类型的数组,例如:
Person[] persons = new Person[10];
而因为c语言中没有对象这种概念所以也没有这种类型。但因为C语言有结构体,所以也有类似的结构体数组,例如:
struct student stu[3];
上面讲的日常的使用,无论是c还是java我们都了解,但是他们在内存中的存储形式了解过吗?了解数组在内存中的存储形式,才能帮助我们更好的了解数组,以此来更好地使用他。下面我是从java的角度来讲述数组在java中的内存形式,至于c语言读者有兴趣可自行学习。
简单了解堆与栈
在java中内存被划分为堆和栈两个区域,在堆中还有一个静态区域,我把它简称为静态域。那么他们分别来干什么的呢?
- 栈区:栈区主要用来存放方法以及引用变量。例如我们调用了eat()这个方法,那么这个方法就会入栈。另外当我们定义
Preson person;
(注意还没new)的时候也会在栈区放一个person引用。 - 堆区:堆区主要用来存放对象。例如
Person person = new Person();
这样new Person()这个实例就放在堆区中。 - 静态域:主要存放类以及静态变量静态方法等。
上面简单讲述一下三种内存,我没具体讲述,因为这不是重点,简单了解一下就好。重点在于Person person = new Person();
这个person对象的存储。“栈区放一个person引用。”“new Person()这个实例就放在堆区中”这两句话不知道读者可否理解?简单点说,我们新建了一个person对象,这个对象的实体就存在堆中。然后这个实例引用,也就是变量person,存在栈中。虽然java没有指针这个东西,但是也可以类比c语言的指针。变量person可以看做一个指针,指向堆内存中存放person这个实例的内存。所以应该可以理解堆和栈的区别了吧。
同样对于数组,例如int[] num = new int[10];
首先会在栈中放一个num引用,再在堆中开辟一个空间存放数组,然后再让引用num指向开辟的这个空间的首地址。这样应该就可以理解java数组在内存中的模型了吧。
引用类型数组
基本类型的数组的内存模型相信读者已经很清晰了,这里就不讲了。重点讲一下引用类型数组。
刚才讲到在java中,有引用类型的数组,例如Person[];c语言中也有结构体数组,但是,和结构体数组不同的是:引用类型数组存放的是引用,而结构体数组存放的是真正的结构体数据而不是引用。怎么理解呢,先看看C语言的结构体数组,看一下代码:
typedef struct{
int a;
int b;
}s;
s test[3];
首先他在内存中开辟了一个8*3字节的空间(一个int4个字节),每8个字节存放一个结构体s;然后再把指针test指向开辟的内存的首地址。这个相信应该很好理解。再来看看java:
Student[] persons = new Student[2];
Student student1 = new Student();
student1.setAge(12);
student1.setName("mike");
Student student2 = new Student();
student2.setAge(13);
student2.setName("sali");
persons[0] = student1;
persons[1] = student2;
这里首先在栈中创建一个persons引用,然后在堆中开辟一个内存,再把persons指向这个内存的首地址。但是,重点,这里开辟的内存并不是像c语言那样一整个结构体的内存,这里开辟的内存是用来存放引用的,也就是存放地址的,并不是真正的person类实例的地址,因为现在还没实例化Student对象,所以开辟的这个内存里面都是null,空指针。
当执行Student student1 = new Student();
和Student student2 = new Student();
的时候,会在栈中放student1和student2两个引用,然后再堆中开辟内存放Student实例。所以这里student1和student2就是指向真正Student实例地址的引用。
persons[0] = student1;persons[1] = student2;
最后这两句,把student1和student2两个引用所指向的地址给了数组persons,这样数组persons的两个引用元素指向的就是student1和student2所指向的实例。有点绕,但是希望读者好好理解一下。
接下来有个问题帮助更好地理解,先看代码:
Student[] persons = new Student[2];
Student student1 = new Student();
student1.setAge(12);
student1.setName("mike");
Student student2 = new Student();
student2.setAge(13);
student2.setName("sali");
persons[0] = student1;
persons[1] = student2;
student1.setName("huan");
System.out.println(persons[0].getName());
这里就多了最后两句,输出的是什么?
答案是huan。其实很好理解,因为student1和persons[0]都是一个引用,都是指向同个实例,那么student1改动的内存,也就是改动了persons[0]所指向的内存,那么值肯定是会改变的。这样不知道你们可否理解了呢?
小结
了解底层内存机制,才能更好的了解数组,才能更好地使用数组。上面讲的有一点绕,但是希望读者可以好好了解。不只是数组,其他的数据结构也是要了解内存模型才能很好地理解并运用。不同的语言有不同的内存模型。看完有不懂得可以评论区留言。
·
·
·
参考资料
《疯狂java讲义》