System.out.println()的背后

本文探讨了`System.out.println()`背后的工作机制。从源码分析开始,揭示`out`静态变量如何在类加载过程中被初始化为一个非空的`PrintStream`对象。通过一系列构造函数和流对象的创建,最终将标准输出流与`PrintStream`关联。文章还提到了`println()`方法的重载,以及如何通过反射调用来模拟`System.out.println()`的行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

System

打开其源码,可以看到out是它的一个静态变量,类型为PrintStream

public final static PrintStream out = null;

因此就可以通过System.out的方式使用它。

但是这里的out是个null,如果直接使用,肯定是不行的,因为它是一个常量,一旦这个类被加载到虚拟机中,就不可再更改了。
于是我猜测可能是有了这个初始化类的方法,使得其在类的加载过程中被赋予了一个新的引用:

/**
     * Initialize the system class.  Called after thread initialization.
     */
    private static void initializeSystemClass() {

        props = new Properties();
        initProperties(props);  // initialized by the VM

        sun.misc.VM.saveAndRemoveProperties(props);


        lineSeparator = props.getProperty("line.separator");
        sun.misc.Version.init();

        FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
        //创建了一个文件输出流对象
        FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
        FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
        setIn0(new BufferedInputStream(fdIn));
        
        //主要是这里调用了设置out值的方法,这个方法是本地方法,无法查看源码
        setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding")));
        
        setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding")));

        Terminator.setup();
        sun.misc.VM.initializeOSEnvironment();
        Thread current = Thread.currentThread();
        current.getThreadGroup().add(current);

        setJavaLangAccess();
        sun.misc.VM.booted();
    }

由上面的代码可以看到,在类的加载过程中初始化的时候,给out设置了一个新对象的引用。

具体的再来看newPrintStream(fdOut, props.getProperty("sun.stdout.encoding"))

/**
     * Create PrintStream for stdout/err based on encoding.
     */
    private static PrintStream newPrintStream(FileOutputStream fos, String enc) {
       if (enc != null) {
            try {
                return new PrintStream(new BufferedOutputStream(fos, 128), true, enc);
            } catch (UnsupportedEncodingException uee) {}
        }
        return new PrintStream(new BufferedOutputStream(fos, 128), true);
    }

可以看到,使用文件输出流对象fos构造了一个缓冲区输出流对象,然后再构造了一个PrintStream对象,给了out。这时out就有了新值,不再为空了。

我们这时再来看看这个文件输出流对象怎么来的:

FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);

可以看到是用了FileDescriptor.out通过构造函数得到的,那就再看看它是什么:

/**
     * A handle to the standard output stream. Usually, this file
     * descriptor is not used directly, but rather via the output stream
     * known as {@code System.out}.
     * @see     java.lang.System#out
     */
    public static final FileDescriptor out = standardStream(1);
	private static native long set(int d);

    private static FileDescriptor standardStream(int fd) {
        FileDescriptor desc = new FileDescriptor();
        desc.handle = set(fd);
        return desc;
    }

到这里彻底终止于一个本地方法。但是我们通过这个方法名standardStream(int fd)可以明白的知道这是一个标准输出流。

System.out是怎么来的

  1. 正是通过这个标准输出流,我们得到了FileDescriptor.out
  2. 然后通过new FileOutputStream(FileDescriptor.out)构造出来了fos对象,
  3. 再然后通过new BufferedOutputStream(fos, 128)构造得到了bos对象,
  4. 又通过new PrintStream(new BufferedOutputStream(fos, 128), true)构造出一个打印流对象赋给了System.out,因此它本质上就是一个PrintStream对象。

我通过查看PrintStream类的方法,发现了一系列被重载的println()方法,它会根据传进来参数的类型与个数调用不同的方法。

最后

最后我们使用刚才的一系列构造方法构造一个我们自己的PrintStream类,并打印"hello world"

FileOutputStream fos = new FileOutputStream(FileDescriptor.out);
BufferedOutputStream bos = new BufferedOutputStream(fos, 128);
PrintStream ps = new PrintStream(bos, true);
ps.println("hello world");

因为FileDescriptor类的standardStream()方法是私有的,因此我们不能够调用以获取一个标准输出流,而只能通过FileDescriptor.out获取。

import java.util.ArrayList; import java.util.List; public class KMeansExample { public static void main(String[] args) { int n = 10; // 随机数的数量 double a = -5, b = 5, c = 10; // [a, b+c] List<Double> data = generateRandomNumbers(n, a, b + c); // 生成随机数 System.out.println("Generated Data:"); for (double num : data) { System.out.print(num + " "); } System.out.println(); kMeansCluster(data); } private static List<Double> generateRandomNumbers(int count, double min, double max) { List<Double> numbers = new ArrayList<>(); for (int i = 0; i < count; i++) { double randomNum = Math.round((min + (max - min) * Math.random()) * 100.0) / 100.0; numbers.add(randomNum); } return numbers; } private static void kMeansCluster(List<Double> data) { if (data.isEmpty()) return; // 初始化两个簇心为第一个和最后一个元素 double centroid1 = data.get(0), centroid2 = data.get(data.size() - 1); boolean centroidsChanged; do { centroidsChanged = false; // 分组 List<Double> cluster1 = new ArrayList<>(); List<Double> cluster2 = new ArrayList<>(); for (Double point : data) { if (Math.abs(point - centroid1) <= Math.abs(point - centroid2)) { cluster1.add(point); } else { cluster2.add(point); } } // 更新簇心 double newCentroid1 = calculateMean(cluster1); double newCentroid2 = calculateMean(cluster2); if (newCentroid1 != centroid1 || newCentroid2 != centroid2) { centroidsChanged = true; centroid1 = newCentroid1; centroid2 = newCentroid2; } } while (centroidsChanged); // 输出分组结果 System.out.printf("Cluster1 Centroid: %.2f -> ", centroid1); for (double d : data.stream().filter(p -> Math.abs(p - centroid1) <= Math.abs(p - centroid2)).toList()) { System.out.printf("%.2f ", d); } System.out.println(); System.out.printf("Cluster2 Centroid: %.2f -> ", centroid2); for (double d : data.stream().filter(p -> Math.abs(p - centroid2) < Math.abs(p - centroid1)).toList()) { System.out.printf("%.2f ", d); } System.out.println(); } private static double calculateMean(List<Double> points) { if (points == null || points.isEmpty()) return Double.NaN; double sum = 0; for (double p : points) sum += p; return su
03-24
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值