一、前言
 
数组是Java最基础、使用频率最高的数据结构之一,不管是业务开发还是算法刷题都离不开它。很多初学者只会简单使用数组存取元素,但对底层内存结构、默认值规则、增强for循环陷阱、二分查找边界一知半解。
 
二、Java数组核心底层特性
 
1. 内存存储特点
 
1. 内存空间连续
数组在堆内存开辟一块连续的内存地址,正因如此支持下标随机访问,读取指定位置元素时间复杂度O(1);对比链表,链表不连续,查询需要遍历。
2. 长度创建时固定,不可扩容
数组初始化时必须确定长度,一旦分配内存,长度永久固定,无法直接新增元素。如果需要变长容器,要用 ArrayList (底层还是数组,扩容是新建数组拷贝)。
3. 下标从0开始
第一个元素下标 0 ,最后一个下标为  数组长度-1 ,越界访问会抛出 ArrayIndexOutOfBoundsException 。
4. 数组自带默认初始值
数组创建分配内存后,会自动填充对应类型默认值,不需要手动赋值。
 
2. 基本数据类型默认值对照表
 
数据类型 默认值 
byte/short/int/long 0 
float/double 0.0 
char 空字符  \u0000  
boolean false 
 
3. 引用类型数组默认值
 
引用类型包括:类、接口、数组、String,数组初始化后默认值为 null 。
示例:
 Cat catArr = new Cat[5]; // 数组中5个元素全是null
String[] strArr = new String[3]; // 三个元素都是null
 
 
4. int与float底层存储补充
 
int :固定32bit,1位符号位+31位数值位;
float :固定32bit,1位符号位+8位阶码+23位数值位;
这也是浮点型计算存在精度丢失的底层原因。
 
5. String底层原理
 
 String 底层是 char[] 字符数组,Java9之后改为 byte[] 节省内存,但数组连续存储、不可变长的特性依旧适用。
 
三、数组3种标准创建方式
 
方式1:直接赋值创建(静态初始化)
 
声明同时直接写入元素,编译器自动推算数组长度
 

  
// 基本类型数组
int[] arr = {1,2,3};
// 引用类型String数组
String[] brr = {"HH","TT","Bob"};
 
 
方式2:先分配内存,后赋值(动态初始化)
 
先指定数组长度,下标逐个赋值,未赋值元素保持默认值
 

  
int[] arr = new int[5];
arr[0] = 1;
arr[1] = 3;
// arr[2] arr[3] arr[4] 默认值0
 
 
方式3:仅声明数组变量,未分配堆内存
 
只定义引用变量,没有 new 开辟堆空间,此时数组引用为 null ,直接使用会空指针 NullPointerException 
 

  
int[] arr;
// arr[0] = 1; 报错,arr为null
 
 
四、数组两种遍历方式:普通for VS 增强for(for-each)
 
1. 普通for循环(带下标,可修改原数组)
 
通过下标直接操作堆内存中的数组元素,修改会永久改变原数组内容:
 
  
String[] arr = {"tom","jck","hello","world"};
for (int i = 0; i < arr.length; i++) {
    arr[i] = "a"; // 直接修改原数组对应下标元素
}
// 输出:[a, a, a, a]
System.out.println(Arrays.toString(arr));
 
 
2. 增强for循环 for-each(仅遍历,无法修改原数组)
 
语法: for(元素类型 临时变量 : 数组) 
核心陷阱:循环内的 s 只是数组元素的副本,修改副本不会影响堆中原始数组。
  
for (String s : arr) {
    s = "aaa"; // 仅修改临时副本,原数组不变
    System.out.println(s);
}
// 输出还是 [a, a, a, a]
System.out.println(Arrays.toString(arr));
 
 
面试必考点:for-each适合单纯遍历读取;需要增删改数组元素必须用普通for循环。
 
五、数组经典算法:二分查找(折半查找)实战
 
1. 二分查找前提
 
数组必须有序(升序/降序),核心逻辑:不断缩小查找区间,每次取中间值对比,时间复杂度O(logn)。
 
2. 完整可运行代码

package com.qcby.Test;

public class test {
    public static void main(String[] args) {
        // 升序有序数组
        int[] arr = new int[]{5,7,9,12,15,23,44,65,88};
        int target = 23;
        System.out.println(binarySearch(arr, target));
    }

 
    public static boolean binarySearch(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;
        // 区间有效条件:左边界 <= 右边界
        while (left <= right) {
            // 防止(left+right)数值溢出,标准写法
            int mid = left + (right - left) / 2;
            if (arr[mid] == target) {
                // 找到目标值,直接返回
                return true;
            } else if (arr[mid] < target) {
                // 中间值小于目标,目标在右区间,左边界右移
                left = mid + 1;
            } else {
                // 中间值大于目标,目标在左区间,右边界左移
                right = mid - 1;
            }
        }
        // 循环结束未找到
        return false;
    }
}
 
 
3. 关键细节讲解
 
1.  mid = left + (right - left) / 2  优于  (left+right)/2 :避免两个大数相加超出int最大值,出现数值溢出;
2. 循环条件 left <= right :如果写 left < right 会漏掉边界元素,导致查找失败;
3. 边界移动 left = mid + 1  /  right = mid -1 :mid已经对比过,不需要再次纳入区间,缩小查找范围。
 
4. 运行结果
 
数组中存在23,控制台输出  true ;修改target为不存在数字则输出 false 。
 
六、面试高频总结
 
1. 数组内存连续,随机访问快,但无法扩容;链表查询慢,增删快;
2. 所有数组创建后一定存在默认值,引用类型默认null;
3. for-each只能读取数组,修改临时变量不会改变原数组;
4. 二分查找只适用于有序数组,注意mid防溢出、循环边界 left <= right ;
5. 仅声明数组变量不new,引用为null,调用下标操作报空指针。
 
七、结尾
 
数组是集合、算法的基础,很多同学只停留在会用层面,面试问到底层存储、for-each原理、二分边界就容易丢分。本文结合手写笔记梳理核心知识点,配套可运行实战代码,适合Java入门夯实基础、面试突击复习。
 

更多推荐