# JavaSE 基础 — Day04 笔记 > **日期**:2026-07-16 > **项目**:`c260716` > **作者**:WanJL --- ## 一、多维数组 ### 1. 多维数组概述 Java 中的多维数组本质上是**数组的数组**: | 维度 | 本质 | 说明 | |------|------|------| | **一维数组** | 元素是数据类型的变量或常量 | `int[]` → 元素是 `int` | | **二维数组** | 元素是一维数组 | `int[][]` → 元素是 `int[]` | | **三维数组** | 元素是二维数组 | `int[][][]` → 元素是 `int[][]` | > **核心概念**:二维数组的每个元素其实是一维数组的**引用地址值**。 #### 二维数组的定义格式 ```java 数组类型[][] 数组名 = new 数组类型[二维数组长度][一维数组长度]; // 示例:3行2列的二维数组 int[][] arr = new int[3][2]; // [行数][列数] ``` --- ### 2. 二维数组的遍历 二维数组需要**两层循环嵌套**来遍历: ```java // 外层循环控制行数(二维数组的长度) for (int i = 0; i < arr.length; i++) { // 内层循环控制列数(每个一维数组的长度) for (int j = 0; j < arr[i].length; j++) { System.out.print(arr[i][j] + " "); } System.out.println(); } ``` - `arr.length` — 二维数组的长度(即行数) - `arr[i].length` — 第 i 行一维数组的长度(即列数) --- ### 3. 二维数组的静态初始化 ```java int[][] arr1 = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; ``` --- ### 4. 不规则的二维数组(每个一维数组长度不同) 二维数组中的一维数组**不必长度相同**: ```java int[] arr01 = {1, 3}; int[] arr02 = {4, 5, 6}; int[] arr03 = {7, 8, 9, 15}; int[][] arr2 = {arr01, arr02, arr03}; // 存放的是一维数组的引用地址 ``` 遍历时同样使用两层循环,每个内层循环以对应一维数组的长度为界: ```java for (int i = 0; i < arr2.length; i++) { for (int j = 0; j < arr2[i].length; j++) { System.out.print(arr2[i][j] + " "); } System.out.println(); } ``` --- ## 二、增强 for 循环(foreach) ### 1. foreach 概述 **增强 for 循环(foreach)** 是 Java 提供的一种简化数组遍历的语法,作用和普通 `for` 循环一样,主要用于**遍历数组或集合中的所有元素**。 #### 语法格式 ```java for (数据类型 变量 : 数组名) { 代码块 } ``` #### 基本示例 ```java int[] arr = {1, 2, 3, 4, 5, 6, 8}; for (int a : arr) { System.out.print(a + " "); } // 输出:1 2 3 4 5 6 8 ``` > ⚠️ **注意**:foreach 遍历数组时**无法获取索引值**,所以只适合**遍历全部元素**。如果需要操作某个指定索引位置的元素,仍需使用普通 `for` 循环。 --- ### 2. foreach 遍历二维数组 ```java int[] arr01 = {1, 3}; int[] arr02 = {4, 5, 6}; int[] arr03 = {7, 8, 9, 15}; int[][] arr2 = {arr01, arr02, arr03}; for (int[] a : arr2) { // 外层 foreach:遍历二维数组,得到每个一维数组 for (int a1 : a) { // 内层 foreach:遍历一维数组,得到每个元素 System.out.print(a1 + " "); } System.out.println(); } ``` > 外层 `int[] a` 表示二维数组中的每个元素是一维数组,内层 `int a1` 表示一维数组中的每个元素是 `int`。 --- ### 3. for 循环 vs foreach 对比 | 特性 | 普通 for 循环 | 增强 for 循环(foreach) | |------|-------------|------------------------| | **语法** | `for(int i=0; i **命名特点**:如果一个类的类名是**名词 + `s`**,大多数情况下这个类就是**工具类**。 **使用前提**:需要导包 ```java import java.util.Arrays; // import 是导包的关键字,后面跟着【类全路径名】—— 包名.类名 ``` --- ### 2. 常用方法 | 方法 | 作用 | 示例 | |------|------|------| | `Arrays.sort(arr)` | 对数组进行**升序排序** | `Arrays.sort(arr);` | | `Arrays.toString(arr)` | 将数组转换为**字符串**输出 | `System.out.println(Arrays.toString(arr));` | | `Arrays.copyOf(arr, newLength)` | **拷贝数组**(扩展或截断,多余补默认值) | `int[] copy = Arrays.copyOf(arr, 10);` | | `Arrays.copyOfRange(arr, from, to)` | 拷贝数组的**指定范围** `[from, to)` | `int[] range = Arrays.copyOfRange(arr, 1, 4);` | | `Arrays.binarySearch(arr, key)` | **二分查找**(数组必须先排序),返回索引值(未找到返回负数) | `int index = Arrays.binarySearch(arr, 689);` | #### 详细示例 ```java int[] arr = {7, 6, 78, 1, 689, 87, 165, 7, 4, 5, 8}; // 排序 Arrays.sort(arr); System.out.println(Arrays.toString(arr)); // 输出:[1, 4, 5, 6, 7, 7, 8, 78, 87, 165, 689] // 拷贝(扩展长度,多余补 0) int[] copy = Arrays.copyOf(arr, 10); System.out.println(Arrays.toString(copy)); // 拷贝指定范围 [1, 4) — 包含索引1,不包含索引4 int[] range = Arrays.copyOfRange(arr, 1, 4); System.out.println(Arrays.toString(range)); // 输出:[4, 5, 6] // 二分查找(需先排序),返回元素在数组中的索引 int index = Arrays.binarySearch(arr, 689); System.out.println(index); // 输出:10(排序后 689 在索引 10) ``` > ⚠️ `Arrays.binarySearch()` 要求数组**必须先排序**,否则结果不确定。 --- ## 四、冒泡排序(Bubble Sort) ### 1. 冒泡排序概述 **冒泡排序**是所有排序算法中最直观、最基础的一种。它的名字来源于排序过程就像水里的泡泡一样,大的元素会逐渐"冒"到数组的末端。 #### 核心思想 > 重复遍历数组,依次比较**相邻的两个元素**,如果顺序错误(如从小到大排序时前一个比后一个大)就交换。每轮遍历把当前未排序部分的最大值"冒泡"到正确位置。 #### 算法步骤图解 以数组 `{5, 3, 8, 6, 4}` 为例,从小到大排序: ``` 第1轮(i=0):将最大值 8 冒泡到最后 [5, 3, 8, 6, 4] → 比较 5>3 → 交换 → [3, 5, 8, 6, 4] [3, 5, 8, 6, 4] → 比较 5<8 → 不交换 [3, 5, 8, 6, 4] → 比较 8>6 → 交换 → [3, 5, 6, 8, 4] [3, 5, 6, 8, 4] → 比较 8>4 → 交换 → [3, 5, 6, 4, 8] ← 8 到位 第2轮(i=1):将最大值 6 冒泡到倒数第二位 [3, 5, 6, 4, 8] → 比较 3<5 → 不交换 [3, 5, 6, 4, 8] → 比较 5<6 → 不交换 [3, 5, 6, 4, 8] → 比较 6>4 → 交换 → [3, 5, 4, 6, 8] ← 6 到位 第3轮(i=2):将最大值 5 冒泡到倒数第三位 [3, 5, 4, 6, 8] → 比较 3<5 → 不交换 [3, 5, 4, 6, 8] → 比较 5>4 → 交换 → [3, 4, 5, 6, 8] ← 5 到位 第4轮(i=3):将最大值 4 冒泡到倒数第四位 [3, 4, 5, 6, 8] → 比较 3<4 → 不交换 ← 4 已在正确位置 排序完成 ✅ ``` --- ### 2. 基础版冒泡排序 ```java int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74}; // 冒泡排序需要两层循环 for (int i = 0; i < arr.length - 1; i++) { // 外层循环:控制冒泡的轮数(共 arr.length-1 轮) // 内层循环:在未排序区间 [0, arr.length-1-i] 中相邻比较 // 因为 i 后面的元素已经有序,不需要再比较了 for (int j = 0; j < arr.length - 1 - i; j++) { if (arr[j] > arr[j + 1]) { // 交换位置(加减法交换) arr[j] = arr[j] + arr[j + 1]; arr[j + 1] = arr[j] - arr[j + 1]; arr[j] = arr[j] - arr[j + 1]; } } } System.out.println(Arrays.toString(arr)); ``` **代码要点**: | 部分 | 说明 | |------|------| | **外层循环** `i` | 控制冒泡的**轮数**,`arr.length - 1` 轮 | | **内层循环** `j` | 在未排序区间 **相邻比较**,范围 `[0, arr.length-1-i)` | | **`arr.length-1-i`** | 每轮结束后,末尾 `i` 个元素已有序,无需再比较 | | **相邻比较** | `arr[j] > arr[j+1]` 表示前大后小,需要交换 | > **为什么外层循环是 `arr.length-1` 轮?** > 因为 n 个元素的数组,最多需要 n-1 轮冒泡就可以完成排序(最后一轮只剩一个元素,无需再排)。 > **为什么内层循环是 `arr.length-1-i`?** > 第 i 轮结束后,数组末尾的 i 个元素已经是有序的(每轮冒泡出一个最大值),所以内层只需要比较前 `arr.length-1-i` 个元素。 --- ### 3. 优化版冒泡排序 如果某轮冒泡过程中**没有发生任何交换**,说明数组已经有序,可以**提前结束排序**。 ```java int[] arr2 = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74}; for (int i = 0; i < arr2.length - 1; i++) { boolean b = false; // 标记本轮是否有交换 for (int j = 0; j < arr2.length - 1 - i; j++) { if (arr2[j] > arr2[j + 1]) { // 交换位置 arr2[j] = arr2[j] + arr2[j + 1]; arr2[j + 1] = arr2[j] - arr2[j + 1]; arr2[j] = arr2[j] - arr2[j + 1]; b = true; // 本轮有交换 } } // 如果本轮没有发生任何交换,说明数组已经有序,提前结束 if (b == false) { break; } System.out.println("第" + (i + 1) + "轮冒泡后:" + Arrays.toString(arr2)); } ``` **优化点**:引入 `boolean` 标记变量,如果某一轮没有发生交换,说明数组已有序,直接 `break` 退出循环。 --- ### 4. 冒泡排序总结 | 特性 | 说明 | |------|------| | **算法类型** | 比较排序、交换排序 | | **时间复杂度(平均)** | O(n²) | | **时间复杂度(最好)** | O(n) — 数组已有序(优化版) | | **时间复杂度(最坏)** | O(n²) — 数组逆序 | | **空间复杂度** | O(1) — 原地排序,不需要额外空间 | | **稳定性** | ✅ 稳定(相等元素的相对位置不变) | | **适用场景** | 小规模数据排序,教学演示 | > **数据结构与算法初探**:数组是最基础的**线性结构**之一,冒泡排序是最基础的**排序算法**之一。后续还会学习选择排序、插入排序、快速排序等更多算法。 --- ## 五、选择排序(Selection Sort) ### 1. 选择排序概述 **选择排序**的思路比冒泡排序更"聪明"——冒泡排序是不断交换相邻元素,而选择排序**每轮只做一次交换**。 #### 核心思想 > 每一轮从**剩余未排序的元素**中找出**最小的元素**,直接把它放到**未排序部分的第 1 位**(即已排序区间的末尾)。 #### 算法步骤图解 以数组 `{29, 10, 14, 37, 13}` 为例,从小到大排序: ``` 第1轮(i=0):在 [0, 4] 找最小值 10,与 arr[0]=29 交换 [29, 10, 14, 37, 13] → 找到最小值 10 (索引 1) → 交换 → [10, 29, 14, 37, 13] ← 10 到位 第2轮(i=1):在 [1, 4] 找最小值 13,与 arr[1]=29 交换 [10, 29, 14, 37, 13] → 找到最小值 13 (索引 4) → 交换 → [10, 13, 14, 37, 29] ← 13 到位 第3轮(i=2):在 [2, 4] 找最小值 14,已在正确位置,不交换 [10, 13, 14, 37, 29] → 找到最小值 14 (索引 2) → minIndex == i,不交换 ← 14 已在正确位置 第4轮(i=3):在 [3, 4] 找最小值 29,与 arr[3]=37 交换 [10, 13, 14, 37, 29] → 找到最小值 29 (索引 4) → 交换 → [10, 13, 14, 29, 37] ← 29、37 到位 排序完成 ✅ ``` --- ### 2. 选择排序代码实现 ```java import java.util.Arrays; int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74}; // 外层循环:i 既是已排序区间的末尾边界,也是当前要确定的位置 for (int i = 0; i < arr.length - 1; i++) { // 假设当前未排序区间的第 1 个元素就是最小的 int minIndex = i; // 假设 i 就是最小元素的索引值 // 内层循环:在未排序区间 [i+1, arr.length-1] 找到真正的最小值 for (int j = i + 1; j < arr.length; j++) { if (arr[j] < arr[minIndex]) { minIndex = j; // 更新最小元素的索引值 } } // 如果最小元素索引值 minIndex 和当前 i 不相等,就交换位置 if (minIndex != i) { arr[minIndex] = arr[minIndex] + arr[i]; arr[i] = arr[minIndex] - arr[i]; arr[minIndex] = arr[minIndex] - arr[i]; } System.out.println("第" + (i + 1) + "轮选择后:" + Arrays.toString(arr)); } ``` **代码要点**: | 部分 | 说明 | |------|------| | **外层循环** `i` | 控制选择的轮数,`arr.length - 1` 轮;`i` 也是当前要确定的位置 | | **内层循环** `j` | 在**未排序区间** `[i+1, arr.length-1]` 中寻找最小值 | | **`minIndex`** | 记录当前找到的最小元素的索引值,初始为 `i` | | **交换条件** | 当 `minIndex != i` 时,说明最小值不在当前位置,需要交换 | | **每轮只交换一次** | 找到最小值后,直接与当前位置交换,比冒泡排序更高效 | --- ### 3. 冒泡排序 vs 选择排序对比 | 特性 | 冒泡排序 | 选择排序 | |------|---------|---------| | **核心思路** | 相邻比较,大的往后冒 | 每轮选最小值,放到正确位置 | | **每轮交换次数** | 多次(可能每轮交换 n 次) | **最多 1 次** | | **比较次数** | 固定 O(n²/2) | 固定 O(n²/2) | | **时间复杂度(平均)** | O(n²) | O(n²) | | **时间复杂度(最好)** | O(n) — 优化版 | O(n²) — 仍要遍历全部 | | **时间复杂度(最坏)** | O(n²) | O(n²) | | **空间复杂度** | O(1) | O(1) | | **稳定性** | ✅ 稳定 | ❌ 不稳定 | | **适用场景** | 小规模数据、教学演示 | 小规模数据、交换成本高的场景 | > **稳定性说明**:选择排序是不稳定的,因为当最小值与前面的元素交换时,可能改变相等元素的相对顺序。例如 `[5, 5, 2]` → 第一轮将 `2` 与第一个 `5` 交换,两个 `5` 的相对顺序就变了。 --- ### 4. 两种排序的过程对比 以数组 `{5, 3, 8, 6, 4}` 为例: **冒泡排序**(每轮可能多次交换): ``` 第1轮:5>3→交换, 5<8不换, 8>6→交换, 8>4→交换 → [3,5,6,4,8] 第2轮:3<5不换, 5<6不换, 6>4→交换 → [3,5,4,6,8] 第3轮:3<5不换, 5>4→交换 → [3,4,5,6,8] 第4轮:3<4不换 → [3,4,5,6,8] ``` **选择排序**(每轮最多一次交换): ``` 第1轮:找最小值 3 (索引1),与 arr[0]=5 交换 → [3,5,8,6,4] 第2轮:找最小值 4 (索引4),与 arr[1]=5 交换 → [3,4,8,6,5] 第3轮:找最小值 5 (索引4),与 arr[2]=8 交换 → [3,4,5,6,8] 第4轮:找最小值 6 (索引3),已在正确位置不交换 → [3,4,5,6,8] ``` --- ## 六、插入排序(Insertion Sort) ### 1. 插入排序概述 **插入排序**是三种基础排序(冒泡、选择、插入)中最实用、使用最广泛的一种 O(n²) 排序算法。它的思想非常符合人类整理东西的习惯——就像**打扑克牌时抓牌插入到手牌的正确位置**。 #### 核心思想 > 把数组分为"**已排序**"和"**未排序**"两部分。每次从**未排序部分**取出第 1 个元素,在**已排序部分**从后往前找到合适的位置并插入。 #### 算法步骤图解 以数组 `{5, 3, 8, 6, 4}` 为例,从小到大排序: ``` 初始: [5 | 3, 8, 6, 4] 竖线左边为已排序,右边为未排序 第1轮(i=1):取未排序第1个元素 current=3,与已排序部分 [5] 比较 5 > 3 → 5 向后移 → [5, 5 | 8, 6, 4] 找到插入位置 j+1=0,插入 current=3 → [3, 5 | 8, 6, 4] ← 3 和 5 有序 第2轮(i=2):取 current=8,与已排序部分 [3, 5] 比较 5 < 8 → 无需移动,直接插入在末尾 → [3, 5, 8 | 6, 4] ← 8 已在正确位置 第3轮(i=3):取 current=6,与已排序部分 [3, 5, 8] 比较 8 > 6 → 8 向后移 → [3, 5, 8, 8 | 4] 5 < 6 → 停止,找到插入位置 j+1=2,插入 current=6 → [3, 5, 6, 8 | 4] ← 6 插入到 5 和 8 之间 第4轮(i=4):取 current=4,与已排序部分 [3, 5, 6, 8] 比较 8 > 4 → 8 向后移 → [3, 5, 6, 8, 8] 6 > 4 → 6 向后移 → [3, 5, 6, 6, 8] 5 > 4 → 5 向后移 → [3, 5, 5, 6, 8] 3 < 4 → 停止,找到插入位置 j+1=1,插入 current=4 → [3, 4, 5, 6, 8] ✅ 排序完成 ``` --- ### 2. 插入排序代码实现 ```java import java.util.Arrays; int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74}; // 从第2个元素开始,假设 arr[0] 已经是已排序部分 for (int i = 1; i < arr.length; i++) { // 获取当前要插入的元素(未排序的第1个) int current = arr[i]; // 已排序部分的最后一个索引值 int j = i - 1; // 从后往前扫描已排序部分,寻找插入位置 // 如果已排序的元素大于 current,就向后移动一位 while (j >= 0 && arr[j] > current) { arr[j + 1] = arr[j]; j--; } // 此时 j+1 就是 current 应该插入的位置 arr[j + 1] = current; System.out.println("第" + i + "轮插入后:" + Arrays.toString(arr)); } ``` **代码要点**: | 部分 | 说明 | |------|------| | **外层循环** `i` | 从索引 1 开始,`i` 是当前要处理的"未排序的第 1 个元素"位置 | | **`current`** | 暂存当前要插入的元素值,防止被覆盖 | | **内层 while 循环** | 从后往前扫描已排序部分,比 `current` 大的元素逐一后移 | | **`arr[j+1] = current`** | 在找到的正确位置插入当前元素 | | **循环条件** | `j >= 0 && arr[j] > current` — 未越界且当前比较值比 `current` 大 | --- ### 3. 三种排序算法对比 | 特性 | 冒泡排序 | 选择排序 | 插入排序 | |------|---------|---------|---------| | **核心思路** | 相邻比较,大的往后冒 | 每轮选最小值交换 | 将元素插入已排序区的正确位置 | | **每轮交换次数** | 多次(可能 n 次) | **最多 1 次** | 多次(元素后移) | | **平均时间复杂度** | O(n²) | O(n²) | O(n²) | | **最好时间复杂度** | O(n) — 优化版 | O(n²) | **O(n)** — 已有序 | | **最坏时间复杂度** | O(n²) | O(n²) | O(n²) | | **空间复杂度** | O(1) | O(1) | O(1) | | **稳定性** | ✅ 稳定 | ❌ 不稳定 | ✅ 稳定 | | **实用性** | 低(教学用) | 中(交换成本高时适用) | **高**(小规模数据首选) | > **三种排序的实用性排序**:插入排序 > 选择排序 > 冒泡排序 > > **为什么插入排序最实用?** > - 当数据**基本有序**时,插入排序的时间复杂度可达到 **O(n)** > - 插入排序是**稳定的**,不会破坏相等元素的相对顺序 > - 插入排序的代码简洁,实际运行效率在 O(n²) 级别中最高 --- ## 七、Scanner 键盘输入 ### 1. Scanner 概述 `java.util.Scanner` 是 Java 提供的**键盘输入工具类**,可以读取用户在控制台中输入的数据。 #### 使用步骤 | 步骤 | 说明 | 代码 | |------|------|------| | **① 导包** | 引入 Scanner 类(IDEA 可自动导包) | `import java.util.Scanner;` | | **② 创建对象** | 创建 Scanner 对象,绑定到系统输入 | `Scanner sc = new Scanner(System.in);` | | **③ 读取数据** | 调用对应的方法读取不同类型的数据 | `sc.nextInt()`、`sc.next()` 等 | | **④ 关闭资源** | 关闭 Scanner 释放资源 | `sc.close();` | > `System.in` 属于**系统标准输入流**,Scanner 通过它来读取用户在控制台的输入。 --- ### 2. 常用方法 | 方法 | 读取类型 | 示例 | |------|---------|------| | `sc.nextByte()` | 整数 byte | `byte b = sc.nextByte();` | | `sc.nextShort()` | 整数 short | `short s = sc.nextShort();` | | `sc.nextInt()` | 整数 int | `int i = sc.nextInt();` | | `sc.nextLong()` | 整数 long | `long l = sc.nextLong();` | | `sc.nextFloat()` | 小数 float | `float f = sc.nextFloat();` | | `sc.nextDouble()` | 小数 double | `double d = sc.nextDouble();` | | `sc.next()` | **单个字符串**(以空格/换行符为结束符) | `String s = sc.next();` | | `sc.nextLine()` | **整行字符串**(以换行符为结束符) | `String line = sc.nextLine();` | | `sc.nextBoolean()` | 布尔值 boolean | `boolean b = sc.nextBoolean();` | | `sc.next().charAt(0)` | 字符 char | `char c = sc.next().charAt(0);` | #### 基本示例 ```java Scanner sc = new Scanner(System.in); // 读取整数 int a = sc.nextInt(); int b = sc.nextInt(); System.out.println("a + b = " + (a + b)); // 读取字符串 String s = sc.next(); System.out.println(s); sc.close(); ``` #### 综合练习 — 用户信息采集 ```java Scanner sc = new Scanner(System.in); System.out.println("请输入您的姓名:"); String name = sc.next(); System.out.println("请输入您的年龄:"); int age = sc.nextInt(); System.out.println("请输入您的身高(米):"); double height = sc.nextDouble(); System.out.println("您是否是在校学生(true/false):"); boolean isStudent = sc.nextBoolean(); System.out.println("\n========用户信息========"); System.out.println("姓名:" + name); System.out.println("年龄:" + age); System.out.println("身高:" + height); System.out.println("在校学生:" + isStudent); sc.close(); ``` --- ### 3. Scanner 的常见陷阱 #### 陷阱:`nextInt()` 后面使用 `nextLine()` 的问题 ```java int a = sc.nextInt(); String s = sc.nextLine(); System.out.println(a); // 输出数字 System.out.println(s); // ❌ 直接输出空字符串,没有等待输入! ``` **问题原因**: - `nextInt()` 读取完数字后,**换行符 `\n` 仍停留在输入缓冲区**中 - `nextLine()` 以换行符为结束符,遇到缓冲区中的换行符就**直接结束了** - 所以 `s` 读取到的是一个空字符串,程序不会等待用户输入 **解决方案**:在 `nextInt()` 之后调用一次 `nextLine()` **吃掉多余的换行符**。 ```java int a = sc.nextInt(); sc.nextLine(); // ✅ 吃掉缓冲区中的换行符 String s = sc.nextLine(); // ✅ 正常读取整行字符串 System.out.println(a); System.out.println(s); ``` > ⚠️ **注意事项**: > - 在控制台中使用键盘输入的值,**必须符合 Scanner 接收的类型**,否则会抛出异常导致程序结束 > - 例如用 `nextInt()` 读取时输入了字母,会抛出 `InputMismatchException` --- ### 4. `next()` vs `nextLine()` 区别 | 特性 | `next()` | `nextLine()` | |------|----------|-------------| | **结束符** | 空格、Tab、换行符 | **仅换行符** | | **能否读取空格** | ❌ 不能(遇到空格停止) | ✅ 能(读取整行) | | **典型场景** | 读取单个单词 | 读取包含空格的句子 | ```java // 输入:Hello World System.out.println(sc.next()); // 输出:Hello(遇到空格停止) System.out.println(sc.nextLine()); // 输出:World(读取剩余部分) ``` --- ## 八、课堂练习详解 ### 练习 01:首尾对称交换数组元素 **题目**:定义数组 `{10, 20, 30, 40, 50}`,将第 1 个与第 5 个交换、第 2 个与第 4 个交换(首尾对称交换)。 **核心思路**:使用**加减法交换**(不借助第三个变量)实现两个位置的元素互换。 ```java int[] arr = {10, 20, 30, 40, 50}; // 首尾交换:第1位和第5位 arr[0] = arr[0] + arr[4]; arr[4] = arr[0] - arr[4]; arr[0] = arr[0] - arr[4]; // 首尾交换:第2位和第4位 arr[1] = arr[1] + arr[3]; arr[3] = arr[1] - arr[3]; arr[1] = arr[1] - arr[3]; ``` **变量交换的三种方式**: | 方式 | 代码 | 优点 | 缺点 | |------|------|------|------| | **借助临时变量** | `tmp = a; a = b; b = tmp;` | 直观、安全 | 多用一个变量 | | **加减法交换** | `a = a + b; b = a - b; a = a - b;` | 无需额外变量 | 可能溢出(大数) | | **异或交换** | `a = a ^ b; b = a ^ b; a = a ^ b;` | 无需额外变量 | 可读性差 | --- ### 练习 02:数组循环右移 **题目**:定义数组 `{1, 2, 3, 4, 5}`,将数组中的所有元素向右移动 2 个位置,超出数组范围的元素循环移到数组开头。 **解题思路**: 先理解**全体向右移动 1 位**的过程: 1. 先把最后一个元素保存起来 2. 倒数第 2 位 → 倒数第 1 位 3. 倒数第 3 位 → 倒数第 2 位 4. ...依此类推... 5. 第 1 位(索引 0)→ 第 2 位(索引 1) 6. 把保存起来的最后一个元素 → 第 1 位(索引 0) **执行步骤图解**(右移 1 位): ``` 原始: [1, 2, 3, 4, 5] 第1步:保存最后一个元素 last = 5 第2步:从后往前移动 ← [1, 2, 3, 4, 4] // arr[4]=arr[3] [1, 2, 3, 3, 4] // arr[3]=arr[2] [1, 2, 2, 3, 4] // arr[2]=arr[1] [1, 1, 2, 3, 4] // arr[1]=arr[0] 第3步:arr[0] = last 结果: [5, 1, 2, 3, 4] ``` **代码实现**: ```java int[] arr = {1, 2, 3, 4, 5}; int k = 2; // 右移位数 // 如果k大于数组长度,取模(移动6次 = 移动1次) k = k % arr.length; // 整体右移k位(循环右移) for (int i = 0; i < k; i++) { // 保存最后一个元素 int last = arr[arr.length - 1]; // 从后往前,每个元素向后移动1位 for (int j = arr.length - 1; j > 0; j--) { arr[j] = arr[j - 1]; } // 把最后一个元素放到第0位 arr[0] = last; } ``` > **关键点**: > - 内层循环必须**从后往前**遍历(`j = length-1` → `j > 0`),否则前面的值会被覆盖 > - `k = k % arr.length` 解决 k 大于数组长度时的重复移动问题(如移动 6 次 = 移动 1 次) --- ### 练习 03:数组去重 **题目**:定义数组 `{5, 2, 8, 2, 5, 9, 1, 8, 3}`,去除重复元素,只保留第一次出现的元素,输出去重后的新数组。 **核心思路**: 1. **第一步**:统计不重复元素的个数 2. **第二步**:创建新数组(长度为统计结果) 3. **第三步**:将不重复元素放入新数组 **判断元素是否重复的方式**:检查当前元素 `arr[i]` 在它**之前**是否出现过(即 `arr[0]` ~ `arr[i-1]` 中是否有相同的值)。 ```java int[] arr = {5, 2, 8, 2, 5, 9, 1, 8, 3}; int count = 0; // 不重复元素的个数 // 第一步:统计不重复元素的个数 for (int i = 0; i < arr.length; i++) { boolean b = false; // 标记:先认为当前元素没有重复 for (int j = 0; j < i; j++) { // 检查 arr[i] 在它之前是否出现过 if (arr[i] == arr[j]) { b = true; // 找到重复,标记改为 true break; // 跳出内层循环 } } if (b == false) { // 没有重复,计数 count++; } } // 第二步:创建新数组 int[] newArr = new int[count]; int index = 0; // 新数组的索引 // 第三步:将不重复元素放入新数组 for (int i = 0; i < arr.length; i++) { boolean b = false; for (int j = 0; j < i; j++) { if (arr[i] == arr[j]) { b = true; break; } } if (b == false) { // 不重复的元素 newArr[index] = arr[i]; // 放入新数组 index++; } } // 输出新数组 for (int i = 0; i < newArr.length; i++) { System.out.print(newArr[i] + " "); } // 输出:5 2 8 9 1 3 ``` > **核心技巧**: > - 使用**布尔标记 `boolean b`** 来判断元素是否重复 > - 内层循环 `j < i` 只检查当前元素**之前**的元素,确保只保留第一次出现的元素 > - 两步走:先统计个数 → 再填充数据(因为数组长度在创建后不可改变) --- ## 九、课后作业讲解(7月15日作业) ### 作业 07:数组元素求和 **题目**:定义数组 `{11, 22, 33, 44, 55}`,用 `for` 循环遍历数组,计算所有元素的总和。 **知识点**:数组遍历、累加求和 ```java int[] arr = {11, 22, 33, 44, 55}; int sum = 0; // 记录累加和 for (int i = 0; i < arr.length; i++) { sum += arr[i]; // 通过 arr[i] 获取每个元素,累加到 sum } System.out.println(sum); // 输出:165 ``` --- ### 作业 08:找数组最大值 **题目**:定义数组 `{3, 8, 1, 9, 4, 7, 2}`,找出数组中的最大值。 **知识点**:数组遍历、打擂法(假设法) ```java int[] arr = {3, 8, 1, 9, 4, 7, 2}; int max = arr[0]; // 先假设第一个元素是最大值 for (int i = 1; i < arr.length; i++) { if (arr[i] > max) { // 如果有元素比当前最大值还大 max = arr[i]; // 更新最大值 } } System.out.println(max); // 输出:9 ``` > **打擂法核心思路**:先假设第一个元素是最大值,然后遍历数组,遇到更大的就更新。 --- ### 作业 09:数组倒序输出 **题目**:定义数组 `{1, 2, 3, 4, 5}`,从后往前遍历,将元素倒序输出。 **知识点**:for 循环逆序遍历 ```java int[] arr = {1, 2, 3, 4, 5}; // 从最大索引开始,递减到 0 for (int i = arr.length - 1; i >= 0; i--) { System.out.println(arr[i]); } // 输出: // 5 // 4 // 3 // 2 // 1 ``` > 从 `arr.length - 1`(最后一个元素的索引)开始,到 `0`(第一个元素的索引)结束,每次递减 1。 --- ### 作业 10:统计奇偶数个数 **题目**:定义数组 `{12, 7, 9, 24, 15, 8, 31, 46}`,遍历数组,统计奇数和偶数的个数。 **知识点**:数组遍历、取模判断、统计计数 ```java int[] arr = {12, 7, 9, 24, 15, 8, 31, 46}; int count = 0; // 奇数个数 for (int i = 0; i < arr.length; i++) { if (arr[i] % 2 != 0) { // 余2不等于0,说明是奇数 count++; } } System.out.println("奇数个数:" + count + ",偶数个数:" + (arr.length - count)); // 输出:奇数个数:4,偶数个数:4 ``` > **偶数的判定**:`arr[i] % 2 == 0` > **奇数的判定**:`arr[i] % 2 != 0` > 偶数个数 = 数组总长度 - 奇数个数 --- ## 十、随堂练习要点 1. **多维数组本质**:数组的数组,二维数组的元素是一维数组的引用地址 2. **二维数组定义**:`int[][] arr = new int[3][2];`(3 行 2 列) 3. **二维数组遍历**:两层循环嵌套,外层行数 `arr.length`,内层列数 `arr[i].length` 4. **不规则二维数组**:每个一维数组长度可以不同 5. **foreach 语法**:`for(数据类型 变量 : 数组名) { }`,适合遍历全部元素 6. **foreach 局限性**:无法获取索引值,无法修改元素,只能读取 7. **Arrays 工具类**:`java.util.Arrays`,操作数组的静态方法集合 8. **`Arrays.sort(arr)`**:对数组进行升序排序 9. **`Arrays.toString(arr)`**:将数组转换为字符串输出 10. **`Arrays.copyOf(arr, n)`**:拷贝数组,指定新长度,多余补默认值 11. **`Arrays.copyOfRange(arr, from, to)`**:拷贝指定范围 [from, to) 12. **`Arrays.binarySearch(arr, key)`**:二分查找(必须先排序) 13. **冒泡排序**:重复遍历数组,相邻元素两两比较,大的往后冒 14. **冒泡排序-外层循环**:控制冒泡轮数,共 `arr.length - 1` 轮 15. **冒泡排序-内层循环**:在未排序区间 `[0, arr.length-1-i]` 相邻比较 16. **冒泡排序-优化**:引入 `boolean` 标记,本轮无交换则提前结束 17. **选择排序**:每轮从未排序区间选出最小值,与未排序区间的第 1 个元素交换 18. **选择排序-外层循环**:`i` 是当前要确定的位置,范围 `[0, arr.length-2]` 19. **选择排序-内层循环**:在未排序区间 `[i+1, arr.length-1]` 找最小值索引 20. **选择排序-minIndex**:记录最小元素的索引值,初始为 `i` 21. **选择排序-交换条件**:`minIndex != i` 时才交换,每轮最多交换 1 次 22. **选择排序 vs 冒泡排序**:选择排序每轮最多交换 1 次,冒泡排序可能交换多次 23. **插入排序**:将未排序的第 1 个元素插入到已排序部分的正确位置 24. **插入排序-分治思想**:把数组分为"已排序"和"未排序"两部分 25. **插入排序-外层循环**:`i` 从 1 开始,标记当前要处理的未排序元素 26. **插入排序-current**:暂存当前要插入的元素值,防止被覆盖 27. **插入排序-内层 while**:从后往前扫描已排序部分,比 current 大的元素逐一后移 28. **插入排序-实用性**:三种基础排序中最实用,数据基本有序时效率可达 O(n) 29. **三种排序实用性排序**:插入排序 > 选择排序 > 冒泡排序 30. **Scanner 键盘输入**:`java.util.Scanner` 类读取控制台输入 31. **Scanner 使用步骤**:导包 → 创建对象 → 调用方法读取 → 关闭资源 32. **Scanner 常用方法**:`nextInt()`、`nextDouble()`、`next()`、`nextLine()`、`nextBoolean()` 等 33. **`next()` vs `nextLine()`**:`next()` 以空格/换行结束,`nextLine()` 仅以换行结束 34. **Scanner 陷阱**:`nextInt()` 后使用 `nextLine()` 会读取到空字符串 35. **Scanner 陷阱解决**:在 `nextInt()` 后调用一次 `nextLine()` 吃掉换行符 36. **Scanner 类型匹配**:输入的值必须符合 Scanner 接收的类型,否则抛出 `InputMismatchException` 37. **变量交换-临时变量法**:`tmp = a; a = b; b = tmp;` 38. **变量交换-加减法**:`a = a + b; b = a - b; a = a - b;` 39. **首尾交换**:利用对称性,第 i 位与第 length-1-i 位交换 40. **循环右移**:从后往前逐个移动,保存最后一个元素放到开头 41. **取模优化**:`k = k % arr.length` 解决 k 大于数组长度的问题 42. **数组去重思路**:统计不重复个数 → 创建新数组 → 填充不重复元素 43. **布尔标记法**:使用 `boolean` 变量标记元素是否重复 44. **打擂法找最大值**:假设第一个是最大值,遇到更大的就更新 45. **数组求和**:遍历数组,累加每个元素 46. **倒序输出**:从 `length-1` 到 `0` 递减遍历 47. **奇偶判断**:`n % 2 == 0` 偶,`n % 2 != 0` 奇 --- ## 十一、知识拓展 ### 1. 数组的地址值 当直接打印数组变量时,输出的是数组在堆内存中的**地址值**: ```java int[] a = new int[10]; System.out.println(a); // 输出类似:[I@b4c966a ``` 地址值含义: - `[` — 一维数组 - `I` — 元素类型为 int - `@` — 分隔符 - `b4c966a` — 堆内存中的十六进制地址 ### 2. 二维数组的内存结构 ``` 二维数组 arr2 = {arr01, arr02, arr03} 在内存中: ┌──────────┐ │ arr2[0] │────→ arr01: [1, 3] ├──────────┤ │ arr2[1] │────→ arr02: [4, 5, 6] ├──────────┤ │ arr2[2] │────→ arr03: [7, 8, 9, 15] └──────────┘ ``` 二维数组的每个元素存储的是**一维数组的引用地址**,而不是元素本身。 ### 3. 二分查找原理 `Arrays.binarySearch()` 基于**二分查找算法**(Binary Search): - **前提**:数组必须已排序 - **原理**:每次将查找范围缩小一半,与中间元素比较 - **时间复杂度**:O(log n) — 效率远高于线性查找 O(n) ``` 已排序数组:[1, 4, 5, 6, 7, 7, 8, 78, 87, 165, 689] 查找目标:689 第1次:比较中间元素 7 → 689 > 7,在右半部分查找 第2次:比较中间元素 87 → 689 > 87,继续在右半部分查找 第3次:比较中间元素 165 → 689 > 165,继续在右半部分查找 第4次:找到 689,返回索引 10 ``` ### 4. 数组拷贝的深浅问题 `Arrays.copyOf()` 和 `Arrays.copyOfRange()` 执行的是**浅拷贝**: - 对于基本数据类型:拷贝的是值,互不影响 ✓ - 对于引用数据类型:拷贝的是引用地址,新数组和原数组指向同一个对象 ⚠️ --- ## 十二、完整代码汇总 ### Demo01.java — 多维数组 ```java /** * @author WanJl * @version 1.0 * @title Demo01 * @description 多维数组 * @create 2026/7/16 */ public class Demo01 { public static void main(String[] args) { /* 一维数组中所有的元素都是数据类型的变量或常量 二维数组中所有的元素都是一维数组 三维数组中所有的元素都是二维数组 ...... Java多维数组的本质:其实就是数组的数组,二维数组的元素其实就是一维数组的引用地址值 二维数组的定义的格式: 数组类型[][] 数组名=new 数组类型[二维数组长度][一维数组长度]; */ //数组类型[][] 数组名=new 数组类型[二维数组长度][一维数组长度]; int[][] arr = new int[3][2]; //[行数][列数] //遍历二维数组需要两层循环嵌套 for (int i = 0; i < arr.length; i++) { for (int j = 0; j < arr[i].length; j++) { System.out.print(arr[i][j] + " "); } System.out.println(); } // 静态初始化二维数组----三行三列 int[][] arr1 = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; for (int i = 0; i < arr1.length; i++) { for (int j = 0; j < arr1[i].length; j++) { System.out.print(arr1[i][j] + " "); } System.out.println(); } // 二维数组中的一维数组,不是必须要相同长度的,长度是不一样的。 int[] arr01 = {1, 3}; int[] arr02 = {4, 5, 6}; int[] arr03 = {7, 8, 9, 15}; int[][] arr2 = {arr01, arr02, arr03}; //二维数组存放的其实就是一维数组的引用地址 for (int i = 0; i < arr2.length; i++) { for (int j = 0; j < arr2[i].length; j++) { System.out.print(arr2[i][j] + " "); } System.out.println(); } } } ``` ### Demo02.java — foreach 增强 for 循环 ```java /** * @author WanJl * @version 1.0 * @title Demo02 * @description foreach迭代-增强for循环 * @create 2026/7/16 */ public class Demo02 { public static void main(String[] args) { //我们之前使用的for循环,是for(int i=0;i<10;i++) //可以使用增强for循环--foreach,作用和for循环一样 //主要是用来进行数组遍历 /* 格式: for(数据类型 变量 : 数组名){ 代码块 } */ int[] arr = {1, 2, 3, 4, 5, 6, 8}; for (int a : arr) { System.out.print(a + " "); } System.out.println(); /* foreach 遍历的数组,无法获取索引值,所以只适合遍历全部元素。想要获取某个索引位置的元素,需要for循环 */ int[] arr01 = {1, 3}; int[] arr02 = {4, 5, 6}; int[] arr03 = {7, 8, 9, 15}; int[][] arr2 = {arr01, arr02, arr03}; for (int[] a : arr2) { //外层foreach for (int a1 : a) { //内层foreach System.out.print(a1 + " "); } System.out.println(); } } } ``` ### Demo03.java — Arrays 数组工具类 ```java import java.util.Arrays; /** * @author WanJl * @version 1.0 * @title Demo03 * @description 数组工具类 : java.util.Arrays; * @create 2026/7/16 */ public class Demo03 { public static void main(String[] args) { /* 数组工具类 : java.util.Arrays; java中的命名特点,如果一个类的类名 是名词+s,那么大多数情况下,这个类就是一个工具类 */ int[] arr = {7, 6, 78, 1, 689, 87, 165, 7, 4, 5, 8}; //使用工具类对数组进行排序 Arrays.sort(arr); //输出arr数组--Arrays.toString方法 是直接把arr数组转换成字符串 System.out.println(Arrays.toString(arr)); // 拷贝(扩展长度,多余补 0)-- 把一个数组的元素复制到一个新的数组并指定新数组的长度,多余补 0 int[] copy = Arrays.copyOf(arr, 10); System.out.println(Arrays.toString(copy)); // 拷贝指定范围 [1,4) int[] range = Arrays.copyOfRange(arr, 1, 4); System.out.println(Arrays.toString(range)); // 二分查找(需先排序),返回索引值 int index = Arrays.binarySearch(arr, 689); System.out.println(index); } } ``` ### Demo04.java — 冒泡排序 ```java import java.util.Arrays; /** * @author WanJl * @version 1.0 * @title Demo04 * @description 数组排序-冒泡排序 * @create 2026/7/16 */ public class Demo04 { /* 我们现在已经学了最基础的 数据结构与算法 数据结构:数组结构--属于线性结构的一种 算法:比如排序算法(冒泡、选择、插入...) 冒泡排序,是所有排序【算法】最直观的,最基础的一种。 冒泡排序就像水里的泡泡一样,大的元素会逐渐的冒到数组的末端。 冒泡排序的核心思想: 重复遍历数组,依次比较相邻的两个元素,如果顺序错误就交换。 每轮遍历把当前未排序的部分的最大值"冒泡"到正确位置。 */ public static void main(String[] args) { /* 冒泡排序的基础版 每一轮把未排序部分的最大值"冒泡"到数组末尾 */ int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74}; //冒泡排序需要两层循环 for (int i = 0; i < arr.length - 1; i++) { //外层循环,控制的一共冒泡多少轮 //内层循环 在未排序的区间[0,arr.length-1-i] 中相邻比较 //因为 i 后面的元素已经有序了,不需要再比较了 for (int j = 0; j < arr.length - 1 - i; j++) { if (arr[j] > arr[j + 1]) { //交换位置(加减法交换) arr[j] = arr[j] + arr[j + 1]; arr[j + 1] = arr[j] - arr[j + 1]; arr[j] = arr[j] - arr[j + 1]; } } } System.out.println(Arrays.toString(arr)); // 优化版 int[] arr2 = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74}; for (int i = 0; i < arr2.length - 1; i++) { boolean b = false; //定义boolean类型的变量,用来标记本轮是否有交换 for (int j = 0; j < arr2.length - 1 - i; j++) { if (arr2[j] > arr2[j + 1]) { //交换位置 arr2[j] = arr2[j] + arr2[j + 1]; arr2[j + 1] = arr2[j] - arr2[j + 1]; arr2[j] = arr2[j] - arr2[j + 1]; b = true; //本轮有交换 } } //判断,如果本轮没有发生任何交换,说明数组已经有序,提前结束 if (b == false) { break; } System.out.println("第" + (i + 1) + "轮冒泡后:" + Arrays.toString(arr2)); } } } ``` ### Demo05.java — 选择排序 ```java import java.util.Arrays; /** * @author WanJl * @version 1.0 * @title Demo05 * @description 数组排序-选择排序 * @create 2026/7/16 */ public class Demo05 { /* 选择排序: 选择排序的思路要比冒泡排序更聪明一点,冒泡排序是不断的交换相邻的元素, 而选择排序,是每轮只做一次交换。 选择的思路是每一轮从剩余未排序的元素中找出最小的元素, 直接把它放到未排序部分的第1位。 核心思路:每一轮从未排序区间选出最小值,与未排序区间的第1个元素进行交换。 优点:交换次数比冒泡更少 缺点:不稳定,可能会破坏相对顺序 */ public static void main(String[] args) { int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74}; //外层循环:i 既是已排序区间的末尾边界,也是当前要确定的位置 for (int i = 0; i < arr.length - 1; i++) { //假设当前未排序区间的第1个元素就是最小的 int minIndex = i; //内层循环:在未排序区间 [i+1, arr.length-1] 找到真正的最小值 for (int j = i + 1; j < arr.length; j++) { if (arr[j] < arr[minIndex]) { minIndex = j; //更新最小元素的索引值 } } //如果最小元素索引值和当前 i 不相等,就交换位置 if (minIndex != i) { arr[minIndex] = arr[minIndex] + arr[i]; arr[i] = arr[minIndex] - arr[i]; arr[minIndex] = arr[minIndex] - arr[i]; } System.out.println("第" + (i + 1) + "轮选择后:" + Arrays.toString(arr)); } } } ``` ### Demo06.java — 插入排序 ```java import java.util.Arrays; /** * @author WanJl * @version 1.0 * @title Demo06 * @description 数组排序-插入排序 * @create 2026/7/16 */ public class Demo06 { /* 插入排序 是三种基础排序中最实用的一种,也是使用最广泛的一种O(n²)排序算法, 他的思想非常符合人整理东西的习惯。 把数组分为"已排序"和"未排序"两部分,每次从未排序部分取一个元素, 插入到已排序部分的正确位置。 就类似于打扑克时,抓牌然后插入到手中已排序好的牌中正确的位置。 核心思想: 把数组视为"已排序"(前部分)和未排序(后部分)。 每次从未排序部分中拿取第1个元素, 从后往前在已排序部分中找到合适的位置(比较)并插入 */ public static void main(String[] args) { int[] arr = {1, 456, 782, 5, 8, 4, 598, 1, 46, 68, 465, 74}; //从第2个元素开始,假设 arr[0] 已经是已排序部分 for (int i = 1; i < arr.length; i++) { //获取当前要插入的元素,未排序的第1个 int current = arr[i]; //已排序部分的最后一个索引值 int j = i - 1; //从后往前扫描已经排序部分,寻找插入位置 //如果已排序的元素大于current,就向后移动一位 while (j >= 0 && arr[j] > current) { arr[j + 1] = arr[j]; j--; } //此时 j+1 就是current应该插入到的位置 arr[j + 1] = current; System.out.println("第" + i + "轮插入后:" + Arrays.toString(arr)); } } } ``` ### Demo07.java — Scanner 键盘输入 ```java import java.util.Scanner; /** * @author WanJl * @version 1.0 * @title Demo07 * @description Scanner键盘输入 * @create 2026/7/16 */ public class Demo07 { /* Java中实现键盘输入的标准方式就是 Scanner 类 它是 java.util 包下的一个工具类,可以读取我们在控制台中输入的数据 操作步骤: 1、导包 import java.util.Scanner 2、创建 Scanner 对象 格式:Scanner sc = new Scanner(System.in); 3、调用方法读取数据 |- nextByte() / nextShort() / nextInt() / nextLong() |- nextFloat() / nextDouble() |- next() 读取单个字符串(空格/换行符结束) |- nextLine() 读取整行字符串(换行符结束) |- nextBoolean() 读取布尔值 |- next().charAt(0) 读取字符 4、关闭 Scanner sc.close(); */ public static void main(String[] args) { Scanner sc = new Scanner(System.in); /* Scanner有陷阱: nextInt()后面使用nextLine()的问题 原因:nextInt()读取完数字后,换行符还停留在缓冲区中, 紧接着的nextLine()遇到了换行符就直接结束了 */ // 解决方案:在nextInt()之后调用一次nextLine() 吃掉换行符 int a = sc.nextInt(); sc.nextLine(); // 吃掉换行符 String s = sc.nextLine(); System.out.println(a); System.out.println(s); sc.close(); } } ``` ### Demo08.java — Scanner 练习 ```java import java.util.Scanner; /** * @author WanJl * @version 1.0 * @title Demo08 * @description Scanner练习 * @create 2026/7/16 */ public class Demo08 { public static void main(String[] args) { // 1、创建Scanner对象 Scanner sc = new Scanner(System.in); // 2、读取不同类型的数据 System.out.println("请输入您的姓名:"); String name = sc.next(); System.out.println("请输入您的年龄:"); int age = sc.nextInt(); System.out.println("请输入您的身高(米):"); double height = sc.nextDouble(); System.out.println("您是否是在校学生(true/false):"); boolean isStudent = sc.nextBoolean(); // 3、输出用户信息 System.out.println("\n========用户信息========"); System.out.println("姓名:" + name); System.out.println("年龄:" + age); System.out.println("身高:" + height); System.out.println("在校学生:" + isStudent); // 4、关闭Scanner 释放资源 sc.close(); } } ``` ### Exercises01.java — 首尾对称交换 ```java /** * @author WanJl * @version 1.0 * @title Exercises01 * @description 课堂练习01:首尾对称交换数组元素 * @create 2026/7/16 */ public class Exercises01 { public static void main(String[] args) { int[] arr = {10, 20, 30, 40, 50}; System.out.print("交换前:"); for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); } System.out.println(); System.out.print("交换后:"); //首尾交换 第1位和第5位,第2位和第4位 arr[0] = arr[0] + arr[4]; arr[4] = arr[0] - arr[4]; arr[0] = arr[0] - arr[4]; arr[1] = arr[1] + arr[3]; arr[3] = arr[1] - arr[3]; arr[1] = arr[1] - arr[3]; for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); } } } ``` ### Exercises02.java — 数组循环右移 ```java /** * @author WanJl * @version 1.0 * @title Exercises02 * @description 课堂练习02:数组循环右移 * @create 2026/7/16 */ public class Exercises02 { public static void main(String[] args) { int[] arr = {1, 2, 3, 4, 5}; int k = 2; //右移位数 //如果k大于数组长度,要取模 k = k % arr.length; System.out.print("移动前:"); for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); } System.out.println(); //整体右移k位(循环右移) for (int i = 0; i < k; i++) { //保存最后一个元素 int last = arr[arr.length - 1]; //从后往前,每个元素向后移动1位 for (int j = arr.length - 1; j > 0; j--) { arr[j] = arr[j - 1]; } //把最后1位给第0位 arr[0] = last; } System.out.print("移动后:"); for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); } } } ``` ### Exercises03.java — 数组去重 ```java /** * @author WanJl * @version 1.0 * @title Exercises03 * @description 课堂练习03:数组去重 * @create 2026/7/16 */ public class Exercises03 { public static void main(String[] args) { /* 先统计不重复元素的个数 */ int[] arr = {5, 2, 8, 2, 5, 9, 1, 8, 3}; int count = 0; //个数 for (int i = 0; i < arr.length; i++) { boolean b = false; //设置false,表示先认为当前的数组元素是没有重复的 for (int j = 0; j < i; j++) { //找 arr[i]元素,在arr数组中,有没有重复的元素。 if (arr[i] == arr[j]) { //只要找到一个重复的 b = true; //就把b设置为 true break; //跳出内层循环 } } if (b == false) { //那就说明这个元素是没有重复的,就计数 count++; } } //创建的新的数组长度就是 count int[] newArr = new int[count]; //动态初始化 int index = 0; //作为新数组的索引值 for (int i = 0; i < arr.length; i++) { boolean b = false; //设置false,表示先认为当前的数组元素是没有重复的 //检查arr[i]在它之前是否有出现过 for (int j = 0; j < i; j++) { if (arr[i] == arr[j]) { b = true; break; } } //判断如果我们设置的标记还是false,说明没有重复 if (b == false) { newArr[index] = arr[i]; index++; } } for (int i = 0; i < newArr.length; i++) { System.out.print(newArr[i] + " "); } } } ``` ### HomeWork07.java — 数组求和 ```java /** * @author WanJl * @version 1.0 * @title HomeWork07 * @description 7月15日作业:数组元素求和 * @create 2026/7/16 */ public class HomeWork07 { public static void main(String[] args) { int[] arr = {11, 22, 33, 44, 55}; int sum = 0; for (int i = 0; i < arr.length; i++) { sum += arr[i]; } System.out.println(sum); } } ``` ### HomeWork08.java — 找数组最大值 ```java /** * @author WanJl * @version 1.0 * @title HomeWork08 * @description 7月15日作业:找数组最大值 * @create 2026/7/16 */ public class HomeWork08 { public static void main(String[] args) { int[] arr = {3, 8, 1, 9, 4, 7, 2}; int max = arr[0]; //假设第一个元素是最大值 for (int i = 1; i < arr.length; i++) { if (arr[i] > max) { max = arr[i]; } } System.out.println(max); } } ``` ### HomeWork09.java — 数组倒序输出 ```java /** * @author WanJl * @version 1.0 * @title HomeWork09 * @description 7月15日作业:数组倒序输出 * @create 2026/7/16 */ public class HomeWork09 { public static void main(String[] args) { int[] arr = {1, 2, 3, 4, 5}; for (int i = arr.length - 1; i >= 0; i--) { System.out.println(arr[i]); } } } ``` ### HomeWork10.java — 统计奇偶数个数 ```java /** * @author WanJl * @version 1.0 * @title HomeWork10 * @description 7月15日作业:统计奇偶数个数 * @create 2026/7/16 */ public class HomeWork10 { public static void main(String[] args) { int[] arr = {12, 7, 9, 24, 15, 8, 31, 46}; int count = 0; //奇数个数 for (int i = 0; i < arr.length; i++) { if (arr[i] % 2 != 0) { count++; } } System.out.println("奇数个数:" + count + ",偶数个数:" + (arr.length - count)); } } ``` --- ## 十三、拓展阅读 | 概念 | 说明 | |------|------| | **多维数组** | Java 中多维数组是数组的数组,二维数组的元素是一维数组的引用地址 | | **二维数组遍历** | 外层循环遍历行(`arr.length`),内层循环遍历列(`arr[i].length`) | | **不规则数组** | 二维数组中的每个一维数组长度可以不同 | | **增强 for 循环** | `for(类型 变量 : 数组)` 语法,简化数组/集合遍历,无法获取索引 | | **Arrays 工具类** | `java.util.Arrays` 包含 sort、toString、copyOf、binarySearch 等静态方法 | | **Arrays.sort()** | 对数组进行快速排序(Dual-Pivot Quicksort) | | **Arrays.toString()** | 将数组转换为可读的字符串格式 `[elem1, elem2, ...]` | | **Arrays.copyOf()** | 数组拷贝,可指定新长度,扩展部分补默认值,截断部分丢弃 | | **Arrays.copyOfRange()** | 拷贝数组指定范围 `[from, to)`,包含 from 不包含 to | | **Arrays.binarySearch()** | 二分查找,数组必须先排序,返回索引值(未找到返回负数) | | **冒泡排序** | 重复遍历数组,相邻元素两两比较,大的往后冒,每轮把最大值冒泡到末尾 | | **冒泡排序-外层循环** | 控制冒泡轮数,n 个元素最多需要 n-1 轮 | | **冒泡排序-内层循环** | 在未排序区间 `[0, arr.length-1-i]` 相邻比较,比较范围随轮数递减 | | **冒泡排序-优化** | 引入 `boolean` 标记判断本轮是否有交换,无交换则提前结束排序 | | **冒泡排序-时间复杂度** | 平均 O(n²),最好 O(n)(优化版已有序),最坏 O(n²) | | **冒泡排序-空间复杂度** | O(1),原地排序,不需要额外空间 | | **选择排序** | 每轮从未排序区间选出最小值,与未排序区间的第 1 个元素交换,每轮最多 1 次交换 | | **选择排序-外层循环** | `i` 是当前要确定的位置,范围 `[0, arr.length-2]` | | **选择排序-内层循环** | 在未排序区间 `[i+1, arr.length-1]` 找最小值索引 | | **选择排序-minIndex** | 记录最小元素的索引值,初始为当前位置 `i`,遇更小值则更新 | | **选择排序-交换条件** | `minIndex != i` 时才执行交换,每轮最多交换 1 次 | | **选择排序-时间复杂度** | 平均 O(n²),最好 O(n²),最坏 O(n²) — 始终要遍历全部 | | **选择排序-空间复杂度** | O(1),原地排序 | | **选择排序-稳定性** | ❌ 不稳定(交换可能改变相等元素的相对顺序) | | **冒泡 vs 选择** | 冒泡多次交换,选择最多 1 次;冒泡稳定,选择不稳定 | | **插入排序** | 将未排序的第 1 个元素插入到已排序部分的正确位置,类似打扑克理牌 | | **插入排序-分治思想** | 把数组分为"已排序"和"未排序"两部分 | | **插入排序-外层循环** | `i` 从 1 开始,标记当前要处理的未排序元素 | | **插入排序-current** | 暂存当前要插入的元素值,防止被覆盖 | | **插入排序-内层 while** | 从后往前扫描,比 current 大的元素逐一后移,找到插入位置 | | **插入排序-时间复杂度** | 平均 O(n²),最好 O(n)(数据基本有序时),最坏 O(n²) | | **插入排序-空间复杂度** | O(1),原地排序 | | **插入排序-稳定性** | ✅ 稳定 | | **插入排序-实用性** | 三种基础 O(n²) 排序中最实用,小规模数据首选 | | **三种排序对比** | 实用性:插入 > 选择 > 冒泡;稳定性:冒泡 ✅、插入 ✅、选择 ❌ | | **Scanner 键盘输入** | `java.util.Scanner` 类,用于读取控制台输入 | | **Scanner 导包** | `import java.util.Scanner;` | | **Scanner 创建对象** | `Scanner sc = new Scanner(System.in);` | | **Scanner 关闭** | `sc.close();` 释放资源 | | **Scanner-nextInt()** | 读取 int 类型整数 | | **Scanner-nextDouble()** | 读取 double 类型小数 | | **Scanner-next()** | 读取单个字符串,以空格/换行结束 | | **Scanner-nextLine()** | 读取整行字符串,以换行结束 | | **Scanner-nextBoolean()** | 读取 boolean 类型值(true/false) | | **Scanner 陷阱** | `nextInt()` 后用 `nextLine()` 会读取到空字符串(缓冲区残留换行符) | | **Scanner 陷阱解决** | 在 `nextInt()` 后调用一次 `nextLine()` 吃掉换行符 | | **Scanner 类型不匹配** | 输入类型与接收类型不符时抛出 `InputMismatchException` | | **变量交换-临时变量** | `tmp = a; a = b; b = tmp;` 最直观的交换方式 | | **变量交换-加减法** | `a = a + b; b = a - b; a = a - b;` 不借助第三个变量 | | **数组循环右移** | 从后往前逐位移动,保存最后一位到第一位,外层套循环控制移动次数 | | **数组去重** | 两层循环 + 布尔标记,检查当前元素在它之前是否出现过 | | **打擂法** | 先假设第一个是最大值/最小值,遍历数组遇到更值就更新 | | **数组求和** | `sum += arr[i]` 遍历累加 | | **倒序遍历** | `for (int i = length-1; i >= 0; i--)` | | **奇偶判断** | `n % 2 == 0` 为偶数,`n % 2 != 0` 为奇数 | | **取模优化** | 对循环移动的位数取模,避免无效重复移动 |