当前位置: 首页 > news >正文

【Java】P7 Java数组完全指南:从基础到进阶

目录

  • 1 数组基本概念
    • 1.1 什么是数组
    • 1.2 数组的本质特点
    • 1.3 数组的概念
  • 2 一维数组操作
    • 2.1 声明与初始化
      • 方式一:静态初始化
      • 方式二:动态初始化
      • 类型推断(Java 10+):让编译器自动判断类型
    • 2.2 核心操作详解
      • 访问和修改元素
      • 获取数组长度
      • 数组遍历的三种方式
      • 实际应用示例:计算平均分
    • 2.3 默认初始化值
  • 3. 内存模型深度解析
    • 3.1 JVM内存区域比喻
    • 3.2 数组创建过程详解
    • 3.3 引用赋值与浅拷贝陷阱
    • 3.4 垃圾回收机制
  • 4. 二维数组进阶
    • 4.1 二维数组的概念理解
    • 4.2 声明与初始化详解
      • 静态初始化:直接指定所有元素
      • 动态初始化:先声明后赋值
    • 4.3 访问与遍历详解
      • 访问特定元素
      • 获取维度信息
      • 完整遍历二维数组
    • 4.4 二维数组默认初始化
  • 5. 常用算法实现
    • 5.1 数组统计算法详解
      • 基础统计:求和、平均值、最值
      • 进阶统计:中位数
    • 5.2 数组复制深度解析
      • 硬拷贝和软拷贝
      • 各种复制方法的性能对比
    • 5.3 数组反转算法
      • 方法1:双指针法(推荐,原地操作)
      • 方法2:创建新数组(不修改原数组)
    • 5.4 数组扩容与缩容详解
  • 6. 搜索与排序算法深度解析
    • 6.1 搜索算法对比
      • 线性搜索(顺序搜索)
      • 二分搜索(折半搜索)
      • 搜索效率对比演示
    • 6.2 排序算法详解
      • 冒泡排序:最容易理解的排序
      • 选择排序:每次选出最小值
      • 插入排序:像整理手中的牌
      • 快速排序:分而治之的典型代表
      • 排序算法性能对比
  • 7. Arrays工具类深度应用
    • 7.1 常用方法详解
      • 数组比较方法
      • 数组转字符串方法
      • 数组填充方法
      • 数组排序方法
      • 数组搜索方法
    • 7.2 高级应用技巧
      • 数组拷贝的多种方式对比
      • 数组类型转换技巧

在这里插入图片描述

1 数组基本概念

1.1 什么是数组

想象一下,你有一排连续的储物柜,每个柜子里都放着同一类型的物品(比如都是书)。数组就是这样的概念——它是存储相同数据类型元素的有序集合,就像一排编了号的储物柜。

为什么需要数组?如果我们要存储30本书的名称,难道要创建30个变量吗?

// 不使用数组:非常麻烦
int book1 = 85, book2 = 92, book3 = 78, ...
// 如果要计算平均分,需要写很长的代码// 使用数组:简洁高效
int[] books = {85, 92, 78, ...};

1.2 数组的本质特点

  • 有序性: 元素按照放入的顺序排列,第一个是索引0,第二个是索引1
  • 同质性: 所有元素必须是相同的数据类型
  • 引用类型: 数组变量存储的是内存地址,不是实际数据
  • 长度固定: 一旦创建,长度不能改变(这是与List的重要区别)

1.3 数组的概念

数组是存储相同数据类型元素的有序集合,通过索引访问元素。本质上,数组是一个引用数据类型,在内存中占用连续空间。


2 一维数组操作

2.1 声明与初始化

数组的创建分为两个步骤:

  1. 声明(告诉编译器要创建什么类型的数组)和
  2. 初始化(实际创建数组并分配内存)

方式一:静态初始化

在创建数组的同时就指定元素值,系统会自动计算数组长度。

// 标准写法
int[] scores = new int[]{95, 87, 92, 78, 85};// 简化写法(更常用)
int[] scores = {95, 87, 92, 78, 85};// 其他类型的示例
String[] names = {"张三", "李四", "王五", "赵六"};
double[] prices = {19.9, 25.5, 99.0, 149.9};
boolean[] flags = {true, false, true, true};

方式二:动态初始化

先指定数组长度,后面再给元素赋值。这种方式适合你知道需要多少空间,但暂时不知道具体值的情况。

// 1. 先创建指定长度的数组
int[] numbers = new int[5];  // 创建能存储5个整数的数组// 2. 再逐个赋值
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;// 此时 numbers 数组等价于 {10, 20, 30, 0, 0}

为什么会有两种方式?

  • 静态初始化: 适合你已经知道要存储什么数据的情况
  • 动态初始化: 适合你知道需要多大空间,但数据需要运行时计算的情况
// 静态:存储已知的学生姓名
String[] classmates = {"小明", "小红", "小刚"};// 动态:存储用户输入的成绩(运行时才知道具体值)
Scanner scanner = new Scanner(System.in);
int[] inputScores = new int[3];
for (int i = 0; i < 3; i++) {System.out.print("请输入第" + (i+1) + "个成绩:");inputScores[i] = scanner.nextInt();
}

类型推断(Java 10+):让编译器自动判断类型

// 传统写法
int[] numbers = new int[]{1, 2, 3, 4, 5};// 类型推断写法
var numbers = new int[]{1, 2, 3, 4, 5};  // var会被推断为int[]

