Java io 流常用基础总结
一、java io 流概念、分类、类层次图
1、概念:
流是一个很形象的概念,当程序需要读取数据的时候,就会开启一个通向数据源的流,这个数据源可以是文件,内存,或是网络连接。类似的,当程序需要写入数据的时候,就会开启一个通向目的地的流。在java中把不同的输入/输出源(键盘,文件,网络连接等)抽象表述为“流”(stream)。
2、分类:
按流向分(以内存或者程序角度看):
3、类层次图
①、先来看下度娘给的一张图
这需要我们自己去验证一下到底是不是这样子的。
上面4幅图是在idea中用show digram打开,用于展示类继承和实现关系;
蓝色实线表示的是继承关系,绿色虚线表示的是接口实现关系,绿色实线表示的是接口与接口的关系。
抽象类InputStream(这个抽象类是表示输入字节流的所有类的超类)里方法:
int |
available() 返回从该输入流中可以读取(或跳过)的字节数的估计值,而不会被下一次调用此输入流的方法阻塞。
|
void |
close() 关闭此输入流并释放与流相关联的任何系统资源。
|
void |
mark(int readlimit) 标记此输入流中的当前位置。
|
boolean |
markSupported() 测试这个输入流是否支持
mark 和 reset 方法。 |
abstract int |
read() 从输入流读取数据的下一个字节。
|
int |
read(byte[] b) 从输入流读取一些字节数,并将它们存储到缓冲区
b 。 |
int |
read(byte[] b, int off, int len) 从输入流读取最多
len 字节的数据到一个字节数组。 |
void |
reset() 将此流重新定位到上次在此输入流上调用
mark 方法时的位置。 |
long |
skip(long n) 跳过并丢弃来自此输入流的
n 字节数据。 |
abstract void |
close() 关闭流并释放与之相关联的任何系统资源。
|
void |
mark(int readAheadLimit) 标记流中的当前位置。
|
boolean |
markSupported() 告诉这个流是否支持mark()操作。
|
int |
read() 读一个字符
|
int |
read(char[] cbuf) 将字符读入数组。
|
abstract int |
read(char[] cbuf, int off, int len) 将字符读入数组的一部分。
|
int |
read(CharBuffer target) 尝试将字符读入指定的字符缓冲区。
|
boolean |
ready() 告诉这个流是否准备好被读取。
|
void |
reset() 重置流。
|
long |
skip(long n) 跳过字符
|
抽象类OutputStream(这个抽象类是表示字节输出流的所有类的超类)里方法:
void |
close() 关闭此输出流并释放与此流相关联的任何系统资源。
|
void |
flush() 刷新此输出流并强制任何缓冲的输出字节被写出。
|
void |
write(byte[] b) 将
b.length 字节从指定的字节数组写入此输出流。 |
void |
write(byte[] b, int off, int len) 从指定的字节数组写入
len 个字节,从偏移 off 开始输出到此输出流。 |
abstract void |
write(int b) 将指定的字节写入此输出流。
|
抽象类Writer(用于写入字符流的抽象类)里方法:
Writer |
append(char c) 将指定的字符附加到此作者。
|
Writer |
append(CharSequence csq) 将指定的字符序列附加到此作者。
|
Writer |
append(CharSequence csq, int start, int end) 将指定字符序列的子序列附加到此作者。
|
abstract void |
close() 关闭流,先刷新。
|
abstract void |
flush() 刷新流。
|
void |
write(char[] cbuf) 写入一个字符数组。
|
abstract void |
write(char[] cbuf, int off, int len) 写入字符数组的一部分。
|
void |
write(int c) 写一个字符
|
void |
write(String str) 写一个字符串
|
void |
write(String str, int off, int len) 写一个字符串的一部分。
|
在借鉴一下这位博主的表格https://blog.****.net/nightcurtis/article/details/51324105
二、常用的文件流基本操作演示代码:
1、FileInputStream流简单读取:
public class FileInputStreamTest { public static void main(String[] args) throws IOException { getFileContentByFileInputStream(); } public static void getFileContentByFileInputStream() throws IOException { FileInputStream fis = null; try{ //创建字节输入流 fis = new FileInputStream("F:\\file\\test.txt"); //创建一个长度为1024字节的竹筒 byte[] b = new byte[1024]; //用于保存的实际字节数 int hasRead = 0; //循环取水滴(字节) while ((hasRead = fis.read(b)) > 0){ System.out.println(new String(b,0,hasRead)); } }catch (Exception e){ System.out.println("FileInputStreamTest_exception:" + e); }finally { if(null!=fis){ fis.close(); } } } }
2、FileOutputStream流简单写入:
public class FileOutputStreamTest { public static void main(String[] args) throws IOException { copyByFileStream(); } /** * @throws IOException */ public static void copyByFileStream() throws IOException { FileInputStream fis = null; FileOutputStream fos = null; try{ //创建字节输入流 fis = new FileInputStream("F:\\file\\test.txt"); //创建字节输出流 fos = new FileOutputStream("F:\\file\\newTest.txt"); //创建一个长度为1024字节数组 byte[] b = new byte[1024]; //保存实际的字节长度 int hasRead = 0; //循环取字节,一次最多拿1024个 while ((hasRead = fis.read(b))>0){ //每读取一次,即写入文件输入流,读了多少,就写多少 fos.write(b,0,hasRead); } System.out.println("复制完成!"); }catch (Exception e){ }finally { if(null!=fis){ fis.close(); } if(null!=fos){ fos.close(); } } } }
3、FileReader流简单读取:
public class FileReaderTest { public static void main(String[] args) throws IOException { getFileContentByFileReader(); } public static void getFileContentByFileReader() throws IOException { FileReader fr = null; try { //创建字符输入流 fr = new FileReader("F:\\file\\test.txt"); //创建一个长度为1024字符的竹筒 char[] b = new char[1024]; //实际保存的字符数 int hasRead = 0; //循环取水滴(字符) while ((hasRead = fr.read(b))>0){ //取出竹筒中的水滴(字符),将字符数组转换成字符串进行输出 System.out.println(new String(b,0,hasRead)); } }catch (Exception e){ System.out.println(""); }finally { if(null!=fr){ fr.close(); } } } }
4、FileWriter流简单写入:
public class FileWriterTest { public static void main(String[] args) throws IOException { copyFileByFileRead(); } public static void copyFileByFileRead() throws IOException { FileReader fr = null; FileWriter fw = null; try{ //创建字符输入流 fr = new FileReader("F:\\file\\test.txt"); //创建字符输出流 fw = new FileWriter("F:\\file\\newTest1.txt"); //创建一个长度为1024字符的竹筒 char[] c = new char[1024]; //实际保存的字符长度 int hasRead = 0; //循环读取字符 while ((hasRead = fr.read(c))>0){ //每读取一次,即写入文件输入流,读了多少,就写多少 fw.write(c, 0 ,hasRead); } System.out.println("复制完成!"); }catch (Exception e){ }finally { if(null!=fr){ fr.close(); } if(null!=fw){ fw.close(); } } } }
5、BufferedReader流简单读取写入:
public class BufferedReaderTest { public static void main(String[] args) throws IOException { brt(); } /** * 原来的代码是: * BufferedReader buffer = new BufferedReader(in); * 这样会出现编码的问题。在bufferedReder后面加上了转换成utf-8的方法: * BufferedReader buffer = new BufferedReader(new InputStreamReader(in,"utf-8")); * 发现还是乱码,试了其他一些编码,最终GBK和GB2312可以成功显示中文。 * BufferedReader buffer = new BufferedReader(new InputStreamReader(in,"GB2312")); * @throws IOException */ public static void brt() throws IOException { FileInputStream fr = null; FileWriter fw = null; BufferedReader br = null; BufferedWriter bw = null; try{ //创建字符输入流 fr = new FileInputStream("F:\\file\\bufferedTest.txt"); //创建字符输出流 fw = new FileWriter("F:\\file\\bufferedNewTest.txt"); //创建字符缓冲区输入流 br = new BufferedReader(new InputStreamReader(fr,"GBK")); //创建字符缓冲区输出流 bw = new BufferedWriter(fw); //创建一个长度为1024的字符竹筒 char[] c = new char[1024]; //实际保存的字符长度 int hasRead = 0; //循环读取 while ((hasRead = br.read(c))>0){ bw.write(c,0,hasRead); } System.out.println("复制完成!"); }catch (Exception e){ }finally { if(null!=br){ br.close(); } if(null!=bw){ bw.close(); } if(null!=fr){ fr.close(); } if(null!=fw){ fw.close(); } } } }
6、BufferedStream流简单读取写入:
public class BufferedStreamTest { public static void main(String[] args) throws IOException { bst(); } public static void bst() throws IOException { FileInputStream fis = null; FileOutputStream fos = null; BufferedInputStream bis = null; BufferedOutputStream bos = null; try{ //创建字节输入流 fis = new FileInputStream("F:\\file\\bstTest.txt"); //创建字节输出流 fos = new FileOutputStream("F:\\file\\newBstTest.txt"); //创建字节缓冲输入流 bis = new BufferedInputStream(fis); //创建字节缓冲输出流 bos = new BufferedOutputStream(fos); //创建一个长度为1024个字节的竹筒 byte[] b = new byte[1024]; //实际保存的字节数 int hasRead = 0; //循环从缓冲流中读取数据 while ((hasRead = bis.read(b))>0){ bos.write(b,0,hasRead); } System.out.println("复制完成!"); }catch (Exception e){ }finally { //这里需要注意一个问题 //先关闭内层流,在关闭外层流 //BufferedInputStream、BufferedOutputStream是依赖FileInputStream、FileOutputStream //如果先关闭了FileInputStream、FileOutputStream,在关闭BufferedInputStream、BufferedOutputStream //则会报错,Steam Closed if(null!=bis){ bis.close(); } if(null!=bos){ bos.close(); } if(null!=fis){ fis.close(); } if(null!=fos){ fos.close(); } } } }
7、转换流的使用(InputStreamReader/OutputStreamWriter):
/** * 转换流的使用(InputStreamReader/OutputStreamWriter): * 下面以获取键盘输入为例来介绍转换流的用法。java使用System.in代表输入。 * 即键盘输入,但这个标准输入流是InputStream类的实例, * 使用不太方便,而且键盘输入内容都是文本内容, * 所以可以使用InputStreamReader将其包装成BufferedReader, * 利用BufferedReader的readLine()方法可以一次读取一行内容, * 如下代码所示: * Created by zw on 2018/6/11. */ public class InputStreamReaderTest { public static void main(String[] args) throws IOException { useInputStreamReader(); } public static void useInputStreamReader() throws IOException { /** * 新语法糖try-with-resource * jdk1.7中try-with-resources语法糖详解:主要是针对所有凡是继承了Closeable这个类 * 系统在方法退出的时候都会自动的关闭资源。 * 不用最后手动关闭流 */ try( //将System.in对象转换为Reader对象 InputStreamReader isr = new InputStreamReader(System.in); //将普通的Reader包装成BufferedReader BufferedReader bufferedReader = new BufferedReader(isr); //打开一个输出流 BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("F:\\file\\bw.txt"))) { //一次读取一行的字符串内容 String buffer; //循环读取 while ((buffer = bufferedReader.readLine()) != null) { //打印读取的内容 System.out.println("输入内容:" + buffer); //写入到新文件中 bufferedWriter.write(buffer,0,buffer.length()); //刷新流 bufferedWriter.flush(); //如果读取到的字符串为“exit”,则程序退出 if ("exit".equals(buffer)) { System.exit(1); } } } catch (Exception e) { System.out.println("exception:" + e); } } }
三、何为NIO,和传统Io有何区别?
我们使用InputStream从输入流中读取数据时,如果没有读取到有效的数据,程序将在此处阻塞该线程的执行。其实传统的输入里和输出流都是阻塞式的进行输入和输出。 不仅如此,传统的输入流、输出流都是通过字节的移动来处理的(即使我们不直接处理字节流,但底层实现还是依赖于字节处理),也就是说,面向流的输入和输出一次只能处理一个字节,因此面向流的输入和输出系统效率通常不高。
从JDk1.4开始,java提供了一系列改进的输入和输出处理的新功能,这些功能被统称为新IO(NIO)。新增了许多用于处理输入和输出的类,这些类都被放在java.nio包及其子包下,并且对原io的很多类都以NIO为基础进行了改写。新增了满足NIO的功能。
NIO采用了内存映射对象的方式来处理输入和输出,NIO将文件或者文件的一块区域映射到内存中,这样就可以像访问内存一样来访问文件了。通过这种方式来进行输入/输出比传统的输入和输出要快的多。
JDk1.4使用NIO改写了传统Io后,传统Io的读写速度和NIO差不了太多。
四、在开发中正确使用Io流
了解了java io的整体类结构和每个类的一下特性后,我们可以在开发的过程中根据需要灵活的使用不同的Io流进行开发。下面是我整理2点原则:
- 如果是操作二进制文件那我们就使用字节流,如果操作的是文本文件那我们就使用字符流。
- 尽可能的多使用处理流,这会使我们的代码更加灵活,复用性更好。
最后:其实在实际开发中一般字节用FileInputStream、FileOutputStream和BufferedInputStream、BufferedOutputStream就够了,大部分都用BufferedInputStream、BufferedOutputStream,因为它们有缓冲区,操作效率会好点;字符用FileReader、FileWriter和BufferedReader、BufferedWriter就够了,大部分使用的是BufferedReader和BufferedWriter,同样它们也有缓冲区。