I/O模型

1. 字节流

InputStream/OutputStream 是字节流的抽象类,这两个抽象类又派生出了若干子类,不同的子类分别处理不同的操作类型。
如果是文件的读写操作,就使用 FileInputStream/FileOutputStream;
如果是数组的读写操作,就使用 ByteArrayInputStream/ByteArrayOutputStream;
如果是普通字符串的读写操作,就使用 BufferedInputStream/BufferedOutputStream;
具体内容如下图所示:


image

2. 字符流

Reader/Writer 是字符流的抽象类,这两个抽象类也派生出了若干子类,不同的子类分别处理不同的操作类型,具体内容如下图所示:


image

传统I/O性能问题

1.多次内存复制

调用流程
  • JVM会发出read系统调用,并通过read系统调用向内核发起读请求
  • 内核向硬件发送读指令,并等待读就绪
  • 内核把将要读取的数据复制到指向的内核缓存中
  • 操作系统内核将数据复制到用户空间缓冲区,然后read系统调用返回
    这个过程中,数据先从外部设备复制到内核空间,再从内科控制复制到用户空间,这就发生了两次内存复制操作,这种操作会导致不必要的数据拷贝和上下文切换,从而降低IO性能

2.阻塞

在传统IO中,InputStream的read是一个while循环操作,它会一直等待数据读取,直到数据就绪才会返回;意味着如果没有数据就绪,这个读取操作就会一直被挂起,用户线程将会处于阻塞状态
如何优化I/O操作

  • 使用缓冲区优化读写流操作
    在传统I/O中,提供了基于流的I/O实现,即InputStream和OutputStream,这种基于流的实现以字节为单位处理数据
    NIO与传统I/O不同,它是基于块(Block),它以块为基本单位处理数据,在NIO中,最为重要的两个组件事缓冲区和通道。Buffer是一块连续的内存快,是NIO读写数据的中转地,Channel表示缓冲数据的源头或者目的地,用于读取缓冲或者写入数据,是访问缓冲的接口。
    传统IO和NIO最大的区别是一个面向流,一个面向Buffer;Buffer可以将文件一次性读入内存再做后续处理,而传统的方式是边读文件边处理数据;
  • 使用 DirectBuffer 减少内存复制
    NIO的Buffer处理做了缓冲块优化之外,还提供了一个可以直接访问物理内存的类DirectBuffer;
    普通的Buffer分配的是JVM堆内存;
    DirectBuffer是直接分配物理内存:数据要输出到外部设备,必须先从用户空间复制到内核空间,再复制到输出设备,而在Java中,在用户空间中又存在一个拷贝,那就是从Java堆内存中拷贝到临时的直接内存中,通过临时的直接内存拷贝到内存空间中去,此时的直接内存和对内存都属于用户空间
    为什么Java需要通过一个临时的非堆内存来复制数据呢?
    如果单纯使用Java堆内存进行数据拷贝,当拷贝的数据量比较大的情况下,Java堆的GC压力会比较大,而使用非堆内存可以减少GC的压力,DirectBuffer则是直接将步骤简化为数据直接保存到非堆内存,从而较少了一次数据拷贝


    image

Q1:AIO 实现了真正意义上的异步 I/O,它是直接将 I/O 操作交给操作系统进行异步处理。这也是对 I/O 操作的一种优化,那为什么现在很多容器的通信框架都还是使用 NIO 呢?
在Linux中,AIO并未真正使用操作系统所提供的异步IO,它仍然使用poll或epoll,并将API封装为异步IO的样子,但是其本只仍然是同步非阻塞I/O
Q2:字符流/字节流的区别?
通常在通信时,使用的是字节流FileInputStream来实现数据的传输,你会发现,我们在读取read和写入write的时候都是先将字符转成字节码再进行写入操作,同样读取也是类型。如果是中文,在gbk中一般一个中文占用两个字符,如果通过字节流方式只读取一个字节,是无法转编码为一个中文汉字;而字符流就是为了解决这种问题秒如果使用字符流来读取,字符流就会根据默认编码一次性的读取一个字符。因此字符流是根据字符所占字节大小而决定读取多少字节的;
(侵删,谢谢)

你可能感兴趣的:(I/O模型)