一、什么是 IO 流?
想象一个场景:我们在电脑上编辑文件,可以保存到硬盘上,也可以拷贝到 U 盘中。那这个看似简单的过程,背后其实是数据的传输。数据的传输,也就是数据的流动。既然是流动也就会有方向,有入方向和出方向。举个上传文件的栗子,现在有三个对象,文件、应用程序、上传的目标地址(服务器)。简化的上传文件有两步:
- 应用程序读文件(此为入方向,文件读入到应用程序)
- 应用程序写文件(此为出方向,读完之后写到目标地址)
注意这个入和出是相对的,它相对于应用程序而言。如果相对于服务器而言,这个上传文件操作就是入方向,从应用程序读入。Java 中 I/O 操作主要是指使用 java.io
包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。
二、IO 流的分类
我不认同网络上很多 IO 流的图,他们只是简单的把 io 流分成字节流和字符流。这样的分类也不是说不好,只是太臃肿、难记。先上一张我自己总结的 IO 留的思维导图,我先把它分成了节点流和处理流,节点流是直接接触数据源的,而处理流是出于各种目的在节点流的基础上再套一层的 IO 流。再按照操作类型,分成 8 个小类,然后才是字节、字符分类,最后才是输入、输出的分类。具体可以看以下思维导图 (可能不清晰,有需要的在后台回复 IO 流获取原思维导图)根据数据的流向分为:输入流和输出流。
- 输入流 :把数据从
其他设备
上读取到内存
中的流。
- 输出流 :把数据从
内存
中写出到其他设备
上的流。
根据数据的类型分为:字节流和字符流。
- 字节流 :以字节为单位,读写数据的流。
- 字符流 :以字符为单位,读写数据的流。
IO 流要说明白需要好几篇才行,今天复习缓冲流。
三、为什么需要缓冲流?
前面我们已经复习过字节流、字符流。使用基本的字节输入流读取文件,就相当于将文件中的数据,通过操作系统,在通过 JVM 一个个传入到内存中,这样的话,文件读取的速度比较慢。如果使用字节缓冲流,就可以建立一个缓冲区(相当于一个数组),将缓冲区里面的数据批量传入到文件中,这样的话就提高了文件的读取速度。一句话概括就是:缓冲流比较高效,因为它减少了 IO 的次数。
四、使用缓冲流
缓冲流,也叫高效流,是对 4 个基本的字节、字符流的增强,所以也是 4 个流,按照数据类型分类:
- 字节缓冲流:
BufferedInputStream
,BufferedOutputStream
- 字符缓冲流:
BufferedReader
,BufferedWriter
它的基本原理是:会在创建流的时候创建一个默认大小的内置缓冲区,从而减少文件系统 IO 次数,提高效率。
3.1 字节缓冲流
字节缓冲流与文件字节流的用法差不多不再赘述,有一点不同的是字节缓冲流的创建是建
立在文件字节流的基础上,这就导致构造方法的变化,字节缓冲流的构造方法是这样的:
// 字节缓冲输入流 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("Z:\\唐顿庄园BD中英双字.mp4")); // 字节缓冲输出流 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("Z:\\movie\\复制唐顿庄园BD中英双字.mp4"));
3.1.1 比较效率
上面说到缓冲流比普通的字节流效率要高,为了验证,我们来做个测试,分别用缓冲流和文件字节流复制同样大小的文件(1.3GB)到相同的目录。首先是文件字节流复制:
/** * Project Name:review_java <br/> * Package Name:com.nasus.io.bufferinoutstream <br/> * Date:2020/2/17 22:41 <br/> * * @author <a href="turodog@foxmail.com">chenzy</a><br/> */ publicclass CopyFileByByteStream { public static void main(String[] args) throws FileNotFoundException { // 开始时间 long startTime = System.currentTimeMillis(); // 计数器,用于判断 int b; try { // 创建字节流、复制电影 FileInputStream fis = new FileInputStream("Z:\\唐顿庄园BD中英双字.mp4"); FileOutputStream fos = new FileOutputStream("Z:\\movie\\复制唐顿庄园BD中英双字.mp4"); // 读写数据 while ((b = fis.read()) != -1) { fos.write(b); } // 关闭资源 fis.close(); fos.close(); } catch (IOException e) { e.printStackTrace(); } // 结束时间 long endTime = System.currentTimeMillis(); // 统计用时 System.out.println("普通流复制时间:" + (endTime - startTime) + " 毫秒"); } }
我等了十分钟,看了一下复制过去的文件大小只有 80M 不到,最终时间等不下去了。。。足以见它的效率有多么低。接着是缓冲流复制文件:
/** * Project Name:review_java <br/> * Package Name:com.nasus.io.bufferinoutstream <br/> * Date:2020/2/17 22:56 <br/> * * @author <a href="turodog@foxmail.com">chenzy</a><br/> */ publicclass CopyFileByBufferStream { public static void main(String[] args) throws FileNotFoundException { // 开始时间 long startTime = System.currentTimeMillis(); // 计数器,用于判断 int b; try { // 创建缓冲流,复制电影 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("Z:\\唐顿庄园BD中英双字.mp4")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("Z:\\movie\\复制唐顿庄园BD中英双字.mp4")); // 读写数据 while ((b = bis.read()) != -1) { bos.write(b); } // 关闭资源 bis.close(); bos.close(); } catch (IOException e) { e.printStackTrace(); } // 结束时间 long endTime = System.currentTimeMillis(); // 统计用时 System.out.println("缓冲流(默认缓冲区)复制时间:" + (endTime - startTime) + " 毫秒"); } }
下图为证,1.3 GB 的文件用了 46.7 秒复制完成。
最后是缓冲流使用数组(一次读多一点):
/** * Project Name:review_java <br/> * Package Name:com.nasus.io.bufferinoutstream <br/> * Date:2020/2/17 23:03 <br/> * * @author <a href="turodog@foxmail.com">chenzy</a><br/> */ publicclass CopyFileByBufferArray { public static void main(String[] args) throws FileNotFoundException { // 开始时间 long startTime = System.currentTimeMillis(); // 计数器,用于判断 int b; // 使用数组,一次读多点 byte[] bytes = newbyte[8*1024]; try { // 创建缓冲流,复制电影 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("Z:\\唐顿庄园BD中英双字.mp4")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("Z:\\movie\\复制唐顿庄园BD中英双字.mp4")); // 读写数据 while ((b = bis.read(bytes)) != -1) { bos.write(bytes, 0 , b); } // 关闭资源 bis.close(); bos.close(); } catch (IOException e) { e.printStackTrace(); } // 结束时间 long endTime = System.currentTimeMillis(); // 统计用时 System.out.println("缓冲流(使用数组)复制时间:" + (endTime - startTime) + " 毫秒"); } }
下入为证,1.3GB 的文件用了 2.3 秒复制完成。
3.2 字符缓冲流
与字节缓冲流一样,字符缓冲流的创建也是建立在文件字符流的基础上:
// 字符缓冲输入流 BufferedReader br = new BufferedReader(new FileReader("br.txt")); // 字符缓冲输出流 BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
字符缓冲流的大部分方法跟文件字符流一样,这里需要说一下的就是 readLine()
和 newLine()
方法:
BufferedReader:public String readLine(): 读一行文字。 BufferedWriter:public void newLine(): 写一行行分隔符(回车),由系统属性定义符号。 // 各系统的符号以及对应关系如下: // windows --> \r\n // linux --> \r // mac --> \n // \r 是回车,return // \n 是换行,newline
3.2.1 逐行读取文件
/** * Project Name:review_java <br/> * Package Name:com.nasus.io.bufferinoutstream <br/> * Date:2020/2/17 23:23 <br/> * * @author <a href="turodog@foxmail.com">chenzy</a><br/> */ publicclass BufferReaderDemo { public static void main(String[] args) throws IOException { // 创建流对象 BufferedReader br = new BufferedReader(new FileReader("br.txt")); // 定义字符串,保存读取的一行文字 String line = null; // 循环读取,读取到最后返回null while ((line = br.readLine()) != null) { System.out.println(line); System.out.println("------"); } // 释放资源 br.close(); } }
3.2.2 写入换行符
/** * Project Name:review_java <br/> * Package Name:com.nasus.io.bufferinoutstream <br/> * Date:2020/2/17 23:24 <br/> * * @author <a href="turodog@foxmail.com">chenzy</a><br/> */ publicclass BufferWriterDemo { public static void main(String[] args) throws IOException { // 创建流对象 BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt")); // 写出数据 bw.write("一个"); // 写出换行 bw.newLine(); bw.write("优秀"); bw.newLine(); bw.write("废人"); bw.newLine(); // 释放资源 bw.close(); } }
结果:
3.2.3 给文件排序
一开始文件是每行打乱序号的,如下:
4.废人 1.czy 3.优秀的 2.是一个
/** * Project Name:review_java <br/> * Package Name:com.nasus.io.bufferinoutstream <br/> * Date:2020/2/17 23:37 <br/> * * @author <a href="turodog@foxmail.com">chenzy</a><br/> */ publicclass BufferSortTest { public static void main(String[] args) throws IOException { // 创建map集合,保存文本数据,键为序号,值为文字 HashMap<String, String> lineMap = new HashMap<>(); // 创建流对象 BufferedReader br = new BufferedReader(new FileReader("乱序.txt")); BufferedWriter bw = new BufferedWriter(new FileWriter("正序.txt")); // 读取数据 String line = null; while ((line = br.readLine())!=null) { // 解析文本 String[] split = line.split("\\."); // 保存到集合 lineMap.put(split[0],split[1]); } // 关闭资源 br.close(); // 遍历map集合 for (int i = 1; i <= lineMap.size(); i++) { String key = String.valueOf(i); // 获取map中文本 String value = lineMap.get(key); // 写出拼接文本 bw.write(key+"."+value); // 写出换行 bw.newLine(); } // 关闭资源 bw.close(); } }
结果:
1.czy 2.是一个 3.优秀的 4.废人