【深入理解Java IO流0x05】Java缓冲流:为提高IO效率而生

1. 引言

我们都知道,内存与硬盘的交互是比较耗时的,因此适当得减少IO的操作次数,能提升整体的效率。
Java 的缓冲流是对字节流和字符流的一种封装(装饰器模式,关于IO流中的一些设计模式,后续会再出博客来讲),通过在内存中开辟缓冲区来提高 I/O 操作的效率。Java 通过 BufferedInputStream 和 BufferedOutputStream 来实现字节流的缓冲,通过 BufferedReader 和 BufferedWriter 来实现字符流的缓冲。
缓冲流的工作原理是将数据先写入缓冲区中,当缓冲区满时再一次性写入文件或输出流,或者当缓冲区为空时一次性从文件或输入流中读取一定量的数据。这样可以减少系统的 I/O 操作次数,提高系统的 I/O 效率,从而提高程序的运行效率。

2. 字节缓冲流

BufferedInputStream 和 BufferedOutputStream 属于字节缓冲流,强化了字节流 InputStream 和 OutputStream。

2.1 构造方法

  • BufferedInputStream(InputStream in) :创建一个新的缓冲输入流,注意参数类型为InputStream。
  • BufferedOutputStream(OutputStream out): 创建一个新的缓冲输出流,注意参数类型为OutputStream。

实战代码:

// 创建字节缓冲输入流,先声明字节流
FileInputStream fps = new FileInputStream(b.txt);
// 再通过装饰器模式创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(fps)// 也可以一步到位
// 创建字节缓冲输入流(一步到位)
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("b.txt"));// 创建字节缓冲输出流(一步到位)
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("b.txt"));

2.2 高效性

通过实战来感受一下缓冲流的高效:
分别通过字节流和字节缓冲流复制一个 524.9 mb 的 PDF 文件对比如下:

@Test
void copy_pdf_to_another_pdf_buffer_stream() {// 记录开始时间long start = System.currentTimeMillis();try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("深入理解计算机操作系统.pdf"));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("深入理解计算机操作系统-副本.pdf"))) {int content;while ((content = bis.read()) != -1) {bos.write(content);}} catch (IOException e) {e.printStackTrace();}// 记录结束时间long end = System.currentTimeMillis();System.out.println("使用缓冲流复制PDF文件总耗时:" + (end - start) + " 毫秒");
}@Test
void copy_pdf_to_another_pdf_stream() {// 记录开始时间long start = System.currentTimeMillis();try (FileInputStream fis = new FileInputStream("深入理解计算机操作系统.pdf");FileOutputStream fos = new FileOutputStream("深入理解计算机操作系统-副本.pdf")) {int content;while ((content = fis.read()) != -1) {fos.write(content);}} catch (IOException e) {e.printStackTrace();}// 记录结束时间long end = System.currentTimeMillis();System.out.println("使用普通流复制PDF文件总耗时:" + (end - start) + " 毫秒");
}---------------------------------------------------------------------------
output:
使用缓冲流复制PDF文件总耗时:15428 毫秒
使用普通字节流复制PDF文件总耗时:2555062 毫秒

当然,上面的代码我们读和写分别调用的是read()write(int b),所以差距会这么大。
如果调用的是read(byte[] b)write(byte[] b, int off, int len)这两个方法的话,只要我们选的字节数组大小合适,两者性能差距其实并不大,不妨试一下:

@Test
void copy_pdf_to_another_pdf_with_byte_array_buffer_stream() {// 记录开始时间long start = System.currentTimeMillis();try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("深入理解计算机操作系统.pdf"));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("深入理解计算机操作系统-副本.pdf"))) {int len;byte[] bytes = new byte[4 * 1024];while ((len = bis.read(bytes)) != -1) {bos.write(bytes, 0, len);}} catch (IOException e) {e.printStackTrace();}// 记录结束时间long end = System.currentTimeMillis();System.out.println("使用缓冲流复制PDF文件总耗时:" + (end - start) + " 毫秒");
}@Test
void copy_pdf_to_another_pdf_with_byte_array_stream() {// 记录开始时间long start = System.currentTimeMillis();try (FileInputStream fis = new FileInputStream("深入理解计算机操作系统.pdf");FileOutputStream fos = new FileOutputStream("深入理解计算机操作系统-副本.pdf")) {int len;byte[] bytes = new byte[4 * 1024];while ((len = fis.read(bytes)) != -1) {fos.write(bytes, 0, len);}} catch (IOException e) {e.printStackTrace();}// 记录结束时间long end = System.currentTimeMillis();System.out.println("使用普通流复制PDF文件总耗时:" + (end - start) + " 毫秒");
}
--------------------------------------------------------------------------------
output:
使用缓冲流复制PDF文件总耗时:695 毫秒
使用普通字节流复制PDF文件总耗时:989 毫秒

