流的概念和作用
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
Java流操作有关的类或接口:
File类
File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。 File类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法。
RandomAccessFile类
该对象并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区(字符数组),通过内部的指针来操作字符数组中的数据。 该对象特点:
- 该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象。
- 该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw)
注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。 可以用于多线程下载或多个线程同时写数据到文件。
Java流类图结构:
java输入/输出流体系中常用的流的分类表
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutputStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream | ||
抽象基类 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
打印流 | PrintStream | PrintWriter | ||
推回输入流 | PushbackInputStream | PushbackReader | ||
特殊流 | DataInputStream | DataOutputStream |
注:表中粗体字所标出的类代表节点流,必须直接与指定的物理节点关联:红色斜体字标出的类代表抽象基类,无法直接创建实例。
- FileInputStream类的使用:读取文件内容
package com.app; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; public class A1 { public static void main(String[] args) {
A1 a1 = new A1(); //电脑d盘中的abc.txt 文档
String filePath = "D:/abc.txt" ;
String reslut = a1.readFile( filePath ) ;
System.out.println( reslut );
} /**
* 读取指定文件的内容
* @param filePath : 文件的路径
* @return 返回的结果
*/
public String readFile( String filePath ){
FileInputStream fis=null;
String result = "" ;
try {
// 根据path路径实例化一个输入流的对象
fis = new FileInputStream( filePath ); //2. 返回这个输入流中可以被读的剩下的bytes字节的估计值;
int size = fis.available() ;
//3. 根据输入流中的字节数创建byte数组;
byte[] array = new byte[size];
//4.把数据读取到数组中;
fis.read( array ) ; //5.根据获取到的Byte数组新建一个字符串,然后输出;
result = new String(array); } catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}finally{
if ( fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} return result ;
} }
- FileOutputStream 类的使用:将内容写入文件
package com.app;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException; public class A2 { public static void main(String[] args) {
A2 a2 = new A2(); //电脑d盘中的abc.txt 文档
String filePath = "D:/abc.txt" ; //要写入的内容
String content = "今天是2017/1/9,天气很好" ;
a2.writeFile( filePath , content ) ; } /**
* 根据文件路径创建输出流
* @param filePath : 文件的路径
* @param content : 需要写入的内容
*/
public void writeFile( String filePath , String content ){
FileOutputStream fos = null ;
try {
//1、根据文件路径创建输出流
fos = new FileOutputStream( filePath ); //2、把string转换为byte数组;
byte[] array = content.getBytes() ;
//3、把byte数组输出;
fos.write( array ); } catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}finally{
if ( fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} }
注意:
- 在实际的项目中,所有的IO操作都应该放到子线程中操作,避免堵住主线程。
-
FileInputStream
在读取文件内容的时候,我们传入文件的路径("D:/abc.txt"
), 如果这个路径下的文件不存在,那么在执行readFile()
方法时会报FileNotFoundException
异常。 -
FileOutputStream
在写入文件的时候,我们传入文件的路径("D:/abc.txt"
), 如果这个路径下的文件不存在,那么在执行writeFile()
方法时, 会默认给我们创建一个新的文件。还有重要的一点,不会报异常。
缓冲流
首先抛出一个问题,有了InputStream
为什么还要有BufferedInputStream
?
BufferedInputStream
和BufferedOutputStream
这两个类分别是FilterInputStream
和FilterOutputStream
的子类,作为装饰器子类,使用它们可以防止每次读取/发送数据时进行实际的写操作,代表着使用缓冲区。
我们有必要知道不带缓冲的操作,每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低。带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多!
同时正因为它们实现了缓冲功能,所以要注意在使用BufferedOutputStream
写完数据后,要调用flush()
方法或close()
方法,强行将缓冲区中的数据写出。否则可能无法写出数据。与之相似还BufferedReader
和BufferedWriter
两个类。
现在就可以回答在本文的开头提出的问题:
BufferedInputStream
和BufferedOutputStream
类就是实现了缓冲功能的输入流/输出流。使用带缓冲的输入输出流,效率更高,速度更快。
总结:
BufferedInputStream
是缓冲输入流。它继承于FilterInputStream
。BufferedInputStream
的作用是为另一个输入流添加一些功能,例如,提供“缓冲功能”以及支持mark()标记
和reset()重置方法
。BufferedInputStream
本质上是通过一个内部缓冲区数组实现的。例如,在新建某输入流对应的BufferedInputStream
后,当我们通过read()
读取输入流的数据时,BufferedInputStream
会将该输入流的数据分批的填入到缓冲区中。每当缓冲区中的数据被读完之后,输入流会再次填充数据缓冲区;如此反复,直到我们读完输入流数据位置。
BufferedInputStream API简介
源码关键字段分析
private static int defaultBufferSize = 8192;//内置缓存字节数组的大小 8KB protected volatile byte buf[]; //内置缓存字节数组 protected int count; //当前buf中的字节总数、注意不是底层字节输入流的源中字节总数 protected int pos; //当前buf中下一个被读取的字节下标 protected int markpos = -1; //最后一次调用mark(int readLimit)方法记录的buf中下一个被读取的字节的位置 protected int marklimit; //调用mark后、在后续调用reset()方法失败之前云寻的从in中读取的最大数据量、用于限制被标记后buffer的最大值
构造函数
BufferedInputStream(InputStream in) //使用默认buf大小、底层字节输入流构建bis BufferedInputStream(InputStream in, int size) //使用指定buf大小、底层字节输入流构建bis
一般方法介绍
int available(); //返回底层流对应的源中有效可供读取的字节数 void close(); //关闭此流、释放与此流有关的所有资源 boolean markSupport(); //查看此流是否支持mark void mark(int readLimit); //标记当前buf中读取下一个字节的下标 int read(); //读取buf中下一个字节 int read(byte[] b, int off, int len); //读取buf中下一个字节 void reset(); //重置最后一次调用mark标记的buf中的位子 long skip(long n); //跳过n个字节、 不仅仅是buf中的有效字节、也包括in的源中的字节
BufferedOutputStream API简介
关键字段
protected byte[] buf; //内置缓存字节数组、用于存放程序要写入out的字节 protected int count; //内置缓存字节数组中现有字节总数
构造函数
BufferedOutputStream(OutputStream out); //使用默认大小、底层字节输出流构造bos。默认缓冲大小是 8192 字节( 8KB ) BufferedOutputStream(OutputStream out, int size); //使用指定大小、底层字节输出流构造bos
构造函数源码:
/**
* Creates a new buffered output stream to write data to the
* specified underlying output stream.
* @param out the underlying output stream.
*/
public BufferedOutputStream(OutputStream out) {
this(out, 8192);
} /**
* Creates a new buffered output stream to write data to the
* specified underlying output stream with the specified buffer
* size.
*
* @param out the underlying output stream.
* @param size the buffer size.
* @exception IllegalArgumentException if size <= 0.
*/
public BufferedOutputStream(OutputStream out, int size) {
super(out);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
一般方法
//在这里提一句,`BufferedOutputStream`没有自己的`close`方法,
//当他调用父类`FilterOutputStrem`的方法关闭时,会间接调用自己实现的`flush`方法将buf中残存的字节flush到out中,
//再`out.flush()`到目的地中,DataOutputStream也是如此。 void flush(); 将写入bos中的数据flush到out指定的目的地中、注意这里不是flush到out中、因为其内部又调用了out.flush() write(byte b); 将一个字节写入到buf中 write(byte[] b, int off, int len); 将b的一部分写入buf中
那么什么时候flush()才有效呢?
答案是:当OutputStream是BufferedOutputStream时。
当写文件需要flush()的效果时,需要
FileOutputStream fos = new FileOutputStream("c:\a.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
也就是说,需要将FileOutputStream作为BufferedOutputStream构造函数的参数传入,然后对BufferedOutputStream进行写入操作,才能利用缓冲及flush()。
查看BufferedOutputStream的源代码,发现所谓的buffer其实就是一个byte[]。
BufferedOutputStream的每一次write其实是将内容写入byte[],当buffer容量到达上限时,会触发真正的磁盘写入。
而另一种触发磁盘写入的办法就是调用flush()了。
1.BufferedOutputStream
在close()
时会自动flush
2.BufferedOutputStream
在不调用close()
的情况下,缓冲区不满,又需要把缓冲区的内容写入到文件或通过网络发送到别的机器时,才需要调用flush.
用缓冲流复制文件
package com.app;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; public class A3 { public static void main(String[] args) throws IOException { String filePath = "F:/123.png" ;
String filePath2 = "F:/abc.png" ;
File file = new File( filePath ) ;
File file2 = new File( filePath2 ) ;
copyFile( file , file2 ); } /**
* 复制文件
* @param oldFile
* @param newFile
*/
public static void copyFile( File oldFile , File newFile){
InputStream inputStream = null ;
BufferedInputStream bufferedInputStream = null ; OutputStream outputStream = null ;
BufferedOutputStream bufferedOutputStream = null ; try {
inputStream = new FileInputStream( oldFile ) ;
bufferedInputStream = new BufferedInputStream( inputStream ) ; outputStream = new FileOutputStream( newFile ) ;
bufferedOutputStream = new BufferedOutputStream( outputStream ) ; byte[] b=new byte[1024]; //代表一次最多读取1KB的内容 int length = 0 ; //代表实际读取的字节数
while( (length = bufferedInputStream.read( b ) )!= -1 ){
//length 代表实际读取的字节数
bufferedOutputStream.write(b, 0, length );
}
//缓冲区的内容写入到文件
bufferedOutputStream.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}finally { if( bufferedOutputStream != null ){
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} if( bufferedInputStream != null){
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} if( inputStream != null ){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} if ( outputStream != null ) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} }
}
}
如何正确的关闭流
在上面的代码中,我们关闭流的代码是这样写的。
finally { if( bufferedOutputStream != null ){
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} if( bufferedInputStream != null){
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} if( inputStream != null ){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} if ( outputStream != null ) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} }
思考:在处理流关闭完成后,我们还需要关闭节点流吗? 让我们带着问题去看源码: bufferedOutputStream.close(); /**
* Closes this input stream and releases any system resources
* associated with the stream.
* Once the stream has been closed, further read(), available(), reset(),
* or skip() invocations will throw an IOException.
* Closing a previously closed stream has no effect.
*
* @exception IOException if an I/O error occurs.
*/
public void close() throws IOException {
byte[] buffer;
while ( (buffer = buf) != null) {
if (bufUpdater.compareAndSet(this, buffer, null)) {
InputStream input = in;
in = null;
if (input != null)
input.close();
return;
}
// Else retry in case a new buf was CASed in fill()
}
}
close()方法的作用
1、关闭输入流,并且释放系统资源
2、BufferedInputStream装饰一个 InputStream 使之具有缓冲功能,is要关闭只需要调用最终被装饰出的对象的 close()方法即可,因为它最终会调用真正数据源对象的 close()方法。因此,可以只调用外层流的close方法关闭其装饰的内层流。
那么如果我们想逐个关闭流,我们该怎么做?
答案是:先关闭外层流,再关闭内层流。一般情况下是:先打开的后关闭,后打开的先关闭;另一种情况:看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b。例如处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b
看懂了怎么正确的关闭流之后,那么我们就可以优化上面的代码了,只关闭外层的处理流。
finally { if( bufferedOutputStream != null ){
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} if( bufferedInputStream != null){
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
BufferedReader
- 构造函数
BufferedReader(Reader in, int sz) //创建一个使用指定大小输入缓冲区的缓冲字符输入流。 BufferedReader(Reader in) //创建一个使用默认大小输入缓冲区的缓冲字符输入流。
- 方法
int read() //读取单个字符。
int read(char[] cbuf, int off, int len) //将字符读入数组的某一部分。
String readLine() //读取一个文本行。
boolean ready() //判断此流是否已准备好被读取。
void reset() //将流重置到最新的标记。
long skip(long n) //跳过字符。
void close() //关闭该流并释放与之关联的所有资源。
void mark(int readAheadLimit) //标记流中的当前位置。
boolean markSupported() //判断此流是否支持 mark() 操作(它一定支持)。
BufferedWriter
- 构造函数
BufferedWriter(Writer out, int sz) //创建一个使用给定大小输出缓冲区的新缓冲字符输出流。 BufferedWriter(Writer out) //建一个使用默认大小输出缓冲区的缓冲字符输出流。
- 方法
void close() // 关闭此流,但要先刷新它。
void flush() //刷新该流的缓冲。
void newLine() //写入一个行分隔符。
void write(char[] cbuf, int off, int len) //写入字符数组的某一部分。
void write(int c) //写入单个字符。
void write(String s, int off, int len) //写入字符串的某一部分。
实战演练
复制F盘里面的一个txt文本
package com.app; import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer; public class A4 {
public static void main(String[] args) { String filePath = "F:/123.txt" ;
String filePath2 = "F:/abc.txt" ; File file = new File( filePath ) ;
File file2 = new File( filePath2 ) ;
copyFile( file , file2 );
} private static void copyFile( File oldFile , File newFile ){
Reader reader = null ;
BufferedReader bufferedReader = null ; Writer writer = null ;
BufferedWriter bufferedWriter = null ;
try {
reader = new FileReader( oldFile ) ;
bufferedReader = new BufferedReader( reader ) ; writer = new FileWriter( newFile ) ;
bufferedWriter = new BufferedWriter( writer ) ; String result = null ; //每次读取一行的内容
while ( (result = bufferedReader.readLine() ) != null ){
bufferedWriter.write( result ); //把内容写入文件
bufferedWriter.newLine(); //换行,result 是一行数据,所以没写一行就要换行
} bufferedWriter.flush(); //强制把数组内容写入文件 } catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}finally {
try {
bufferedWriter.close(); //关闭输出流
} catch (IOException e) {
e.printStackTrace();
} try {
bufferedReader.close(); //关闭输入流
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
转换流
InputStreamReader
简介
InputStreamReader
是字符流Reader
的子类,是字节流通向字符流的桥梁。你可以在构造器重指定编码的方式,如果不指定的话将采用底层操作系统的默认编码方式,例如 GBK 等。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。一次只读一个字符。
-
InputStreamReader
构造函数
InputStreamReader(Inputstream in) //创建一个使用默认字符集的 InputStreamReader。 InputStreamReader(Inputstream in,Charset cs) //创建使用给定字符集的 InputStreamReader。 InputStreamReader(InputStream in, CharsetDecoder dec) //创建使用给定字符集解码器的 InputStreamReader。 InputStreamReader(InputStream in, String charsetName) //创建使用指定字符集的 InputStreamReader。
- 一般方法
void close() // 关闭该流并释放与之关联的所有资源。 String getEncoding() //返回此流使用的字符编码的名称。 int read() //读取单个字符。 int read(char[] cbuf, int offset, int length) //将字符读入数组中的某一部分。 boolean ready() //判断此流是否已经准备好用于读取。
OutputStreamWriter
简介
OutputStreamWriter
是字符流Writer
的子类,是字符流通向字节流的桥梁。每次调用 write()
方法都会导致在给定字符(或字符集)上调用编码转换器。在写入底层输出流之前,得到的这些字节将在缓冲区中累积。一次只写一个字符。
-
OutputStreamWriter
构造函数
OutputStreamWriter(OutputStream out) //创建使用默认字符编码的 OutputStreamWriter OutputStreamWriter(OutputStream out, String charsetName) //创建使用指定字符集的 OutputStreamWriter。 OutputStreamWriter(OutputStream out, Charset cs) //创建使用给定字符集的 OutputStreamWriter。 OutputStreamWriter(OutputStream out, CharsetEncoder enc) //创建使用给定字符集编码器的 OutputStreamWriter。
- 一般方法
void write(int c) //写入的字符长度 void write(char cbuf[]) //写入的字符数组 void write(String str) //写入的字符串 void write(String str, int off, int len) //应该写入的字符串,开始写入的索引位置,写入的长度 void close() //关闭该流并释放与之关联的所有资源。
需要注意的事项
InputStreamReader
、OutputStreamWriter
实现从字节流到字符流之间的转换,使得流的处理效率得到提升,但是如果我们想要达到最大的效率,我们应该考虑使用缓冲字符流包装转换流的思路来解决问题。比如:
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
实战演练,复制文本
package com.app; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter; public class A5 { public static void main(String[] args) {
String filePath = "F:/123.txt" ;
String filePath2 = "F:/abc.txt" ;
File file = new File( filePath ) ;
File file2 = new File( filePath2 ) ;
copyFile( file , file2 ); } private static void copyFile( File oldFile , File newFile ){
InputStream inputStream = null ;
InputStreamReader inputStreamReader = null ; OutputStream outputStream = null ;
OutputStreamWriter outputStreamWriter = null ; try {
inputStream = new FileInputStream( oldFile ) ; //创建输入流
inputStreamReader = new InputStreamReader( inputStream ) ; //创建转换输入流 outputStream = new FileOutputStream( newFile ) ; //创建输出流
outputStreamWriter = new OutputStreamWriter( outputStream ) ; //创建转换输出流 int result = 0 ; while( (result = inputStreamReader.read()) != -1){ //一次只读一个字符
outputStreamWriter.write( result ); //一次只写一个字符
} outputStreamWriter.flush(); //强制把缓冲写入文件 } catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}finally{ if ( outputStreamWriter != null) {
try {
outputStreamWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
} if ( inputStreamReader != null ) {
try {
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} }
}
PrintWriter
1、 类功能简介:
打印字符流、用于将各种java数据一字符串的形式打印到底层字符输出流中、本身不会产生任何IOException、但是可以通过他的一个方法来查看是否抛出异常、可以指定autoFlush、若为true则当调用newLine、println、format方法时都会自动刷新、即将底层字符输出流out中的字符flush到目的地中。对PrintWriter有许多构造方法、和一般方法、但是这些方法都有个核心方法、构造方法:一个是传入字符流时的构造方法PrintWriter(Writer out, autoFlush)、一个是传入字节流时的构造方法PrintWriter(OutputStream out, boolean autoFlush)、一般方法:一个是write(char[] cbuf, int off, int len)、一个是write(String, int off, int len);
2、PrintWriter API简介:
A:关键字
B:构造方法
C:一般方法
3、源码分析
package com.chy.io.original.code; import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.util.Formatter;
import java.util.IllegalFormatException;
import java.util.Locale; /**
*
* @version 1.1, 13/11/17
* @author andyChen
*/ public class PrintWriter extends Writer { /**
* 被PrintWriter装饰的底层Writer、OutputStream实现类out
*/
protected Writer out; //是否自动刷新
private boolean autoFlush = false; //是否抛异常
private boolean trouble = false; //格式化类
private Formatter formatter; //字节打印流、用于checkError方法
private PrintStream psOut = null; /**
* 换行符
*/
private String lineSeparator; /**
* 根据传入的Writer实现类out创建PrintWriter、不具有自动flush功能。
*/
public PrintWriter (Writer out) {
this(out, false);
} /**
* 创建PrintWriter、指定autoFlush、并且根据平台不同初始化linSeparator
*/
public PrintWriter(Writer out,
boolean autoFlush) {
super(out);
this.out = out;
this.autoFlush = autoFlush;
lineSeparator = (String) java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("line.separator"));
} /**
* 根据OutputStream out创建PrintWriter。不具有自动flush功能。
*/
public PrintWriter(OutputStream out) {
this(out, false);
} /**
* 根据底层OutputStream out创建pw、指定是否具有autoFlush功能。
* 本质只是将out转换成writer、然后为其添加缓冲功能、再用PrintWriter装饰一下。
* 如果传入的是PrintStream、则赋给全局变量psOut
*/
public PrintWriter(OutputStream out, boolean autoFlush) {
this(new BufferedWriter(new OutputStreamWriter(out)), autoFlush); // save print stream for error propagation
if (out instanceof java.io.PrintStream) {
psOut = (java.io.PrintStream) out;
}
} /**
* 根据传入的文件名构造不具有自动flush功能的PrintWriter。
* 本质还是根据文件名创建文件字节输出流out、再将out转换成writer、对其添加缓冲功能、最后用pw装饰。
*/
public PrintWriter(String fileName) throws FileNotFoundException {
this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName))),
false);
} /**
* 与上面一样、只是多了一个可以使用指定编码读取的功能。
*/
public PrintWriter(String fileName, String csn)
throws FileNotFoundException, UnsupportedEncodingException
{
this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName), csn)),
false);
} /**
* 根据传入的文件构造不具有自动flush功能的PrintWriter。
* 本质还是根据文件名创建文件字节输出流out、再将out转换成writer、对其添加缓冲功能、最后用pw装饰。
*/
public PrintWriter(File file) throws FileNotFoundException {
this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))),
false);
} /**
* 与上面一样、只是多了一个可以使用指定编码读取的功能。
*/
public PrintWriter(File file, String csn)
throws FileNotFoundException, UnsupportedEncodingException
{
this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), csn)),
false);
} /** 检测底层流是否关闭 */
private void ensureOpen() throws IOException {
if (out == null)
throw new IOException("Stream closed");
} /**
* flush底层流
*/
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;
}
} /**
* 同PrintStream一样、不会抛出IO异常、出现异常时只会自己内部消化掉、
* 可以通过此方法来查看print过程中是否有异常出现。
*/
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;
} /**
* 可以显示的设置print产生异常。
*/
protected void setError() {
trouble = true;
} /**
* 清除异常信息
*/
protected void clearError() {
trouble = false;
} /**
* 写入一个字符、
* 这里产生IOException的原因是此方法是继承自Writer
*/
public void write(int c) {
try {
synchronized (lock) {
ensureOpen();
out.write(c);
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
} /**
* 将从下标off开始、len个字符写入到out中。
* 这里也许会有个疑问?当传入的out是OutputStream时还能使用
* out.write(char[] buf, int off, int len)吗?
* 可以:当传入OutputStream时、调用的构造方法内部会将OutputStream
* 包装成OutputStreamWriter、同时使用BufferedWriter来封装、最后再用PrintWriter来装饰。
* 这里的out表示的已经变成BufferedWriter。
*/
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;
}
} /**
* 将一个字符数组中所有字符写入到out中
*/
public void write(char buf[]) {
write(buf, 0, buf.length);
} /**
* 将s的一部分写入out中
*/
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;
}
} /**
* 将String写入out中
*/
public void write(String s) {
write(s, 0, s.length());
} /**
* 将一个换行符写入out中、如果设置了自动刷新、则刷新out。
*/
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;
}
} /**
* 将一个boolean写入out中、不管有没有设置autoFlush、都不会自动flush
*/
public void print(boolean b) {
write(b ? "true" : "false");
} /**
* 将一个char写入out中、
*/
public void print(char c) {
write(c);
} /**
* 将一个int写入out中、
*/
public void print(int i) {
write(String.valueOf(i));
} /**
* 将一个long写入out中、
*/
public void print(long l) {
write(String.valueOf(l));
} /**
* 将一个float写入out中、 rinted
*/
public void print(float f) {
write(String.valueOf(f));
} /**
* 将一个double写入out中、
*/
public void print(double d) {
write(String.valueOf(d));
} /**
*将一个char[]写入out中、
*/
public void print(char s[]) {
write(s);
} /**
* 将一个String写入out中、
*/
public void print(String s) {
if (s == null) {
s = "null";
}
write(s);
} /**
* 将一个Object写入out中、
*/
public void print(Object obj) {
write(String.valueOf(obj));
} /* Methods that do terminate lines */ /**
* 将一个换行符写入out中、
*/
public void println() {
newLine();
} /**
* 将一个换行符写入out中、
*/
public void println(boolean x) {
synchronized (lock) {
print(x);
println();
}
} /**
* 将一个换行符写入out中、
*/
public void println(char x) {
synchronized (lock) {
print(x);
println();
}
} /**
* 将一个换行符写入out中、
*/
public void println(int x) {
synchronized (lock) {
print(x);
println();
}
} /**
* 将一个换行符写入out中、
*/
public void println(long x) {
synchronized (lock) {
print(x);
println();
}
} /**
* 将一个换行符写入out中、
*/
public void println(float x) {
synchronized (lock) {
print(x);
println();
}
} /**
* 将一个换行符写入out中、
*/
public void println(double x) {
synchronized (lock) {
print(x);
println();
}
} /**
* 将一个换行符写入out中、
*/
public void println(char x[]) {
synchronized (lock) {
print(x);
println();
}
} /**
* 将一个换行符写入out中、
*/
public void println(String x) {
synchronized (lock) {
print(x);
println();
}
} /**
* 将一个换行符写入out中、
*/
public void println(Object x) {
String s = String.valueOf(x);
synchronized (lock) {
print(s);
println();
}
} /**
* 使用指定格式字符串和参数将格式化的字符串写入此 writer 的便捷方法。
*/
public PrintWriter printf(String format, Object ... args) {
return format(format, args);
} /**
* 使用指定格式字符串和参数将格式化的字符串写入此 writer 的便捷方法。
*/
public PrintWriter printf(Locale l, String format, Object ... args) {
return format(l, format, args);
} /**
*使用指定格式字符串和参数将一个格式化字符串写入此 writer 中。
*/
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;
} /**
*使用指定格式字符串和参数将一个格式化字符串写入此 writer 中。
*/
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;
} /**
* 将一个有序字符序列追加到out中
*/
public PrintWriter append(CharSequence csq) {
if (csq == null)
write("null");
else
write(csq.toString());
return this;
} /**
* 将一个有序字符序列一部分追加到out中
*/
public PrintWriter append(CharSequence csq, int start, int end) {
CharSequence cs = (csq == null ? "null" : csq);
write(cs.subSequence(start, end).toString());
return this;
} /**
* 将一个字符追加到out中
*/
public PrintWriter append(char c) {
write(c);
return this;
}
}
4、实例演示:
package com.chy.io.original.test; import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter; import com.chy.io.original.code.FileWriter;
import com.chy.io.original.utils.StudentDTO; @SuppressWarnings("all")
public class PrintWriterTest { private final static String fileName = "D:\\pw.txt";
private final static File file = new File(fileName);
private final static String targetStr = "马心心"; public static void showPrintWriterconstructor() throws IOException{ //下面这一组个构造方法效果一样、使用默认编码
PrintWriter pw1 = new PrintWriter(new FileWriter(file));
PrintWriter pw2 = new PrintWriter(new FileWriter(fileName));
PrintWriter pw3 = new PrintWriter(file);
PrintWriter pw4 = new PrintWriter(fileName); //下面这组构造方法效果一样、具有自动刷新功能
PrintWriter pw5 = new PrintWriter(new FileWriter(file), true);
PrintWriter pw6 = new PrintWriter(new FileWriter(fileName), true);
PrintWriter pw7 = new PrintWriter(new FileOutputStream(file), true);
PrintWriter pw8 = new PrintWriter(new FileOutputStream(fileName), true); //下面这一组构造方法效果一样、使用指定的GBK编码
PrintWriter pw9 = new PrintWriter(file, "GBK");
PrintWriter pw10 = new PrintWriter(fileName, "GBK");
} /**
* 这里只测试一下向文件流中打印一个中文字符串、然后读取出来查看乱码情况、
* 和写入一个Object对象、再读取出来以字符串的形式打印出来。
*/
public static void testPrintWriterDefault() throws IOException{
PrintWriter pw = new PrintWriter(file);
pw.println(targetStr); StudentDTO student = new StudentDTO(1,"陈华应");
pw.println(student);
pw.close(); BufferedReader br = new BufferedReader(new java.io.FileReader(file));
String str;
while((str = br.readLine()) != null){
System.out.println(str);
}
br.close();
} /**
* 构造打印流时使用指定GBK编码
* @throws IOException
*/
public static void testPrintWriterGBK() throws IOException{
PrintWriter pw = new PrintWriter(file, "GBK");
pw.println(targetStr); StudentDTO student = new StudentDTO(1,"陈华应");
pw.println(student);
pw.close(); BufferedReader br = new BufferedReader(new java.io.FileReader(file));
String str;
while((str = br.readLine()) != null){
System.out.println(str);
}
br.close();
} public static void main(String[] args) throws IOException {
//testPrintWriterDefault();
testPrintWriterGBK();
} }
总结:
PrintWriter与PrintStream很类似、要注意的就是一个是自动刷新的问题、另一个就是PrintStream可以实现将字节写入底层流中write(intb) write(byte[] b, int off, int len)、而PrintWriter没有相关方法来直接将字节写入到out中、还有一点就不管是PrintWriter还是PrintStream、他写入到底层流中的java基础类型或者java对象都是以字符串的形式写入的、所以当我们读取之后要做一些处理、而关于print(Object)只是将Object的表示此对象的Object.toString()这个结果写入到out中、所以读取出来并不是强转一下就可以使用、要想操作java基础类型和对象、应该使用对应的流DataInputStream/DataOutputStream ObjectInputStream/ObjectOutputStream。