java IO流全面总结
流的概念和作用
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
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类的使用:读取文件内容
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:关键字
B:构造方法
C:一般方法
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。