JAVA IO基础知识整理

本文解析了Java IO流的基本概念,包括inputStream、outputStream、reader、writer等组件的区别和作用,并详细探讨了BufferInputStream的工作原理及其与FileInputStream之间的差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简述

说到IO,就充斥着各种 stream、reader、writer、buffer,搞的人昏头转向,那么这些名词所描述的对象,到底有什么区别,它们各自的作用又是什么,下面将会简单阐述下我的理解。

IO操作分为两种,分别是输入操作和输出操作。像 inputStream、reader这种,很明显就是输入操作,而outputStream、writer这种就是输出操作。

何为输入输出?

刚刚接触io的同学可能会跟我有一样的烦恼,一段信息,从a端传输到b端,可以描述为从a输入到b,也可以说从a输出到b。貌似在博大精深的中华文化中两种语义都说的通,所以说输入、输出到底如何区分?

由于中文的逻辑体系和英文的逻辑体系有一定区别,导致我们理解这种 input、output词汇时一脸懵逼。而正确的思路,应该是顺着英语的逻辑思想走,也就是所有的事物以描述对象为中心点去看待。从java层面来说,这个中心点就是我们的程序。那么,数据的传输就应该描述为:“数据从a端输入到我们的程序””从我们的程序输出到a端“。总而言之,输入是往描述对象上加东西,输出是从描述对象中拿东西。

关于各种 stream、buffer、reader、writer的解释

对于数据的传输,input 和 output ,read 和 Write 是相对的,本质是共通的,所以对这些工具只要解释其中一方即可。

以下是各类 stream、buffer、reader 的关系:

说明
InputStream抽象类,提供字节流操作
FileInputStreamInputStream的实现类
BufferInputStream负责包装InputStream实现类,提供缓冲功能
Reader抽象类,提供字符流操作
InputStreamReaderReader的实现类,包装InputStream实现类,提供字节转字符操作
FileReaderISR的子类,类似 is 和 fis 的关系
BufferReader包装Reader实现类,提供缓冲功能,类似 is 和 bis 的关系

BufferInputStream源码解读

  1. BufferInputStream 只有两个核心的读方法,分别是 read()、read(byte b[ ], int off, int len)
  2. java 的 BufferInputStream 的 read() 方法,并不是直接从 IO 端直接读数据,而是从内存的缓冲区里取的数据,直到缓冲区里的数据取完,它会一次性从 IO 端取批量字节的数据填满缓冲区。
  3. BufferInputStream 的 read(byte b[ ], int off, int len) ,指的是从 io 端读取数据到指定 byte数组 b[ ]中【fill()操作】。
  • 当缓冲区有数据,则直接从缓冲区读;
  • 当缓冲区没数据,如果 b 的长度大于io剩余资源的长度,直接从IO读入;
  • 如果 b 的长度小于 io 剩余资源长度,先把批量字节的io 数据读到缓冲区,再从缓冲区取。

关于IO缓冲区的2种解释

(1)

IO缓冲区是系统两端处理速度平衡用到的工具。
直白的说缓冲区就类似于现实生活中的中继站。试想一下如果你收快递,没有快递柜或者便利店这样的中转站,那么来一个快递,你就要下楼去收,来一个下一次楼,显然这样是非常消耗资源的,你会常常在忙的时候,被一个快递电话打断,不得不下楼去收快递,收完快递再回来继续自己的事情,极大的影响了效率。而有了这种缓冲区,我们可以让所有的快递先暂存便利店,等我们出门回家时,再一次过带回家,这样便完美解决了浪费资源的问题。

(2)

一个一个字节的读,读一次就要访问一次io端,这是很浪费资源的事情,就如吃一次饭就下楼买一次米。用了缓冲区后,一次性从io读批量数据到缓冲区(内存中),实际处理数据的时候再慢慢从缓冲区拿,就如一次性从商店买了一大袋米回来装米桶里,每次吃饭都从米桶拿一部分就行了。

区别

对于第(1)种解释,其实是有误区的,看起来好像没毛病,然而和io缓冲区原理还是有本质上的区别。要理解io缓冲区的本质,需先了解如下基础知识:

1、磁盘、网卡等设备只支持并发执行io操作,不支持并行操作
2、io传输,一端写的速度和一端读的速度肯定不一样

(快)内存 —— 网络 (慢)
(快)内存 —— 磁盘(慢)

此时,我们在来对比一下,正确解释1解释2到底有什么本质上的区别。

解释1中,描述的就是典型的生产者/消费者模式,为了解决生产和消费速度不一致的问题,需要引入一个buffer区,在实际应用中,我们通常使用消息系统(MQ)作为这种解决生产消费问题的中间件。注意此时存在两个主体,一个是生产者,一个是消费者,它们对数据会产生主动推拉的动作。

而在解释2中,主体只有一个,那就是我们的程序本身,整个io过程,只不过单纯是我们的程序把资源从io端取到内存中,或者是把内存中的资源存到IO一端,整个过程只有一个主体,以及资源的转移。所以解释2更为准确

总结

前者核心在于解决两个主体处理速度不平衡的问题,后者核心在于解决频繁与慢IO端交互拖慢处理速度的问题。

使用BufferInputStream 和直接用 read(byte b[ ],…) 的区别

实际上, 使用 BufferInputStream 和直接用 FileInputStream 的 read(byte b[ ],…) 并没有本质上的区别,它们底层都是调用了 FileInputStream 的 readBytes 方法。对于这两者分别的使用建议可以参考该提问:How BufferedInputStream makes the read operation faster?

另本人在看源码时就陷入了一个误区,一直在对着 InputStream 思考明明 read(byte b[ ],…)只是循环调用read()而已,那么两者效率有什么区别,遂顺其自然的把这种印象代入到 FileInputStream 中,琢磨了大半天。参考资料后才发现FIS重写了父类方法,面向对象思想不过关的代价

最后附上各类的方法性能关系如下:

效率关系原因
BufferInputStreamread() 等同 read(byte b[ ],…)底层都用缓冲区
FileInputStreamread() 不如 read(byte b[ ],…)底层native方法不同
InputStreamread() 等同 read(byte b[ ],…)read(byte b[ ],…)只是循环调用read()而已
BIS、FISBIS.read(byte b[ ],…) 不如 FIS.read(byte b[ ],…)在相同的缓冲区长度的情况下,前者多余的填充操作反而浪费性能

另外如果想查看 native 层面源码,可以去查看OpenJdk源码,内容路径可参考/ jdk8u / jdk8u40 / jdk/src/share/native/,百度下就知道怎么用本文不再赘述

以上均为个人学习整理及理解,欢迎交流,如有错漏,欢迎指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值