图解Java数组的内存分配

1. Java数组是静态的

Java是静态语言,所以Java的数组也是静态的,即:数组被初始化后,长度不可变

静态初始化:显式指定每个数组元素的初始值,系统决定数组长度

String[] books = new String[]{"疯狂Java讲义","Thinking in Java","Java核心技术卷"};
String[] names = new String[]{"张三","李四","王五","赵六"};

动态初始化:显式指定数组的长度,系统决定每个数组元素的初始值

String[] strArr = new String[5];

以上两种初始化的内存使用情况如图所示:


动态初始化数组由系统决定每个数组元素的初始值,遵循以下规则:

  • byte、short、int、long类型的数组的初始值为0
  • float、double类型的数组的初始值为0.0
  • char类型的数组的初始值为'\u0000',表现为空字符'',但要占位
  • boolean类型的数组的初始值为false
  • 引用类型的数组的初始值为null

一旦数组初始化完成,则长度不可改变,以下代码造成了数组初始化后长度可改变的假象:

books = names;
strArr = books;
System.out.println(books.length); // 4
System.out.println(strArr.length); // 4

Java的数组变量是一种引用类型的变量,数组变量并不是数组本身,它只是指向堆内存中的数组对象,改变一个数组变量所引用的数组,可以造成数组长度可变的假象。

上述代码让books数组变量和strArr数组变量都指向了names数组变量引用的数组,这样做的结果就是让三个数组变量都引用了相同的数组对象。内存图如下:


而原来strArr和books引用的数组对象的长度不会改变,由于已经没有变量引用它们,它们成为垃圾,等待垃圾回收机制来回收。此时,程序使用strArr、books、和names这三个变量时,将会访问同一个数组对象。

JavaScript是动态语言,其数组的长度可以改变

<script type="text/javascript">
    var arr = [];
    document.write("arr的长度为:" + arr.length + "<br>"); // 0
    arr[0] = 1;
    document.write("arr的长度为:" + arr.length); // 1
</script>

2. 数组一定要初始化吗?

实际上,懂得了Java数组的内存机制后,我们可以说:数组不是必须初始化,或者说,数组初始化不仅有之前介绍的两种方法,可以完全换一种方法:

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

对于数组对象来说,必须初始化,也就是为该数组对象分配一块连续的内存空间,连续内存空间的长度就是数组对象的长度。
对于数组变量来说,不需要进行初始化,只需让其指向一个有效的数组对象就可以。
实际上,所有引用类型的变量,其变量本身不需要任何初始化,需要初始化的是它所引用的对象。

3. 基本类型数组的初始化

程序直接先为数组分配内存空间,再将数组元素的值存入对应的内存中。

int nums;   ①
System.out.println(nums);   ②
nums = new int[]{1, 2, 3, 4};   ③
System.out.println(nums.length);    ④

我们来分析①-④步的内存:

① 定义nums数组变量后的存储示意图如下:


对于②行代码来说没有任何问题,虽然nums变量并未引用到有效的数组对象,但此时并未通过nums变量访问数组对象的方法或属性,所以没有问题。

③ 静态初始化nums数组后的存储示意图如下:


对于④行代码来说,此时访问了nums数组对象的属性,所以要求nums必须引用一个有效的对象。

当通过引用变量来访问实例属性或者调用非静态方法时,如果该引用变量还未指向有效的对象,程序就会抛出运行时异常:NullPointerException,例如,第二行代码如果为:
System.out.println(nums.length);
就会抛出NullPointerException

误区:基本类型的数据的值存储在栈内存中
实际上,应该是所有的局部变量都保存在栈内存中,不管是基本类型还是引用类型,局部变量都保存在各自的方法栈中

4. 引用类型数组的初始化

引用类型数组的数组元素仍然是引用类型,因此数组元素里存储的还是引用,它指向另一块内存,这块内存里存储了该引用变量所引用的对象,包括数组和Java对象。

下面程序的执行代表了引用类型数组初始化的典型过程:

class Person{
    String name;
    int age;
    public void info(){
        System.out.println("name:" + name + ", age:" + age);
    }
}

// 创建两个Person对象
Person p1 = new Person();
p1.name = "张三";
p1.age = 20;
Person p2 = new Person();
p2.name = "李四";
p2.age = 30;
        
