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

print()和println()的区别

作者: Horps | 来源:发表于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方法或者遇到下一个换行符。

相关文章

  • Go 学习笔记 01 | 输出、变量、常量、命名规则和代码风格

    一、输出 Println 和 Print 区别 Println 会自动换行输出,Print 不会自动换行输出。pa...

  • print()和println()的区别

    简要写demo的时候意外发现print()和printnln()的一些特别的地方,是有关于Java输入输出流的知识...

  • 02 Go基础语法

    Go基础语法 输出语句 注意:每行语句结束后没有分号; Print和Println的区别 Print没有换号,Pr...

  • Go语言输出格式

    Print()和Println() Print()函数不换行,Println()换行输出 输出提示 fmt.Pri...

  • Kotlin(四)输出与输入

    println()、print()输出 使用println()和print()方法将输出的内容打印在控制台,例如:...

  • Java教程之《print()和println()区别》

    println()的源码和运行结果 打开记事本,输入: public class Test{ public sta...

  • Day05

    Java中print、printf、println的区别? print将它的参数显示在命令窗口,并将输出光标定位在...

  • swift debug技术

    主要有两个函数用于输出的文本表示对象和变量,那就是 print( ) 和println( )。 println( ...

  • java基础2

    print\println\printf的区别 数组 数组的赋值 数组的复制,要避免数组下标越界 数组的扩容数组的...

  • 输出语句

    main函数:与java几乎没有区别 kotlin中不用写封号 输出语句: println输出换行 print输出不换行

网友评论

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

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