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

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

一、什么是 IO 流?


想象一个场景:我们在电脑上编辑文件,可以保存到硬盘上,也可以拷贝到 U 盘中。那这个看似简单的过程,背后其实是数据的传输。数据的传输,也就是数据的流动。既然是流动也就会有方向,有入方向和出方向。举个上传文件的栗子,现在有三个对象,文件、应用程序、上传的目标地址(服务器)。简化的上传文件有两步:


  • 应用程序读文件(此为入方向,文件读入到应用程序)


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


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


二、IO 流的分类


我不认同网络上很多 IO 流的图,他们只是简单的把 io 流分成字节流和字符流。这样的分类也不是说不好,只是太臃肿、难记。先上一张我自己总结的 IO 留的思维导图,我先把它分成了节点流处理流节点流是直接接触数据源的,而处理流是出于各种目的在节点流的基础上再套一层的 IO 流再按照操作类型,分成 8 个小类,然后才是字节、字符分类,最后才是输入、输出的分类。具体可以看以下思维导图 (可能不清晰,有需要的在后台回复 IO 流获取原思维导图)根据数据的流向分为:输入流输出流


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


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


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


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


640.jpg


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/>
 */
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 秒复制完成。


640.png


最后是缓冲流使用数组(一次读多一点):


/**
 * 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 秒复制完成。


640.png


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();
    }
}


结果:

640.png


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.废人


五、源码地址


https://github.com/turoDog/review_java

相关文章
|
30天前
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
65 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
|
2月前
|
Java
缓冲流和转换流的使用【 File类+IO流知识回顾③】
这篇文章介绍了Java中缓冲流(BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter)和转换流(InputStreamReader, OutputStreamWriter)的使用,包括它们的构造方法和如何利用它们提高IO操作的效率及处理字符编码问题。
缓冲流和转换流的使用【 File类+IO流知识回顾③】
|
1月前
|
Java 数据处理 开发者
揭秘Java IO流:字节流与字符流的神秘面纱!
揭秘Java IO流:字节流与字符流的神秘面纱!
32 1
|
1月前
|
自然语言处理 Java 数据处理
Java IO流全解析:字节流和字符流的区别与联系!
Java IO流全解析:字节流和字符流的区别与联系!
70 1
|
2月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
23天前
|
Java
Java 中 IO 流的分类详解
【10月更文挑战第10天】不同类型的 IO 流具有不同的特点和适用场景,我们可以根据具体的需求选择合适的流来进行数据的输入和输出操作。在实际应用中,还可以通过组合使用多种流来实现更复杂的功能。
39 0
|
2月前
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
|
1月前
|
存储 Java 程序员
【Java】文件IO
【Java】文件IO
35 0
|
5天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
34 4
|
2月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
377 37
下一篇
无影云桌面