system.in.read() 读不到换行后输入是因为它逐字节读取,将换行符(\n或\r\n)作为有效输入返回,导致后续read()立即读到10或13;需手动跳过或用stringbuilder累积至换行符再截断。

为什么 System.in.read() 读不到换行后的输入?
因为 System.in.read() 只读单字节,遇到 \n 或 \r\n 会照常返回其 ASCII 值(10 或 13),不会自动跳过或缓冲。你按回车,它就停在换行符上,下一次 read() 立刻返回 10——看起来像“没读到内容”,其实是读到了换行符本身。
- 用
System.in.read()做行输入必须手动跳过\r和\n,否则连续调用会卡在换行符上 - 更稳妥的做法是配合
StringBuilder逐字节累积,直到遇到\n(Unix)或\r\n(Windows),再截断并清空 - 注意 Windows 下记事本保存的文本可能含
\r\n,而 IDE 控制台通常只发\n,兼容处理时建议检测\r后跟\n,或单独忽略\r
用 StringBuilder 拼接字符流时,append(int) 和 append(char) 有什么区别?
StringBuilder.append(int) 会把数字当 Unicode 码点转成字符串(比如 append(65) → "65"),而 append((char)65) 才是真正拼 "A"。从 System.in.read() 得到的是 int 类型的字节值,直接 append(c) 会拼出数字字符串,不是你想要的字符。
- 必须显式强转:
sb.append((char) c),否则所有字母都会变成三位数字符串 - 如果输入含非 ASCII 字符(如中文),
System.in.read()无法正确读取多字节 UTF-8 编码,此时应改用InputStreamReader包装,但控制台环境默认编码不统一,简单工具中建议限定 ASCII 输入 -
StringBuilder初始容量设为 128 足够应付多数单行编辑场景,避免频繁扩容
如何安全终止控制台输入而不依赖 Ctrl+C?
Ctrl+C 会直接中断 JVM,无法执行清理逻辑(比如保存缓冲区、释放资源)。应该约定一个退出指令,比如输入 :q 或空行,并在主循环中识别后跳出。
- 每次读完一行后,用
line.trim().equals(":q")判断退出,比检查null更可靠(readLine()在流关闭时才返回null) - 若坚持用
read()+StringBuilder,需在每次检测到\n后,将sb.toString().trim()拿去匹配退出命令,再调用sb.setLength(0)清空 - 不要用
System.exit(0)强退——它绕过finally和 shutdown hook,缓冲区里还没写出去的内容就丢了
为什么不用 Scanner?它不是更简单吗?
是更简单,但 Scanner 内部做了大量解析(正则匹配、类型转换、分隔符跳过),对纯字符流拼接场景属于过度封装。尤其当你要精确控制每个字节行为(比如实现撤销、光标移动、部分重绘)时,Scanner.nextLine() 会吃掉换行符且不可逆,而 System.in.read() + StringBuilder 让你能看到每一个输入事件。
-
Scanner在遇到非法输入(如期待 int 却输字母)时抛InputMismatchException,需要额外 try-catch;而字节流方式完全由你决定何为“合法” -
Scanner默认使用平台默认编码解析字节,若终端编码和 JVM 不一致(如 Windows cmd 是 GBK,JVM 启动未设-Dfile.encoding=UTF-8),中文会乱码;而字节流+手动转char至少能保证 ASCII 安全 - 真要加高级功能(比如行内编辑、退格响应),你迟早得绕开
Scanner回到底层InputStream,早换早省事
最易被忽略的是:控制台输入没有“当前光标位置”概念,System.in 是单向流,一旦读过就无法倒带。所以所有编辑操作(删前一个字符、插入、撤回)都必须靠 StringBuilder 在内存里模拟,而不是指望 IO 层支持。










