java IO流全面总结
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java IO流全面总结相关的知识,希望对你有一定的参考价值。
流的概念和作用
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
Java流操作有关的类或接口:
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 }
以上是关于java IO流全面总结的主要内容,如果未能解决你的问题,请参考以下文章
java缓冲字符字节输入输出流:java.io.BufferedReaderjava.io.BufferedWriterjava.io.BufferedInputStreamjava.io.(代码片段