2.2 核心操作详解

访问和修改元素

数组访问就像是告诉管理员:“请给我第X号储物柜里的东西"或"请把这个东西放到第X号储物柜”。

int[] scores = {85, 92, 78, 95, 88};// 获取元素(读取操作)
int firstScore = scores[0];     // 获取第一个成绩:85
int thirdScore = scores[2];     // 获取第三个成绩:78// 修改元素(写入操作)
scores[2] = 100;                // 把第三个成绩改为100
System.out.println(scores[2]);  // 输出:100// 注意:索引必须在有效范围内
// scores[5] = 90;  // 错误!会抛出ArrayIndexOutOfBoundsException

获取数组长度

length 是数组对象的一个属性(不是方法,所以不需要括号)

int[] numbers = {10, 20, 30, 40, 50};
int size = numbers.length;      // 获取长度:5// 常用于循环中避免硬编码
for (int i = 0; i < numbers.length; i++) {System.out.println("索引" + i + "的值是:" + numbers[i]);
}

数组遍历的三种方式

  1. 传统for循环: 当你需要索引时使用
int[] scores = {95, 87, 92, 78, 85};for (int i = 0; i < scores.length; i++) {System.out.println("学生" + (i+1) + "的成绩:" + scores[i]);// 输出:学生1的成绩:95//      学生2的成绩:87//      ...
}
  1. 增强for循环(for-each): 当你只需要元素值时使用(推荐)
int[] scores = {95, 87, 92, 78, 85};for (int score : scores) {  // 读作:对于scores中的每一个scoreSystem.out.println("成绩:" + score);
}
// 注意:for-each循环中不能修改数组元素
  1. while循环: 特殊情况使用
int[] scores = {95, 87, 92, 78, 85};
int i = 0;
while (i < scores.length) {System.out.println("成绩:" + scores[i]);i++;
}

实际应用示例:计算平均分

public static double calculateAverage(int[] scores) {if (scores.length == 0) return 0;  // 避免除零错误int sum = 0;for (int score : scores) {sum += score;}return (double) sum / scores.length;  // 强制类型转换确保得到小数
}// 使用示例
int[] classScores = {95, 87, 92, 78, 85};
double average = calculateAverage(classScores);
System.out.println("班级平均分:" + average);  // 输出:87.4

2.3 默认初始化值

当你使用动态初始化创建数组时,系统会自动给每个元素分配默认值。这就像是给每个空储物柜贴上一个默认的标签。

为什么有默认值?
Java要求所有变量在使用前必须有值,所以系统自动给了一个"安全"的默认值。

数据类型默认值原因解释示例代码
byte、short、int、long0整数的"零值"最安全int[] nums = new int[3];{0, 0, 0}
float、double0.0浮点数的"零值"double[] prices = new double[2];{0.0, 0.0}
char‘\u0000’Unicode的空字符char[] chars = new char[2];{'\u0000', '\u0000'}
booleanfalse布尔型的"假"值boolean[] flags = new boolean[2];{false, false}
引用类型null表示"没有指向任何对象"String[] names = new String[2];{null, null}

实际示例对比:

// 动态初始化 - 有默认值
int[] numbers = new int[5];
System.out.println(Arrays.toString(numbers)); 
// 输出:[0, 0, 0, 0, 0]String[] names = new String[3];
System.out.println(Arrays.toString(names));   
// 输出:[null, null, null]// 静态初始化 - 没有默认值概念,直接是你指定的值
int[] scores = {95, 87, 92};
System.out.println(Arrays.toString(scores));  
// 输出:[95, 87, 92]

注意事项:

String[] names = new String[3];
// 此时names[0]是null,不是空字符串""!
if (names[0] == null) {System.out.println("第一个元素是null");  // 会执行这行
}// 如果要使用,需要先赋值
names[0] = "张三";
System.out.println(names[0].length());  // 现在可以安全使用了

3. 内存模型深度解析

理解内存模型是掌握数组的关键。很多初学者在这里容易困惑,我们用生活化的比喻来解释。

3.1 JVM内存区域比喻

想象内存是一座大楼:

虚拟机栈(Stack) - 就像办公楼里的办公桌

  • 存储方法内的局部变量(包括数组引用变量)
  • 空间小但访问速度快
  • 方法执行完毕,桌面就清空了

堆内存(Heap) - 就像大楼里的仓库

  • 存储所有对象的实际数据(包括数组元素)
  • 空间大但访问相对较慢
  • 需要垃圾回收器(GC)来清理无用数据

3.2 数组创建过程详解

让我们跟踪一个数组从创建到使用的完整过程:

public static void main(String[] args) {// 步骤1:在栈中声明引用变量int[] arr1;                 // 栈中有了arr1这个"便利贴",但还没写地址// 步骤2:在堆中创建数组对象,并把地址赋给变量值arr1 = new int[4];          // 堆中分配空间存储{0,0,0,0},栈中arr1记录地址// 步骤3:通过引用访问和修改堆中的数据arr1[0] = 10;               // 通过arr1找到堆中的数组,修改第1个元素arr1[2] = 20;               // 修改第3个元素System.out.println(Arrays.toString(arr1)); // 输出:[10, 0, 20, 0]
}

内存示意图:

栈内存(Stack)          堆内存(Heap)
┌──────────────┐       ┌──---───────────────┐
│  arr1: 0x001 │ ────→ │ 0x001: [10,0,20,0] │
└──────────────┘       └──---───────────────┘

