前言
操作系统中,大多数应用运行在用户空间里。在用户空间中,执行的代码并不能之间操控硬件。因此,用户空间中的代码需要用过调用内核代码来读写存储设备上的数据。
内核代码将磁盘上的数据,存储到内核空间的缓冲区,再被用户空间读取到用户空间的缓冲区。数据来往与用户空间与存储设备的过程中,内核充当着中间人的角色。
但是,当用户空间有多个缓冲区进行读写时,多次调用内核代码的代价不小。对于这种情况,用户进程可以选择利用一次系统调用,将多个缓冲区地址传递给内核,有内核进行发散、汇聚,同时满足多个缓冲的需求。
导航
IO
java io 相关的类比较多,一眼看下来可能有些崩溃。因此,我先很粗糙的切分了一下:字节流与字符流,也就是 Stream 与 Reader/Writer。然后输入、输出流往往也是对应的,因此,任选其一后(如 InputStream)后,大体剩下了这些类:
- ByteArrayInputStream、StringBufferInputStream、FileInputStream、PipedInputStream、ObjectInputStream
- FilterInputStream
- BufferedInputStream
- DataInputStream
- LineNumberInputStream
- PushBackInputStream
可以看到,以上的类被分为了两类:一类是各种数据类型的输入(数组、字符串、文件输入等等),一类则继承了 FilterInputStream 类:将基本数据进行转换或者提供其他功能的类,也就是装饰者模式的主要对象。
总体来说,io 包下,首先划分成了 stream 与 Reader/Writer,其下有分成了输入、输出两种,其下再次分为了基本数据传输流与功能性装饰流。
stream 与 Reader/Writer 的区别显而易见,一个按字节读取,一个按字符读取:
1 | // OutputStream,按字节数组写 |
根据不同数据类型而区分的数据流更是直观:
1 | // FileInputStream,从file读取 |
我们来以常见的装饰流 BufferedInputStream 举例:
1 | // BufferedInputStream |
fill()方法会读取更多的数据到 buf,也就是缓存之中。将下次读取数据的时候,优先从缓存中读取,因此减少了本地 io api 的调用。
而基本的数据传输流,是没有 fill 与 buf 的,每次读将会直接调用本地 api,如 FileInputStream
1 | public int read() throws IOException { |
NIO
java nio与io相比,多了几个概念:buffer(缓冲)、channel(通道)、selector(选择器)。相信有了多缓冲io的那张图,理解这些概念并不困难。简而言之,从buffer中读写数据,channel传输数据,selector控制channel。
与io相比,nio无疑复杂了许多,那么nio的优势在哪里呢?这里,我们得先提一下io模型:
关于io模型,推荐阅读:漫话:如何给女朋友解释什么是Linux的五种IO模型?
io是阻塞式,而nio则是I/O复用形式。通过将多个channel注册到一个selector上,等待数据的时间会被分摊,空等数据的时间将会减少。
1 | public static void main(String[] args) throws Exception{ |
至于异步IO(AIO)模型,则是java nio 2了:
1 | AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(80)); |