Java IO流介绍以及缓冲为何能提升性能

发布时间:2024年01月03日

????????流是一种抽象概念,它代表了数据的无结构化传递。按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列。从流中取得数据的操作称为提取操作,而向流中添加数据的操作称为插入操作。

????????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 有很多,但是最基础的四个类是InputStreamOutputStreamReaderWriter。其他的诸如文件流都是在这四个类的基础上扩展的。比如,我们经常使用的文件读写流,实际上是调用了底层系统的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 的数据,它们最终执行的结果如下图所示:

通过结果可以看出有缓冲区的是性能最强的?

------------------------------------------与正文内容无关------------------------------------
如果觉的文章写对各位读者老爷们有帮助的话,麻烦点赞加关注呗!小弟在这拜谢了!
如果您觉得我的文章在某些地方写的不尽人意或者写的不对,从而让你对你人生观产生颠覆(概不负责),需要斧正,麻烦在评论区不吝赐教,作者看到后会根据您的提示查阅文章进行修改,还这世间一个公理一片蓝天

文章来源:https://blog.csdn.net/weixin_43895362/article/details/135368822
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。