3.3 引用赋值与浅拷贝陷阱

最容易犯错的一个场景,容易将赋值地址误以为是赋值数组值。

public static void main(String[] args) {// 创建第一个数组int[] arr1 = new int[]{5, 6, 7};// 这里发生了什么?int[] arr2 = arr1;  // ← 关键:这不是复制数组,而是复制地址!// 修改arr2,看看arr1会不会变化arr2[1] = 99;System.out.println("arr1[1] = " + arr1[1]); // 输出:99(惊不惊喜?)System.out.println("arr2[1] = " + arr2[1]); // 输出:99
}

为什么会这样?

执行 int[] arr2 = arr1; 后的内存状态:栈内存                    堆内存
┌──────────────┐         ┌─────────────────┐
│  arr1: 0x001 │ ────→   │                 │
│  arr2: 0x001 │ ────→   │ 0x001: [5,99,7] │
└──────────────┘         └─────────────────┘

两个引用变量指向同一个堆内存区域,所以修改其中一个,另一个也会"看到"变化。

如何真正复制数组?

int[] arr1 = {5, 6, 7};// 方法1:使用clone()方法
int[] arr2 = arr1.clone();// 方法2:使用Arrays.copyOf()
int[] arr3 = Arrays.copyOf(arr1, arr1.length);// 方法3:手动复制
int[] arr4 = new int[arr1.length];
for (int i = 0; i < arr1.length; i++) {arr4[i] = arr1[i];
}// 现在修改arr2不会影响arr1
arr2[1] = 99;
System.out.println("arr1[1] = " + arr1[1]); // 输出:6
System.out.println("arr2[1] = " + arr2[1]); // 输出:99

3.4 垃圾回收机制

什么时候会发生垃圾回收?

public static void main(String[] args) {int[] arr = new int[]{1, 2, 3, 4, 5};System.out.println("arr指向:" + Arrays.toString(arr));// 重新分配:原来的数组变成垃圾arr = new int[]{10, 20, 30};  System.out.println("arr现在指向:" + Arrays.toString(arr));// 原来的{1,2,3,4,5}数组没有任何引用指向它,成为垃圾// GC会在合适的时机自动回收这块内存
}

手动提示垃圾回收:

int[] bigArray = new int[1000000];  // 创建大数组
// 使用完毕后
bigArray = null;    // 断开引用,帮助GC回收
System.gc();        // 建议JVM进行垃圾回收(不保证立即执行)

4. 二维数组进阶

4.1 二维数组的概念理解

二维数组就像是一个表格或矩阵。如果一维数组是一排储物柜,那么二维数组就是多排储物柜组成的储物间。

生活化比喻:

一维数组:一排座位 [座位1, 座位2, 座位3]
二维数组:电影院多排座位
第1排:[座位1, 座位2, 座位3]
第2排:[座位1, 座位2, 座位3]  
第3排:[座位1, 座位2, 座位3]

4.2 声明与初始化详解

静态初始化:直接指定所有元素

// 表示一个3行3列的成绩表
int[][] classScores = {{85, 92, 78},   // 第1排学生的成绩{95, 88, 91},   // 第2排学生的成绩  {87, 93, 89}    // 第3排学生的成绩
};// 也可以写成一行(但可读性较差)
int[][] scores = {{85, 92, 78}, {95, 88, 91}, {87, 93, 89}};

动态初始化:先声明后赋值

二维数组的动态初始化如一维数组的动态初始化,先声明数组整体结构,然后赋值。

// 创建3行4列的数组
int[][] matrix = new int[3][4];// 给每个位置赋值
matrix[0][0] = 1;
matrix[1][1] = 6;
matrix[2][2] = 11;// 结果:
// [1,  0,  0,  0]
// [0,  6,  0,  0]
// [0,  0, 11,  0]

4.3 访问与遍历详解

访问特定元素

int[][] scores = {{85, 92, 78}, {95, 88, 91}, {87, 93, 89}};// 访问第2行第3列的元素(注意索引从0开始)
int element = scores[1][2];  // 获取91
System.out.println("第2排第3个学生的成绩:" + element);// 修改元素
scores[0][1] = 100;  // 把第1排第2个学生的成绩改为100

获取维度信息

int[][] matrix = {{1, 2, 3, 4}, {5, 6, 7, 8}};int rows = matrix.length;           // 行数:2
int colsInFirstRow = matrix[0].length;  // 第1行的列数:4
int colsInSecondRow = matrix[1].length; // 第2行的列数:4System.out.println("这是一个" + rows + "行" + colsInFirstRow + "列的矩阵");

完整遍历二维数组

  1. 嵌套传统for循环(需要索引时使用)
int[][] scores = {{85, 92, 78}, {95, 88, 91}, {87, 93, 89}};for (int row = 0; row < scores.length; row++) {for (int col = 0; col < scores[row].length; col++) {System.out.print("第" + (row+1) + "排第" + (col+1) + "个学生成绩:" + scores[row][col] + " ");}System.out.println(); // 换行
}
  1. 嵌套增强for循环(推荐,代码最简洁)
int[][] scores = {{85, 92, 78}, {95, 88, 91}, {87, 93, 89}};for (int[] row : scores) {          // 对于每一行for (int score : row) {         // 对于这一行中的每个成绩System.out.print(score + " ");}System.out.println();
}