两者耗时差别不是很大,缓冲流的性能要略微好一点点。

2.3 为什么字节缓冲流这么快?

传统的 Java IO 是阻塞模式的,它的工作状态就是“读/写,等待,读/写,等待…”
字节缓冲流解决的就是这个问题:一次多读点多写点,减少读写的频率,用空间换时间

  • 减少系统调用次数:在使用字节缓冲流时,数据不是立即写入磁盘或输出流,而是先写入缓冲区,当缓冲区满时再一次性写入磁盘或输出流。这样可以减少系统调用的次数,从而提高 I/O 操作的效率。
  • 减少磁盘读写次数:在使用字节缓冲流时,当需要读取数据时,缓冲流会先从缓冲区中读取数据,如果缓冲区中没有足够的数据,则会一次性从磁盘或输入流中读取一定量的数据。同样地,当需要写入数据时,缓冲流会先将数据写入缓冲区,如果缓冲区满了,则会一次性将缓冲区中的数据写入磁盘或输出流。这样可以减少磁盘读写的次数,从而提高 I/O 操作的效率。
  • 提高数据传输效率:在使用字节缓冲流时,由于数据是以块的形式进行传输,因此可以减少数据传输的次数,从而提高数据传输的效率。

来看BufferedInputStream的read方法:

