• JAVA I/O操作那些事之标准I/0


           java 提供的标准模型有 System.in, System.out, System.err。平日里我们经常用到他们3个,其中用的最多的就是System.out.println()等了,最近突然想到,他们是怎么实现的呢?

    下面是JDK中者三者的定义的源码:

         public final static InputStream in = nullInputStream();
    
         public final static PrintStream out = nullPrintStream();
    
         public final static PrintStream err = nullPrintStream();

            从上面的代码上我们可以用到,除了in,其他两者都已经封装成了PrintStream格式,我们可以直接使用他们。而in则有点特殊,它只是一个没有被包装过的未经加工的InputStream(这是Thinking in java的原话,但是在实际我发现,这个InputStream的真实类型应该是BufferedInputStream,有可能是我对这句话的理解有误导致的吧),鄙人在学C++的时候,依稀记得,当出现cin>>这样的标识的时候,程序会停下来等待键盘的输入操作,但是在Java中用起来要费劲些,直接这么简单粗暴的输入,是没有效果的。一般情况下,我们需要对其进行包装,下面的例子就是其中的一个方法

            InputStream in = System.in;
            BufferedReader stdin = new BufferedReader(new InputStreamReader(in));//对其进行包装
            System.out.print("请输入字符:   "); 
            String str = stdin.readLine();//等待键盘输入,按回车结束
            System.out.println("你输入的字符为:   " + str);

       回到开篇讲到的内容,我们注意到in,out返回的是方法 nullInputStream()、nullPrintStream()方法执行后的结果,我第一次看的时候,想当然就以为这两个方法返回的是inputStream、和PrintStream。后来走到源码中一看,才傻眼了,这两个方法是这样的

     /**
         * The following two methods exist because in, out, and err must be
         * initialized to null.  The compiler, however, cannot be permitted to
         * inline access to them, since they are later set to more sensible values
         * by initializeSystemClass().
         */
        private static InputStream nullInputStream() throws NullPointerException {
    	if (currentTimeMillis() > 0) { //一般情况下,不会出现<=0的情况
    	    return null;
    	}
    	throw new NullPointerException();
        }
    
        private static PrintStream nullPrintStream() throws NullPointerException {
    	if (currentTimeMillis() > 0) {
    	    return null;
    	}
    	throw new NullPointerException();
        }
    

      从上段代码中我们可以看到,其实这两个方法返回的是null(或者是出现一个运行时的异常)。那么问题来了,竟然是null值,那它们是如何操作与初始化的。

     好在,方法之前有一大段的注释,说明这连个方法之所以存在时应为in,out等必须初始化为空(why?等待高手解答啊。。。),那么何时才是真正的执行初始化操作呢?

    答案就在initializeSystemClass()这个方法里

               

    /**
         * Initialize the system class.  Called after thread initialization.
         */
        private static void initializeSystemClass() {
    	props = new Properties();
    	initProperties(props);
    	sun.misc.Version.init();
    
    	// Load the zip library now in order to keep java.util.zip.ZipFile
    	// from trying to use itself to load this library later.
    	loadLibrary("zip");
    
    	FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
    	FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
    	FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
    	setIn0(new BufferedInputStream(fdIn));
    	setOut0(new PrintStream(new BufferedOutputStream(fdOut, 128), true));
    	setErr0(new PrintStream(new BufferedOutputStream(fdErr, 128), true));
    /**
    *   以上只是部分代码,下面是无关的代码,略去
    */
    

      在上面这段代码里,setIn0等方法的代码如下

        private static native void setIn0(InputStream in);
        private static native void setOut0(PrintStream out);
        private static native void setErr0(PrintStream err);

    可以看到上面这三个方法都是native方法,那么我们就不能继续跟下去了,但是我们可以看setIn,setOut,setErr方法,我们知道,这三个方法用于冲定向标准的I/O流。

    public static void setIn(InputStream in) {
    	checkIO(); //用于权限的检查
    	setIn0(in);
        }
    
     public static void setOut(PrintStream out) {
    	checkIO();
    	setOut0(out);
        }
    
       public static void setErr(PrintStream err) {
    	checkIO();
    	setErr0(err);
        }
    

      这三个方法都调用了相应的setXX0()方法,由此我们可以推出setIn0等方法用于重定向输入输出流。

        那么重定位到哪呢?  答案就是

    new FileInputStream(FileDescriptor.in)
    new FileOutputStream(FileDescriptor.out)
    new FileOutputStream(FileDescriptor.err)

     public static final FileDescriptor in = standardStream(0);
     public static final FileDescriptor out = standardStream(1);
     public static final FileDescriptor err = standardStream(2);
    
    /**
    * 返回handle为fd的FileDescriptor; 在传统的unix的系统中,fd为0,1,2分别表示为标准输入,标准输出和错误输出。
    */
    private static FileDescriptor standardStream(int fd) { FileDescriptor desc = new FileDescriptor(); desc.handle = set(fd); return desc; }
    
    
          所以 setIn0(new BufferedInputStream(fdIn)); 就是将标准输入先封装成文件输入流(FileInputstream),再封装成BufferedInputStream(典型的装饰模式啊)
          差不多这些了,大致过程都清楚了,但是仍有一些细节后续需要继续弄明白,如:为什么初始化必须为空,什么时候调用的initializeSystemClass()方法。
     

     

  • 相关阅读:
    【Azure 环境】 介绍两种常规的方法来监视Window系统的CPU高时的进程信息: Performance Monitor 和 Powershell GetCounter
    云边协同架构助力智能工厂视觉 AI 缺陷检测应用构建
    桥接 Mosquitto MQTT 消息至 EMQX
    如何保障物联网平台的安全性与健壮性
    eKuiper Newsletter 202208|多平台插件一键安装,使用更便捷
    使用 Prometheus 监控 eKuiper 规则运行状态
    EMQX Operator 如何快速创建弹性伸缩的 MQTT 集群
    EMQX 5.0 全新网关框架:轻松实现多物联网协议接入
    云原生赋能智能网联汽车消息处理基础框架构建
    MQTT X Newsletter 202208 | v1.8.2 发布、支持使用 Docker
  • 原文地址:https://www.cnblogs.com/chenfei0801/p/2987899.html
Copyright © 2020-2023  润新知