专栏首页用户5521492的专栏Java基础(五)| IO 流之使用缓冲流的正确姿势

Java基础(五)| IO 流之使用缓冲流的正确姿势

整理下以前自学的笔记,留个念想,不喜轻喷。希望基础不好的同学看完这篇文章,能掌握缓冲流,而基础好的同学权当复习,希望看完这篇文章能够起一点你的青涩记忆。

一、什么是 IO 流?

想象一个场景:我们在电脑上编辑文件,可以保存到硬盘上,也可以拷贝到 U 盘中。那这个看似简单的过程,背后其实是数据的传输。

数据的传输,也就是数据的流动。既然是流动也就会有方向,有入方向和出方向。举个上传文件的栗子,现在有三个对象,文件、应用程序、上传的目标地址(服务器)。简化的上传文件有两步:

  • 应用程序读文件(此为入方向,文件读入到应用程序)
  • 应用程序写文件(此为出方向,读完之后写到目标地址)

注意这个入和出是相对的,它相对于应用程序而言。如果相对于服务器而言,这个上传文件操作就是入方向,从应用程序读入。Java 中 I/O 操作主要是指使用 java.io 包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。

二、IO 流的分类

我不认同网络上很多 IO 流的图,他们只是简单的把 io 流分成字节流和字符流。这样的分类也不是说不好,只是太臃肿、难记。

先上一张我自己总结的 IO 留的思维导图,我先把它分成了节点流处理流节点流是直接接触数据源的,而处理流是出于各种目的在节点流的基础上再套一层的 IO 流。再按照操作类型,分成 8 个小类,然后才是字节、字符分类,最后才是输入、输出的分类。具体可以看以下思维导图 (可能不清晰,有需要的在后台回复 IO 流获取原思维导图)

根据数据的流向分为:输入流输出流

  • 输入流 :把数据从其他设备上读取到内存中的流。
  • 输出流 :把数据从内存 中写出到其他设备上的流。

根据数据的类型分为:字节流字符流

  • 字节流 :以字节为单位,读写数据的流。
  • 字符流 :以字符为单位,读写数据的流。

Java IO 流

IO 流要说明白需要好几篇才行,今天复习缓冲流。

三、为什么需要缓冲流?

前面我们已经复习过字节流、字符流。使用基本的字节输入流读取文件,就相当于将文件中的数据,通过操作系统,在通过 JVM 一个个传入到内存中,这样的话,文件读取的速度比较慢。如果使用字节缓冲流,就可以建立一个缓冲区(相当于一个数组),将缓冲区里面的数据批量传入到文件中,这样的话就提高了文件的读取速度。一句话概括就是:缓冲流比较高效,因为它减少了 IO 的次数

四、使用缓冲流

缓冲流,也叫高效流,是对 4 个基本的字节、字符流的增强,所以也是 4 个流,按照数据类型分类:

  • 字节缓冲流BufferedInputStreamBufferedOutputStream
  • 字符缓冲流BufferedReaderBufferedWriter

它的基本原理是:会在创建流的时候创建一个默认大小的内置缓冲区,从而减少文件系统 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/>
 */
public class 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/>
 */
public class 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/>
 */
public class CopyFileByBufferArray {

    public static void main(String[] args) throws FileNotFoundException {
        // 开始时间
        long startTime = System.currentTimeMillis();
        // 计数器,用于判断
        int b;
        // 使用数组,一次读多点
        byte[] bytes = new byte[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/>
 */
public class 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/>
 */
public class 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/>
 */
public class 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.废人

五、源码地址

https://github.com/turoDog/review_java

-END-

本文分享自微信公众号 - 一个优秀的废人(feiren_java),作者:nasus

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-02-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java 基础(四)| IO 流之使用文件流的正确姿势

    为跳槽面试做准备,今天开始进入 Java 基础的复习。希望基础不好的同学看完这篇文章,能掌握泛型,而基础好的同学权当复习,希望看完这篇文章能够起一点你的青涩记忆...

    一个优秀的废人
  • Java 项目热部署,节省构建时间的正确姿势

    简而言之,不管你修改了类还是资源,只需要重新 Build 一下相关的类,改动就直接反映到你的应用程序了。

    一个优秀的废人
  • 国庆抢不到票?可以试试这两个工具

    哈喽,还有一周就到国庆节了,相信很多人都坐不住了。这 7 天的假期想回家看看父母,有些人想出外游玩。不管是那样,都躲不开买票。而根据以往经验,国庆节这种假期的票...

    一个优秀的废人
  • 【图像分类】 关于图像分类中类别不平衡那些事

    欢迎大家来到图像分类专栏,类别不平衡时是很常见的问题,本文介绍了类别不平衡图像分类算法的发展现状,供大家参考学习。

    用户1508658
  • java基础io流——OutputStream和InputStream的故事(温故知新)

    IO流用来处理设备之间的数据传输,上传文件和下载文件,Java对数据的操作是通过流的方式,Java用于操作流的对象都在IO包中。

    100000860378
  • JS编写的银行木马是怎样的存在?一起逆向来看看

    Gootkit——在一些地方也被称为Xswkit ,是一款几乎完全用JavaScript编写的银行恶意软件。在这篇博客,我们将逆向该恶意软件,解密其webinj...

    FB客服
  • 商城项目-商品详情

    当用户搜索到商品,肯定会点击查看,就会进入商品详情页,接下来我们完成商品详情页的展示,

    cwl_java
  • 二分查找真的很快吗

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    Alan Lee
  • [javaSE] GUI(打开文件对话框)

    使用FileDialog可以打开文件对话框,根据模式不同,可以分为打开文件和保存文件对话框

    陶士涵
  • 《从案例中学习JavaScript》之酷炫音乐播放器(三)进度条音轨

    剽悍一小兔

扫码关注云+社区

领取腾讯云代金券