public synchronized int read() throws IOException{if (pos >= count) {     // 如果当前位置已经到达缓冲区末尾fill();             // 填充缓冲区if (pos >= count)   // 如果填充后仍然到达缓冲区末尾,说明已经读取完毕return -1;      // 返回 -1 表示已经读取完毕}return getBufIfOpen()[pos++] & 0xff; // 返回当前位置的字节,并将位置加 1
}

这段代码主要有两部分:

  • fill():该方法会将缓冲 buf 填满。
  • getBufIfOpen()[pos++] & 0xff:返回当前读取位置 pos 处的字节(getBufIfOpen()返回的是 buffer 数组,是 byte 类型),并将其与 0xff 进行位与运算。这里的目的是将读取到的字节 b 当做无符号的字节处理,因为 Java 的 byte 类型是有符号的,而将 b 与 0xff 进行位与运算,就可以将其转换为无符号的字节,其范围为 0 到 255。

byte & 0xFF 我们后面会讲。

再来看FileInputStream的read方法:
image.png
在这段代码中,read0()方法是一个本地方法,它的实现是由底层操作系统提供的,并不是 Java 语言实现的。在不同的操作系统上,read0()方法的实现可能会有所不同,但是它们的功能都是相同的,都是用于读取一个字节
再来看一下 BufferedOutputStream 的 write(byte b[], int off, int len) 方法:

public synchronized void write(byte b[], int off, int len) throws IOException {if (len >= buf.length) {    // 如果写入的字节数大于等于缓冲区长度/* 如果请求的长度超过了输出缓冲区的大小,先刷新缓冲区,然后直接将数据写入。这样可以避免缓冲流级联时的问题。*/flushBuffer();          // 先刷新缓冲区out.write(b, off, len); // 直接将数据写入输出流return;}if (len > buf.length - count) { // 如果写入的字节数大于空余空间flushBuffer();              // 先刷新缓冲区}System.arraycopy(b, off, buf, count, len); // 将数据拷贝到缓冲区中count += len;                             // 更新计数器
}

首先,该方法会检查写入的字节数是否大于等于缓冲区长度,如果是,则先将缓冲区中的数据刷新到磁盘中,然后直接将数据写入输出流。这样做是为了避免缓冲流级联时的问题,即缓冲区的大小不足以容纳写入的数据时,可能会引发级联刷新,导致效率降低。

级联问题(Cascade Problem)是指在一组缓冲流(Buffered Stream)中,由于缓冲区的大小不足以容纳要写入的数据,导致数据被分割成多个部分,并分别写入到不同的缓冲区中,最终需要逐个刷新缓冲区,从而导致性能下降的问题。

其次,如果写入的字节数小于缓冲区长度,则检查缓冲区中剩余的空间是否足够容纳要写入的字节数,如果不够,则先将缓冲区中的数据刷新到磁盘中。然后,使用 System.arraycopy() 方法将要写入的数据拷贝到缓冲区中,并更新计数器 count。
最后,如果写入的字节数小于缓冲区长度且缓冲区中还有剩余空间,则直接将要写入的数据拷贝到缓冲区中,并更新计数器 count。也就是说,只有当 buf 写满了,才会 flush,将数据刷到磁盘。
缓冲区的默认大小为 8192 个字节
对比一下 FileOutputStream 的 write 方法,同样是本地方法,一次只能写入一个字节。
image.png

2.4 byte & 0xFF

byte 类型通常被用于存储二进制数据,例如读取和写入文件、网络传输等场景。在这些场景下,byte 类型的变量可以用来存储数据流中的每个字节,从而进行读取和写入操作。
byte 类型是有符号的,即其取值范围为 -128 到 127。如果我们希望得到的是一个无符号的 byte 值,就需要使用 byte & 0xFF来进行转换。
这是因为 0xFF 是一个无符号的整数,它的二进制表示为 11111111。当一个 byte 类型的值与 0xFF 进行位与运算时,会将 byte 类型的值转换为一个无符号的整数,其范围为 0 到 255。
0xff 是一个十六进制的数,相当于二进制的 11111111,& 运算符的意思是:如果两个操作数的对应位为 1,则输出 1,否则为 0;由于 0xff 有 8 个 1,单个 byte 转成 int 其实就是将 byte 和 int 类型的 255 进行(&)与运算。
例如,如果我们有一个 byte 类型的变量 b,其值为 -1,那么 b & 0xFF 的结果就是 255。这样就可以将一个有符号的 byte 类型的值转换为一个无符号的整数。
& 运算是一种二进制数据的计算方式, 两个操作位都为1,结果才为1,否则结果为0. 在上面的 getBufIfOpen()[pos++] & 0xff 计算过程中, byte 有 8bit, 0XFF 是16进制的255, 表示的是 int 类型, int 有 32bit。如果 getBufIfOpen()[pos++] 为 -118, 那么其原码/反码/补码表示为

00000000 00000000 00000000 10001010  // 原码
11111111 11111111 11111111 11110101  // 反码
11111111 11111111 11111111 11110110  // 补码

0xFF 表示16进制的数据255, 原码, 反码, 补码都是一样的, 其二进制数据为

00000000 00000000 00000000 11111111

0xFF和-118的补码相&:

00000000 00000000 00000000 11110110

还原为原码:

00000000 00000000 00000000 10001010

其表示的 int 值为 138,可见将 byte 类型的 -118 与 0XFF 进行与运算后值由 -118 变成了 int 类型的 138,其中低8位和byte的-118完全一致。

3. 字符缓冲流

BufferedReader 类继承自 Reader 类,提供了一些便捷的方法,例如 readLine() 方法可以一次读取一行数据,而不是一个字符一个字符地读取。
BufferedWriter 类继承自 Writer 类,提供了一些便捷的方法,例如 newLine() 方法可以写入一个系统特定的行分隔符。

3.1 构造方法

  • BufferedReader(Reader in) :创建一个新的缓冲输入流,注意参数类型为Reader。
  • BufferedWriter(Writer out): 创建一个新的缓冲输出流,注意参数类型为Writer。

实战代码:

// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("b.txt"));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));

3.2 独有的方法

字符缓冲流的基本方法与普通字符流调用方式一致,这里不再赘述,我们来看字符缓冲流特有的方法。

  • BufferedReader:String readLine(): 读一行数据,读取到最后返回 null
  • BufferedWriter:newLine(): 换行,由系统定义换行符。

来看readLine()实战:

// 创建流对象
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
// 定义字符串,保存读取的一行文字
String line  = null;
// 循环读取,读取到最后返回null
while ((line = br.readLine())!=null) {System.out.print(line);System.out.println("------");
}
// 释放资源
br.close();

newLine()实战:

// 创建流对象
BfferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
// 写出数据
bw.write("你");
// 写出换行
bw.newLine();
bw.write("好");
bw.newLine();
bw.write("世");
bw.newLine();
bw.write("界");
bw.newLine();
// 释放资源
bw.close();

4. 字符缓冲流实战

来看这样一段文本test.txt:

6.岑夫子,丹丘生,将进酒,杯莫停。
1.君不见黄河之水天上来,奔流到海不复回。
8.钟鼓馔玉不足贵,但愿长醉不愿醒。
3.人生得意须尽欢,莫使金樽空对月。
5.烹羊宰牛且为乐,会须一饮三百杯。
2.君不见高堂明镜悲白发,朝如青丝暮成雪。
7.与君歌一曲,请君为我倾耳听。
4.天生我材必有用,千金散尽还复来。

要求正确排序并输出到test2.txt。
来实战一把:

@Test
public void test005() throws IOException {BufferedReader br = new BufferedReader(new FileReader("test.txt"));BufferedWriter bw = new BufferedWriter(new FileWriter("test2.txt"));HashMap<String,String> map = new HashMap<>();String line;while((line = br.readLine()) != null){if(line.isEmpty()){continue;}String[] arr = line.split(Pattern.quote("."));map.put(arr[0],arr[1]);}for (int i = 1; i <= map.size(); i++) {String key = String.valueOf(i);String value = map.get(key);bw.write(key+"."+value);bw.newLine();}br.close();bw.close();
}

效果:
image.png

注意,我们要用Pattern.quote(“.”)来表示"."
如果直接写line.split(“.”),这里会将".“识别为正则表达式的"点”,表示任何字符,就会报错。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/800237.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

详解简单的shell脚本 --- 命令行解释器【Linux后端开发】

首先附上完整代码 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> //命令行解释器 //shell 运行原理&#xff1a;通过让子进程执行命令&#xff0c;父进…

谷歌浏览器插件开发速成指南:弹窗

诸神缄默不语-个人CSDN博文目录 本文介绍谷歌浏览器插件开发的入门教程&#xff0c;阅读完本文后应该就能开发一个简单的“hello world”插件&#xff0c;效果是出现写有“Hello Extensions”的弹窗。 作为系列文章的第一篇&#xff0c;本文还希望读者阅读后能够简要了解在此基…

电影特效渲染为什么费时间?「瑞云渲染」

影视特效渲染过程通常耗时且资源密集&#xff0c;因为它涉及处理复杂的视觉元素和光影效果。瑞云渲染通过云技术提供解决方案&#xff0c;加快渲染速度并降低成本。简而言之&#xff0c;电影特效渲染之所以费时&#xff0c;是因为其对计算机资源的高需求。 电影特效渲染费时间原…

Redis的三种部署方案

文章目录 单机模式主从复制哨兵模式分片集群 在Redis中提供的集群方案总共有三种&#xff1a;单机模式&#xff0c;主从复制集群、哨兵模式&#xff0c;Redis分片集群 单机模式 Redis 只运行在一台服务器上&#xff0c;并且所有的数据都存储在这一台服务器的内存中。 主从复制…

Django之REST Client插件

一、接口测试工具介绍 在开发前后端分离项目时,无论是开发后端,还是前端,基本都是需要测试API接口的内容,而目前我们需要开发遵循RESTFul规范的项目,也是必然的(自己不开发前端页面)。 在网上有很多这样的工具,常用的postman,但还是需要下载安装。在这我们介绍一个VSCod…

【小白学机器学习12】假设检验之3:t 检验 (t检验量,t分布,查t值表等)

目录 1 t 检验的定义 1.1 来自维基百科和百度百科 1.2 别名 1.3 和其他检验的区别 2 适用情况&#xff1a; 2.1 关于样本情况 2.2 适合检查的情况 2.2.1 单样本均值检验&#xff08;One-sample t-test&#xff09; 2.2.2 两独立样本均值检验&#xff08;Independent …

hydra九头蛇

一、hydra简介 Hydra是一款非常强大的暴力破解工具&#xff0c;它是由著名的黑客组织THC开发的一款开源暴力破解工具。Hydra是一个验证性质的工具&#xff0c;主要目的是&#xff1a;展示安全研究人员从远程获取一个系统认证权限。 目前该工具支持以下协议的爆破&#xff1a; A…

【网站项目】农业信息管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

[C#]OpenCvSharp改变图像的对比度和亮度

目的 访问像素值mat.At<T>(y,x) 用0初始化矩阵Mat.Zeros 饱和操作SaturateCast.ToByte 亮度和对比度调整 g(x)αf(x)β 用α(>0)和β一般称作增益(gain)和偏置(bias)&#xff0c;分别控制对比度和亮度 把f(x)看成源图像像素&#xff0c;把g(x)看成输出图像像素…

身份证实名认证接口的价格一般是多少呢?基于PHP身份核验接口

身份证实名认证接口分为身份证二要素、三要素、三要素人像核验接口&#xff0c;被广泛的应用于婚恋、交友、电商等等一系列行业领域&#xff0c;身份证实名认证需要实时数据&#xff0c;对于数据源来说也需要可靠&#xff0c;那么&#xff0c;身份证实名认证的价格是不是很贵呢…

小小算式(1 + 2) * (3 + 4)背后的大道理

目录 前缀表示法&#xff08;波兰表达式&#xff09; 中缀表达法 后缀表达法&#xff08;逆波兰表达式&#xff09; 三种表达法的相互转换 练习&#xff1a;逆波兰表达式求值 前缀表示法&#xff08;波兰表达式&#xff09; 波兰表示法&#xff08;英语&#xff1a;Polis…

Python学习,记录不熟悉知识点

目录 Set&#xff08;集合&#xff09; 集合内置方法完整列表 根据字符串的表达式计算结果 ​编辑 条件控制&#xff1a; if – elif – else match...case 循环语句&#xff1a; while循环 for循环 在同一行中有多个赋值操作&#xff08;先计算&#xff0c;再赋值&…

【Vue】Vue3中的OptionsAPI与CompositionAPI

文章目录 OptionsAPICompositionAPI对比总结 OptionsAPI 中文名:选项式API通过定义methods,computed,watch,data等属性方法&#xff0c;处理页面逻辑。以下是OptionsAPI代码结构 实例代码: <script lang"ts">// js或者tsimport { defineComponent } from vu…

javaScript手写专题——防抖/节流/闭包/Promise/深浅拷贝

目录 目录 一、 防抖/节流/闭包/定时器 编写一个组件&#xff0c;在input中输入文本&#xff0c;在给定的数据中查找相关的项目&#xff0c;并渲染搜索结果列表 1.新增InputSearch.vue组件 key的作用 2.新增 InputView.vue 3.添加路由 4.效果演示 follow up加上防抖怎么处理 1.…

「51媒体网」邀请媒体采访报道对企业宣传有何意义?

传媒如春雨&#xff0c;润物细无声的&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 邀请媒体采访报道对企业宣传具有多重意义&#xff1a; 提升品牌知名度和曝光度&#xff1a;媒体是信息传播的重要渠道&#xff0c;通过媒体的报道&#xff0c;企业及其活动、产品能够迅…

软考信息处理技术员2024年5月报名流程及注意事项

2024年5月软考信息处理技术员报名入口&#xff1a; 中国计算机技术职业资格网&#xff08;http://www.ruankao.org.cn/&#xff09; 2024年软考报名时间暂未公布&#xff0c;考试时间上半年为5月25日到28日&#xff0c;下半年考试时间为11月9日到12日。不想错过考试最新消息的…

Sketch是免费软件吗?这款软件支持导入!

Sketch 是一款针对网页、图标、插图等设计的矢量绘图软件。Sketch 的操作界面非常简单易懂&#xff0c;帮助全世界的设计师创作出许多不可思议的作品。但是同时&#xff0c;Sketch 也有一些痛点&#xff1a;使用 Sketch 需要安装 InVision、Abstract 、Zeplin 等插件&#xff0…

配置 施耐德 modbusTCP 分布式IO子站 PRA0100

模块官方介绍&#xff1a;https://www.schneider-electric.cn/zh/product/BMXPRA0100 1. 总体步骤 2. 软件组态&#xff1a;在 Unity Pro 软件中创建编辑 PRA 模块工程 2.1 新建项目 模块箱硬件型号如下 点击 Unity Pro 软件左上方【新建】按钮&#xff0c;选择正确的 DIO …

Filter Listener Interceptor

文章目录 第一章 Filter1. 目标2. 内容讲解2.1 Filter的概念2.2 Filter的作用2.3 Filter的入门案例2.3.1 案例目标2.3.2 代码实现2.3.2.1 创建ServletDemo012.3.2.2 创建EncodingFilter 2.4 Filter的生命周期2.4.1 回顾Servlet生命周期2.4.1.1 Servlet的创建时机2.4.1.2 Servle…

git提交代码时报错,提不了

问题 今天在换了新电脑&#xff0c;提交代码时报错 ✖ eslint --fix found some errors. Please fix them and try committing again. ✖ 21 problems (20 errors, 1 warning) husky > pre-commit hook failed (add --no-verify to bypass) 解决 通过 --no-verify 解决&…