【深入理解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;是因为其对计算机资源的高需求。 电影特效渲染费时间原…

LangChain - 文本嵌入

文章目录 一、关于 文本嵌入模型二、入门1、设置 OpenAI2、embed_documents3、embed_query 三、集成示例1、DashScope2、OpenAI3、Hugging Face Hub4、Fake Embeddings 本文转载改编自&#xff1a; https://python.langchain.com.cn/docs/modules/data_connection/text_embeddi…

Java对象转型

&#xff08;一&#xff09;向上转型 向上转型&#xff08;自动转型&#xff09;&#xff0c;指子类类型转父类类型 //父类 public class Father {public String fatherAttr "父类属性";public void fatherMethod(){System.out.println("父类成员方法")…

1077:统计满足条件的4位数

1077&#xff1a;统计满足条件的4位数 时间限制: 1000 ms 内存限制: 65536 KB 提交数:79300 通过数: 54638 【题目描述】 给定若干个四位数&#xff0c;求出其中满足以下条件的数的个数&#xff1a;个位数上的数字减去千位数上的数字&#xff0c;再减去百位数上的数…

2024.3.29力扣每日一题——元素和最小的山形三元组1

2024.3.29 题目来源我的题解方法一 暴力解法方法二 规律 题目来源 力扣每日一题&#xff1b;题序&#xff1a;2908 我的题解 方法一 暴力解法 使用三层循环&#xff0c;分别控制左边界、峰值、右边界&#xff0c;依次遍历。 时间复杂度&#xff1a;O( n 3 n^3 n3) 空间复杂度…

Redis的三种部署方案

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

MYSQL数据库故障排除与优化

目录 MySQL 单实例故障排查 MySQL 主从故障排查 MySQL 优化 MySQL 单实例故障排查 故障现象 1 ERROR 2002 (HY000): Cant connect to local MySQL server through socket /data/mysql/mysql.sock (2) 问题分析&#xff1a;以上这种情况一般都…

80V输入1.5A,DC/DC高效率降压型电源芯片--ZCC2480

产品概述&#xff1a; ZCC2480 是一款内部集成有功率 MOSFET 管的降压型开关稳压器。以电流模式控制方式达到快速 环路响应并提高环路的稳定性。宽范围输入电压&#xff08; 4.5 V 至 80V &#xff09;提供最大 1.5A 电流的高效率输出&#xff0c; 可在移动环境输入的条件下实现…

Java编程规范及最佳实践

文章目录 一、命名规范二、代码风格规范三、注释规范四、推荐的编程实践五、类和接口六、异常处理七、可见性八、并发九、代码复用十、代码组织和模块化十一、Java集合框架十二、输入验证十三、资源管理十四、文档和注释十五、测试和代码质量十六、代码可读性十七、性能优化十八…

Django之REST Client插件

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

[C++][C++类型转换]详解

目录 1.C语言中的类型转换2.为什么C需要四种类型转换&#xff1f;3.C强制类型转换1.static_cast2.reinterpret_case3.const_cast4.dynamic_cast 4.RTTI(了解) 1.C语言中的类型转换 在C语言中&#xff0c;如果赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配&…

【小白学机器学习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 …

2024.4.6力扣每日一题——树节点的第 K 个祖先

2024.4.6 题目来源我的题解方法一 哈希表 超内存方法二 树上倍增 题目来源 力扣每日一题&#xff1b;题序&#xff1a;1483 我的题解 方法一 哈希表 超内存 使用一个哈希表存储每个节点的祖先节点。 时间复杂度&#xff1a;O(n) 空间复杂度&#xff1a;O( n 2 n^2 n2) class…

hydra九头蛇

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

2024年华为OD机试真题-启动多任务排序-Java-OD统一考试(C卷)

题目描述: 一个应用启动时,会有多个初始化任务需要执行,并且任务之间有依赖关系,例如A任务依赖B任务,那么必须在B任务执行完成之后,才能开始执行A任务。 现在给出多条任务依赖关系的规则,请输入任务的顺序执行序列,规则采用贪婪策略,即一个任务如果没有依赖的任务,则…

蓝桥杯小白入门赛第9场第4题 字典树考试

问题描述 蓝桥学院最近教学了字典树这一数据结构,小蓝是全班的第一名,他不仅掌握了普通字典树,还自学了 01 字典树的使用。为了展示自己的能力,他向全班同学出了以下问题: 给定一个长度为 N N N 的数组 A A A , 你能否求出表达式 ∑ i = 1 N ∑ j = i + 1 N f ( A i …

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

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

基于STM32f103芯片的应用程序在线升级功能框架的实现

目录 基于STM32f103芯片的应用程序在线升级功能框架的实现 一、原理简介 二、KEIL软件主要设置 三、应用程序app部分 四、Bootloader部分 五、补充部分 基于STM32f103芯片的应用程序在线升级功能框架的实现 一、原理简介 我们在使用stm32的过程中&#xff0c;如果需要对…