4.4 二维数组默认初始化

// 创建3行2列的整型数组
int[][] matrix = new int[3][2];
System.out.println(Arrays.deepToString(matrix));
// 输出:[[0, 0], [0, 0], [0, 0]]// 创建2行3列的字符串数组
String[][] names = new String[2][3];
System.out.println(Arrays.deepToString(names));
// 输出:[[null, null, null], [null, null, null]]// 只指定行数,不指定列数
String[][] irregular = new String[3][];
System.out.println(Arrays.toString(irregular));
// 输出:[null, null, null]  ← 外层元素都是null// 为某一行分配空间
irregular[1] = new String[2];
System.out.println(Arrays.deepToString(irregular));
// 输出:[null, [null, null], null]// 如果试图访问没有分配空间的行的元素,会出错
try {irregular[0][0] = "测试";  // NullPointerException!
} catch (NullPointerException e) {System.out.println("错误:第1行还没有分配空间");
}

5. 常用算法实现

5.1 数组统计算法详解

统计操作是处理数组时最常见的需求。让我们从简单到复杂逐步实现。

基础统计:求和、平均值、最值

public class ArrayStatistics {/*** 计算数组的基本统计信息* @param arr 输入数组*/public static void calculateBasicStats(int[] arr) {if (arr == null || arr.length == 0) {System.out.println("数组为空,无法计算");return;}// 初始化变量long sum = 0;           // 使用long防止大数溢出int max = arr[0];       // 假设第一个元素是最大值int min = arr[0];       // 假设第一个元素是最小值// 遍历数组进行统计for (int num : arr) {sum += num;                    // 累加求和max = Math.max(max, num);      // 更新最大值min = Math.min(min, num);      // 更新最小值}double average = (double) sum / arr.length;  // 计算平均值// 输出结果System.out.println("数组长度:" + arr.length);System.out.println("总和:" + sum);System.out.printf("平均值:%.2f%n", average);System.out.println("最大值:" + max);System.out.println("最小值:" + min);System.out.println("极差:" + (max - min));}// 使用示例public static void main(String[] args) {int[] testScores = {85, 92, 78, 95, 88, 91, 87, 93, 89, 94};calculateBasicStats(testScores);// 输出:// 数组长度:10// 总和:892// 平均值:89.20// 最大值:95// 最小值:78// 极差:17}
}

进阶统计:中位数

public static void calculateAdvancedStats(int[] arr) {if (arr == null || arr.length == 0) return;// 创建副本避免修改原数组int[] sortedArr = arr.clone();Arrays.sort(sortedArr);// 计算中位数double median;int n = sortedArr.length;if (n % 2 == 0) {// 偶数个元素:取中间两个数的平均值median = (sortedArr[n/2 - 1] + sortedArr[n/2]) / 2.0;} else {// 奇数个元素:取中间的数median = sortedArr[n/2];}
}

5.2 数组复制深度解析

数组复制是一个容易出错的概念,让我们详细分析各种复制方式的区别。

硬拷贝和软拷贝

想象你有一本书的目录页:

  • 浅拷贝: 复制目录页,但书的内容还是同一本书
  • 深拷贝: 不仅复制目录页,还把整本书的内容都重新抄写一遍
public class ArrayCopyDemo {/*** 演示引用赋值(不是真正的复制)*/public static void demonstrateReferenceAssignment() {System.out.println("=== 引用赋值演示 ===");int[] original = {1, 2, 3, 4, 5};int[] reference = original;  // 这不是复制!只是创建了另一个引用System.out.println("修改前:");System.out.println("original: " + Arrays.toString(original));System.out.println("reference: " + Arrays.toString(reference));// 通过reference修改数组reference[0] = 999;System.out.println("通过reference修改后:");System.out.println("original: " + Arrays.toString(original));   // [999, 2, 3, 4, 5]System.out.println("reference: " + Arrays.toString(reference)); // [999, 2, 3, 4, 5]System.out.println("两个数组是同一个对象:" + (original == reference)); // true}/*** 演示真正的数组复制*/public static void demonstrateArrayCopy() {System.out.println("\n=== 数组复制演示 ===");int[] original = {1, 2, 3, 4, 5};// 方法1:使用clone()int[] copy1 = original.clone();// 方法2:使用Arrays.copyOf()int[] copy2 = Arrays.copyOf(original, original.length);// 方法3:使用System.arraycopy()int[] copy3 = new int[original.length];System.arraycopy(original, 0, copy3, 0, original.length);// 方法4:手动循环复制int[] copy4 = new int[original.length];for (int i = 0; i < original.length; i++) {copy4[i] = original[i];}// 修改原数组original[0] = 999;System.out.println("修改original后:");System.out.println("original: " + Arrays.toString(original)); // [999, 2, 3, 4, 5]System.out.println("copy1:    " + Arrays.toString(copy1));    // [1, 2, 3, 4, 5]System.out.println("copy2:    " + Arrays.toString(copy2));    // [1, 2, 3, 4, 5]System.out.println("copy3:    " + Arrays.toString(copy3));    // [1, 2, 3, 4, 5]System.out.println("copy4:    " + Arrays.toString(copy4));    // [1, 2, 3, 4, 5]}
}

各种复制方法的性能对比

public static void performanceBenchmark() {int[] largeArray = new int[1000000];Arrays.fill(largeArray, 42);  // 填充测试数据// 测试clone()方法long start = System.nanoTime();int[] copy1 = largeArray.clone();long cloneTime = System.nanoTime() - start;// 测试Arrays.copyOf()方法start = System.nanoTime();int[] copy2 = Arrays.copyOf(largeArray, largeArray.length);long copyOfTime = System.nanoTime() - start;// 测试System.arraycopy()方法start = System.nanoTime();int[] copy3 = new int[largeArray.length];System.arraycopy(largeArray, 0, copy3, 0, largeArray.length);long arraycopyTime = System.nanoTime() - start;// 测试手动循环start = System.nanoTime();int[] copy4 = new int[largeArray.length];for (int i = 0; i < largeArray.length; i++) {copy4[i] = largeArray[i];}long loopTime = System.nanoTime() - start;System.out.println("复制100万个整数的性能对比:");System.out.printf("clone():           %d ns%n", cloneTime);System.out.printf("Arrays.copyOf():   %d ns%n", copyOfTime);System.out.printf("System.arraycopy():%d ns%n", arraycopyTime);System.out.printf("手动循环:          %d ns%n", loopTime);
}

5.3 数组反转算法

数组反转是一个经典的算法题,有多种实现方法。

方法1:双指针法(推荐,原地操作)

public static void reverseArrayInPlace(int[] arr) {if (arr == null || arr.length <= 1) return;int left = 0;                    // 左指针从开头开始int right = arr.length - 1;      // 右指针从末尾开始while (left < right) {// 交换左右指针指向的元素int temp = arr[left];arr[left] = arr[right];arr[right] = temp;// 移动指针left++;right--;}
}// 使用示例
int[] numbers = {1, 2, 3, 4, 5, 6};
System.out.println("反转前:" + Arrays.toString(numbers)); // [1, 2, 3, 4, 5, 6]
reverseArrayInPlace(numbers);
System.out.println("反转后:" + Arrays.toString(numbers)); // [6, 5, 4, 3, 2, 1]

方法2:创建新数组(不修改原数组)

public static int[] reverseArrayCopy(int[] arr) {if (arr == null) return null;int[] reversed = new int[arr.length];for (int i = 0; i < arr.length; i++) {reversed[i] = arr[arr.length - 1 - i];  // 从末尾开始取元素}return reversed;
}// 使用示例
int[] original = {1, 2, 3, 4, 5};
int[] reversed = reverseArrayCopy(original);
System.out.println("原数组:" + Arrays.toString(original)); // [1, 2, 3, 4, 5]
System.out.println("反转后:" + Arrays.toString(reversed));  // [5, 4, 3, 2, 1]

5.4 数组扩容与缩容详解

数组长度固定,但我们可以通过创建新数组来模拟扩容和缩容。

int[] arr = new int[]{1,2,3,4,5};
int[] newarr = new int[arr.length * 2];
// 不如
int[] newarr = new int[arr.length << 1];
arr = newarr

GC 会根据实际情况,比如在通过 arr = newarr 命令后更换指针,从而 GC 自动删除 arr 地址下的值,从而回收内存,减少内存空间浪费。


6. 搜索与排序算法深度解析

6.1 搜索算法对比

线性搜索(顺序搜索)

这就像在一排书中一本一本地找你要的书。

public class SearchAlgorithms {/*** 线性搜索:从头到尾逐个检查* 时间复杂度:O(n)* 空间复杂度:O(1)* 优点:简单,适用于任何数组(无需排序)* 缺点:效率低,大数组查找慢*/public static int linearSearch(int[] arr, int target) {if (arr == null) return -1;for (int i = 0; i < arr.length; i++) {if (arr[i] == target) {System.out.println("线性搜索:检查了" + (i + 1) + "个元素");return i;  // 返回找到的索引}}return -1;  // 没有找到}
}

二分搜索(折半搜索)

这就像在字典中查单词,每次翻到中间,根据字母顺序决定往前还是往后找。

/*** 二分搜索:在有序数组中快速查找* 时间复杂度:O(logn)* 空间复杂度:O(1)* 前提:数组必须已排序* 优点:效率高,大数组查找很快* 缺点:需要排序,插入删除后需重新排序*/
public static int binarySearch(int[] sortedArray, int target) {if (sortedArray == null || sortedArray.length == 0) return -1;int left = 0;int right = sortedArray.length - 1;int steps = 0;  // 记录查找步数while (left <= right) {steps++;// 防止大数溢出的写法int mid = left + (right - left) / 2;System.out.println("第" + steps + "步:检查索引" + mid + ",值为" + sortedArray[mid]);if (sortedArray[mid] == target) {System.out.println("二分搜索:用了" + steps + "步找到");return mid;} else if (sortedArray[mid] < target) {left = mid + 1;   // 目标在右半部分} else {right = mid - 1;  // 目标在左半部分}}System.out.println("二分搜索:用了" + steps + "步,未找到");return -1;
}

搜索效率对比演示

public static void compareSearchMethods() {// 创建大数组进行测试int[] largeArray = new int[100000];for (int i = 0; i < largeArray.length; i++) {largeArray[i] = i * 2;  // 偶数数组:0, 2, 4, 6, ...}int target = 99998;  // 查找倒数第二个元素// 线性搜索long startTime = System.nanoTime();int linearResult = linearSearch(largeArray, target);long linearTime = System.nanoTime() - startTime;// 二分搜索startTime = System.nanoTime();int binaryResult = binarySearch(largeArray, target);long binaryTime = System.nanoTime() - startTime;System.out.println("在10万个元素中查找" + target + ":");System.out.println("线性搜索结果:索引" + linearResult + ",用时:" + linearTime + "ns");System.out.println("二分搜索结果:索引" + binaryResult + ",用时:" + binaryTime + "ns");System.out.println("二分搜索比线性搜索快:" + (linearTime / binaryTime) + "倍");
}

6.2 排序算法详解

冒泡排序:最容易理解的排序

就像水中的气泡一样,小的元素会慢慢"冒"到前面。

public class SortingAlgorithms {/*** 冒泡排序:相邻元素两两比较,大的往后"冒"* 时间复杂度:O(n²) - 最坏和平均情况* 时间复杂度:O(n) - 最好情况(已排序)* 空间复杂度:O(1)* 稳定性:稳定(相等元素顺序不变)*/public static void bubbleSort(int[] arr) {if (arr == null || arr.length <= 1) return;int n = arr.length;boolean hasSwapped;  // 优化:检测是否已排序for (int i = 0; i < n - 1; i++) {hasSwapped = false;// 每轮比较,最大元素会"冒泡"到末尾for (int j = 0; j < n - i - 1; j++) {if (arr[j] > arr[j + 1]) {// 交换相邻元素swap(arr, j, j + 1);hasSwapped = true;}}// 如果这一轮没有交换,说明已经排序完成if (!hasSwapped) {System.out.println("冒泡排序在第" + (i + 1) + "轮后完成");break;}System.out.println("第" + (i + 1) + "轮后:" + Arrays.toString(arr));}}/*** 辅助方法:交换数组中两个元素*/private static void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}
}

选择排序:每次选出最小值

/*** 选择排序:每次从未排序部分选择最小元素,放到已排序部分末尾* 时间复杂度:O(n²) - 所有情况都是* 空间复杂度:O(1)* 稳定性:不稳定*/
public static void selectionSort(int[] arr) {if (arr == null || arr.length <= 1) return;int n = arr.length;for (int i = 0; i < n - 1; i++) {// 假设当前位置就是最小值int minIndex = i;// 在剩余未排序部分找真正的最小值for (int j = i + 1; j < n; j++) {if (arr[j] < arr[minIndex]) {minIndex = j;}}// 如果找到更小的值,就交换if (minIndex != i) {swap(arr, i, minIndex);}System.out.println("第" + (i + 1) + "轮选择排序后:" + Arrays.toString(arr));}
}

插入排序:像整理手中的牌

/*** 插入排序:将每个元素插入到已排序部分的正确位置* 时间复杂度:O(n²) - 最坏情况,O(n) - 最好情况* 空间复杂度:O(1)* 稳定性:稳定* 适用:小数组或基本有序的数组*/
public static void insertionSort(int[] arr) {if (arr == null || arr.length <= 1) return;for (int i = 1; i < arr.length; i++) {int current = arr[i];  // 待插入的元素int j = i - 1;         // 已排序部分的末尾// 向后移动比current大的元素while (j >= 0 && arr[j] > current) {arr[j + 1] = arr[j];j--;}// 插入current到正确位置arr[j + 1] = current;System.out.println("插入" + current + "后:" + Arrays.toString(arr));}
}

快速排序:分而治之的典型代表

/*** 快速排序:选择一个基准,将数组分为小于和大于基准的两部分,递归排序* 时间复杂度:O(n log n) - 平均情况,O(n²) - 最坏情况* 空间复杂度:O(log n) - 递归栈* 稳定性:不稳定* 优点:实际应用中很快,原地排序*/
public static void quickSort(int[] arr) {if (arr == null || arr.length <= 1) return;quickSortHelper(arr, 0, arr.length - 1);
}private static void quickSortHelper(int[] arr, int low, int high) {if (low < high) {// 分割数组,返回基准位置int pivotIndex = partition(arr, low, high);System.out.println("基准" + arr[pivotIndex] + "就位,当前状态:" + Arrays.toString(Arrays.copyOfRange(arr, low, high + 1)));// 递归排序基准左右两部分quickSortHelper(arr, low, pivotIndex - 1);quickSortHelper(arr, pivotIndex + 1, high);}
}private static int partition(int[] arr, int low, int high) {// 选择最后一个元素作为基准int pivot = arr[high];int i = low - 1;  // 小于基准的元素的边界for (int j = low; j < high; j++) {// 如果当前元素小于等于基准if (arr[j] <= pivot) {i++;swap(arr, i, j);}}// 将基准放到正确位置swap(arr, i + 1, high);return i + 1;
}

排序算法性能对比

public static void sortingBenchmark() {int[] sizes = {1000, 5000, 10000};for (int size : sizes) {System.out.println("\n=== 数组大小:" + size + " ===");// 生成随机测试数据int[] original = generateRandomArray(size);// 测试冒泡排序int[] bubbleArray = original.clone();long start = System.currentTimeMillis();bubbleSort(bubbleArray);long bubbleTime = System.currentTimeMillis() - start;// 测试选择排序int[] selectionArray = original.clone();start = System.currentTimeMillis();selectionSort(selectionArray);long selectionTime = System.currentTimeMillis() - start;// 测试插入排序int[] insertionArray = original.clone();start = System.currentTimeMillis();insertionSort(insertionArray);long insertionTime = System.currentTimeMillis() - start;// 测试快速排序int[] quickArray = original.clone();start = System.currentTimeMillis();quickSort(quickArray);long quickTime = System.currentTimeMillis() - start;// 测试Arrays.sort()(优化的快速排序)int[] arraysArray = original.clone();start = System.currentTimeMillis();Arrays.sort(arraysArray);long arraysTime = System.currentTimeMillis() - start;System.out.printf("冒泡排序:%d ms%n", bubbleTime);System.out.printf("选择排序:%d ms%n", selectionTime);System.out.printf("插入排序:%d ms%n", insertionTime);System.out.printf("快速排序:%d ms%n", quickTime);System.out.printf("Arrays.sort():%d ms%n", arraysTime);}
}private static int[] generateRandomArray(int size) {Random random = new Random();int[] arr = new int[size];for (int i = 0; i < size; i++) {arr[i] = random.nextInt(1000);  // 0-999的随机数}return arr;
}

7. Arrays工具类深度应用

java.util.Arrays 类提供了丰富的数组操作方法,在实际开发中使用频率很高。

7.1 常用方法详解

数组比较方法

public class ArraysUtilDemo {public static void demonstrateComparison() {int[] arr1 = {1, 2, 3, 4, 5};int[] arr2 = {1, 2, 3, 4, 5};int[] arr3 = {1, 2, 3, 5, 4};// 错误的比较方式:比较引用地址System.out.println("arr1 == arr2: " + (arr1 == arr2)); // false// 正确的比较方式:比较内容System.out.println("Arrays.equals(arr1, arr2): " + Arrays.equals(arr1, arr2)); // trueSystem.out.println("Arrays.equals(arr1, arr3): " + Arrays.equals(arr1, arr3)); // false// 多维数组比较int[][] matrix1 = {{1, 2}, {3, 4}};int[][] matrix2 = {{1, 2}, {3, 4}};System.out.println("Arrays.equals(matrix1, matrix2): " + Arrays.equals(matrix1, matrix2)); // false!System.out.println("Arrays.deepEquals(matrix1, matrix2): " + Arrays.deepEquals(matrix1, matrix2)); // true}
}

数组转字符串方法

public static void demonstrateToString() {int[] numbers = {1, 2, 3, 4, 5};// 错误的输出方式System.out.println("直接输出:" + numbers); // 输出类似:[I@1b6d3586(内存地址)// 正确的输出方式System.out.println("Arrays.toString():" + Arrays.toString(numbers));// 输出:[1, 2, 3, 4, 5]// 多维数组的字符串转换int[][] matrix = {{1, 2, 3}, {4, 5, 6}};System.out.println("Arrays.toString(matrix):" + Arrays.toString(matrix));// 输出:[[I@..., [I@...] (仍然是地址)System.out.println("Arrays.deepToString(matrix):" + Arrays.deepToString(matrix));// 输出:[[1, 2, 3], [4, 5, 6]]
}

数组填充方法

public static void demonstrateFill() {// 填充整个数组int[] arr = new int[5];Arrays.fill(arr, 42);System.out.println("填充42:" + Arrays.toString(arr)); // [42, 42, 42, 42, 42]// 填充部分数组int[] arr2 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};Arrays.fill(arr2, 2, 7, 0);  // 从索引2到6(不包括7)填充0System.out.println("部分填充:" + Arrays.toString(arr2)); // [1, 2, 0, 0, 0, 0, 0, 8, 9, 10]// 实际应用:重置数组boolean[] flags = new boolean[10];Arrays.fill(flags, true);    // 全部设为trueArrays.fill(flags, 0, 3, false); // 前3个设为falseSystem.out.println("布尔数组:" + Arrays.toString(flags));// [false, false, false, true, true, true, true, true, true, true]
}

数组排序方法

public static void demonstrateSort() {// 基本排序int[] numbers = {64, 34, 25, 12, 22, 11, 90};System.out.println("排序前:" + Arrays.toString(numbers));Arrays.sort(numbers);System.out.println("排序后:" + Arrays.toString(numbers));// 部分排序int[] partialSort = {64, 34, 25, 12, 22, 11, 90};Arrays.sort(partialSort, 1, 5);  // 只排序索引1-4的元素System.out.println("部分排序:" + Arrays.toString(partialSort));// [64, 12, 22, 25, 34, 11, 90]// 字符串排序String[] names = {"张三", "李四", "王五", "赵六"};Arrays.sort(names);System.out.println("姓名排序:" + Arrays.toString(names));// 自定义排序(降序)Integer[] nums = {64, 34, 25, 12, 22, 11, 90};Arrays.sort(nums, Collections.reverseOrder());System.out.println("降序排序:" + Arrays.toString(nums));// 按长度排序字符串String[] words = {"apple", "pie", "washington", "book"};Arrays.sort(words, (a, b) -> a.length() - b.length());System.out.println("按长度排序:" + Arrays.toString(words));// [pie, book, apple, washington]
}

数组搜索方法

public static void demonstrateSearch() {int[] sortedArray = {10, 20, 30, 40, 50, 60, 70, 80, 90};// 二分查找:返回索引int index = Arrays.binarySearch(sortedArray, 50);System.out.println("找到50在索引:" + index); // 4// 查找不存在的元素int notFound = Arrays.binarySearch(sortedArray, 25);System.out.println("查找25的结果:" + notFound); // 负数,表示插入点if (notFound < 0) {int insertPoint = -notFound - 1;System.out.println("25应该插入在索引:" + insertPoint); // 2}// 在部分数组中搜索int partialSearch = Arrays.binarySearch(sortedArray, 2, 6, 40);System.out.println("在索引2-5中找40:" + partialSearch); // 3// 字符串搜索String[] names = {"Alice", "Bob", "Charlie", "David", "Eve"};int nameIndex = Arrays.binarySearch(names, "Charlie");System.out.println("找到Charlie在索引:" + nameIndex); // 2
}

7.2 高级应用技巧

数组拷贝的多种方式对比

public static void demonstrateCopyMethods() {int[] original = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};// 1. Arrays.copyOf() - 复制指定长度int[] copy1 = Arrays.copyOf(original, 5);  // 只复制前5个System.out.println("copyOf(5):" + Arrays.toString(copy1));// [1, 2, 3, 4, 5]int[] copy2 = Arrays.copyOf(original, 15); // 长度超过原数组System.out.println("copyOf(15):" + Arrays.toString(copy2));// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0]// 2. Arrays.copyOfRange() - 复制指定范围int[] copy3 = Arrays.copyOfRange(original, 2, 7); // 索引2-6System.out.println("copyOfRange(2,7):" + Arrays.toString(copy3));// [3, 4, 5, 6, 7]// 3. System.arraycopy() - 最灵活的复制int[] copy4 = new int[8];System.arraycopy(original, 1, copy4, 2, 5); // 从original[1]复制5个到copy4[2]System.out.println("System.arraycopy():" + Arrays.toString(copy4));// [0, 0, 2, 3, 4, 5, 6, 0]
}

数组类型转换技巧

public static void demonstrateArrayConversion() {// 基本类型数组转包装类型int[] primitiveArray = {1, 2, 3, 4, 5};Integer[] wrapperArray = Arrays.stream(primitiveArray).boxed().toArray(Integer[]::new);System.out.println("转为包装类型:" + Arrays.toString(wrapperArray));// 包装类型转基本类型Integer[] integers = {10, 20, 30, 40, 50};int[] primitives = Arrays.stream(integers).mapToInt(Integer::intValue).toArray();System.out.println("转为基本类型:" + Arrays.toString(primitives));// 字符串数组转整数数组String[] stringNumbers = {"100", "200", "300", "400"};int[] numbers = Arrays.stream(stringNumbers).mapToInt(Integer::parseInt).toArray();System.out.println("字符串转数字:" + Arrays.toString(numbers));// 数组转ListString[] names = {"Alice", "Bob", "Charlie"};List<String> nameList = Arrays.asList(names);System.out.println("数组转List:" + nameList);// 注意:Arrays.asList()返回的List不能修改大小try {nameList.add("David"); // 会抛出UnsupportedOperationException} catch (UnsupportedOperationException e) {System.out.println("Arrays.asList()返回的List不支持添加元素");}// 如果需要可修改的ListList<String> mutableList = new ArrayList<>(Arrays.asList(names));mutableList.add("David");System.out.println("可修改的List:" + mutableList);
}

数组作为Java编程的基石,其重要性不言而喻。掌握好数组操作,不仅能提高编程效率,还能为学习更高级的数据结构和算法打下坚实基础。希望这篇指南能够帮助你彻底理解和熟练运用Java数组!

http://www.dtcms.com/a/398328.html

相关文章:

  • PTZ相机AI相关的知识体系
  • Python 2025:新型解释器与性能优化实战
  • go 持续集成、持续部署之gitlab流水线+docker-compose踩坑之旅
  • 声明式事务5
  • 时序数据库选型指南:Apache IoTDB引领数字化转型新时代
  • [Android] apkm安装器 APKMirror Installer v1.10.1
  • spring boot项目使用Torna生成在线接口文档
  • 两学一做教育纪实评价系统网站店铺小程序如何开通
  • 10分钟快速部署PHP+Nginx+MySQL+Redis开发环境
  • 通过智享直播,企业如何实现精准的受众定位与内容分发
  • 【Prompt学习技能树地图】零样本与少样本提示技术实战:原理、应用与调优
  • group_points自定义tensorrt算子编写
  • 20250925问答课题-多标签分类模型
  • 唯品会库存API集成问题与技术方案解析
  • Python开发一个系统
  • 02-教务管理系统(选课管理系统)
  • 从入门到精通:逆向工程完全工具指南与桌面环境搭建
  • 注册网站做推广衡阳网站搜索引擎优化
  • 从零开始学Flink:数据转换的艺术
  • 公司做网站的流程wordpress 放大镜插件
  • 《系统与软件工程 功能规模测量 NESMA方法》(GBT 42588-2023)标准解读
  • React Testing完全指南:Jest、React Testing Library实战
  • python+springboot+django/flask的医院食堂订餐系统 菜单发布 在线订餐 餐品管理与订单统计系统
  • 半导体制造常见检测之拉曼光谱
  • Python 第七节 循环语句for和while使用详解及注意事项
  • 怎么把svg做网站背景谷歌关键词挖掘工具
  • Vue3中的computed属性
  • 7. 临时变量的常量性
  • SNK施努卡有色冶炼自动化解决方案
  • SpringCloud项目阶段七:延迟任务技术选项对比以及接入redis实现延迟队列添加/取消/消费等任务