为什么在Java中打印"B"比"#"慢?示例分析

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么"B"变慢,而"#"飞快?Java经典问题的解析



如果你曾经在Java中编写过简单的循环来向控制台输出字符,你可能会注意到一个奇怪的现象:输出小写字母'B'可能明显比输出井号'#'慢。这个效果曾经在Stack Overflow上引起轰动——该问题获得了数千次浏览和点赞。今天我们将分析这一现象背后的原因,以及它与Java、控制台输入输出和缓冲的内部机制有何关联。



实验设置

假设我们运行两个循环,每个循环向控制台打印100,000个字符:一个循环输出'#',另一个输出'B'。乍一看,操作是相同的。然而在许多系统上(尤其是Windows),第一个循环大约在100毫秒内完成,而第二个循环则需要几秒钟。为什么?

让我们用Java重现这个现象。



测试代码

public class SlowPrintTest {    public static void main(String[] args) {        long start = System.nanoTime();        for (int i = 0; i < 100_000; i++) {            System.out.print("#");        }        long end = System.nanoTime();        System.out.println("'#'的时间: " + (end - start) / 1_000_000 + " ms");

start = System.nanoTime(); for (int i = 0; i < 100_000; i++) { System.out.print("B"); } end = System.nanoTime(); System.out.println("'B'的时间: " + (end - start) / 1_000_000 + " ms"); }}


在终端中运行这段代码,而不是在IDE中(例如,通过java SlowPrintTest)。结果会让你惊讶。在Windows的CMD中,差异可能达到10到50倍。



为什么会这样?根源在于字体渲染

问题不在于System.out.print()本身,也不在于JVM。主要原因在于操作系统和终端如何处理字符输出。Java中的System.out是标准输出流(stdout)的封装。数据被传递到控制台,但屏幕输出的速度取决于:

  • 字符宽度——许多旧式控制台(例如Windows的CMD)使用等宽字体,但字符'B'可能需要比'#'更复杂的渲染。
  • 屏幕刷新——控制台可能在输出每个字符时执行完整的行重绘,而不仅仅是添加它。
  • 终端缓冲——某些终端会按块缓冲输出,但如果字符需要计算字形宽度,这会减慢速度。


这种效果在Windows命令提示符(CMD)使用位图字体时最为明显。当你打印'B'时,控制台尝试以图形模式显示该字符,而对于'#',则使用简化方法。在现代终端(Windows Terminal、iTerm2、Alacritty)中,差异可能不那么明显或根本不存在。



Java中缓冲的作用

输出优化的一个重要方面是使用缓冲流。默认情况下,System.out是不缓冲的(更准确地说,在JVM级别有缓冲,但缓冲区很小)。每次调用print()都可能触发系统调用write(),这是非常昂贵的。如果我们通过BufferedWriter添加缓冲,性能会提升,但字符之间的差异仍然存在——这取决于终端。



// 带缓冲的示例BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));for (int i = 0; i < 100_000; i++) {    bw.write("#");}bw.flush();


即使有缓冲,如果终端渲染'B'很慢,总时间仍然会增加。一

博客

书籍推荐