Day 21.Java数组的内存分配

今日

  1. 数组本质
    数组是连续内存的容器,存放一组相同类型数据;属于引用数据类型,无论存基本类型还是包装类,数组对象本身永远在堆,栈只存地址。
  2. 内存两大分区分工
    栈内存(Stack)
    存放局部变量:数组引用变量、方法形参、方法内普通变量;
    方法执行完毕,栈帧直接销毁,引用变量消失;
    只存内存地址,不存数组真实元素。
    堆内存(Heap)
    new 出来的数组实体全部在堆;内存地址连续;
    数组有固定长度,创建后长度不可修改;
    堆对象没有引用指向时,等待 GC 垃圾回收。
  3. 三种数组创建方式内存图解
    方式 1:动态初始化(指定长度,默认赋值)
    int[] arr = new int[5];
    栈:创建变量 arr;
    堆开辟连续 5 块 int 空间,分配堆内存地址;
    栈 arr 保存堆地址,指向堆数组;
    默认初始化规则:
    基本类型:int=0、double=0.0、boolean=false、char='\u0000'
    引用类型数组(String []、自定义类 []):所有元素默认null
    方式 2:静态初始化(直接赋值,长度自动计算)
    int[] arr = {10,20,30};
    int[] arr2 = new int[]{1,2,3};
    堆直接开辟对应长度内存,存入给定值;栈保存地址。
    方式 3:先声明、后赋值
    int[] arr; //栈只有变量,无指向
    arr = new int[3]; //栈arr拿到堆地址
    只声明没 new:数组引用是空,不能使用。
  4. 数组赋值的内存机制(重中之重)
    int[] a = new int[2];
    int[] b = a;
    a 在栈存堆地址;b=a是地址拷贝,不是新建数组;
    a、b 两个引用指向堆中同一个数组;
    b 修改数组元素,a 读取也会变化,共用堆数据。
  5. 基本类型数组 vs 引用类型数组内存
    int[]:堆里存实实在在 int 数值;
    String[] / Person[]:堆数组里存的是对象地址,数组每个格子都是引用;
    Person[] pArr = new Person[2];
    pArr[0] = new Person();
    数组在堆,数组元素存 Person 对象的堆地址。
  6. 数组参数传递(值传递)
    Java 只有值传递:
    数组做方法入参,传递的是堆地址副本;
    方法内修改数组元素,原数组变化(共用堆);
    方法内给形参整体 new 新数组,只是修改副本地址,外部原数组完全不受影响。
  7. 多维数组内存(二维数组)
    int[][] arr = new int[3][4]
    堆第一层:长度为 3 的一维数组,每个元素存子数组地址;
    第二层:3 个长度为 4 的小数组;
    不规则二维数组:int[][] arr = new int[3][],只创建外层,内层需要手动 new,默认内层元素为 null。
  8. 数组长度不可变
    数组创建时就固定占用连续堆内存,不能加长缩短;想要变长只能新建更大数组,复制元素。
    明日
    Java数组在方法中传递
    问题及解决方法
  9. 数组下标越界:ArrayIndexOutOfBoundsException
    现象:运行报错,索引超出范围
    原因:数组长度 length,合法下标0 ~ length-1;下标 < 0 或 ≥数组长度;循环 i 终止条件写错(i<=length)。
    解决:
    严格用i < arr.length做循环条件;
    打印 length 核对数组真实长度,排查手写死下标;
    遍历一律用 fori / 增强 for,不要硬写数字下标。
  10. 空指针异常 NullPointerException(数组最高频)
    场景 1:数组引用为 null,调用 length、访问下标
    int[] arr = null;
    System.out.println(arr.length); //空指针
    原因:栈引用没有指向堆任何数组。
    解决:使用前先判空if(arr != null);保证数组被 new 初始化。
    场景 2:引用类型数组元素是 null,调用元素属性 / 方法
    String[] strs = new String[2];
    strs[0].length(); //空指针,strs[0]默认null
    原因:数组存在,但下标位置没有存对象,是空地址。
    解决:先判断strs[0] != null再调用方法。
    场景 3:二维数组外层不为 null,内层子数组为 null
    int[][] arr = new int[3][];
    arr[0][1] = 10; //空指针
    解决:手动初始化内层:arr[0] = new int[2];
  11. 数组声明语法报错
    错误写法:int[5] arr;
    原因:长度只能写在 new 后面,声明不能带长度。
    正确:int[] arr = new int[5];