一、方法定义

System 类中的这三个方法用于重新定向标准输入、输出和错误流:

public static void setIn(InputStream in)
public static void setOut(PrintStream out)
public static void setErr(PrintStream err)
  • 所属类java.lang.System
  • 静态方法:直接通过 System 类调用
  • 参数类型
    • setIn() 接收 InputStream 类型
    • setOut()setErr() 接收 PrintStream 类型

二、功能说明

方法 原始默认目标 功能
setIn() 键盘输入(System.in 重定向标准输入源,可从文件、字符串、网络等读取
setOut() 控制台输出(System.out 重定向标准输出目标,可输出到文件、日志、字符串等
setErr() 控制台错误输出(System.err 重定向错误输出目标,便于错误日志集中管理

⚠️ 注意:这些方法修改的是 JVM 全局的输入/输出流,影响整个应用。


三、示例代码

1. 重定向输出到文件

import java.io.*;

public class RedirectExample {
    public static void main(String[] args) throws IOException {
        // 重定向标准输出到文件
        PrintStream fileOut = new PrintStream(new FileOutputStream("output.txt"));
        System.setOut(fileOut);

        // 重定向错误输出到另一个文件
        PrintStream errOut = new PrintStream(new FileOutputStream("error.log"));
        System.setErr(errOut);

        System.out.println("这条消息会写入 output.txt");
        System.err.println("这条错误消息会写入 error.log");

        fileOut.close();
        errOut.close();
    }
}

2. 从字符串输入模拟用户输入

import java.io.*;
import java.util.Scanner;

public class SimulateInput {
    public static void main(String[] args) {
        String simulatedInput = "Alice\n25\n";
        InputStream mockIn = new ByteArrayInputStream(simulatedInput.getBytes());
        System.setIn(mockIn);

        Scanner scanner = new Scanner(System.in);
        String name = scanner.nextLine();
        int age = scanner.nextInt();

        System.out.println("Name: " + name + ", Age: " + age);
        // 输出: Name: Alice, Age: 25
    }
}

3. 捕获输出用于测试或日志

import java.io.*;

public class CaptureOutput {
    public static void main(String[] args) throws IOException {
        // 捕获标准输出
        ByteArrayOutputStream capturedOutput = new ByteArrayOutputStream();
        PrintStream captureStream = new PrintStream(capturedOutput);
        System.setOut(captureStream);

        System.out.println("This is captured output.");
        System.out.println("Another line.");

        // 恢复原始输出
        captureStream.close();
        System.setOut(System.out);

        // 获取捕获的内容
        String output = capturedOutput.toString("UTF-8");
        System.out.println("Captured: " + output);
    }
}

四、使用技巧

  1. 测试自动化:在单元测试中模拟输入、捕获输出,避免依赖控制台。
  2. 日志集中化:将 System.outSystem.err 重定向到日志框架(如 Log4j)。
  3. GUI 应用:将控制台输出重定向到文本框或日志窗口。
  4. 批处理/脚本化:从配置文件或网络流读取输入,实现自动化流程。
  5. 临时重定向:使用 try-finally 块确保恢复原始流。
PrintStream originalOut = System.out;
try {
    System.setOut(new PrintStream("temp.txt"));
    // 执行需要重定向的操作
} finally {
    System.setOut(originalOut); // 确保恢复
}

五、常见错误

错误 原因 解决方案
NullPointerException 传入 null 参数 确保传入非 null 的流对象
文件未关闭导致资源泄露 忘记关闭 PrintStreamInputStream 使用 try-with-resources 或显式 close()
输出乱码 编码不一致 指定字符编码(如 UTF-8)
重定向后无法恢复 未保存原始引用 保存原始 System.in/out/err 引用
多线程竞争 多个线程同时修改全局流 避免在多线程环境中随意修改,或加锁同步

六、注意事项

  1. 全局影响:修改会影响整个 JVM,可能干扰其他模块(如日志框架、库代码)。
  2. 线程安全PrintStream 是线程安全的,但频繁重定向可能导致不可预期行为。
  3. 性能开销:频繁创建/销毁流对象会带来性能损耗。
  4. 编码问题:注意输入/输出流的字符编码一致性。
  5. 异常处理setXxx() 方法本身不抛异常,但后续 I/O 操作可能抛出 IOException

七、最佳实践

推荐做法

  • 保存原始流:重定向前保存原始 System.in/out/err,便于恢复。
  • 使用 try-finally:确保流被正确关闭和状态恢复。
  • 避免长期重定向:仅在必要时临时重定向。
  • 结合日志框架:将 System.err 重定向到 SLF4J/Log4j 等专业日志系统。
  • 测试专用:主要用于测试、调试或特殊场景,生产环境慎用。

避免做法

  • 在生产代码中长期重定向标准流。
  • 多个模块竞争修改标准流。
  • 忽略资源关闭。

八、性能优化

  1. 缓冲流:使用 BufferedInputStream / BufferedOutputStream 包装,减少 I/O 次数。
  2. 复用流对象:避免频繁创建 PrintStream
  3. 选择合适实现:如使用 ByteArrayOutputStream 捕获输出比写文件更快。
  4. 异步输出:对高频率输出,考虑异步写入(如通过队列 + 后台线程)。
// 使用缓冲提升性能
BufferedOutputStream bufferedOut = new BufferedOutputStream(new FileOutputStream("output.txt"));
System.setOut(new PrintStream(bufferedOut));

九、总结

项目 说明
核心用途 动态重定向标准输入/输出/错误流
适用场景 测试、日志、GUI、自动化脚本
关键优势 灵活控制 I/O 源和目标,便于集成与调试
主要风险 全局副作用、资源泄露、线程安全问题
使用原则 临时、可控、可恢复、资源安全

💡 一句话总结System.setIn/out/err() 是强大的 I/O 控制工具,适合测试和特殊场景,但应谨慎使用,避免污染全局状态。