源码分析
首先,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 flush 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.
*
* 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.
*
* The flush method of OutputStream 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的底层方法,把数据压入缓冲区。