可能Java 数组大家都很熟悉,最近我遇到了一个关于Java 数组内存分配的问题。
呵呵。突然就发现许多书上“基本数据类型存储在栈内存当中,对象则保存在堆内存”这句话完全是错误的。下面是个简单的例子代码:
public class Test { public static void main(String[] argv) { // 静态初始化数组 String[] names = { "Michael", "Orson", "Andrew" }; // 动态初始化数组 String[] animal = new String[4]; // 让animal 指向 namens 数组所引用的数组 names = animal; System.out.println(names.length); System.out.println(animal.length); } }
“Java 数组大小是不能改变的”这可能大家都听过,那上面这段代码就有问题了,animal [] 长度为4,而names [] 数组的长度只有3,但是经过一个赋值语句,两个数组的大小就都变为4了。这不是改变了数组的大小吗? 问题就这样挡在面前了!好吧,问问技术前辈吧,就这样对数组的存储方式有了全新的认识。下面是我的一点理解:(如果有错误的,刚好被大神你看到了,也请你能够指出来。)
上面的的 names 和 animal 不代表这个数组对象,而仅仅是数组的变量而已,和C里面的指针是一样的,这样的变量叫做引用变量。数组对象是保存在堆内存当中,大小当然是不能改变的,但是数组变量却能够指向其他的数组对象,可以看看下面这个图:
蓝虚线是赋值语句 names = animal; 之前 names 和 animal 数组变量指向的堆内存当中数组对象;
红线是是赋值语句 names = animal;之后 names 和 animal 数组变量都同时指向一个数组对象。当然这时候 Java 垃圾回收机制这时候就会发现那个没人引用的数组对象然后把它带走。
从上面还可以看到,“Michael”,"Orson","Andrew" 这些都是基本的数据类型吧。但是他们却存储在堆内存当中。
实际上应该这样说:局部变量放在栈内存当中,(像上面的 names[],animal[] 这种引用类型的变量,还有一些基本类型的变量),但应用变量所引用的对象是保存是堆内存当中的。(包括数组还有一些我们平常写的普通的类对象)
Java在堆内存当中的对象通常是不允许直接访问的,但你可以想到直接访问的后果。为了访问堆内存当中的对象,这时候就需要引用变量这个中介。
什么时候Java存储在栈内存中的变量是仅仅是引用变量? 什么时候它又换了身份变为货真价实的JAVA对象纳?嗯,看看下面这个例子:
public class Animal { private String name; private int age; Animal(String name, int age) { this.name = name; this.age = age; } public void info() { System.out.println(name + " " + age); } } public class Test { public static void main(String[] argv) { // 动态初始化数组 Animal[] animal = new Animal[2]; Animal cat = new Animal("cat", 1); Animal dog = new Animal("dog", 2); animal[0] = dog; animal[1] = cat; // 当数组变量引用对象的方法(或者属性)的时候,它就变为实际的Java 对象 System.out.println(animal.length); //dog 这个原本存储在栈内存当中的对象引用通过调用对象的方法变为实际的对象 dog.info(); animal[0].info(); } }
只有当栈内存中的引用变量调用了对象的方法或者是指向了对象的属性的时候,它就从变量真正成了对象了。(比如上面例子中的 cat,dog 对象引用变量,animal[]数组变量)。
通过
animal[0] = dog; animal[1] = cat;
使得两个变量都指向了存储在堆内存当中的对象,所以他们俩个打印出来的信息是一模一样的。
上图中蓝线是赋值语句:
animal[0] = dog; animal[1] = cat;
之前的变量指向的状态,红虚线是赋值语句之后的状态,animal[0]和dog ,animal[1] 和cat 所指向的都是相同的堆内存空间。
(PS:我还是要感谢这几个月来那几个面试官对我从头到尾的虐,虽然现在实习的事情还没有个准信,当我发现我要走的路还很长很长,上面这个问题的起因也是一个面试官的提问,“你知道Java 当中数组是怎样存储的吗?”)
几乎所有的程序设计语言都支持数组。Java也不例外。当我们需要多个类型相同的变量的时候,就考虑定义一个数组。在Java中,数组变量是引用类型的变量,同时因为Java是典型的静态语言,因此它的数组也是静态的,所以想要使用就必须先初始化(为数组对象的元素分配空间)。
1.数组的初始化方式及其内存分配
对于Java数组的初始化,有以下两种方式,这也是面试中经常考到的经典题目:
- 静态初始化:初始化时由程序员显式指定每个数组元素的初始值,由系统决定数组长度,如:
1 //只是指定初始值,并没有指定数组的长度,但是系统为自动决定该数组的长度为4 2 String[] computers = {"Dell", "Lenovo", "Apple", "Acer"}; //① 3 //只是指定初始值,并没有指定数组的长度,但是系统为自动决定该数组的长度为3 4 String[] names = new String[]{"多啦A梦", "大雄", "静香"}; //②- 动态初始化:初始化时由程序员显示的指定数组的长度,由系统为数据每个元素分配初始值,如:
1 //只是指定了数组的长度,并没有显示的为数组指定初始值,但是系统会默认给数组数组元素分配初始值为null 2 String[] cars = new String[4]; //③
前面提到,因为Java数组变量是引用类型的变量,所以上述几行初始化语句执行后,三个数组在内存中的分配情况如下图所示:
,
由上图可知,静态初始化方式,程序员虽然没有指定数组长度,但是系统已经自动帮我们给分配了,而动态初始化方式,程序员虽然没有显示的指定初始化值,但是因为Java数组是引用类型的变量,所以系统也为每个元素分配了初始化值null,当然不同类型的初始化值也是不一样的,假设是基本类型int类型,那么为系统分配的初始化值也是对应的默认值0。
对于多维数组,假设有这么一段代码:
1 int[][] nums = new int[2][2];2 nums[0][1] = 2;那么他在内存中的分配情况如下:
由上图可知,并没有真正的多维数组,它的本质其实是一维数组。