NIO源码解析-FileChannel高阶知识点map和transferTo、transferFrom
2021/9/12 1:06:28
本文主要是介绍NIO源码解析-FileChannel高阶知识点map和transferTo、transferFrom,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
前言:
上文我们介绍了下FileChannel的基本API使用。本文中,我们就一起看下FileChannel中的高阶API。
说是高阶,还真的就是,这些知识点大量利用了操作系统的对文件传输映射的高级玩法,极大的提高了我们操作文件的效率。我们熟知的kafka、rocketMQ等也是用了这些高阶API,才有如此的高效率。
我们提出一个需求,描述如下:提供一个对外的socket服务,该服务就是获取指定文件目录下的文件,并写出到socket中,最终展现在client端。
1.传统的文件网络传输过程
按照此需求,常规方式,我们使用如下代码来完成:
File file = new File("D:\\test.txt"); Long size = file.length(); byte[] arr = new byte[size.intValue()]; try { // 1.将test.txt文件内容读取到arr中 FileInputStream fileInputStream = new FileInputStream(file); fileInputStream.read(arr); // 2.提供对外服务 Socket socket = new ServerSocket(9999).accept(); // 3.传输到客户端 socket.getOutputStream().write(arr); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
以上是一个最简单版本的实现。
那么从操作系统的角度,以上传输经历了哪些过程呢?
这中间的过程我们可以分为以下几步: fileInputStream.read方法对应于: 1)第一次复制:read方法调用,用户态切换到内核态。数据从硬盘拷贝到内核缓冲区,基于DMA自动操作,不需要CPU支持 2)第二次复制:从内核缓冲区拷贝到用户缓冲区(也就是byte[] arr中)。read方法返回,用内核态到用户态的转换。 socket.getOutputStream().write(arr)对应于: 3)第三次复制:从用户缓冲区拷贝数据到socket的内核缓冲区。write方法调用,用户态切换到内核态。 4)数据从socket内核缓冲区,使用DMA拷贝到网络协议引擎。write方法返回,内核态切换到用户态。 从上面的过程我们可以发现,数据发生了四次拷贝,四次上下文切换。 那么还有没有优化方式呢?答案是肯定的,我们接着往下看。2.mmap优化
mmap通过内存映射,将文件直接映射到内存中。此时,用户空间和内核空间可以共享这段内存空间的内容。用户对内存内容的修改可以直接反馈到磁盘文件上。 FileChannel提供了map方法来实现mmap功能File file = new File("D:\\test.txt"); Long size = file.length(); byte[] arr = new byte[size.intValue()]; try { // 1.将test.txt文件内容读取到arr中 RandomAccessFile raFile = new RandomAccessFile(file, "rwd"); FileChannel channel = raFile.getChannel(); MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, size); // 2.提供对外服务 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(9999)); serverSocketChannel.configureBlocking(false); while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); if(socketChannel != null){ // 3.传输到客户端 socketChannel.write(mappedByteBuffer); } } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }我们直接将file的内容映射到mappedByteBuffer,然后直接将mappedByteBuffer的内容传递出去。 那么从操作系统的角度,以上传输经历了哪些过程呢?
参考1中的四个步骤,少了一次内存拷贝,就是将文件从内核缓冲区拷贝到用户进程缓冲区这一步;但是上下文切换并没有减少。
3.sendFile优化(Linux2.1版本)
Linux2.1版本提供了sendFile函数,该函数对本例有哪些优化呢? 就是可以将数据不经过用户态,直接从内核文件缓冲区传输到Socket缓冲区FileChannel提供transferTo(和transferFrom)方法来实现sendFile功能
File file = new File("D:\\test.txt"); Long size = file.length(); try { // 1.将test.txt文件内容读取到arr中 RandomAccessFile raFile = new RandomAccessFile(file, "rwd"); FileChannel channel = raFile.getChannel(); // 2.提供对外服务 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(9999)); serverSocketChannel.configureBlocking(false); while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); if(socketChannel != null){ // 3.使用transferTo方法将文件数据传输到客户端 channel.transferTo(0, size, socketChannel); } } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }同2中的代码,只是在最后一步将文件内容传输到socket时,使用了不一样的方法,本例中使用了FileChannel.transferTo方法来传递数据。 那么从操作系统的角度,以上传输经历了哪些过程呢?
参照1中的4个过程,少了用户空间的参与,那么就不存在用户态与内核态的切换。 所以,总结下来,就是减少了两次上下文切换,同时,减少了一次数据拷贝。 注意:剩下的是哪两次上下文切换呢?用户进程调用transferTo方法,用户态切换到内核态;调用方法返回,内核态切换到用户态。
4.sendFile优化(Linux2.4版本)
在Linux2.4版本,sendFile做了一些优化,避免了从内核文件缓冲区拷贝到Socket缓冲区的操作,直接拷贝到网卡,再次减少了一次拷贝。 代码同3,只是具体实现时的操作系统不太一样而已。 那么从操作系统的角度,其传输经历了哪些过程呢?
参照1中的4个操作过程,同样少了用户空间的参与,也不存在用户态与内核态的切换。
所以总结下来,就是两次数据拷贝,两次上下文切换(相比较3就是减少了内核文件缓冲区到内核socket缓冲区的拷贝)
总结:
下面我们通过一个图表来展示下以上四种传输方式的异同
传输方式 | 上下文切换次数 | 数据拷贝次数 |
传统IO方式 | 4 | 4 |
mmap方式 | 4 | 3 |
sendFile(Linux2.1) | 2 | 3 |
sendFile(Linux2.4) | 2 | 2 |
首先我们说零拷贝,是从操作系统的角度来说的。因为内核缓冲区之间,没有数据是重复的(只有 kernel buffer 有一份数据, sendFile 2.1 版本实际上有 2 份数据,算不上零拷贝)。例如我们刚开始的例子,内核缓存区和 Socket 缓冲区的数据就是重复的。 而零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的 CPU 缓存伪共享以及无 CPU 校验和计算。 再稍微讲讲 mmap 和 sendFile 的区别。
参考:
linux下的mmap和零拷贝技术 - 简书
mmap与sendfile() - 简书
这篇关于NIO源码解析-FileChannel高阶知识点map和transferTo、transferFrom的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2025-01-10Rakuten 乐天积分系统从 Cassandra 到 TiDB 的选型与实战
- 2025-01-09CMS内容管理系统是什么?如何选择适合你的平台?
- 2025-01-08CCPM如何缩短项目周期并降低风险?
- 2025-01-08Omnivore 替代品 Readeck 安装与使用教程
- 2025-01-07Cursor 收费太贵?3分钟教你接入超低价 DeepSeek-V3,代码质量逼近 Claude 3.5
- 2025-01-06PingCAP 连续两年入选 Gartner 云数据库管理系统魔力象限“荣誉提及”
- 2025-01-05Easysearch 可搜索快照功能,看这篇就够了
- 2025-01-04BOT+EPC模式在基础设施项目中的应用与优势
- 2025-01-03用LangChain构建会检索和搜索的智能聊天机器人指南
- 2025-01-03图像文字理解,OCR、大模型还是多模态模型?PalliGema2在QLoRA技术上的微调与应用