System2.java

System.out.println(""); System.err.println("");

复制代码
package further.util;

import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;

/**
 * 控制台同步输出(项目扩展,与 {@link java.lang.System} 不同包名,避免替 JDK 引导类)。
 * <p>
 * <strong>顺序:</strong>Eclipse 等对 stdout / stderr <strong>两条管道分别缓冲</strong>,即使 JVM 里加锁、逐行 flush,
 * 控制台仍常出现「先整块黑字、再整块红字」,与源码交替调用顺序不一致------这在 IDE 侧无法仅靠 Java 根治。
 * 要保证<strong>显示顺序与调用顺序一致</strong>,只能<strong>合并为一条管道</strong>(只写 {@link FileDescriptor#out}),见 {@link #installOrderedConsole()}。
 * </p>
 * <p>
 * <strong>红色:</strong>IDE 的红字来自<strong>真实 stderr(fd2)</strong>,见 {@link #installDualFileDescriptorConsole()};
 * 与「合并管道保顺序」二者在 Eclipse 内置控制台里<strong>不可兼得</strong>。合并模式下 {@link Kind#ERR} 时默认加
 * {@code [stderr] } 前缀区分错误语义;若控制台支持 ANSI,可加 {@code -Dsyncconsole.ansiErr=true} 尝试红色转义(部分 Eclipse 版本仍不会着色)。
 * </p>
 *
 * @author ZengWenFeng
 * @mobile 13805029595
 * @email 117791303@qq.com
 * @date 2026.05.08
 */
public final class System2
{
	/**
	 * 打印目标:标准输出 / 标准错误(语义),用于 {@link #println(Kind, String)} 第一个参数。
	 */
	public enum Kind
	{
		/** 标准输出 */
		OUT,
		/** 标准错误 */
		ERR
	}

	/** 与 {@link Kind#OUT} 相同,便于书写 {@code System2.println(System2.OUT, ...)} */
	public static final Kind OUT = Kind.OUT;

	/** 与 {@link Kind#ERR} 相同 */
	public static final Kind ERR = Kind.ERR;

	private static final Object CONSOLE_LOCK = new Object();

	private static final String STDERR_PREFIX = "[stderr] ";

	private static final String ANSI_RED = "\u001B[31m";

	private static final String ANSI_RESET = "\u001B[0m";

	private static volatile boolean installed;

	/** true:合并管道模式;false:双 fd(JDK 式)。 */
	private static volatile boolean mergedStdoutMode;

	private static volatile PrintStream outStream;

	private static volatile PrintStream errStream;

	private static volatile PrintStream unifiedStream;

	private static volatile boolean ansiErrRed = Boolean.parseBoolean(java.lang.System.getProperty("syncconsole.ansiErr", "false"));

	private System2()
	{
	}

	/**
	 * <strong>默认推荐:</strong>只使用标准输出 fd,{@link java.lang.System#out} 与 {@link java.lang.System#err} 指向同一 {@link PrintStream},
	 * 控制台收到的字节顺序与 {@link #println(Kind, String)} 调用顺序一致。{@link Kind#ERR} 时默认带 {@code [stderr] } 前缀;
	 * 需要 ANSI 红字时可 {@code -Dsyncconsole.ansiErr=true}(视控制台是否解析转义而定)。
	 */
	public static void installOrderedConsole()
	{
		installMergedStdoutInternal();
	}

	/**
	 * 与 {@link #installOrderedConsole()} 相同实现(合并管道、顺序优先)。
	 */
	public static void installMergedStdoutStrictOrder()
	{
		installMergedStdoutInternal();
	}

	private static void installMergedStdoutInternal()
	{
		synchronized (CONSOLE_LOCK)
		{
			if (installed)
			{
				return;
			}
			try
			{
				OutputStream raw = new FileOutputStream(FileDescriptor.out);
				OutputStream gated = new GatedOutputStream(raw);
				PrintStream ps = new PrintStream(gated, true, "UTF-8");
				unifiedStream = ps;
				java.lang.System.setOut(ps);
				java.lang.System.setErr(ps);
				mergedStdoutMode = true;
				installed = true;
			}
			catch (UnsupportedEncodingException e)
			{
				throw new IllegalStateException("UTF-8 must be supported", e);
			}
		}
	}

	/**
	 * JDK 式双 fd:{@link java.lang.System#err} 走真实 stderr,在 Eclipse 中一般为红色,但<strong>不要期望</strong>与 {@link Kind#OUT} 交替时
	 * 显示顺序与源码一致(IDE 分管道缓冲)。
	 */
	public static void installDualFileDescriptorConsole()
	{
		synchronized (CONSOLE_LOCK)
		{
			if (installed)
			{
				return;
			}
			try
			{
				FileOutputStream fosOut = new FileOutputStream(FileDescriptor.out);
				FileOutputStream fosErr = new FileOutputStream(FileDescriptor.err);
				outStream = new PrintStream(fosOut, true, "UTF-8");
				errStream = new PrintStream(fosErr, true, "UTF-8");
				java.lang.System.setOut(outStream);
				java.lang.System.setErr(errStream);
				mergedStdoutMode = false;
				installed = true;
			}
			catch (UnsupportedEncodingException e)
			{
				throw new IllegalStateException("UTF-8 must be supported", e);
			}
		}
	}

	/**
	 * @deprecated 语义同 {@link #installDualFileDescriptorConsole()}
	 */
	@Deprecated
	public static void installPrintStreamsLikeSystemClass()
	{
		installDualFileDescriptorConsole();
	}

	public static void setAnsiErrRedEnabled(boolean enabled)
	{
		ansiErrRed = enabled;
	}

