1.数组的特点?
Java 数组是 定长容器 ,一旦通过语法 数组类型[] 数组名 = new 数组类型[长度]; 初始化后,数组的长度(容量)永久固定,永远不能改变。
- 数组分配的内存空间在初始化时就完全确定,后续无法扩容、无法缩容;
- 如果想要「扩容」数组,只能新建一个更大的数组,再把原数组的元素复制过去;
- 可以通过数组的
length属性 获取数组长度(注意:是属性不是方法,没有小括号())。
java
public class ArrayDemo {
public static void main(String[] args) {
// 初始化一个长度为3的int数组,长度永久固定为3
int[] arr = new int[3];
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
System.out.println("数组长度:" + arr.length); // 输出:3
// ❌ 错误写法:length是属性,不能写arr.length(),会编译报错
// System.out.println(arr.length());
// ❌ 错误:无法修改数组长度,没有setLength方法,编译报错
// arr.length = 5;
}
}
Java 数组是同质容器 ,一个数组在定义时就确定了元素类型,数组中只能存放「同一种数据类型」的元素,不允许混合存放不同类型的数据。
- 支持存储:基本数据类型(int、double、char 等)、引用数据类型(String、对象、数组等);
- 补充:如果数组的元素类型是
Object(所有类的父类),看似能存不同类型,本质还是存的Object类型,不属于「混合类型」。
java
public class ArrayDemo {
public static void main(String[] args) {
// ✅ 合法:int数组,只能存int类型
int[] intArr = {1,2,3,4};
// ✅ 合法:String数组,只能存String类型
String[] strArr = {"Java","数组","特点"};
// ❌ 非法:int数组中存放字符串,编译直接报错
// int[] errArr = {1, "abc", 3};
}
}
Java 数组的元素不能通过元素名访问 ,只能通过 「下标(索引)」 访问,这是数组唯一的访问方式。
核心规则
- 数组下标是 从 0 开始的整数;
- 下标的取值范围:
0 <= 下标 < 数组.length; - 访问语法:
数组名[下标](取值 / 赋值都用这个语法); - 下标越界后果:运行时抛出
ArrayIndexOutOfBoundsException异常(编译不报错,运行报错)。
java
public class ArrayDemo {
public static void main(String[] args) {
int[] arr = {10,20,30};
// ✅ 合法访问:下标0、1、2
System.out.println(arr[0]); // 取值:10
arr[1] = 200; // 赋值:把下标1的元素改为200
// ❌ 非法访问:下标3,超过范围(最大是2),运行时报ArrayIndexOutOfBoundsException
// System.out.println(arr[3]);
}
}
因为数组的长度固定、支持下标访问,Java 提供了两种专属的遍历方式,都可以完整遍历数组的所有元素,二者适用场景不同:
1. 普通 for 循环(带下标)
- 语法:
for(int i=0; i<数组.length; i++){ 数组名[i] } - 优点:能获取下标,支持「取值 + 赋值」操作,适合需要操作下标的场景(比如元素修改、逆序遍历)。
2. 增强 for 循环(foreach,无下标)
- 语法:
for(元素类型 变量名 : 数组名){ 变量名 } - 优点:语法简洁,代码量少;
- 缺点:无法获取下标 ,只能取值、不能赋值修改数组元素,适合单纯的「读取元素」场景。
java
public class ArrayDemo {
public static void main(String[] args) {
int[] arr = {10,20,30};
// 方式1:普通for循环(推荐,可取值+赋值)
for (int i = 0; i < arr.length; i++) {
arr[i] += 5; // 给每个元素+5,赋值修改
System.out.println(arr[i]); // 输出:15、25、35
}
// 方式2:增强for循环(只读,简洁)
for (int num : arr) {
System.out.println(num); // 输出:15、25、35
// num +=5; 无意义,只是修改局部变量,不会改变原数组
}
}
}
- 长度不可变 ,初始化后容量永久固定,通过
length属性获取长度; - 存储同一种数据类型的元素,元素类型统一;
- 是引用类型,栈存地址、堆存实际元素;
- 通过下标(索引) 访问元素,下标从 0 开始,越界抛运行时异常;
- 未赋值的元素有默认初始化值,不同类型默认值固定;
- 支持普通 for 循环、增强 for 循环两种遍历方式;
- 支持多维数组,本质是「数组的数组」;
- 元素可以是基本类型,也可以是引用类型。
2.什么是二分查找,基本编程思路是什么?
二分查找的前提条件 :查找的数组 / 集合必须是「有序」的(升序 / 降序均可,常用升序)
时间复杂度为 O(log₂n),n 是数据量,比如找 100 万条数据最多只需要 20 次计算,性能极强。它的查找逻辑特别像「猜数字游戏」:猜一个 1~100 的数字,猜 50,大了就只在 1~49 猜,小了就只在 51~100 猜,每次都把范围缩小一半,直到找到目标值。
举个例子:数组长度n=1024,顺序查找最坏 1024 次,二分查找最坏仅需要 10 次 就一定能找到结果。
有序数组:arr = [1,3,5,7,9,11,13,15] (升序),查找目标值 target = 7
初始化:left=0,right=7(数组最后一个元素下标);
第一次循环:mid=(0+7)/2=3 → arr[3]=7,正好等于目标值 → 返回下标 3,查找结束。
有序数组:arr = [1,3,5,7,9,11,13,15],查找目标值 target = 11
执行步骤:
- 初始化:
left=0,right=7; - 第一次循环:
mid=3→arr[3]=7 < 11→ 目标在右半区,更新left=mid+1=4; - 第二次循环:
left=4,right=7→mid=(4+7)/2=5→arr[5]=11,等于目标值 → 返回下标 5。
java
/**
* 二分查找标准实现(升序数组)
* @param arr 有序的升序数组
* @param target 需要查找的目标值
* @return 找到则返回对应下标,找不到返回-1
*/
public static int binarySearch(int[] arr, int target) {
// 1. 初始化左右指针
int left = 0;
int right = arr.length - 1;
// 2. 核心循环:查找范围有效(左指针 <= 右指针)
while (left <= right) {
// 3. 计算中间下标(推荐写法,避免溢出)
int mid = left + (right - left) / 2;
// 4. 核心判断:三分法
if (arr[mid] == target) {
return mid; // 找到目标值,返回下标
} else if (arr[mid] > target) {
right = mid - 1; // 目标在左半区,缩小右边界
} else {
left = mid + 1; // 目标在右半区,缩小左边界
}
}
// 5. 循环结束未返回,说明无目标值
return -1;
}
// 测试代码
public static void main(String[] args) {
int[] arr = {1,3,5,7,9,11,13,15};
int target1 = 7;
int target2 = 10;
System.out.println(binarySearch(arr, target1)); // 输出:3(正确下标)
System.out.println(binarySearch(arr, target2)); // 输出:-1(无此元素)
}
注意 :计算 mid 推荐用 left + (right-left)/2,而非 (left+right)/2
- 问题根源:如果数组很大,
left和right都是很大的整数,left+right会超出整数的取值范围,导致「数值溢出」,计算出错误的 mid 值; - 等价性:
left + (right-left)/2和(left+right)/2的计算结果完全一致,只是前者能完美避免溢出问题。 - 但在我们日常使用的数组中,很少碰见数据特别庞大的数组,这时我们使用后者来计算mid值也可以。