流的概念和作用
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
Java流操作有关的类或接口:
File类
File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。 File类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法。
RandomAccessFile类
该对象并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区(字符数组),通过内部的指针来操作字符数组中的数据。 该对象特点:
- 该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象。
- 该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw)
注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。 可以用于多线程下载或多个线程同时写数据到文件。
Java流类图结构:
java输入/输出流体系中常用的流的分类表
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | ||
访问数组 | ||||
访问管道 | ||||
访问字符串 | | | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | | | InputStreamReader | OutputStreamWriter |
| | |||
抽象基类 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
打印流 | | | PrintWriter | |
推回输入流 | | |||
特殊流 | | |
注:表中粗体字所标出的类代表节点流,必须直接与指定的物理节点关联:红色斜体字标出的类代表抽象基类,无法直接创建实例。
- FileInputStream类的使用:读取文件内容
1 package com.app;
2
3 import java.io.FileInputStream;
4 import java.io.FileNotFoundException;
5 import java.io.IOException;
6
7 public class A1 {
8
9 public static void main(String[] args) {
10 A1 a1 = new A1();
11
12 //电脑d盘中的abc.txt 文档
13 String filePath = "D:/abc.txt" ;
14 String reslut = a1.readFile( filePath ) ;
15 System.out.println( reslut );
16 }
17
18
19 /**
20 * 读取指定文件的内容
21 * @param filePath : 文件的路径
22 * @return 返回的结果
23 */
24 public String readFile( String filePath ){
25 FileInputStream fis=null;
26 String result = "" ;
27 try {
28 // 根据path路径实例化一个输入流的对象
29 fis = new FileInputStream( filePath );
30
31 //2. 返回这个输入流中可以被读的剩下的bytes字节的估计值;
32 int size = fis.available() ;
33 //3. 根据输入流中的字节数创建byte数组;
34 byte[] array = new byte[size];
35 //4.把数据读取到数组中;
36 fis.read( array ) ;
37
38 //5.根据获取到的Byte数组新建一个字符串,然后输出;
39 result = new String(array);
40
41 } catch (FileNotFoundException e) {
42 e.printStackTrace();
43 }catch (IOException e) {
44 e.printStackTrace();
45 }finally{
46 if ( fis != null) {
47 try {
48 fis.close();
49 } catch (IOException e) {
50 e.printStackTrace();
51 }
52 }
53 }
54
55 return result ;
56 }
57
58
59 }
- FileOutputStream 类的使用:将内容写入文件
1 package com.app;
2 import java.io.FileNotFoundException;
3 import java.io.FileOutputStream;
4 import java.io.IOException;
5
6 public class A2 {
7
8 public static void main(String[] args) {
9 A2 a2 = new A2();
10
11 //电脑d盘中的abc.txt 文档
12 String filePath = "D:/abc.txt" ;
13
14 //要写入的内容
15 String content = "今天是2017/1/9,天气很好" ;
16 a2.writeFile( filePath , content ) ;
17
18 }
19
20 /**
21 * 根据文件路径创建输出流
22 * @param filePath : 文件的路径
23 * @param content : 需要写入的内容
24 */
25 public void writeFile( String filePath , String content ){
26 FileOutputStream fos = null ;
27 try {
28 //1、根据文件路径创建输出流
29 fos = new FileOutputStream( filePath );
30
31 //2、把string转换为byte数组;
32 byte[] array = content.getBytes() ;
33 //3、把byte数组输出;
34 fos.write( array );
35
36 } catch (FileNotFoundException e) {
37 e.printStackTrace();
38 }catch (IOException e) {
39 e.printStackTrace();
40 }finally{
41 if ( fos != null) {
42 try {
43 fos.close();
44 } catch (IOException e) {
45 e.printStackTrace();
46 }
47 }
48 }
49 }
50
51
52 }
注意:
- 在实际的项目中,所有的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.
用缓冲流复制文件
1 package com.app;
2 import java.io.BufferedInputStream;
3 import java.io.BufferedOutputStream;
4 import java.io.File;
5 import java.io.FileInputStream;
6 import java.io.FileNotFoundException;
7 import java.io.FileOutputStream;
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.io.OutputStream;
11
12
13 public class A3 {
14
15 public static void main(String[] args) throws IOException {
16
17 String filePath = "F:/123.png" ;
18 String filePath2 = "F:/abc.png" ;
19 File file = new File( filePath ) ;
20 File file2 = new File( filePath2 ) ;
21 copyFile( file , file2 );
22
23 }
24
25 /**
26 * 复制文件
27 * @param oldFile
28 * @param newFile
29 */
30 public static void copyFile( File oldFile , File newFile){
31 InputStream inputStream = null ;
32 BufferedInputStream bufferedInputStream = null ;
33
34 OutputStream outputStream = null ;
35 BufferedOutputStream bufferedOutputStream = null ;
36
37 try {
38 inputStream = new FileInputStream( oldFile ) ;
39 bufferedInputStream = new BufferedInputStream( inputStream ) ;
40
41 outputStream = new FileOutputStream( newFile ) ;
42 bufferedOutputStream = new BufferedOutputStream( outputStream ) ;
43
44 byte[] b=new byte[1024]; //代表一次最多读取1KB的内容
45
46 int length = 0 ; //代表实际读取的字节数
47 while( (length = bufferedInputStream.read( b ) )!= -1 ){
48 //length 代表实际读取的字节数
49 bufferedOutputStream.write(b, 0, length );
50 }
51 //缓冲区的内容写入到文件
52 bufferedOutputStream.flush();
53 } catch (FileNotFoundException e) {
54 e.printStackTrace();
55 }catch (IOException e) {
56 e.printStackTrace();
57 }finally {
58
59 if( bufferedOutputStream != null ){
60 try {
61 bufferedOutputStream.close();
62 } catch (IOException e) {
63 e.printStackTrace();
64 }
65 }
66
67 if( bufferedInputStream != null){
68 try {
69 bufferedInputStream.close();
70 } catch (IOException e) {
71 e.printStackTrace();
72 }
73 }
74
75 if( inputStream != null ){
76 try {
77 inputStream.close();
78 } catch (IOException e) {
79 e.printStackTrace();
80 }
81 }
82
83 if ( outputStream != null ) {
84 try {
85 outputStream.close();
86 } catch (IOException e) {
87 e.printStackTrace();
88 }
89 }
90
91 }
92 }
93 }
如何正确的关闭流
在上面的代码中,我们关闭流的代码是这样写的。
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文本
1 package com.app;
2
3 import java.io.BufferedReader;
4 import java.io.BufferedWriter;
5 import java.io.File;
6 import java.io.FileNotFoundException;
7 import java.io.FileReader;
8 import java.io.FileWriter;
9 import java.io.IOException;
10 import java.io.Reader;
11 import java.io.Writer;
12
13 public class A4 {
14 public static void main(String[] args) {
15
16 String filePath = "F:/123.txt" ;
17 String filePath2 = "F:/abc.txt" ;
18
19 File file = new File( filePath ) ;
20 File file2 = new File( filePath2 ) ;
21 copyFile( file , file2 );
22 }
23
24 private static void copyFile( File oldFile , File newFile ){
25 Reader reader = null ;
26 BufferedReader bufferedReader = null ;
27
28 Writer writer = null ;
29 BufferedWriter bufferedWriter = null ;
30 try {
31 reader = new FileReader( oldFile ) ;
32 bufferedReader = new BufferedReader( reader ) ;
33
34 writer = new FileWriter( newFile ) ;
35 bufferedWriter = new BufferedWriter( writer ) ;
36
37 String result = null ; //每次读取一行的内容
38 while ( (result = bufferedReader.readLine() ) != null ){
39 bufferedWriter.write( result ); //把内容写入文件
40 bufferedWriter.newLine(); //换行,result 是一行数据,所以没写一行就要换行
41 }
42
43 bufferedWriter.flush(); //强制把数组内容写入文件
44
45 } catch (FileNotFoundException e) {
46 e.printStackTrace();
47 }catch (IOException e) {
48 e.printStackTrace();
49 }finally {
50 try {
51 bufferedWriter.close(); //关闭输出流
52 } catch (IOException e) {
53 e.printStackTrace();
54 }
55
56 try {
57 bufferedReader.close(); //关闭输入流
58 } catch (IOException e) {
59 e.printStackTrace();
60 }
61 }
62 }
63 }
转换流
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));
实战演练,复制文本
1 package com.app;
2
3 import java.io.File;
4 import java.io.FileInputStream;
5 import java.io.FileNotFoundException;
6 import java.io.FileOutputStream;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.io.InputStreamReader;
10 import java.io.OutputStream;
11 import java.io.OutputStreamWriter;
12
13 public class A5 {
14
15 public static void main(String[] args) {
16 String filePath = "F:/123.txt" ;
17 String filePath2 = "F:/abc.txt" ;
18 File file = new File( filePath ) ;
19 File file2 = new File( filePath2 ) ;
20 copyFile( file , file2 );
21
22 }
23
24 private static void copyFile( File oldFile , File newFile ){
25 InputStream inputStream = null ;
26 InputStreamReader inputStreamReader = null ;
27
28 OutputStream outputStream = null ;
29 OutputStreamWriter outputStreamWriter = null ;
30
31 try {
32 inputStream = new FileInputStream( oldFile ) ; //创建输入流
33 inputStreamReader = new InputStreamReader( inputStream ) ; //创建转换输入流
34
35 outputStream = new FileOutputStream( newFile ) ; //创建输出流
36 outputStreamWriter = new OutputStreamWriter( outputStream ) ; //创建转换输出流
37
38 int result = 0 ;
39
40 while( (result = inputStreamReader.read()) != -1){ //一次只读一个字符
41 outputStreamWriter.write( result ); //一次只写一个字符
42 }
43
44 outputStreamWriter.flush(); //强制把缓冲写入文件
45
46 } catch (FileNotFoundException e) {
47 e.printStackTrace();
48 }catch (IOException e) {
49 e.printStackTrace();
50 }finally{
51
52 if ( outputStreamWriter != null) {
53 try {
54 outputStreamWriter.close();
55 } catch (IOException e) {
56 e.printStackTrace();
57 }
58 }
59
60 if ( inputStreamReader != null ) {
61 try {
62 inputStreamReader.close();
63 } catch (IOException e) {
64 e.printStackTrace();
65 }
66 }
67 }
68
69 }
70 }
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:关键字
protected Writer out; 传入的底层字符输出流
private boolean autoFlush = false; 是否自动刷新
private boolean trouble = false; 是否抛异常
private Formatter formatter; 格式化类
private PrintStream psOut = null; 字节打印流、用于checkError方法
B:构造方法
PrintWriter(File file) 使用指定文件创建不具有自动行刷新的新 PrintWriter。
PrintWriter(File file, String csn) 创建具有指定文件和字符集且不带自动刷行新的新 PrintWriter。
PrintWriter(OutputStream out 根据现有的 OutputStream 创建不带自动行刷新的新 PrintWriter。
PrintWriter(OutputStream out, boolean autoFlush) 通过现有的 OutputStream 创建新的 PrintWriter。
PrintWriter(String fileName) 创建具有指定文件名称且不带自动行刷新的新 PrintWriter。
PrintWriter(String fileName, String csn) 创建具有指定文件名称和字符集且不带自动行刷新的新 PrintWriter。
PrintWriter(Writer out) 创建不带自动行刷新的新 PrintWriter。
PrintWriter(Writer out, boolean autoFlush) 创建新 PrintWriter。
C:一般方法
PrintWriter append(char c) 将指定字符添加到此 writer。
PrintWriter append(CharSequence csq) 将指定的字符序列添加到此 writer。
PrintWriter append(CharSequence csq, int start, int end) 将指定字符序列的子序列添加到此 writer。
boolean checkError() 如果流没有关闭,则刷新流且检查其错误状态。
protected void clearError() 清除此流的错误状态。
void close() 关闭该流并释放与之关联的所有系统资源。
void flush() 刷新该流的缓冲。
PrintWriter format(Locale l, String format, Object... args) 使用指定格式字符串和参数将一个格式化字符串写入此 writer 中。
PrintWriter format(String format, Object... args) 使用指定格式字符串和参数将一个格式化字符串写入此 writer 中。
void print(boolean b) 打印 boolean 值。
void print(char c) 打印字符。
void print(char[] s) 打印字符数组。
void print(double d) 打印 double 精度浮点数。
void print(float f) 打印一个浮点数。
void print(int i) 打印整数。
void print(long l) 打印 long 整数。
void print(Object obj) 打印对象。
void print(String s) 打印字符串。
PrintWriter printf(Locale l, String format, Object... args) 使用指定格式字符串和参数将格式化的字符串写入此 writer 的便捷方法。
PrintWriter printf(String format, Object... args) 使用指定格式字符串和参数将格式化的字符串写入此 writer 的便捷方法。
void println() 通过写入行分隔符字符串终止当前行。
void println(boolean x) 打印 boolean 值,然后终止该行。
void println(char x) 打印字符,然后终止该行。
void println(char[] x) 打印字符数组,然后终止该行。
void println(double x) 打印双精度浮点数,然后终止该行。
void println(float x) 打印浮点数,然后终止该行。
void println(int x) 打印整数,然后终止该行。
void println(long x) 打印 long 整数,然后终止该行。
void println(Object x) 打印 Object,然后终止该行。
void println(String x) 打印 String,然后终止该行。
protected void setError() 指示已发生错误。
void write(char[] buf) 写入字符数组。
void write(char[] buf, int off, int len) 写入字符数组的某一部分。
void write(int c) 写入单个字符。
void write(String s) 写入字符串。
void write(String s, int off, int len) 写入字符串的某一部分。
3、源码分析
1 package com.chy.io.original.code;
2
3 import java.io.File;
4 import java.io.FileNotFoundException;
5 import java.io.IOException;
6 import java.io.InterruptedIOException;
7 import java.io.UnsupportedEncodingException;
8 import java.util.Formatter;
9 import java.util.IllegalFormatException;
10 import java.util.Locale;
11
12 /**
13 *
14 * @version 1.1, 13/11/17
15 * @author andyChen
16 */
17
18 public class PrintWriter extends Writer {
19
20 /**
21 * 被PrintWriter装饰的底层Writer、OutputStream实现类out
22 */
23 protected Writer out;
24
25 //是否自动刷新
26 private boolean autoFlush = false;
27
28 //是否抛异常
29 private boolean trouble = false;
30
31 //格式化类
32 private Formatter formatter;
33
34 //字节打印流、用于checkError方法
35 private PrintStream psOut = null;
36
37 /**
38 * 换行符
39 */
40 private String lineSeparator;
41
42 /**
43 * 根据传入的Writer实现类out创建PrintWriter、不具有自动flush功能。
44 */
45 public PrintWriter (Writer out) {
46 this(out, false);
47 }
48
49 /**
50 * 创建PrintWriter、指定autoFlush、并且根据平台不同初始化linSeparator
51 */
52 public PrintWriter(Writer out,
53 boolean autoFlush) {
54 super(out);
55 this.out = out;
56 this.autoFlush = autoFlush;
57 lineSeparator = (String) java.security.AccessController.doPrivileged(
58 new sun.security.action.GetPropertyAction("line.separator"));
59 }
60
61 /**
62 * 根据OutputStream out创建PrintWriter。不具有自动flush功能。
63 */
64 public PrintWriter(OutputStream out) {
65 this(out, false);
66 }
67
68 /**
69 * 根据底层OutputStream out创建pw、指定是否具有autoFlush功能。
70 * 本质只是将out转换成writer、然后为其添加缓冲功能、再用PrintWriter装饰一下。
71 * 如果传入的是PrintStream、则赋给全局变量psOut
72 */
73 public PrintWriter(OutputStream out, boolean autoFlush) {
74 this(new BufferedWriter(new OutputStreamWriter(out)), autoFlush);
75
76 // save print stream for error propagation
77 if (out instanceof java.io.PrintStream) {
78 psOut = (java.io.PrintStream) out;
79 }
80 }
81
82 /**
83 * 根据传入的文件名构造不具有自动flush功能的PrintWriter。
84 * 本质还是根据文件名创建文件字节输出流out、再将out转换成writer、对其添加缓冲功能、最后用pw装饰。
85 */
86 public PrintWriter(String fileName) throws FileNotFoundException {
87 this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName))),
88 false);
89 }
90
91 /**
92 * 与上面一样、只是多了一个可以使用指定编码读取的功能。
93 */
94 public PrintWriter(String fileName, String csn)
95 throws FileNotFoundException, UnsupportedEncodingException
96 {
97 this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName), csn)),
98 false);
99 }
100
101 /**
102 * 根据传入的文件构造不具有自动flush功能的PrintWriter。
103 * 本质还是根据文件名创建文件字节输出流out、再将out转换成writer、对其添加缓冲功能、最后用pw装饰。
104 */
105 public PrintWriter(File file) throws FileNotFoundException {
106 this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))),
107 false);
108 }
109
110 /**
111 * 与上面一样、只是多了一个可以使用指定编码读取的功能。
112 */
113 public PrintWriter(File file, String csn)
114 throws FileNotFoundException, UnsupportedEncodingException
115 {
116 this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), csn)),
117 false);
118 }
119
120 /** 检测底层流是否关闭 */
121 private void ensureOpen() throws IOException {
122 if (out == null)
123 throw new IOException("Stream closed");
124 }
125
126 /**
127 * flush底层流
128 */
129 public void flush() {
130 try {
131 synchronized (lock) {
132 ensureOpen();
133 out.flush();
134 }
135 }
136 catch (IOException x) {
137 trouble = true;
138 }
139 }
140
141 /**
142 * 关闭此流、释放与此流有关的所有资源
143 */
144 public void close() {
145 try {
146 synchronized (lock) {
147 if (out == null)
148 return;
149 out.close();
150 out = null;
151 }
152 }
153 catch (IOException x) {
154 trouble = true;
155 }
156 }
157
158 /**
159 * 同PrintStream一样、不会抛出IO异常、出现异常时只会自己内部消化掉、
160 * 可以通过此方法来查看print过程中是否有异常出现。
161 */
162 public boolean checkError() {
163 if (out != null) {
164 flush();
165 }
166 if (out instanceof java.io.PrintWriter) {
167 PrintWriter pw = (PrintWriter) out;
168 return pw.checkError();
169 } else if (psOut != null) {
170 return psOut.checkError();
171 }
172 return trouble;
173 }
174
175 /**
176 * 可以显示的设置print产生异常。
177 */
178 protected void setError() {
179 trouble = true;
180 }
181
182 /**
183 * 清除异常信息
184 */
185 protected void clearError() {
186 trouble = false;
187 }
188
189
190 /**
191 * 写入一个字符、
192 * 这里产生IOException的原因是此方法是继承自Writer
193 */
194 public void write(int c) {
195 try {
196 synchronized (lock) {
197 ensureOpen();
198 out.write(c);
199 }
200 }
201 catch (InterruptedIOException x) {
202 Thread.currentThread().interrupt();
203 }
204 catch (IOException x) {
205 trouble = true;
206 }
207 }
208
209 /**
210 * 将从下标off开始、len个字符写入到out中。
211 * 这里也许会有个疑问?当传入的out是OutputStream时还能使用
212 * out.write(char[] buf, int off, int len)吗?
213 * 可以:当传入OutputStream时、调用的构造方法内部会将OutputStream
214 * 包装成OutputStreamWriter、同时使用BufferedWriter来封装、最后再用PrintWriter来装饰。
215 * 这里的out表示的已经变成BufferedWriter。
216 */
217 public void write(char buf[], int off, int len) {
218 try {
219 synchronized (lock) {
220 ensureOpen();
221 out.write(buf, off, len);
222 }
223 }
224 catch (InterruptedIOException x) {
225 Thread.currentThread().interrupt();
226 }
227 catch (IOException x) {
228 trouble = true;
229 }
230 }
231
232 /**
233 * 将一个字符数组中所有字符写入到out中
234 */
235 public void write(char buf[]) {
236 write(buf, 0, buf.length);
237 }
238
239 /**
240 * 将s的一部分写入out中
241 */
242 public void write(String s, int off, int len) {
243 try {
244 synchronized (lock) {
245 ensureOpen();
246 out.write(s, off, len);
247 }
248 }
249 catch (InterruptedIOException x) {
250 Thread.currentThread().interrupt();
251 }
252 catch (IOException x) {
253 trouble = true;
254 }
255 }
256
257 /**
258 * 将String写入out中
259 */
260 public void write(String s) {
261 write(s, 0, s.length());
262 }
263
264 /**
265 * 将一个换行符写入out中、如果设置了自动刷新、则刷新out。
266 */
267 private void newLine() {
268 try {
269 synchronized (lock) {
270 ensureOpen();
271 out.write(lineSeparator);
272 if (autoFlush)
273 out.flush();
274 }
275 }
276 catch (InterruptedIOException x) {
277 Thread.currentThread().interrupt();
278 }
279 catch (IOException x) {
280 trouble = true;
281 }
282 }
283
284
285 /**
286 * 将一个boolean写入out中、不管有没有设置autoFlush、都不会自动flush
287 */
288 public void print(boolean b) {
289 write(b ? "true" : "false");
290 }
291
292 /**
293 * 将一个char写入out中、
294 */
295 public void print(char c) {
296 write(c);
297 }
298
299 /**
300 * 将一个int写入out中、
301 */
302 public void print(int i) {
303 write(String.valueOf(i));
304 }
305
306 /**
307 * 将一个long写入out中、
308 */
309 public void print(long l) {
310 write(String.valueOf(l));
311 }
312
313 /**
314 * 将一个float写入out中、 rinted
315 */
316 public void print(float f) {
317 write(String.valueOf(f));
318 }
319
320 /**
321 * 将一个double写入out中、
322 */
323 public void print(double d) {
324 write(String.valueOf(d));
325 }
326
327 /**
328 *将一个char[]写入out中、
329 */
330 public void print(char s[]) {
331 write(s);
332 }
333
334 /**
335 * 将一个String写入out中、
336 */
337 public void print(String s) {
338 if (s == null) {
339 s = "null";
340 }
341 write(s);
342 }
343
344 /**
345 * 将一个Object写入out中、
346 */
347 public void print(Object obj) {
348 write(String.valueOf(obj));
349 }
350
351 /* Methods that do terminate lines */
352
353 /**
354 * 将一个换行符写入out中、
355 */
356 public void println() {
357 newLine();
358 }
359
360 /**
361 * 将一个换行符写入out中、
362 */
363 public void println(boolean x) {
364 synchronized (lock) {
365 print(x);
366 println();
367 }
368 }
369
370 /**
371 * 将一个换行符写入out中、
372 */
373 public void println(char x) {
374 synchronized (lock) {
375 print(x);
376 println();
377 }
378 }
379
380 /**
381 * 将一个换行符写入out中、
382 */
383 public void println(int x) {
384 synchronized (lock) {
385 print(x);
386 println();
387 }
388 }
389
390 /**
391 * 将一个换行符写入out中、
392 */
393 public void println(long x) {
394 synchronized (lock) {
395 print(x);
396 println();
397 }
398 }
399
400 /**
401 * 将一个换行符写入out中、
402 */
403 public void println(float x) {
404 synchronized (lock) {
405 print(x);
406 println();
407 }
408 }
409
410 /**
411 * 将一个换行符写入out中、
412 */
413 public void println(double x) {
414 synchronized (lock) {
415 print(x);
416 println();
417 }
418 }
419
420 /**
421 * 将一个换行符写入out中、
422 */
423 public void println(char x[]) {
424 synchronized (lock) {
425 print(x);
426 println();
427 }
428 }
429
430 /**
431 * 将一个换行符写入out中、
432 */
433 public void println(String x) {
434 synchronized (lock) {
435 print(x);
436 println();
437 }
438 }
439
440 /**
441 * 将一个换行符写入out中、
442 */
443 public void println(Object x) {
444 String s = String.valueOf(x);
445 synchronized (lock) {
446 print(s);
447 println();
448 }
449 }
450
451 /**
452 * 使用指定格式字符串和参数将格式化的字符串写入此 writer 的便捷方法。
453 */
454 public PrintWriter printf(String format, Object ... args) {
455 return format(format, args);
456 }
457
458 /**
459 * 使用指定格式字符串和参数将格式化的字符串写入此 writer 的便捷方法。
460 */
461 public PrintWriter printf(Locale l, String format, Object ... args) {
462 return format(l, format, args);
463 }
464
465 /**
466 *使用指定格式字符串和参数将一个格式化字符串写入此 writer 中。
467 */
468 public PrintWriter format(String format, Object ... args) {
469 try {
470 synchronized (lock) {
471 ensureOpen();
472 if ((formatter == null)
473 || (formatter.locale() != Locale.getDefault()))
474 formatter = new Formatter(this);
475 formatter.format(Locale.getDefault(), format, args);
476 if (autoFlush)
477 out.flush();
478 }
479 } catch (InterruptedIOException x) {
480 Thread.currentThread().interrupt();
481 } catch (IOException x) {
482 trouble = true;
483 }
484 return this;
485 }
486
487 /**
488 *使用指定格式字符串和参数将一个格式化字符串写入此 writer 中。
489 */
490 public PrintWriter format(Locale l, String format, Object ... args) {
491 try {
492 synchronized (lock) {
493 ensureOpen();
494 if ((formatter == null) || (formatter.locale() != l))
495 formatter = new Formatter(this, l);
496 formatter.format(l, format, args);
497 if (autoFlush)
498 out.flush();
499 }
500 } catch (InterruptedIOException x) {
501 Thread.currentThread().interrupt();
502 } catch (IOException x) {
503 trouble = true;
504 }
505 return this;
506 }
507
508 /**
509 * 将一个有序字符序列追加到out中
510 */
511 public PrintWriter append(CharSequence csq) {
512 if (csq == null)
513 write("null");
514 else
515 write(csq.toString());
516 return this;
517 }
518
519
520 /**
521 * 将一个有序字符序列一部分追加到out中
522 */
523 public PrintWriter append(CharSequence csq, int start, int end) {
524 CharSequence cs = (csq == null ? "null" : csq);
525 write(cs.subSequence(start, end).toString());
526 return this;
527 }
528
529 /**
530 * 将一个字符追加到out中
531 */
532 public PrintWriter append(char c) {
533 write(c);
534 return this;
535 }
536 }
4、实例演示:
1 package com.chy.io.original.test;
2
3 import java.io.BufferedReader;
4 import java.io.File;
5 import java.io.FileOutputStream;
6 import java.io.IOException;
7 import java.io.PrintWriter;
8
9 import com.chy.io.original.code.FileWriter;
10 import com.chy.io.original.utils.StudentDTO;
11
12 @SuppressWarnings("all")
13 public class PrintWriterTest {
14
15 private final static String fileName = "D:\\pw.txt";
16 private final static File file = new File(fileName);
17 private final static String targetStr = "马心心";
18
19 public static void showPrintWriterconstructor() throws IOException{
20
21
22 //下面这一组个构造方法效果一样、使用默认编码
23 PrintWriter pw1 = new PrintWriter(new FileWriter(file));
24 PrintWriter pw2 = new PrintWriter(new FileWriter(fileName));
25 PrintWriter pw3 = new PrintWriter(file);
26 PrintWriter pw4 = new PrintWriter(fileName);
27
28 //下面这组构造方法效果一样、具有自动刷新功能
29 PrintWriter pw5 = new PrintWriter(new FileWriter(file), true);
30 PrintWriter pw6 = new PrintWriter(new FileWriter(fileName), true);
31 PrintWriter pw7 = new PrintWriter(new FileOutputStream(file), true);
32 PrintWriter pw8 = new PrintWriter(new FileOutputStream(fileName), true);
33
34
35 //下面这一组构造方法效果一样、使用指定的GBK编码
36 PrintWriter pw9 = new PrintWriter(file, "GBK");
37 PrintWriter pw10 = new PrintWriter(fileName, "GBK");
38 }
39
40
41 /**
42 * 这里只测试一下向文件流中打印一个中文字符串、然后读取出来查看乱码情况、
43 * 和写入一个Object对象、再读取出来以字符串的形式打印出来。
44 */
45 public static void testPrintWriterDefault() throws IOException{
46 PrintWriter pw = new PrintWriter(file);
47 pw.println(targetStr);
48
49 StudentDTO student = new StudentDTO(1,"陈华应");
50 pw.println(student);
51 pw.close();
52
53 BufferedReader br = new BufferedReader(new java.io.FileReader(file));
54 String str;
55 while((str = br.readLine()) != null){
56 System.out.println(str);
57 }
58 br.close();
59 }
60
61 /**
62 * 构造打印流时使用指定GBK编码
63 * @throws IOException
64 */
65 public static void testPrintWriterGBK() throws IOException{
66 PrintWriter pw = new PrintWriter(file, "GBK");
67 pw.println(targetStr);
68
69 StudentDTO student = new StudentDTO(1,"陈华应");
70 pw.println(student);
71 pw.close();
72
73 BufferedReader br = new BufferedReader(new java.io.FileReader(file));
74 String str;
75 while((str = br.readLine()) != null){
76 System.out.println(str);
77 }
78 br.close();
79 }
80
81
82 public static void main(String[] args) throws IOException {
83 //testPrintWriterDefault();
84 testPrintWriterGBK();
85 }
86
87 }
总结:
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。