	public static boolean isAnsiErrRedEnabled()
	{
		return ansiErrRed;
	}

	/**
	 * 按通道输出一行(换行)。示例:{@code System2.println(System2.OUT, "")}、{@code System2.println(System2.ERR, "msg")}。
	 *
	 * @param kind {@link #OUT} 或 {@link #ERR},不可为 {@code null}
	 * @param message 文本;{@code null} 时与 {@link PrintStream#println(String)} 一致
	 */
	public static void println(Kind kind, String message)
	{
		if (kind == null)
		{
			throw new IllegalArgumentException("kind is null");
		}
		if (kind == Kind.OUT)
		{
			printlnOut(message);
		}
		else
		{
			printlnErr(message);
		}
	}

	/**
	 * @param kind {@link #OUT} 或 {@link #ERR}
	 * @param obj 将 {@link String#valueOf(Object)} 再写出
	 */
	public static void println(Kind kind, Object obj)
	{
		println(kind, String.valueOf(obj));
	}

	/**
	 * @param kind {@link #OUT} 或 {@link #ERR}
	 * @param message 不换行;{@code null} 当作 ""
	 */
	public static void print(Kind kind, String message)
	{
		if (kind == null)
		{
			throw new IllegalArgumentException("kind is null");
		}
		if (kind == Kind.OUT)
		{
			printOut(message);
		}
		else
		{
			printErr(message);
		}
	}

	private static void printlnOut(String message)
	{
		ensureInstalled();
		synchronized (CONSOLE_LOCK)
		{
			if (mergedStdoutMode)
			{
				unifiedStream.println(message);
			}
			else
			{
				outStream.println(message);
				outStream.flush();
			}
		}
	}

	private static void printlnErr(String message)
	{
		ensureInstalled();
		synchronized (CONSOLE_LOCK)
		{
			if (mergedStdoutMode)
			{
				if (ansiErrRed)
				{
					unifiedStream.print(ANSI_RED);
					unifiedStream.println(message);
					unifiedStream.print(ANSI_RESET);
				}
				else
				{
					unifiedStream.print(STDERR_PREFIX);
					unifiedStream.println(message);
				}
			}
			else
			{
				errStream.println(message);
				errStream.flush();
			}
		}
	}

	private static void printOut(String message)
	{
		ensureInstalled();
		synchronized (CONSOLE_LOCK)
		{
			if (mergedStdoutMode)
			{
				unifiedStream.print(message == null ? "" : message);
			}
			else
			{
				outStream.print(message == null ? "" : message);
				outStream.flush();
			}
		}
	}

	private static void printErr(String message)
	{
		ensureInstalled();
		synchronized (CONSOLE_LOCK)
		{
			if (mergedStdoutMode)
			{
				if (ansiErrRed)
				{
					unifiedStream.print(ANSI_RED);
					unifiedStream.print(message == null ? "" : message);
					unifiedStream.print(ANSI_RESET);
				}
				else
				{
					unifiedStream.print(STDERR_PREFIX);
					unifiedStream.print(message == null ? "" : message);
				}
			}
			else
			{
				errStream.print(message == null ? "" : message);
				errStream.flush();
			}
		}
	}

	public static void flush()
	{
		synchronized (CONSOLE_LOCK)
		{
			if (!installed)
			{
				return;
			}
			if (mergedStdoutMode)
			{
				if (unifiedStream != null)
				{
					unifiedStream.flush();
				}
			}
			else
			{
				if (outStream != null)
				{
					outStream.flush();
				}
				if (errStream != null)
				{
					errStream.flush();
				}
			}
		}
	}

	private static void ensureInstalled()
	{
		if (!installed)
		{
			throw new IllegalStateException("Call System2.installOrderedConsole() (or installMergedStdoutStrictOrder()) first");
		}
	}

	private static final class GatedOutputStream extends OutputStream
	{
		private final OutputStream delegate;

		GatedOutputStream(OutputStream delegate)
		{
			this.delegate = delegate;
		}

		@Override
		public void write(int b) throws IOException
		{
			synchronized (CONSOLE_LOCK)
			{
				delegate.write(b);
			}
		}

		@Override
		public void write(byte[] b, int off, int len) throws IOException
		{
			synchronized (CONSOLE_LOCK)
			{
				delegate.write(b, off, len);
			}
		}

		@Override
		public void flush() throws IOException
		{
			synchronized (CONSOLE_LOCK)
			{
				delegate.flush();
			}
		}
	}
}
相关推荐
222you1 小时前
Claude Code接入DeepSeek-v4模型
java·服务器·前端
i220818 Faiz Ul1 小时前
高校教务|教务管理|基于springboot+vue的高校教务管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·高校教务系统
Ting-yu1 小时前
SpringCloud快速入门(4)---- nacos安装与使用
java·spring·spring cloud
木子墨5161 小时前
工程算法实战 | 从LRU到手写本地缓存:LinkedHashMap → 双向链表+哈希表 → Caffeine 原理
java·数据结构·算法·链表·缓存
无尽冬.2 小时前
个人八股之三层架构
java·经验分享·后端·架构·异世界
贫民窟的勇敢爷们2 小时前
SpringBoot多环境配置全解+配置优先级管控
java·spring boot·后端
tellmewhoisi2 小时前
单独抽取用户服务(请求不通):feign添加拦截器(添加token)
java·开发语言
YL200404262 小时前
035LRU缓存
java·leetcode·缓存
不像程序员的程序媛2 小时前
mysql 0000-00-00 00:00:00零日期问题
java·mysql