美文网首页
print()和println()的区别

print()和println()的区别

作者: 就叫汉堡吧 | 来源:发表于2020-11-27 19:28 被阅读0次
    • 简要

      写demo的时候意外发现print()和printnln()的一些特别的地方,是有关于Java输入输出流的知识,都是些基础知识,但这个偶然的发现加深了一些理解,特别记录一下。

      fun main(){
              GlobalScope.launch {
                  delay(3000L)
                  println("World!")
              }
              print("Hello,")
      //        println("Hello,")
                    //Jvm保活
              Thread.sleep(5000L)
      
      }
      

      这段代码是关于kotlin协程的,程序的流程应该是先输出"Hello,",然后等待三秒输出"World!"。但是当我使用print()的时候是输出台等待三秒之后一次性输出字符串"Hello,World!",使用println的时候才是期望中的先输出"Hello,",再输出"World!",当然换行的不同我们不用说了,但是为什么print()会有种delay()方法没有发挥期望中的作用呢?处于求知欲我点开了源码。

    • 源码分析

      首先,kotlin中print()方法就是Java中的System.out.print()方法,同样,println()就是System.out.println()。

      out是System中的一个静态常量:

      public final static PrintStream out;
      

      它的初始化在同类的static块中:

      static {
          unchangeableProps = initUnchangeableSystemProperties();
          props = initProperties();
          addLegacyLocaleSystemProperties();
          sun.misc.Version.initSystemProperties();
      
          // TODO: Confirm that this isn't something super important.
          // sun.misc.VM.saveAndRemoveProperties(props);
      
          lineSeparator = props.getProperty("line.separator");
      
          FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
          FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
          FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
          // BEGIN Android-changed: lower buffer size.
          // in = new BufferedInputStream(fdIn);
          in = new BufferedInputStream(fdIn, 128);
          // END Android-changed: lower buffer size.
          out = newPrintStream(fdOut, props.getProperty("sun.stdout.encoding"));
          err = newPrintStream(fdErr, props.getProperty("sun.stderr.encoding"));
      
         ......
      }
      

      通过newPrintStream方法创建:

      /**
       * 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);
      }
      

      现在我们的重心来到PrintStream中:

      private PrintStream(boolean autoFlush, OutputStream out, Charset charset) {
          super(out);
          this.autoFlush = autoFlush;
          // Android-changed: Lazy initialization of charOut and textOut.
          // this.charOut = new OutputStreamWriter(this, charset);
          // this.textOut = new BufferedWriter(charOut);
          this.charset = charset;
      }
      

      注意这里的autoFlush传进来的是true。

      接下来看一下他们的print方法和println方法:

      public void print(Object obj) {
          write(String.valueOf(obj));
      }
      
      public void println(Object x) {
          String s = String.valueOf(x);
          synchronized (this) {
              print(s);
              newLine();
          }
      }
      

      这两个方法有很多重构方法,所以传参不限于Object。可以看到,println()内部也是调用了print()方法,只不过最后多调用了一个newLine(),我们看一下newLine方法里面:

      private void newLine() {
          try {
              synchronized (this) {
                  ensureOpen();
                  // Android-added: Lazy initialization of charOut and textOut.
                  BufferedWriter textOut = getTextOut();
                  textOut.newLine();
                  textOut.flushBuffer();
                  charOut.flushBuffer();
                  if (autoFlush)
                      out.flush();
              }
          }
          catch (InterruptedIOException x) {
              Thread.currentThread().interrupt();
          }
          catch (IOException x) {
              trouble = true;
          }
      }
      

      作为对比,我们看一下单纯的print有什么不一样,也就是write方法:

      private void write(String s) {
          try {
              synchronized (this) {
                  ensureOpen();
                  // Android-added: Lazy initialization of charOut and textOut.
                  BufferedWriter textOut = getTextOut();
                  textOut.write(s);
                  textOut.flushBuffer();
                  charOut.flushBuffer();
                  if (autoFlush && (s.indexOf('\n') >= 0))
                      out.flush();
              }
          }
          catch (InterruptedIOException x) {
              Thread.currentThread().interrupt();
          }
          catch (IOException x) {
              trouble = true;
          }
      }
      

      看出不同了吧,单纯的调用write方法只有在输出的字符中含有换行符'\n'才会执行flush方法,而newLine方法则会保证只要autoFlush是true就会执行flush方法,前面说到autoFlush这种情况下传入的都是true,所以区别就在于字符串中是否含有\n,flush方法会打印缓冲区中的信息到控制台,我们就会看到。

      PrintStream中的out就是传入的BufferedOutputStream,所以flush就是调用它的方法:

      public synchronized void flush() throws IOException {
          flushBuffer();
          out.flush();
      }
      

      它的out就是构造时的FileOutputStream,它的flush方法就是父类OutputStream的flush方法:

      /**
       * Flushes this output stream and forces any buffered output bytes
       * to be written out. The general contract of <code>flush</code> is
       * that calling it is an indication that, if any bytes previously
       * written have been buffered by the implementation of the output
       * stream, such bytes should immediately be written to their
       * intended destination.
       * <p>
       * If the intended destination of this stream is an abstraction provided by
       * the underlying operating system, for example a file, then flushing the
       * stream guarantees only that bytes previously written to the stream are
       * passed to the operating system for writing; it does not guarantee that
       * they are actually written to a physical device such as a disk drive.
       * <p>
       * The <code>flush</code> method of <code>OutputStream</code> does nothing.
       *
       * @exception  IOException  if an I/O error occurs.
       */
      public void flush() throws IOException {
      }
      

      根据注释,这个方法会立即把数据写入到流中,但是不保证会写入到硬件存储设备,因为这是操作系统的职责,它只需要把数据交给和操作系统交互的流中,这部分实现是JVM实现的。这就是为什么正常情况下flush调用之后我们才能看到控制台输出信息的原因。

      好了,那我们知道了这个之后,回过头来看,flush前面的代码做了什么。

      ensureOpen()确保输入流不能为空,无需赘言。

      getTextOut中做了什么:

      // BEGIN Android-added: Lazy initialization of charOut and textOut.
      private BufferedWriter getTextOut() {
          if (textOut == null) {
              charOut = charset != null ? new OutputStreamWriter(this, charset) :
                      new OutputStreamWriter(this);
              textOut = new BufferedWriter(charOut);
          }
          return textOut;
      }
      // END Android-added: Lazy initialization of charOut and textOut.
      

      charOut把PrintStream封装进OutputStreamWriter中,然后textOut又把charOut封装进BufferedWriter中,然后调用write方法把数据写入,其实就是移交给charOut去做,OutputStreamWriter内部又是通过一个叫StreamEncoder的类去做的,经查询,StreamEncoder的处理过程和我们分析flush的调用过程一样,最终也是调用了JVM的底层方法,把数据压入缓冲区。

    • 总结

      经过以上的分析,我们知道,print()方法和println()方法的不同就是后者在把数据压入缓冲区之后还调用flush方法把缓冲区中的数据推到和操作系统交互的流中,然后操作系统和硬件交互从而显示出控制台信息,print()若是输出的字符串中含有'\n',则换行符前面的内容会直接显示出来,而换行符之后的内容则会暂存到缓冲区,直到调用下一次的flush方法或者遇到下一个换行符。

    相关文章

      网友评论

          本文标题:print()和println()的区别

          本文链接:https://www.haomeiwen.com/subject/wzssiktx.html