先来偷个图,然后扶好小板凳,别摔着了。
这是传统IO的文件操作
传统IO传输文件.png
假设有以下场景,浏览器请求磁盘上的文件a.txt,那么会大致经历以下几个步骤:
- 从磁盘读文件
- 把文件传输给浏览器
好,大致步骤完毕,那么接下来是详细步骤
- 从磁盘读文件属于内核操作,由于jvm运行在用户空间,那么当jvm接收到从磁盘读取文件的事件时,需要调用系统函数通过内核来读取a.txt,并且将a.txt缓存在内核空间的缓冲区
- 内核将a.txt读取到内核缓冲区后,jvm将其a.txt从内核缓冲区copy到用户空间缓冲区
- jvm copy完成之后,将数据发送到浏览器,那么需要通过网卡,网卡属于外设,操作外设需要内核操作,于是:
3.1 jvm将数据copy到内核缓冲区
3.2 内核将数据写入网卡
3.3 内核返回jvm
3.4 jvm操作结束
以上操作就是传统io的文件下载操作,可以发现,整个过程中经历了4次copy操作,分别是:
1. 磁盘 -> 内核
2. 内核 -> 用户
3. 用户 -> 内核
4. 内核 -> 网卡
这种操作有个显而易见的缺点,从内核到用户从用户到内核,这2步似乎是多余的,为何不直接在内核中操作呢。为了实现不经过用户缓冲区的copy,nio提供了零拷贝,请往下看
再来偷个图
NIO文件操作
依然是浏览器请求磁盘上的文件a.txt,那么使用零拷贝会经历以下步骤:
- jvm发起sendfile()调起内核操作
- 内核读取a.txt,并存放到内核缓冲区(data to kernel)
- 内核将缓冲区(data to kernel)里的内容写入到 内核的另一个缓冲区(data to target socket),写入网卡
- 完成后返回sendfile()系统调用
零拷贝的方式显然比传统io减少了2次从内核到用户,从用户到内核的copy过程,只是在内核空间里做了2次缓冲区数据的copy(对于操作系统来说,这2次缓冲区的copy也是可以合并成一次的,不再深究)
零拷贝的问题:
假设另一个场景,浏览器请求磁盘文件b.txt,并需要吧b.txt里的sb都替换成xxx。
上述的零拷贝,看起来不能满足这个场景,因为使用零拷贝的只调用一次sendfile(),剩下的操作都有内核完成,jvm不再介入了。
那么,如何解决这个问题呢?
内存映射可以解决这个问题,将内核空间缓冲区的内容映射到jvm内存里,通过jvm,即可操作文件内容
网友评论