Java 编程实战知识点总结(掘金版)
本文总结了 Java 集合框架、字符串处理、输入输出等核心知识点,结合多个编程题实战案例,覆盖 HashMap/TreeSet 应用、字符频次统计、反向索引构建、输入输出优化 等关键内容,适合 Java 入门学习者系统梳理。
一、Java 导入语法规则
1. 核心结论
- ❌ 错误写法:import java.*
Java 导入语法要求必须导入到具体子包或类级别,java 是根包前缀,不是可直接导入的包。
- ✅ 正确写法:分包子包导入
import java.util.*; // 导入util包下所有类(HashMap/TreeSet等)
import java.io.*; // 导入io包下所有类(BufferedReader等)
2. 原理
Java 包结构是分层的,import 关键字仅支持 包级别(如 java.util.* ) 或 类级别(如 java.util.HashMap ) 导入,根包无法直接导入。
二、集合框架核心应用
1. HashMap 核心用法
(1)存储键值对映射
- 适用场景:需要快速查找(O (1) 时间复杂度)、去重的场景,如字符频次统计、反向索引。
- 关键 API
-
- put(K key, V value):存入键值对,重复键会覆盖值。
-
- getOrDefault(K key, V defaultValue):获取键对应的值,无则返回默认值(避免空指针)。
-
- 初始化容量优化:处理大规模数据时,指定初始容量减少扩容次数
// 初始容量设为n/2,减少HashMap扩容开销
HashMap<Long, Long> map = new HashMap<>((int) (n / 2));
(2)典型案例:数值映射与累加计算
需求:读取 n 次操作,维护 x→f(x) 的映射,计算 ∑(i×f(x))
long ans = map.getOrDefault(x, 0L); // 无x时默认返回0
sum += i * ans;
map.put(x, y); // 更新映射关系
2. TreeSet 核心用法
(1)特性
- 自动升序排序 + 去重,基于红黑树实现,查询 / 插入时间复杂度 O (log n)。
- 适合需要有序集合的场景,如文档编号的升序存储。
(2)关键 API
- add(E e):添加元素,自动排序并去重。
- getOrDefault(Object key, V defaultValue):结合 HashMap 使用,构建 单词→有序文档编号集合。
3. HashSet 核心用法
(1)特性
- 基于 HashMap 实现,无序、去重,查询时间复杂度 O (1)。
- 适合快速去重场景,如单篇文档的单词去重。
(2)典型案例:单词去重
Set<String> wordsInDoc = new HashSet<>();
for (int i = 1; i < parts.length; i++) {
wordsInDoc.add(parts[i]); // 自动忽略重复单词
}
4. 集合选型对比
| 集合类 | 核心特性 | 适用场景 |
|---|---|---|
| HashMap | 键值对映射、快速查找 | 字符频次统计、反向索引键值存储 |
| TreeSet | 有序、去重 | 文档编号升序存储 |
| HashSet | 无序、去重、高效 | 单词 / 元素去重 |
三、字符串处理核心技巧
1. 字符频次统计(小写字母场景)
(1)最优方案:数组替代集合
小写字母共 26 个,用长度为 26 的数组统计频次,空间复杂度 O (1),效率高于 HashMap。
- 核心原理:char - 'a' 将字符转为 0-25 的数组下标(如 'a'→0,'z'→25)。
- 典型案例 1:判断字母异位词
// 步骤1:长度校验(长度不同直接返回-1)
if (s.length() != c.length()) return -1;
// 步骤2:统计频次
int[] count = new int[26];
for (char ch : s.toCharArray()) count[ch - 'a']++;
for (char ch : c.toCharArray()) count[ch - 'a']--;
// 步骤3:校验频次是否全为0
for (int num : count) {
if (num != 0) return -1;
}
return s.length();
- 典型案例 2:判断能否从 T 构造 S
int[] count = new int[26];
for (char ch : magazine.toCharArray()) count[ch - 'a']++;
for (char ch : ransomNote.toCharArray()) {
count[ch - 'a']--;
if (count[ch - 'a'] < 0) return false; // 字符不足
}
return true;
(2)优势
相比 HashMap,数组无需处理哈希冲突,访问速度更快,适合固定范围的字符统计。
2. 字符串分割与遍历
- 分割:String[] parts = str.split(" "),按空格分割字符串(注意处理首尾空格)。
- 遍历:
-
- 字符遍历:for (char ch : str.toCharArray())
-
- 数组遍历:for (int i = 1; i < parts.length; i++)(跳过第一个元素,如文档的单词数 Li)
3. 无符号数处理
(1)问题场景
当数值超过 Long.MAX_VALUE(2^63-1)时,Long.parseLong() 会抛出 NumberFormatException。
(2)解决方案
使用 Long.parseUnsignedLong() 解析无符号 64 位整数,Long.toUnsignedString() 转为无符号字符串输出
// 解析无符号数
long x = Long.parseUnsignedLong(line[0]);
// 输出无符号数(避免负数显示)
System.out.println(Long.toUnsignedString(sum));
四、输入输出优化与异常处理
1. 输入方式选择
| 输入类 | 适用场景 | 注意事项 |
|---|---|---|
| Scanner | 小规模输入、简单场景 | nextInt() 后需用 nextLine() 吸收换行符 |
| BufferedReader | 大规模输入(如 5×10^5 数据) | 效率高于 Scanner,需处理 IOException |
2. 核心注意点
- 换行符吸收:nextInt() 仅读取数字,会残留换行符,导致后续 nextLine() 读取空字符串
int N = in.nextInt();
in.nextLine(); // 必须添加,吸收换行符
- 空格处理:查询单词需用 trim() 去除首尾空格,避免匹配失败
String queryWord = in.nextLine().trim();
3. 输出格式优化
- 使用 StringBuilder 拼接字符串,避免频繁 + 拼接的性能损耗。
- 去除末尾多余空格:
StringBuilder sb = new StringBuilder();
for (int id : docIds) sb.append(id).append(" ");
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1); // 删除最后一个空格
}
System.out.println(sb.toString());
五、实战编程题核心思路
1. 两数组交集(唯一 + 升序)
- 思路:HashSet 去重 + 遍历筛选 + 数组排序
Set<Integer> set1 = new HashSet<>();
for (int num : nums1) set1.add(num);
Set<Integer> intersection = new HashSet<>();
for (int num : nums2) {
if (set1.contains(num)) intersection.add(num);
}
int[] res = intersection.stream().mapToInt(Integer::intValue).toArray();
Arrays.sort(res);
return res;
2. 单词 - 文档反向索引(高频面试题)
(1)核心需求
构建 单词→包含该单词的文档编号集合,查询时输出升序文档编号。
(2)核心步骤
- 初始化反向索引:Map<String, TreeSet> wordToDocs = new HashMap<>()
- 读取文档并填充索引
for (int docId = 1; docId <= N; docId++) {
String[] parts = in.nextLine().split(" ");
Set<String> wordsInDoc = new HashSet<>();
for (int i = 1; i < parts.length; i++) wordsInDoc.add(parts[i]);
for (String word : wordsInDoc) {
wordToDocs.putIfAbsent(word, new TreeSet<>());
wordToDocs.get(word).add(docId);
}
}
-
- 文档编号从 1 开始(符合题目要求)。
-
- 用 HashSet 对单篇文档单词去重。
-
- 用 TreeSet 存储文档编号,自动升序。
- 处理查询:用 getOrDefault 处理不存在的单词,避免空指针。
3. 数值累加与取模
- 原理:Java long 类型是 64 位有符号整数,累加溢出时自动按 2^64 取模,无需手动计算。
- 场景:大规模数据累加(如 ∑(i×ans_i))。
六、常见错误与避坑指南
| 错误类型 | 错误代码示例 | 修复方案 |
|---|---|---|
| 循环条件错误(漏处理最后一个元素) | for (int docId = 1; docId < N; docId++) | for (int docId = 1; docId <= N; docId++) |
| 包含无效元素(如文档 Li) | for (int i = 0; i < parts.length; i++) | for (int i = 1; i < parts.length; i++) |
| 输出末尾多余空格 | System.out.println(sb.toString()) | 删除最后一个空格后输出 |
| 未处理无符号数 | Long.parseLong(str) | 使用 Long.parseUnsignedLong(str) |
| 换行符残留导致空读 | int N = in.nextInt(); 后无 nextLine() | 添加 in.nextLine() 吸收换行符 |
七、总结
- 集合框架:HashMap 适合键值映射,TreeSet 适合有序去重,HashSet 适合高效去重,根据场景选择最优集合。
- 字符串处理:小写字母频次统计优先用数组,分割字符串注意跳过无效元素,查询单词需去空格。
- 输入输出:大规模数据用 BufferedReader,小规模用 Scanner,注意处理换行符和输出格式。
- 细节优化:初始化集合容量减少扩容,使用 StringBuilder 提升拼接效率,利用自动溢出简化取模逻辑。
以上知识点均来自实战编程题,覆盖 Java 入门核心考点,建议结合代码反复练习,加深理解。