第一阶段的最后一天前三天的 Java 基础、面向对象、IO/NIO、多线程学习让我建立了扎实的编程根基。今天将完成Linux 基础、Java 异常处理、三大经典排序算法和正则表达式的学习并用所学知识完成第一阶段的小项目——AI 自动提取机。Linux 命令行操作比想象中顺手快速排序的分治思想也让我对递归有了更深的理解。四天打基础明天就要进入 Spring 框架的世界了LinuxJava异常处理排序算法正则表达式7.5h今日学习04/70总进度8学习阶段目录一、Linux 基础后端工程师的瑞士军刀二、Java 异常处理让程序优雅地失败三、三大经典排序算法从 O(n²) 到 O(n log n)四、正则表达式文本处理的核武器五、第一阶段项目AI 自动提取机六、今日踩坑记录七、收获总结与学习感悟八、明日学习计划一、Linux 基础后端工程师的瑞士军刀如果说 Java 是后端开发的主武器那 Linux 就是随身携带的瑞士军刀。无论是部署服务、排查问题还是自动化运维Linux 命令行都是必修课。今天我在 Windows 上通过 WSL2Ubuntu 22.04进行了实战练习。1.1 文件与目录操作最常用的一亩三分地文件操作是 Linux 最基础也最常用的技能。以下命令是每天工作中都会高频使用的Bashfile-ops.sh# 目录操作 cd /var/www/html # 切换目录 cd ~ # 回到用户主目录 cd - # 回到上次所在目录 pwd # 显示当前路径 ls -lah # 列出文件-l 详细 -a 全部 -h 人类可读 mkdir -p project/src/main # 递归创建目录 # 文件操作 touch app.log # 创建空文件 cp -r source/ dest/ # 递归复制目录 mv old.txt new.txt # 重命名/移动 rm -rf temp/ # 强制递归删除⚠️ 慎用 # 查看文件内容 cat app.log # 一次性显示全部内容 less app.log # 分页查看支持搜索按 q 退出 head -n 20 app.log # 看前 20 行 tail -f app.log # 实时追踪日志末尾排查问题神器tail -f 排查线上问题的标准流程线上服务出问题时我常用的组合拳是tail -f app.log | grep ERROR实时过滤错误日志配合ps aux | grep java查看进程状态再用netstat -tlnp检查端口占用。这三个命令能解决 80% 的线上排查场景。1.2 权限管理读懂 rwx 的秘密Linux 权限模型是安全的基础。每个文件/目录都有三组权限所有者owner、所属组group、其他人others每组包含读r4、写w2、执行x1三种权限。Bash# 查看权限 ls -l script.sh # -rwxr-xr-- 1 user group 1024 Jul 3 10:00 script.sh # ↑↑↑ ↑↑↑ ↑↑↑ # 所有者 组 其他人 chmod 755 script.sh # rwxr-xr-x所有者全权限组和其他人读执行 chmod ux script.sh # 给所有者添加执行权限 chown www:www data/ # 修改所有者和所属组 # 实际场景部署 Java 应用 chmod x deploy.sh # 让脚本可执行 chmod 600 config.yml # 配置文件只有所有者能读写保护数据库密码1.3 进程管理与系统监控了解系统运行状态是运维的基本功。当服务卡死或者机器变慢时这些命令能帮你快速定位问题。Bash# 进程管理 ps aux | grep java # 查找 Java 进程 kill -9 1234 # 强制终止 PID 为 1234 的进程 killall java # 终止所有 Java 进程⚠️ 生产环境慎用 # 系统监控 top # 实时查看 CPU、内存占用按 q 退出 df -h # 查看磁盘空间h 表示 human-readable free -h # 查看内存使用情况1.4 Shell 脚本入门自动化重复的体力劳动Shell 脚本是 Linux 自动化的核心。学会写脚本后一键部署、定时备份、日志清理这些重复工作都可以交给机器完成。Bashdeploy.sh#!/bin/bash # AI 自动提取机部署脚本 APP_NAMEai-extractor JAR_FILEai-extractor-1.0.jar LOG_FILElogs/app.log # 检查日志目录 if [ ! -d logs ]; then mkdir -p logs echo 创建日志目录 fi # 查找并停止旧进程 PID$(ps aux | grep $JAR_FILE | grep -v grep | awk {print $2}) if [ -n $PID ]; then echo 停止旧进程: $PID kill -9 $PID sleep 2 fi # 启动新进程 echo 启动 $APP_NAME... nohup java -jar $JAR_FILE $LOG_FILE 21 # 检查启动状态 sleep 3 NEW_PID$(ps aux | grep $JAR_FILE | grep -v grep | awk {print $2}) if [ -n $NEW_PID ]; then echo 启动成功PID: $NEW_PID echo 日志: tail -f $LOG_FILE else echo 启动失败请检查日志 exit 1 fiShell 脚本核心语法速记#!/bin/bash是指定解释器$变量名取变量值if [ 条件 ]; then ... fi是条件判断-d判断目录是否存在-n判断字符串非空$?是上条命令的退出码nohup ... 让程序后台运行且不受终端关闭影响。***二、Java 异常处理让程序优雅地失败异常处理是写出健壮代码的关键。一个优秀的程序员不仅要考虑正常流程怎么走更要思考出错了怎么办。Java 的异常体系设计得非常清晰理解它能让你的代码更加可靠。2.1 异常类体系Throwable 的家族树Java 中所有的异常都继承自java.lang.Throwable它有两个直接子类Error严重系统错误如 OutOfMemoryError、StackOverflowError程序通常无法恢复不需要捕获Exception程序可以处理的异常又分为受检异常Checked Exception编译器强制要求处理如IOException、SQLException非受检异常Unchecked Exception运行时异常如NullPointerException、ArrayIndexOutOfBoundsException2.2 try-catch-finally异常处理的三板斧JavaExceptionDemo.javaimport java.io.*; public class ExceptionDemo { public static void main(String[] args) { // 经典结构try-catch-finally BufferedReader reader null; try { reader new BufferedReader(new FileReader(data.txt)); String line reader.readLine(); System.out.println(line); } catch (FileNotFoundException e) { // 具体异常先捕获 System.err.println(文件不存在: e.getMessage()); } catch (IOException e) { // 父类异常后捕获 System.err.println(读取失败: e.getMessage()); } finally { // 无论是否异常都会执行释放资源 if (reader ! null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } } }try-with-resourcesJava 7 后的优雅写法上面的 finally 块手动关闭资源非常繁琐。Java 7 引入的 try-with-resources 可以自动关闭实现了AutoCloseable接口的资源JavaTryWithResources.javapublic class TryWithResources { public static void main(String[] args) { // 资源会在 try 结束时自动关闭无需 finally try (BufferedReader reader new BufferedReader( new FileReader(data.txt))) { String line; while ((line reader.readLine()) ! null) { System.out.println(line); } } catch (IOException e) { System.err.println(操作失败: e.getMessage()); } // reader 已自动关闭省心又安全 } }2.3 自定义异常让业务错误更语义化当系统内置异常无法准确表达业务错误时应该自定义异常。好的异常命名能让代码自我解释。JavaBizException.java// 自定义业务异常继承 RuntimeException非受检 public class BizException extends RuntimeException { private final String errorCode; public BizException(String errorCode, String message) { super(message); this.errorCode errorCode; } public String getErrorCode() { return errorCode; } } // 使用场景 public class UserService { public User findById(Long id) { if (id null || id 0) { throw new BizException(E1001, 用户ID非法: id); } // ... 查询逻辑 User user userDao.findById(id); if (user null) { throw new BizException(E1002, 用户不存在: id); } return user; } }异常处理最佳实践1.不要吞掉异常空的 catch 块是代码的噩梦出了问题根本无法排查。2.优先使用标准异常如IllegalArgumentException、IllegalStateException避免自定义过多异常类。3.异常信息要具体操作失败不如用户 12345 的订单 67890 支付超时有价值。4.不要用来做流程控制异常处理有性能开销预期内的分支用 if/else 更合适。***三、三大经典排序算法从 O(n²) 到 O(n log n)排序算法是数据结构的基础也是面试中的常客。今天学习了三种经典算法选择排序、插入排序和快速排序。它们分别代表了不同的设计思想——从简单粗暴的遍历到借位插入的巧妙再到分治递归的优雅。3.1 选择排序每次选出最小的选择排序的思想非常直观每一轮从未排序区间选出最小元素放到已排序区间的末尾。JavaSelectionSort.javapublic class SelectionSort { public static void sort(int[] arr) { int n arr.length; for (int i 0; i n - 1; i) { int minIdx i; // 在未排序区间找最小值 for (int j i 1; j n; j) { if (arr[j] arr[minIdx]) { minIdx j; } } // 交换到已排序区间末尾 if (minIdx ! i) { int temp arr[i]; arr[i] arr[minIdx]; arr[minIdx] temp; } } } public static void main(String[] args) { int[] arr {64, 25, 12, 22, 11}; sort(arr); // 结果: [11, 12, 22, 25, 64] System.out.println(java.util.Arrays.toString(arr)); } }选择排序分析时间复杂度O(n²)两层循环空间复杂度O(1)原地排序稳定性不稳定交换可能改变相同元素的相对顺序。优点是实现简单、交换次数少最多 n-1 次缺点是无论数据是否有序都要完整比较。3.2 插入排序像整理扑克牌一样插入排序模拟了整理扑克牌的过程左手持已排序的牌右手每次抽一张插入到左手正确的位置。JavaInsertionSort.javapublic class InsertionSort { public static void sort(int[] arr) { for (int i 1; i arr.length; i) { int key arr[i]; // 右手抽出的牌 int j i - 1; // 左手已排序的牌从右往左比较比 key 大的后移 while (j 0 arr[j] key) { arr[j 1] arr[j]; j--; } arr[j 1] key; // 插入到正确位置 } } public static void main(String[] args) { int[] arr {23, 12, 34, 11, 27}; sort(arr); // 结果: [11, 12, 23, 27, 34] System.out.println(java.util.Arrays.toString(arr)); } }插入排序的隐藏优势插入排序的时间复杂度也是 O(n²)但当数据接近有序时时间复杂度接近 O(n)。这个特性让它成为高级排序算法如快速排序、归并排序的兜底策略——当子数组很小的时候切换到插入排序比继续递归更高效。Java 的Arrays.sort()内部就使用了这种优化。3.3 快速排序分治思想的经典范例快速排序是实际应用中最广泛的高效排序算法。它的核心思想是分治选择一个基准值pivot将数组分为小于 pivot和大于 pivot的两部分然后递归地对两部分进行同样的操作。JavaQuickSort.javapublic class QuickSort { public static void sort(int[] arr, int left, int right) { if (left right) return; int pivotIndex partition(arr, left, right); sort(arr, left, pivotIndex - 1); // 递归排序左半部分 sort(arr, pivotIndex 1, right); // 递归排序右半部分 } // 分区返回 pivot 的最终位置 private static int partition(int[] arr, int left, int right) { int pivot arr[right]; // 选择最右元素作为基准 int i left; // i 指向小于 pivot 区域的下一个位置 for (int j left; j right; j) { if (arr[j] pivot) { swap(arr, i, j); i; } } swap(arr, i, right); // pivot 放到正确位置 return i; } private static void swap(int[] arr, int i, int j) { int temp arr[i]; arr[i] arr[j]; arr[j] temp; } public static void main(String[] args) { int[] arr {33, 10, 55, 71, 23, 8, 42}; sort(arr, 0, arr.length - 1); // 结果: [8, 10, 23, 33, 42, 55, 71] System.out.println(java.util.Arrays.toString(arr)); } }快速排序分析平均时间复杂度O(n log n)最坏时间复杂度O(n²)数据已有序且每次选最值作为 pivot空间复杂度O(log n)递归栈空间。实际场景中快速排序通常配合三数取中选 pivot 和小数组切换插入排序的优化策略。***四、正则表达式文本处理的核武器正则表达式Regular Expression是处理字符串的终极武器。无论是日志解析、数据校验、文本提取还是替换掌握正则都能让你事半功倍。今天结合第一阶段的 AI 自动提取机项目学习了 Java 中 Pattern 和 Matcher 的使用。4.1 核心语法速查元字符含义示例.任意单个字符a.c 匹配 abc, a1c*前一个字符出现 0 次或多次ab*c 匹配 ac, abc, abbc前一个字符出现 1 次或多次abc 匹配 abc, abbc?前一个字符出现 0 次或 1 次ab?c 匹配 ac, abc\d数字 [0-9]\d{3} 匹配 123\w单词字符 [a-zA-Z0-9_]\w 匹配变量名\s空白字符\s 匹配空格、Tab^ $行首和行尾^\d$ 匹配纯数字行()分组捕获(\d{4})-(\d{2}) 提取年月4.2 Java Pattern/Matcher 实战JavaRegexExtractor.javaimport java.util.regex.*; import java.util.*; public class RegexExtractor { // 预编译正则提升性能可复用 private static final Pattern EMAIL_PATTERN Pattern.compile( [a-zA-Z0-9._%-][a-zA-Z0-9.-]\\.[a-zA-Z]{2,}); private static final Pattern PHONE_PATTERN Pattern.compile( 1[3-9]\\d{9}); private static final Pattern IP_PATTERN Pattern.compile( \\b(?:25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)){3}\\b); public static ListString extractEmails(String text) { ListString results new ArrayList(); Matcher matcher EMAIL_PATTERN.matcher(text); while (matcher.find()) { results.add(matcher.group()); } return results; } public static boolean isValidPhone(String phone) { return PHONE_PATTERN.matcher(phone).matches(); } public static void main(String[] args) { String text 联系我zhangsanexample.com 或 lisicompany.cn 电话 13800138000服务器 IP 192.168.1.1; System.out.println(邮箱: extractEmails(text)); // 邮箱: [zhangsanexample.com, lisicompany.cn] System.out.println(手机号合法? isValidPhone(13800138000)); // true // 使用分组提取结构化数据 Pattern datePattern Pattern.compile((\\d{4})-(\\d{2})-(\\d{2})); Matcher m datePattern.matcher(发布日期: 2026-07-03); if (m.find()) { System.out.println(年: m.group(1)); System.out.println(月: m.group(2)); System.out.println(日: m.group(3)); } } }正则表达式性能优化1.预编译 Pattern如果正则会被多次使用一定用Pattern.compile()预编译避免重复解析开销。2.避免贪婪回溯如(.*)在大文本上可能导致灾难性回溯尽量使用具体字符类或量词限定。3.敏感操作加超时对用户输入的正则要限制执行时间防止 ReDoS 攻击。***五、第一阶段项目AI 自动提取机第一阶段的收尾项目——AI 自动提取机。这是一个命令行工具能够从文本中自动提取邮箱、手机号、IP 地址、日期等信息并支持将结果导出为 JSON 格式。项目整合了本周学习的 IO 流、正则表达式、异常处理、集合框架等知识。5.1 项目结构Text项目目录ai-extractor/ ├── src/ │ └── com/ │ └── extractor/ │ ├── Main.java # 程序入口 │ ├── TextExtractor.java # 核心提取引擎 │ ├── ExtractResult.java # 结果数据对象 │ └── PatternRegistry.java # 正则模式注册中心 ├── input/ │ └── sample.txt # 测试文本 └── output/ └── result.json # 导出结果5.2 核心代码JavaTextExtractor.javapackage com.extractor; import java.io.*; import java.nio.file.*; import java.util.*; import java.util.regex.*; public class TextExtractor { private final MapString, Pattern patterns new LinkedHashMap(); public TextExtractor() { // 注册内置提取规则 register(email, [a-zA-Z0-9._%-][a-zA-Z0-9.-]\\.[a-zA-Z]{2,}); register(phone, 1[3-9]\\d{9}); register(ip, \\b(?:25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)){3}\\b); register(date, \\d{4}-\\d{2}-\\d{2}); register(url, https?://[a-zA-Z0-9.-](?:/[^\\s]*)?); } public void register(String name, String regex) { patterns.put(name, Pattern.compile(regex)); } public ExtractResult extract(String text) { ExtractResult result new ExtractResult(); for (Map.EntryString, Pattern entry : patterns.entrySet()) { String type entry.getKey(); Matcher matcher entry.getValue().matcher(text); while (matcher.find()) { result.add(type, matcher.group()); } } return result; } public ExtractResult extractFromFile(String filePath) throws IOException { String content Files.readString(Path.of(filePath)); return extract(content); } }JavaExtractResult.javapackage com.extractor; import java.util.*; public class ExtractResult { private final MapString, ListString data new LinkedHashMap(); public void add(String type, String value) { data.computeIfAbsent(type, k - new ArrayList()).add(value); } public ListString get(String type) { return data.getOrDefault(type, Collections.emptyList()); } public String toJson() { StringBuilder sb new StringBuilder({\n); for (Map.EntryString, ListString entry : data.entrySet()) { sb.append( \).append(entry.getKey()).append(\: ); sb.append(entry.getValue()).append(,\n); } if (!data.isEmpty()) sb.setLength(sb.length() - 2); // 去掉最后的逗号 sb.append(\n}); return sb.toString(); } Override public String toString() { StringBuilder sb new StringBuilder(); for (Map.EntryString, ListString entry : data.entrySet()) { sb.append([).append(entry.getKey()).append(] ); sb.append(发现 ).append(entry.getValue().size()).append( 条: ); sb.append(entry.getValue()).append(\n); } return sb.toString(); } }JavaMain.javapackage com.extractor; import java.io.*; import java.nio.file.*; public class Main { public static void main(String[] args) { if (args.length 0) { System.out.println(用法: java com.extractor.Main 输入文件路径); System.exit(1); } String inputPath args[0]; String outputPath output/result.json; try { TextExtractor extractor new TextExtractor(); ExtractResult result extractor.extractFromFile(inputPath); System.out.println( 提取结果 ); System.out.println(result); // 导出 JSON Files.createDirectories(Path.of(output)); Files.writeString(Path.of(outputPath), result.toJson()); System.out.println(已导出到: outputPath); } catch (IOException e) { System.err.println(文件操作失败: e.getMessage()); System.exit(2); } } }5.3 运行效果Bash$ javac -d out src/com/extractor/*.java $ java -cp out com.extractor.Main input/sample.txt 提取结果 [email] 发现 3 条: [aliceexample.com, bobcompany.cn, charliegmail.com] [phone] 发现 2 条: [13800138000, 15912345678] [ip] 发现 1 条: [192.168.1.100] [date] 发现 2 条: [2026-07-03, 2025-12-25] [url] 发现 1 条: [https://github.com/example] 已导出到: output/result.json项目扩展思路1.配置文件化将正则规则放到 YAML/JSON 配置文件中无需改代码即可扩展。2.多线程处理对大文件分块用多线程并行提取。3.对接大模型 API对无法正则匹配的复杂信息如地址、人名调用 LLM API 进行智能提取。4.Web 界面用 Spring Boot 提供 REST API Vue3 前端做成在线工具。***六、今日踩坑记录坑 1Shell 脚本在 Windows Git Bash 中运行报错写完 deploy.sh 后直接在 Windows Git Bash 中运行结果#!/bin/bash后面的命令执行正常但[[ ]]语法报错 syntax error。原因Git Bash 默认使用 sh 而不是 bash而[[ ]]是 bash 的扩展语法。解决方案显式用bash deploy.sh运行或者将脚本中的[[ ]]改为兼容 sh 的[ ]。坑 2正则表达式在 Java 字符串中需要双重转义写 IP 地址匹配正则时源代码写的是\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b结果编译报错。原因Java 字符串中\本身就是转义符正则中的\d在 Java 字符串里要写成\\d\.要写成\\.。解决方案Java 正则需要双重转义或者使用Pattern.quote()辅助。建议在 regex101.com 先测试好正则再翻译成 Java 字符串。Java// 错误Java 字符串中 \d 会被解析为转义序列编译报错 Pattern p Pattern.compile(\d); // 正确\\d 在字符串层面变成 \d正则层面解析为数字 Pattern p Pattern.compile(\\d); // 验证\\ 在字符串里变成一个反斜杠\\d 变成 \d坑 3快速排序递归导致栈溢出测试快速排序时对一个已有序的 10000 个元素的数组排序抛出了StackOverflowError。原因每次选最右元素作为 pivot有序数组会导致分区极度不平衡递归深度达到 O(n)。解决方案采用三数取中法选择 pivot取 left、mid、right 的中位数或者随机选择 pivot保证分区相对均衡。Java// 三数取中优化 pivot 选择 private static int medianOfThree(int[] arr, int left, int right) { int mid left (right - left) / 2; if (arr[left] arr[mid]) swap(arr, left, mid); if (arr[left] arr[right]) swap(arr, left, right); if (arr[mid] arr[right]) swap(arr, mid, right); swap(arr, mid, right - 1); // 将中位数放到 right-1 return arr[right - 1]; }坑 4try-with-resources 中资源初始化异常难排查使用 try-with-resources 同时打开多个资源时如果第二个资源初始化失败第一个资源已经打开但异常信息只显示第二个资源的错误第一个资源的真实错误被抑制了。解决方案Java 7 中异常对象有getSuppressed()方法可以获取被抑制的异常数组。排查问题时记得打印 suppressed exceptions。坑 5Linux 路径大小写敏感导致文件找不到在 Windows 上开发时代码中写的是new FileReader(Input/Sample.txt)在 Windows 上运行正常因为 Windows 文件系统大小写不敏感但部署到 Linux 服务器后报 FileNotFoundException。原因Linux 文件系统是大小写敏感的。解决方案养成统一使用小写路径的习惯或者在项目根目录放一份路径校验脚本。***七、收获总结与学习感悟今日收获Linux 基础掌握了文件操作、权限管理、进程监控、Shell 脚本编写能够独立完成服务部署和日志排查。Java 异常处理深入理解了异常类体系、try-catch-finally 机制、try-with-resources 语法以及自定义异常的最佳实践。排序算法动手实现了选择排序、插入排序和快速排序理解了它们的时间复杂度差异和适用场景特别是分治思想的运用。正则表达式掌握了核心元字符和 Java Pattern/Matcher API能够独立完成邮箱、手机号、IP 等常见信息的提取。项目实战完成了 AI 自动提取机项目将 IO 流、正则表达式、集合框架、异常处理等知识点融会贯通。第一阶段总结Day 01-04四天的基础阶段学习覆盖了成为一名 Java 后端工程师的核心根基Java 基础语法变量、数据类型、流程控制、数组面向对象编程封装、继承、多态、抽象类与接口集合框架List、Set、Map 体系及底层原理IO/NIO字节流、字符流、Channel、Buffer多线程Thread/Runnable、线程状态、synchronized/Lock网络编程TCP 三次握手、Socket 编程MySQLSQL 语法、约束、JOIN、事务、索引Linux常用命令、权限、Shell 脚本算法冒泡排序、选择排序、插入排序、快速排序、二分查找工具正则表达式、异常处理学习感悟完成 AI 自动提取机项目的时候我突然意识到编程不是背语法而是把一个个独立的知识点像积木一样搭建成能解决实际问题的工具。正则表达式单独学的时候觉得枯燥但当它从文本中精准抓取出第一个邮箱地址时那种成就感无可替代。快速排序的递归分治也让我对复杂问题分解为小问题有了更深的体感。四天的高强度学习每天 7 小时笔记本记了 50 多页。虽然累但每天睡前回顾时都能清晰感受到自己的成长。明天就要进入第二阶段——AI 应用编程进阶Spring 框架的大门正在打开。保持节奏继续前进***八、明日学习计划Day 05 · 2026-07-04周六第二阶段启动AI 应用编程进阶RESTful API 设计接口规范、HTTP 方法、状态码、版本控制接口安全鉴权机制、JWT Token、接口签名防篡改接口文档Swagger/OpenAPI 规范、文档自动化生成Spring 框架入门IoC 容器、依赖注入、Bean 生命周期