Skip to content

System.out / System.err 控制台输出

你有没有好奇过:为什么 Java 要设计 System.outSystem.err 两个东西?它们看起来一模一样。

答案藏在历史里。

System.in / System.out / System.err 的历史

1995 年,Java 诞生之初就定义了三个标准流:

System.in   → 标准输入(stdin)  → 默认连接键盘
System.out  → 标准输出(stdout) → 默认连接显示器
System.err  → 标准错误(stderr)→ 默认连接显示器

这三个是 Java 对操作系统标准流的封装。它们在 JVM 启动时就被初始化,永远存在。

System.out vs System.err

java
System.out.println("普通输出");  // 标准输出(stdout)
System.err.println("错误输出");   // 标准错误(stderr)
对比System.outSystem.err
用途普通输出信息错误、警告信息
缓冲区有缓冲有缓冲
默认目标控制台控制台
重定向可以重定向可以单独重定向

在 IDE 中,outerr 通常混在一起显示。但在命令行中,可以通过重定向分开:

bash
java MyApp > output.txt 2> error.txt    # out 和 err 分开
java MyApp > all.txt 2>&1               # err 合并到 out

System.in 标准输入

java
// 读取一行输入
Scanner scanner = new Scanner(System.in);
System.out.print("请输入姓名: ");
String name = scanner.nextLine();

// 更底层的读取
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String line = reader.readLine();

重定向输出到文件

重定向 System.out

java
// 保存原始 System.out
PrintStream originalOut = System.out;

// 重定向到文件
try (PrintStream fileOut = new PrintStream("app.log")) {
    System.setOut(fileOut);
    System.out.println("这条日志会写进文件");
}

// 恢复原始 System.out
System.setOut(originalOut);
System.out.println("这条显示在控制台");

重定向 System.err

java
try (PrintStream errorOut = new PrintStream("errors.log")) {
    System.setErr(errorOut);
    System.err.println("错误信息写进 errors.log");
}

同时重定向 out 和 err

java
try (
    PrintStream outLog = new PrintStream("stdout.log");
    PrintStream errLog = new PrintStream("stderr.log")
) {
    System.setOut(outLog);
    System.setErr(errLog);
    System.out.println("标准输出");
    System.err.println("错误输出");
}

自定义日志工具

java
public class Logger {
    private static PrintStream out = System.out;
    private static PrintStream err = System.err;
    private static Level level = Level.INFO;

    public enum Level { DEBUG, INFO, WARN, ERROR }

    public static void debug(String msg) {
        if (level.compareTo(Level.DEBUG) <= 0) {
            out.println("[DEBUG] " + msg);
        }
    }

    public static void info(String msg) {
        if (level.compareTo(Level.INFO) <= 0) {
            out.println("[INFO] " + msg);
        }
    }

    public static void error(String msg) {
        if (level.compareTo(Level.ERROR) <= 0) {
            err.println("[ERROR] " + msg);
        }
    }

    // 重定向到文件
    public static void redirectToFile(String outFile, String errFile)
            throws FileNotFoundException {
        System.setOut(new PrintStream(outFile));
        System.setErr(new PrintStream(errFile));
    }
}

System.console() 的安全读取

System.console() 可以读取密码等敏感信息,输入不会显示在屏幕上:

java
Console console = System.console();
if (console != null) {
    // 读取普通输入
    String username = console.readLine("用户名: ");

    // 读取密码(不显示输入)
    char[] password = console.readPassword("密码: ");
    // 输入不显示在终端,返回 char[] 而不是 String
    // 使用完后手动清零,防止内存泄露
    Arrays.fill(password, '0');
}

常见问题

IDE 中 out 和 err 混在一起

java
// 在 IDE 中运行,以下两条输出可能颜色不同(err 可能是红色)
System.out.println("普通消息");
System.err.println("错误消息");
// 但它们通常显示在同一个控制台窗口

缓冲区问题

System.outSystem.err 都有缓冲区,可能导致日志顺序错乱:

java
System.out.print("请输入: ");  // 没有换行,可能还在缓冲区
System.err.println("错误!");   // 先显示出来

// 解决方案:使用 flush
System.out.print("请输入: ");
System.out.flush();

记住这个设计意图

System.out 用于正常输出,System.err 用于错误信息。 生产环境建议用专业日志框架(SLF4J + Logback)。

基于 VitePress 构建