// 将两个Person对象赋给数组元素
person[0] = p1;
person[1] = p2;     
        
// 结果一样,p1和person[0]指向同一个对象
p1.info();
person[0].info();

分析内存:



5. 数组元素就是变量

当数组引用变量指向一个有效的数组对象之后,程序就可以通过该数组变量访问数组对象。
Java语言不允许直接访问堆内存中的对象,因此无法直接访问堆内存中的数组对象,程序将通过数组引用变量来访问数组对象。
Java语言避免直接访问堆内存可以使程序更加健壮,如果程序直接访问并修改堆内存中的数据,可能破坏内存的数据完整性,从而导致程序Crash。
无论哪种类型的数组,其数组元素其实相当于一个普通变量。

下面程序师范了将数组元素和普通变量相互赋值的情形:

class Cat{
    String name;
    double height;
    
    public Cat(String name, double height){
        this.name = name;
        this.height = height;
    }
    public void info(){
        System.out.println("name:" + name + ", height:" + height);
    }
}

public class TestArray {
    public static void main(String[] args) {
        int[] a = new int[5];
        for(int i=0; i<a.length; i++){
            a[i] = ++i;
        }
        int x = a[0];
        int y = 99;
        a[4] = 99;
        
        Cat[] cats = new Cat[2];
        cats[0] = new Cat("咪咪", 10.0);
        cats[1] = new Cat("卡卡", 12.2);
        Cat c1 = cats[0];
        Cat c2 = new Cat("豆豆", 11.1);
        cats[1] = c2;
    }
}

内存分析:


6. 没有多维数组

所谓多维数组,其实只是数组元素元素依然是数组的数组,1维数组的数组元素的基本类型或者引用类型,2维数组的数组元素是1维数组,3维数组的数组元素是2维数组...N维数组的数组元素是N-1维数组。
Java允许将多维数组当成1维数组来处理,初始化多维数组时可以先初始化最左边的维数,此时该数组的每个数组元素都相当于一个数组引用变量,这些数组还需要进一步初始化。

如下程序示范了多维数组的用法:

public static void main(String[] args) {
    int[][] a;
    a = new int[4][];
    // 把a当成一维数组,遍历a数组的每个数组元素
    for(int i=0; i<a.length; i++){
          System.out.println(a[i]); // 输出都为null
    }
            
    a[0] = new int[2];
    a[0][1] = 2;
    for(int i=0; i<a[0].length; i++){
          System.out.println(a[0][i]); // 输出0和2
    }
}

内存分析:

说明:一般我们认为通过数组的length属性可以获取数组的长度,其实这个说法不够准确,例如数组a,它是一个传统的“二维数组”,它的长度好像定义不是很明确,其实,数组的长度指的是数组所分配的连续内存空间的长度,所以说a数组的长度为4.

我们用一个“极端”的程序来更好的理解数组在内存中的分配机制:

public static void main(String[] args) {
     Object[] objArr1 = new Object[3];
     objArr1[1] = new Object[2];
     Object[] objArr2 = (Object[]) objArr1[1];
     objArr2[1] = new Object[3];
     Object[] objArr3 = (Object[]) objArr2[1];
     objArr3[1] = new int[5];
     int[] iArr = (int[]) objArr3[1];
     for(int i=0; i<iArr.length; i++){
         iArr[i] = ++i;
     }
}

内存分析:






此时的objArr1不再是一个简单的一维数组,它是一个四维数组。
最后我们执行如下语句:

System.out.println(((int[])((Object[])((Object[])objArr1[1])[1])[1])[2]);

通过以上内存分析不难得出结果为3.

通过上面的分析不难得出,多维数组的本质仍然是一维数组

因为Java是强类型语言,所以上诉代码需要多次强制类型转化,如果用弱类型语言实现会简单很多,比如用JavaScript实现:

<script type="text/javascript">
     var objArr1 = [];
     objArr1[1] = [];
     var objArr2 = objArr1[1];
     objArr2[1] = [];
     var objArr3 = objArr2[1];
     objArr3[1] = [];
     var iArr = objArr3[1];
     for(var i=0; i<5; i++){
          iArr[i] = i;
     }
     document.write(objArr1[1][1][1][2]);
</script>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容