PrintWriter
缓冲
Java默认的缓冲区大小是8kb的字节缓冲。也就是8192个字节。
缓冲的作用
应用程序每一次进行I/O操作都需要和设备进行通信,而且通信效率非常低。因此缓冲区的存在,大大提高了I/O效率。
例如写入设备时,每一次通信时,尽可能多的将字节流写入缓冲区,然后等到缓冲区满了(使用flush()
或者close()
),将数据一次性写入设备。这样一来,避免每写入一个字节都需要与设备进行一次通信的操作。大大提高了效率。
flush()&close()
代码示例1
package flush;
import java.io.PrintWriter;
public class TestFlush{
public static void main(String[] args) {
String s = "Hello World!\n";
PrintWriter p = null;
try {
p = new PrintWriter(System.out);
p.write(s);
//p.flush(); //1
}catch(Exception e) {
e.printStackTrace();
}finally{
//p.close(); //2
}
}
}
flush()
当缓冲区没有满时,无法将字节流输出到目的地。flush()
的主要作用就是强制将缓冲区存储的剩余字节清出,输出到目的地。
如果目的地是另一个字节流或者字符流,它也会被flush,输出给下一个目的地。所以一旦flush()
调用,整个输出链都会被flush,输出给最后的目的地。
如果最终目的地是底层系统提供的抽象的目的地(例如file),flush()
只能够保证之前写入缓冲的字节可以作为底层系统去写入的数据源,但是不能保证写入硬盘。
以PrintWriter为例,它的flush()
实际上调用的是成员变量out
的方法,类型是Writer。
不使用flush()
在代码示例1中,注释了代码行1
和2
,Console并不会输出Hello World!
。这是因为缓冲区没有满,不会自动输出。
使用flush()
将代码中示例1中注释1
去掉,执行,最终Console会输出:Hello World!
。这是因为flush()
强制清空缓冲区,输出到目的地。
close()
由于这里以PrintWriter为例,带有缓冲区,所以调用close()
后首先回去flush缓冲区。而如果是OutputStream,则只会关闭流。
关闭流同时会释放所有与该流关联的系统资源。一个被关闭的流无法在进行相应的I/O操作,而且无法被重新打开。
一旦流最外层包装类调用了close()
,整个链中的流对象都会被close。
将代码示例1中注释2
去掉,执行,最终Console一样输出了:Hello World!
。
参考
PrintWriter中的缓冲区
构造器
- PrintWriter(File file)
- PrintWriter(File file, String csn)
- PrintWriter(OutputStream out)
- PrintWriter(OutputStream out, boolean autoFlush)
- PrintWriter(String fileName)
- PrintWriter(String fileName, String csn)
- PrintWriter(Writer out)
- PrintWriter(Writer out, boolean autoFlush)
有无缓冲区
前六种构造器都是通过BufferedWriter包装后作为参数使用最后一种构造器创建PrintWriter对象。所以使用前六种构造器创建的PrintWriter对象一定有缓冲区。
而使用后两种构造器创建的PrintWriter对象有无缓冲区取决于传递的Writer参数自身有无缓冲区。PrintWriter类并不会自动提供缓冲区。
实际上在使用BufferedWriter包装之前,还会使用OutputStreamWriter进行包装。其作用是用指定或者默认的字符集对字节进行编码,转换成字符。而OutputStreamWriter内部通过StreamEncoder实现。在StreamEncoder中内置了8kb的字节缓冲区。而前六种构造器都是这样包装,生成流。
BufferedWriter作用
既然有无BufferedWriter的存在,PrintWriter的前六种创建的对象必定有缓冲区,那么BufferedWriter对于PrintWriter存在的必要性是什么?BufferedWriter主要作用是减少方法调用的次数。这种优化在大量数据输出时尤为明显。
/**
* Flushes the stream.
* @see #checkError()
*/
public void flush() {
try {
synchronized (lock) {
ensureOpen();
out.flush();
}
}
catch (IOException x) {
trouble = true;
}
}
举个例子,在源代码中可以PrintWriter.flush()
实现是去调用成员变量out
的flush()
。而out
是通过构造器初始化的,对于前六种构造器out
变量的实际类型就是BufferedWriter。所以会直接去调用BufferedWriter.flush()。
而如果使用PrintWriter(OutputStreamWriter(new FileOutputStream(fileName)))
来创建对象,保证了对象拥有缓冲区,但是out
实际类型就是OutputStreamWriter。一旦调用PrintWriter.flush()
后回去调用OutputStreamWriter.flush()
,而它内部回去调用StreamEncoder.flush()
。
构造器参数csn和autoFlush
参数csn用于指定字符集(Charset),然后通过toCharset()
转换为Charset对象。
/**
* Returns a charset object for the given charset name.
* @throws NullPointerException is csn is null
* @throws UnsupportedEncodingException if the charset is not supported
*/
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);
}
}
最后使用私有构造器
private PrintWriter(Charset charset, File file)
throws FileNotFoundException
{
this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), charset)),
false);
}
创建对象。可以看到最后还是用了最后一种构造器。
参数autoFlush如果为true,println, printf, or format
方法会触发缓冲区flush操作。
参考
作用
PrintWriter是字符型打印输出流,继承于Writer。可以对字符或者字符序列进行格式化。属于处理流。
PrintWriter优势
除了构造器,PrintWriter其他方法都不会抛出I/O异常。
如果要检查I/O操作过程中是否有错误,可以调用checkError()
进行查询。如果流还没有关闭,会进行flush,同时检查有无错误。如果有错误,就会返回true。出错可能在底层流输出时或者格式化时。
常用方法
write()
public void write(int c) {
try {
synchronized (lock) {
ensureOpen();
out.write(c);
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
write()
的实现主要是利用成员变量out.write()
,而且文档中说明:write()并不会直接从Writer基类中继承,因为PrintWriter需要压制异常。
一旦发生IOException就会将trouble至为true,通过checkError()
可以检测有无错误。
append()
该系列重载方法,将指定的字符或者字符序列追加到PrintWriter流中。其内部实现主要是利用了write()
方法。
public PrintWriter append(CharSequence csq) {
if (csq == null)
write("null");
else
write(csq.toString());
return this;
}
print()
该系列重载方法,将指定类型的数值转换为String、字符、字符数组、字符序列等参数通过内部write()方法添加到PrintWriter流中。
public void print(boolean b) {
write(b ? "true" : "false");