前言
Java I/O 功能封装的很好, 使用起来很方便, 就是刚开始学的时候, 如果不了解装饰器模式, 会被他繁多的类给吓到. 用多了也就习惯了, 而且现在有很多实用的封装良好的实用类, 可直接读写整个文件. 开发者不知道底层实现细节, 也可以灵活使用, 这是封装的一大优点. 但是, 作为一名软件开发人员, 对其所使用的代码不能仅仅停留在熟悉功能特性上, 最好对其实现原理也要有一定了解.
注: 本文引用了部分外文内容, 并根据自己的理解进行了翻译, 连接将在文末贴出.
-------------------------------------- 外文引用内容 Begin(已翻译)----------------------------------------------------------------
缓冲处理, 内核空间 vs 用户空间
缓冲, 以及如何处理缓冲是所有 IO 的基本内容. 术语 "I/O"(输入输出)指的不过就是从缓冲区移入或移除数据. 通常, 进程执行 I/O 操作的方式是, 向操作系统发送请求, 请求其填充自己的缓冲区(或者把自己缓冲区的内容写出). 这就是 I/O 这个概念的全部内容. 要实现这些传输操作, 操作系统底层的实现非常复杂. 但是在概念上, 本文所要讲述的内容则非常直白.
注意: User space 和 Kernel space 都属于内存. 内存分为两个区, 用户区和系统区(内核区).
上图简要展示了, 块数据如何从外部源头 (比如硬盘) 移入到进程的内存空间的过程. 首先, 这个进程通过系统调用 read(), 请求填充自己的缓冲区. 这将导致内核发送一个命令到磁盘控制器, 使其从磁盘中抓取数据. 磁盘控制器通过 DMA 把数据直接写入到内核空间缓冲区, 这个过程不需要 CPU 干预. 一旦磁盘控制器完成了填充数据的任务, 内核就将数据从内核空间的临时缓冲区转移到进程指定的缓冲区内.
有一件事需要注意, 内核会试图缓冲或者说预加载一些数据, 所以有可能进程所请求的数据已经在内核空间里了. 如果这样的话, 进程请求的数据, 只需要从内核缓冲区拷贝一份即可. 如果数据不在内核空间内, 则在内核获取数据到内存的过程中, 此进程将被挂起.
-------------------------------------- 外文引用内容 End(已翻译)-------------------------------------------------------------------
从上述内容可知:
Java 的读写操作, 底层由 C/C++ 实现. 而不是直接与 OS 接触
C/C++ 读写操作, 需要 OS 服务
内核自带缓冲, 会过分加载
如果内存中没有数据的缓冲, 读写操作将阻塞当前线程(OS 会帮你挂起线程)
DMA
DMA(Direct Memory Access, 直接内存存取)是 I/O 设备控制方式的一种. 我个人认为它们的主要差别在于 CPU 的参与 I/O 控制的程度
I/O 设备控制方式有:
程序 I/O 方式 --CPU 需反复检查
中断 I/O 方式 -- 每完成一个字节的读写, 通知 CPU
DMA 方式 -- 每完成一个块 (多字节) 的读写, 通知 CPU
I/O 通道方式(暂不了解)
在 DMA 读写 I/O 设备的时候, CPU 不会被影响, 它可以继续执行. 注意! 这里能继续执行, 指的是 CPU 可以继续运行, 而此 I/O 操作的线程已经被挂起, 不参与 CPU 调度. I/O 操作完成后, 该线程才被唤醒, 参与调度(加入就绪队列, 等待时间片)
系统调用
系统调用是应用程序间接调用 OS 函数的方式. C 语言有提供与系统调用相对应的库函数. 这里就是 read,write.
BufferedXXStream
注意, 对于 Java 来说, 系统调用的开销是比较大的. 首先读写操作要触发的是本地方法 read0,readBytes,write0,writeBytes, 这里 JNI 需要一定开销. 还有就是每产生一个系统调用, 就可能产生上千个机器指令, 这种开销是不容小觑的. 所以, 我们要尝试减少系统调用. 那有人就会问了, 不行啊, 我数据又不能缺斤少两, 少读少写肯定出问题, 怎么减少调用? 这不是很好解决吗, 每次多读写一点, 调用的次数不就少了嘛. 而 BufferedXXStream 就是这么用的, 例如, BufferedInputStream 的 read 无参方法只读取一个字节, 而实际上 BufferedInputStream 默认读取了 8kb, 这些数据用字节数组保留.
对了, 如果对上图, 不是很理解, 可以看看这张.
即运行时, 有一个对象 BufferedInputStream, 其调用一次 read()方法, 数据保留到 buf 数组中.
参考文献
How Java I/O Works Internally at Lower Level?(需 FQ)
来源: https://www.cnblogs.com/longfurcat/p/9932348.html