一、PrintStreamWriter的介绍
PrintStream是字符类型的打印输出流,用于格式化文本输出流的对象表示形式。此类实现了PrintStream类的所有print方法,除了写入原始字节的方法,若有写入原始字节的需求应该使用未编码的字节输出流写入,例如FileInputStream等。与其他输出流不同的是,PrintStream永远不会抛出IO异常,取而代之的是它在内部维护了一个异常标志,可以通过checkError方法去检测。我们在创建PrintStream的时候可以通过指定autoFlush为true设置PrintStreamWriter输出流自动刷新,这意味着当写入字节数组,println方法调用,写入换行符或者“\n”字符都会引发PrintStreamWriter输出流的自动刷新。所有PrintStream打印的字符都是使用平台默认的字符编码方法将字符转化为字节写入,PrintStreamWriter适用于写入字符而不是字节的场合。
二、PrintStreamWriter数据结构
public class PrintWriter extends Writer { // PrintWriter绑定的底层字符输出流 protected Writer out; //是否自动刷新 private final boolean autoFlush; //异常标志,当PrintWriter产生异常会被自身捕获,并且设置trouble为true private boolean trouble = false; //格式化字符串的对象 private Formatter formatter; //字节打印输出流 private PrintStream psOut = null; //行分隔符 private final String lineSeparator;}
三、PrintStreamWriter的源码分析
1 - 构造函数
/** * 构造函数,指定底层的字符输出流,默认不自动flush,采用默认的字符编码方式 */ public PrintWriter (Writer out) { this(out, false); } /** * 构造函数,指定底层的字符输出流和是否自动flush,采用默认的字符编码方式 */ public PrintWriter(Writer out, boolean autoFlush) { super(out); this.out = out; this.autoFlush = autoFlush; lineSeparator = java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction("line.separator")); } /** * 构造函数,默认不自动刷新, 采用默认字符集,底层字符输出流绑定的是基于方法指定的字符输出流对象out创建的 * BufferedWriter对象 */ public PrintWriter(OutputStream out) { this(out, false); } /** * 构造函数,基于方法指定底层字节输出流和自动flush模式. 此构造函数将为指定的底层字节输出流创建中间的 * OutputStreamWriter它将使用平台默认的字符编码方式将字符转化为字节存储,并在外部使用BufferedWriter装饰它 * 为PrintWriter对象提供额外的字符缓冲功能 */ public PrintWriter(OutputStream out, boolean autoFlush) { this(new BufferedWriter(new OutputStreamWriter(out)), autoFlush); //若out为PrintStream对象 if (out instanceof java.io.PrintStream) { psOut = (PrintStream) out; } } /** * 构造函数, 指定写入的文件名,默认不自动flush,采用默认的字符编码方式. */ public PrintWriter(String fileName) throws FileNotFoundException { this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName))), false); } /* 私有构造函数, 指定写入文件和字符编码方式,默认不自动flush */ private PrintWriter(Charset charset, File file) throws FileNotFoundException { this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), charset)), false); } /** * 构造函数, 默认不自动flush, 指定写入文件的文件名和使用的字符编码名称,这个构造方法将使用字符字节转化的中间 * 桥梁OutputStreamWriter包裹底层基于fileName的字节文件输出流FileOutputStream使用方法指定的字符编码将 * 字符转化为字节 */ public PrintWriter(String fileName, String csn) throws FileNotFoundException, UnsupportedEncodingException { this(toCharset(csn), new File(fileName)); } /** * 构造函数, 指定写入文件,默认不自动flush,采用默认的字符编码方式将写入字符转化为字节存储 */ public PrintWriter(File file) throws FileNotFoundException { this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))), false); } /** * 构造函数,指定写入的文件file,默认不自动flush,采用指定的字符编码将字符转化为字节数据 */ public PrintWriter(File file, String csn) throws FileNotFoundException, UnsupportedEncodingException { this(toCharset(csn), file); }
通过阅读PrintStream的构造函数源码,我们基于底层字符输出流的产生方式可以将PrintStream的构造函数分为两种类型,一种是构造方法直接指定了底层字符输出流out,例如PrintWriter(Writer out),另一种构造方法不直接指定底层字符输出流out,而是通过指定底层字节输出流、FIle对象或者fileName,基于这些要素创建字符字节转化的中间流对象OutputStreamWriter,并让PrintWriter对象内部的底层字符输出流引用指向内部包裹了该OutputStreamWriter的BufferedWriter对象,提供了额外的数据缓冲功能。
2 - int write(String s)方法 - 写入字符串
PrintWriter支持int、char[]、String对象的写入我们不准备将所有数据类型的write方法拿出来分析,这里只分析写入字符串方法write(String s),其他方法参照本方法研读即可,下面是方法源码:
/** * 写入字符串,该方法不能从Writer方法继承因为他不能抛出IOException异常而要自己处理 */ public void write(String s) { write(s, 0, s.length()); }
方法内部不直接处理而是调用了write(s,0,s.length)方法,进入该方法源码:
public void write(String s, int off, int len) { try { synchronized (lock) { ensureOpen(); out.write(s, off, len); } } catch (InterruptedIOException x) { Thread.currentThread().interrupt(); } catch (IOException x) { trouble = true; } }
write写入字符串的方法在方法内部对父类lock对象加锁,首先调用了ensureOpen方法检验该流的状态是否已关闭(通过判断绑定的底层字符输出流对象是否为空),若未关闭调用底层字符输出流对象out的write方法写入字符串。
3 - println(String s)方法 - 打印字符串并换行
/** * 打印字符串x然后终止方法 */ public void println(String x) { synchronized (lock) { print(x); println(); } }
该方法的逻辑也比较简单,首先调用本类方法print(x),我们跟着进入方法源码:
public void print(String s) { if (s == null) { s = "null"; } write(s); }
该方法没做啥当方法参数为null时为它分配一个值为"null"的字符串,最后调用write方法,将字符串s写入到底层字符输出流对象out中,我们继续回到顶层的int write(String s)方法继续分析接下来的println方法做了什么?
public void println() { newLine(); } private void newLine() { try { synchronized (lock) { ensureOpen(); out.write(lineSeparator); if (autoFlush) out.flush(); } } catch (InterruptedIOException x) { Thread.currentThread().interrupt(); } catch (IOException x) { trouble = true; } }
println方法内部直接调用了newLine方法将一个换行符写入了底层的字符输出流对象out中,基本逻辑就是首先判断底层字符输出流是否关闭,未关闭则调用它的write方法写入换行符lineSeparator字符串,最后判断是否开启自动flush,若是指定自动flush则刷新底层字符输出流。
总结println(String s)方法基本逻辑如下:
1)判断写入的字符串是否为null,若为null则重新为它分配一个值为“null”的字符串;
2)底层字符输出流对象out写入该字符串;
3)底层字符输出流对象out写入换行符lineSeperator;
4)若指定了自动刷新(autoflush=true)则刷新底层字符输出流。
4 - 其他成员方法
/** * 返回指定字符编码名称csn对应的Charset对象,若csn为null或者是不支持的字符编码方式则抛出异常 */ private static Charset toCharset(String csn) throws UnsupportedEncodingException { Objects.requireNonNull(csn, "charsetName"); try { return Charset.forName(csn); } catch (IllegalCharsetNameException|UnsupportedCharsetException unused) { // UnsupportedEncodingException should be thrown throw new UnsupportedEncodingException(csn); } } /** 检查输出流是否开启,若底层字符输出流不为null则认为是开启的 **/ private void ensureOpen() throws IOException { if (out == null) throw new IOException("Stream closed"); } /** * 刷新流,其实就是刷新底层字符输出流out的缓冲区 */ public void flush() { try { synchronized (lock) { ensureOpen(); out.flush(); } } catch (IOException x) { trouble = true; } } /** * 关闭该流并释放相关的系统资源 */ public void close() { try { synchronized (lock) { if (out == null) return; out.close(); out = null; } } catch (IOException x) { trouble = true; } } /** * 若流没有关闭,则刷新流并检查其错误状态,这里注意若底层字符输出流为PrintWriter或者绑定的底层字节输出流是 * PrintOutputStream则调用它们本身的checkError检查流的异常状态,若底层输出流不在上述范围则返回本类标识异常状 * 态的成员变量 trouble */ public boolean checkError() { if (out != null) { flush(); } if (out instanceof java.io.PrintWriter) { PrintWriter pw = (PrintWriter) out; return pw.checkError(); } else if (psOut != null) { return psOut.checkError(); } return trouble; } /** * 标志该流已发生IO异常,调用该方法将导致在调用clearError方法之前checkError方法返回为true */ protected void setError() { trouble = true; } /** * 清除PrintWriter流的异常状态 */ protected void clearError() { trouble = false; } /* * 写入单个字符到PrintWriter底层字符输出流 */ public void write(int c) { try { synchronized (lock) { ensureOpen(); out.write(c); } } catch (InterruptedIOException x) { Thread.currentThread().interrupt(); } catch (IOException x) { trouble = true; } } /** * 写入字符数组的一部分到PrintWriter底层字符输出流 */ public void write(char buf[], int off, int len) { try { synchronized (lock) { ensureOpen(); out.write(buf, off, len); } } catch (InterruptedIOException x) { Thread.currentThread().interrupt(); } catch (IOException x) { trouble = true; } } /** * 将指定字符数组写入PrintWriter底层字符输出流out */ public void write(char buf[]) { write(buf, 0, buf.length); } /** * 写入字符串的一部分 */ public void write(String s, int off, int len) { try { synchronized (lock) { ensureOpen(); out.write(s, off, len); } } catch (InterruptedIOException x) { Thread.currentThread().interrupt(); } catch (IOException x) { trouble = true; } } /** * 打印boolean值,先将boolean值转为为字符串true和false基于平台默认的字符编码方式写入底层 * 输出流中 */ public void print(boolean b) { write(b ? "true" : "false"); } /** * 打印单个字符,基于平台默认的字符编码方式写入底层输出流中 */ public void print(char c) { write(c); } /** * 打印指定整数i,先将整数转为字符串,基于平台默认的字符编码方式写入底层输出流out中 */ public void print(int i) { write(String.valueOf(i)); } /** * 打印指定长整形l,先将长整形转为字符串,基于平台默认的字符编码方式写入底层输出流out中 */ public void print(long l) { write(String.valueOf(l)); } /** * 打印指定浮点数f,先将浮点形转为字符串,基于平台默认的字符编码方式写入底层输出流out中 */ public void print(float f) { write(String.valueOf(f)); } /** * 打印指定双精度浮点数d,先将浮点数d转为字符串,基于平台默认的字符编码方式写入底层输出流out中 */ public void print(double d) { write(String.valueOf(d)); } /** * 打印指定字符数组s,基于平台默认的字符编码方式写入底层输出流 */ public void print(char s[]) { write(s); } /** * 打印对象obj,先将obj转为字符串形式,再基于平台默认的字符编码方式写入底层输出流 */ public void print(Object obj) { write(String.valueOf(obj)); } /* Methods that do terminate lines */ /** * 底层字符输出流写入行分隔符终止当前行,行分隔符由系统属性line.seperator定义。 */ public void println() { newLine(); } /** * 打印boolean值,然后终止该行,实质就是在底层字符输出流将boolean转为字符串后写入然后在后面写入行分隔符结束该行 */ public void println(boolean x) { synchronized (lock) { print(x); println(); } } /** * 打印字符x,然后终止该行,实质就是在底层字符输出流写入字符x然后在后面写入行分隔符结束该行 */ public void println(char x) { synchronized (lock) { print(x); println(); } } /** * 打印int值x,然后终止该行,实质就是在底层字符输出流将int转为字符串后写入然后在后面写入行分隔符结束该行 */ public void println(int x) { synchronized (lock) { print(x); println(); } } /** * 打印long值x,然后终止该行,实质就是在底层字符输出流将long转为字符串后写入然后在后面写入行分隔符结束该行 */ public void println(long x) { synchronized (lock) { print(x); println(); } } /** * 打印float值x,然后终止该行,实质就是在底层字符输出流将float转为字符串后写入然后在后面写入行分隔符结束该行 */ public void println(float x) { synchronized (lock) { print(x); println(); } } /** * 打印double值x,然后终止该行,实质就是在底层字符输出流将double 转为字符串后写入然后在后面写入行分隔符结束该行 */ public void println(double x) { synchronized (lock) { print(x); println(); } } /** * 打印字符数组x,然后终止该行,实质就是在底层字符输出流写入字符数组x然后在后面写入行分隔符结束该行 */ public void println(char x[]) { synchronized (lock) { print(x); println(); } } /** * 打印字符串x然后换行. 实质就是在底层字符输出流写入字符串x然后在后面写入行分隔符结束该行 */ public void println(String x) { synchronized (lock) { print(x); println(); } } /** * 打印对象x,然后换行。实质就是获取对象x的字符串形式str在底层字符输出流写入str然后在后面写入行分隔符结束该行 */ public void println(Object x) { String s = String.valueOf(x); synchronized (lock) { print(s); println(); } } /** * 基于方法指定的格式化字符串format,将参数格式化的字符串写入该PrintWriter底层字符输出流out * 若启用自动刷新autoflush则刷新底层字符输出流out的缓冲区,作用和format(String format, Object ... args) * 相同 */ public PrintWriter printf(String format, Object ... args) { return format(format, args); } /** * 基于方法指定的格式化字符串format和指定地区对象l,将参数格式化的字符串写入该PrintWriter底层字符输出流out * 若启用自动刷新autoflush则刷新底层字符输出流out的缓冲区,和format(Locale l,String format, Object ... * args)方法相同 */ public PrintWriter printf(Locale l, String format, Object ... args) { return format(l, format, args); } /** * 基于方法指定的格式化字符串format,将参数格式化的字符串写入该PrintWriter底层字符输出流out * 若启用自动刷新autoflush则刷新底层字符输出流out的缓冲区 */ public PrintWriter format(String format, Object ... args) { try { synchronized (lock) { ensureOpen(); if ((formatter == null) || (formatter.locale() != Locale.getDefault())) formatter = new Formatter(this); formatter.format(Locale.getDefault(), format, args); if (autoFlush) out.flush(); } } catch (InterruptedIOException x) { Thread.currentThread().interrupt(); } catch (IOException x) { trouble = true; } return this; } /** * 基于方法指定的格式化字符串format和指定地区对象l,将参数格式化的字符串写入该PrintWriter底层字符输出流out * 若启用自动刷新autoflush则刷新底层字符输出流out的缓冲区 */ public PrintWriter format(Locale l, String format, Object ... args) { try { synchronized (lock) { ensureOpen(); if ((formatter == null) || (formatter.locale() != l)) formatter = new Formatter(this, l); formatter.format(l, format, args); if (autoFlush) out.flush(); } } catch (InterruptedIOException x) { Thread.currentThread().interrupt(); } catch (IOException x) { trouble = true; } return this; } /** * 往PrintWriter底层字符输出流追加指定字符序列csq,通过该方法可能将整个字符序列的字符数据写入底层输出流out也可 * 能只写入一部分,具体取决于字符序列的toString方法实现 */ public PrintWriter append(CharSequence csq) { if (csq == null) write("null"); else write(csq.toString()); return this; } /** * 往PrintWriter底层字符输出流追加指定字符序列csq下标start到end不包括end的子字符序列 * 注意,通过该方法可能将整个字符序列的字符数据写入底层输出流out也可能只写入一部分,具体取决于字符序列的 * toString方法实现 */ public PrintWriter append(CharSequence csq, int start, int end) { CharSequence cs = (csq == null ? "null" : csq); write(cs.subSequence(start, end).toString()); return this; } /** * 往PrintWriter底层字符输出流追加指定字符 */ public PrintWriter append(char c) { write(c); return this; }