命令行记事本需规避Scanner换行符残留、路径非法、编码不一致、流未关闭、保存覆盖错误等问题;应统一用nextLine()、预检路径与权限、用Files工具类安全读写、显式flush/close或try-with-resources。

用 Scanner 读取用户输入时别卡死在换行符上
很多人写命令行记事本第一步就栽在输入逻辑里:用 nextLine() 读内容,但前面用了 next() 或 nextInt(),导致残留的换行符被立刻消费,后续 nextLine() 返回空字符串。这不是 bug,是 Scanner 的缓冲区行为。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 统一用
nextLine()获取所有输入,包括菜单选项(比如输入"1"而不是nextInt()),再手动转类型 - 如果必须混用,每次调用
next()类方法后,补一句scanner.nextLine()清掉换行符 - 避免用
hasNext()做循环条件——它不阻塞,可能反复返回true却读不到新内容;改用hasNextLine()
保存文件前务必检查 File 路径是否合法且可写
直接拼接路径字符串(如 "notes/" + title + ".txt")容易忽略目录不存在、权限不足或文件名含非法字符(Windows 下的 :"/\|?*)等问题,FileWriter 构造时抛 IOException 是常态,不是异常情况。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 用
Paths.get(dir, filename).toFile().getParentFile().mkdirs()确保父目录存在 - 用
filename.replaceAll("[:\"/\\\\|?*]", "_")过滤危险字符(注意 Windows 反斜杠要双写) - 写入前用
file.canWrite()或尝试Files.isWritable(path)预检(JDK 7+ 推荐后者) - 别用
FileWriter(file, true)追加模式存整篇内容——它会把多段编辑变成叠在一起的乱码;每次保存都应覆盖写入
用 Files.readAllLines() 读文件比手动 BufferedReader 更安全
手写 BufferedReader + while ((line = br.readLine()) != null) 容易漏关流、编码错误(默认平台编码,中文 Windows 是 GBK,Linux/macOS 是 UTF-8),或没处理 BOM 头导致首行乱码。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 优先用
Files.readAllLines(path, StandardCharsets.UTF_8),自动处理编码和关闭资源(底层用try-with-resources) - 如果文件超大(>10MB),才考虑逐行流式读取,此时必须指定
StandardCharsets.UTF_8,并用try (BufferedReader br = Files.newBufferedReader(path, UTF_8)) - 读取后立即用
String.join("\n", lines)拼回文本——别用StringBuilder手动拼,避免最后一行少换行
退出前用 System.exit(0) 不够稳妥,得先确保文件已刷盘
直接调 System.exit(0) 可能导致最后保存的缓冲区数据没真正写入磁盘,尤其用 FileWriter 时。Java 不保证 JVM 终止前自动 flush 所有流。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 所有写操作必须显式调用
fw.flush()和fw.close(),或用try-with-resources确保关闭 - 退出逻辑里加一层检查:如果当前文档已修改但未保存,提示“是否保存?(y/n)”,并阻塞等待输入,不能跳过
- 不要依赖
Runtime.getRuntime().addShutdownHook()—— 它在强制 kill(如kill -9)下不执行,且可能和主线程抢资源
public class SimpleNote {
private static String content = "";
private static String currentPath = null;
private static boolean modified = false;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("note> ");
String cmd = scanner.nextLine().trim();
if ("quit".equalsIgnoreCase(cmd)) {
if (modified && !confirmSave(scanner)) break;
System.exit(0);
} else if (cmd.startsWith("save ")) {
String path = cmd.substring(5).trim();
saveToFile(path, scanner);
} else if ("edit".equals(cmd)) {
editContent(scanner);
}
}
}
private static void saveToFile(String path, Scanner scanner) {
try {
Path p = Paths.get(path);
Files.createDirectories(p.getParent());
String safeName = p.getFileName().toString().replaceAll("[<>:\"/\\\\|?*]", "_");
Path safePath = p.getParent().resolve(safeName);
Files.write(safePath, content.getBytes(StandardCharsets.UTF_8));
currentPath = safePath.toString();
modified = false;
System.out.println("Saved to " + safePath);
} catch (Exception e) {
System.err.println("Save failed: " + e.getMessage());
}
}
private static void editContent(Scanner scanner) {
System.out.println("Enter text (press Enter twice to finish):");
StringBuilder sb = new StringBuilder();
while (true) {
String line = scanner.nextLine();
if (line.isEmpty() && sb.length() > 0 && sb.charAt(sb.length() - 1) == '\n') break;
sb.append(line).append("\n");
}
content = sb.toString();
modified = true;
}
private static boolean confirmSave(Scanner scanner) {
System.out.print("Unsaved changes. Save? (y/n): ");
return "y".equalsIgnoreCase(scanner.nextLine().trim());
}}
命令行记事本真正的难点不在功能,而在每一步 IO 操作背后的隐式契约:路径合法性、编码一致性、流生命周期、用户中断响应——这些地方不显眼,但出错时根本不会报“记事本错误”,只会表现为乱码、空文件、卡死或静默丢失数据。










