概念:
流是一种抽象概念,它代表了数据的无结构化传递。按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列。从流中取得数据的操作称为提取操作,而向流中添加数据的操作称为插入操作。
Java IO 也称为IO流,它的核心就是对系统文件的操作、和系统之间读写。IO流主要分为两大类,字节流和字符流。字节流可以处理任何类型的数据,如图片,视频等,字符流只能处理字符类型的数据。字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,非要用字符流处理也行,但是可能会遇到问题或数据损坏,因为字符流可能会将二进制数据解释为字符,导致意外的结果。如果是关系到中文(文本)的,用字符流好点。
IO流的本质是数据传输,并且流是单向的。
字节流的基本单位为字节(Byte),一个字节通常为 8 位。字节(Byte,简写为B)一般用作数据存储单位,而数据传输大多以位(bit,简写为b)为单位。 一个位代表一个0 或1(即二进制),每8 个位组成1 个字节。
数据持久化:
所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件(特别是文本文件)时,也是一个字节一个字节地读取以形成字节序列。
我们使用FileWriter向demo.txt中写入了“demo”这四个字符,我们写入的“demo”被编码为了“64 65 6D 6F”,字符流在输出前实际上是要完成Unicode码元序列到相应编码方式的字节序列的转换,所以它会使用内存缓冲区来存放转换后得到的字节序列,等待都转换完毕再一同写入磁盘文件中。
流关闭
流是有起点和终点的字节序列的集合,通过流可以读写设备中的数据, 这儿的设备可以是文件, 内存, …. IO操作属于资源操作,一定要记得关闭 。
示例方法:
为什么一定要close()呢?
* A:让流对象变成垃圾,这样就可以被垃圾回收器回收了
* B:通知系统去释放跟该文件相关的资源
流分类
根据数据处理的不同类型分为:字节流和字符流
字节流:OutputStream 和 InputStream 为基类。
字符流:Writer 和 Reader 为基类。
根据数据流向不同分为:输入流和输出流
输出流:OutputStream 和 Writer 为基类。
输入流:InputStream 和 Reader 为基类。
Io框架图
虽然IO 类有很多,但是最基础的四个类是InputStream、OutputStream、Reader、Writer。其他的诸如文件流都是在这四个类的基础上扩展的。比如,我们经常使用的文件读写流,实际上是调用了底层系统的read()和write()方法。其他;流都是上面这四类流子类,方法也是通过这两个方法衍生的。而且大部分的IO源码都是native的,也就是说它们是用C/C++写的,这是我们在其他高级语言中无法实现的。
InputStream -- 字节输入流
OutputStream -- 字节输出流
Reader -- 字符输入流
Writer -- 字符输出流
所有以Stream结尾的流类都是字节流类, 在字节流中输出数据主要是使用OutputStream完成,输入使的是InputStream,在字符流中输出主要是使用Writer类完成,输入流主要使用Reader类完成。以Reader结尾的流类是字符输入流类, 以Writer结尾的流类是字符输出流类。输入/输出流, 是以当前程序为参照, 把数据从其他资源里面传送到内存里面,就是输入流,反之。把数据从内存传送到其他资源就是输出流。
不同流的作用
流的性能提升
使用缓冲区能大幅度提升流性能,什么是缓冲
缓冲:
缓冲的作用
如果不设置缓冲的话,CPU相对于一个文件的每个字节都需要取一个存一个。设置缓冲区的话:
CPU通常会使用 DMA (将字节积攒一定量在请求cpu发送数据就是DMA方式,cpu成为被调用者,这之后cpu在执行操作将内存数据持久化到硬盘)方式去执行 I\O流 操作。CPU 将这个工作交给DMA控制器来做,自己腾出时间做其他的事,当DMA完成工作时,DMA会主动告诉CPU“操作完成”。这时,CPU接管后续工作。在此,CPU 是被动的。DMA是专门 做 I\O 与 内存 数据交换的,不仅自身效率高,也节约了CPU时间。
缓冲区就是内存里的一块区域,把数据先存内存里,然后一次性写入,类似数据库的批量操作,显然效率比较高。还节省CPU的使用。
总结:使用缓冲处理流包装就是一堆一堆的干活,还能不用CPU多次处理数据转换,只是设置一下数据转换成功后的文件。不使用缓冲处理流包装就是CPU傻傻的一个字节一个字节循环来干活存储写入文件中,相比可见效率明显变慢 。
再简单说缓冲区的优势以文件流的写入为例,如果我们不使用缓冲区,那么每次写操作 CPU 都会和低速存储设备也就是磁盘进行交互,那么整个写入文件的速度就会受制于低速的存储设备(磁盘)。但如果使用缓冲区的话,每次写操作会先将数据保存在高速缓冲区内存上,当缓冲区的数据到达某个阈值之后,再将文件一次性写入到磁盘上。因为内存的写入速度远远大于磁盘的写入速度,所以当有了缓冲区之后,文件的写入速度就被大大提升了。
BufferedInputStream/BufferedOutputStream
BufferedReader/BufferedWriter
BufferedWriter 和 BufferedReader 为带有默认缓冲的字符输出输入流,因为有缓冲区所以很效率比没有缓冲区的很高。
默认有8192个字符的缓冲区。
br.readLine() 一次可以读一行
bw.newLine(); 换行
bw.flush();使用缓冲区中的方法,将数据刷新到目的地文件中去。
bw.close();关闭缓冲区,同时关闭了fw流对象
性能测试示例
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
public class WriteExample {
public static void main(String[] args) throws IOException {
// 构建写入内容
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 1000000; i++) {
stringBuilder.append("ABCDEFGHIGKLMNOPQRSEUVWXYZ");
}
// 写入内容
final String content = stringBuilder.toString();
// 存放文件的目录
final String filepath1 = "/Users/mac/Downloads/io_test/write1.txt";
final String filepath2 = "/Users/mac/Downloads/io_test/write2.txt";
final String filepath3 = "/Users/mac/Downloads/io_test/write3.txt";
final String filepath4 = "/Users/mac/Downloads/io_test/write4.txt";
final String filepath5 = "/Users/mac/Downloads/io_test/write5.txt";
final String filepath6 = "/Users/mac/Downloads/io_test/write6.txt";
// 方法一:使用 FileWriter 写文件
long stime1 = System.currentTimeMillis();
fileWriterTest(filepath1, content);
long etime1 = System.currentTimeMillis();
System.out.println("FileWriter 写入用时:" + (etime1 - stime1));
// 方法二:使用 BufferedWriter 写文件
long stime2 = System.currentTimeMillis();
bufferedWriterTest(filepath2, content);
long etime2 = System.currentTimeMillis();
System.out.println("BufferedWriter 写入用时:" + (etime2 - stime2));
// 方法三:使用 PrintWriter 写文件
long stime3 = System.currentTimeMillis();
printWriterTest(filepath3, content);
long etime3 = System.currentTimeMillis();
System.out.println("PrintWriterTest 写入用时:" + (etime3 - stime3));
// 方法四:使用 FileOutputStream 写文件
long stime4 = System.currentTimeMillis();
fileOutputStreamTest(filepath4, content);
long etime4 = System.currentTimeMillis();
System.out.println("FileOutputStream 写入用时:" + (etime4 - stime4));
// 方法五:使用 BufferedOutputStream 写文件
long stime5 = System.currentTimeMillis();
bufferedOutputStreamTest(filepath5, content);
long etime5 = System.currentTimeMillis();
System.out.println("BufferedOutputStream 写入用时:" + (etime5 - stime5));
// 方法六:使用 Files 写文件
long stime6 = System.currentTimeMillis();
filesTest(filepath6, content);
long etime6 = System.currentTimeMillis();
System.out.println("Files 写入用时:" + (etime6 - stime6));
}
/**
* 方法六:使用 Files 写文件
* @param filepath 文件目录
* @param content 待写入内容
* @throws IOException
*/
private static void filesTest(String filepath, String content) throws IOException {
Files.write(Paths.get(filepath), content.getBytes());
}
/**
* 方法五:使用 BufferedOutputStream 写文件
* @param filepath 文件目录
* @param content 待写入内容
* @throws IOException
*/
private static void bufferedOutputStreamTest(String filepath, String content) throws IOException {
try (BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(
new FileOutputStream(filepath))) {
bufferedOutputStream.write(content.getBytes());
}
}
/**
* 方法四:使用 FileOutputStream 写文件
* @param filepath 文件目录
* @param content 待写入内容
* @throws IOException
*/
private static void fileOutputStreamTest(String filepath, String content) throws IOException {
try (FileOutputStream fileOutputStream = new FileOutputStream(filepath)) {
byte[] bytes = content.getBytes();
fileOutputStream.write(bytes);
}
}
/**
* 方法三:使用 PrintWriter 写文件
* @param filepath 文件目录
* @param content 待写入内容
* @throws IOException
*/
private static void printWriterTest(String filepath, String content) throws IOException {
try (PrintWriter printWriter = new PrintWriter(new FileWriter(filepath))) {
printWriter.print(content);
}
}
/**
* 方法二:使用 BufferedWriter 写文件
* @param filepath 文件目录
* @param content 待写入内容
* @throws IOException
*/
private static void bufferedWriterTest(String filepath, String content) throws IOException {
try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filepath))) {
bufferedWriter.write(content);
}
}
/**
* 方法一:使用 FileWriter 写文件
* @param filepath 文件目录
* @param content 待写入内容
* @throws IOException
*/
private static void fileWriterTest(String filepath, String content) throws IOException {
try (FileWriter fileWriter = new FileWriter(filepath)) {
fileWriter.append(content);
}
}
}
在查看结果之前,我们先去对应的文件夹看看写入的文件是否正常,如下图所示
从上述结果可以看出,每种方法都正常写入了 26 MB 的数据,它们最终执行的结果如下图所示:
通过结果可以看出有缓冲区的是性能最强的
------------------------------------------与正文内容无关------------------------------------
如果觉的文章写对各位读者老爷们有帮助的话,麻烦点赞加关注呗!小弟在这拜谢了!
如果您觉得我的文章在某些地方写的不尽人意或者写的不对,从而让你对你人生观产生颠覆(概不负责),需要斧正,麻烦在评论区不吝赐教,作者看到后会根据您的提示查阅文章进行修改,还这世间一个公理一片蓝天