NIO零拷贝传文件案例(附可用代码)

零拷贝比传统的IO拷贝性能高很多,主要是减少了内核和用户模式之间的上下文切换次数。零拷贝完全依赖于操作系统。操作系统支持,就有;不支持,就没有。不依赖Java本身。

传统I/O

在Java中,我们可以通过InputStream从源数据中读取数据流到一个缓冲区里,然后再将它们输入到OutputStream里。我们知道,这种IO方式传输效率是比较低的。那么,当使用上面的代码时操作系统会发生什么情况:

这是一个从磁盘文件读取并且通过socket写出的过程,对应的系统调用如下:

 

read(file,tmp_buf,len)
write(socket,tmp_buf,len)

 

  1. 程序使用read()系统调用。系统由用户态转换为内核态(第一次上线文切换),磁盘中的数据有DMA(Direct Memory Access)的方式读取到内核缓冲区(kernel buffer)。DMA过程中CPU不需要参与数据的读写,而是DMA处理器直接将硬盘数据通过总线传输到内存中。
  2. 系统由内核态转换为用户态(第二次上下文切换),当程序要读取的数据已经完成写入内核缓冲区以后,程序会将数据由内核缓存区,写入用户缓存区),这个过程需要CPU参与数据的读写。
  3. 程序使用write()系统调用。系统由用户态切换到内核态(第三次上下文切换),数据从用户态缓冲区写入到网络缓冲区(Socket Buffer),这个过程需要CPU参与数据的读写。
  4. 系统由内核态切换到用户态(第四次上下文切换),网络缓冲区的数据通过DMA的方式传输到网卡的驱动(存储缓冲区)中(protocol engine)

案例是从一个文件中传送数据,如图所示为从CodeForces.rar传数据。

 

所传送文件的大小如图:是19130字节

客户端NewIOClient.java :

import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;

public class NewIOClient {
    public static void main(String[] args) throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("localhost",4399));
        String filename="CodeForces.rar";

        //得到文件的channel
        FileChannel fileChannel = new FileInputStream(filename).getChannel();

        long startTimeMillis = System.currentTimeMillis();

        //在Linux下一个transto函数可以完成传输
        //在windows下一次调用只能发送8M的文件,需要分段传输文件,而且要注意传送时的位置
        //transferto底层用到零拷贝,传送到socketchannel中
        long transfercount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
        System.out.println("发送的字节数为:"+transfercount+",耗时为:" + (System.currentTimeMillis() - startTimeMillis));
        fileChannel.close();

    }
}

服务端NewIOServer.java :

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class NewIOServer {
    public static void main(String[] args) throws IOException {
        InetSocketAddress inetSocketAddress = new InetSocketAddress(4399);
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        ServerSocket serverSocket = serverSocketChannel.socket();
        serverSocket.bind(inetSocketAddress);

        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        while (true){
            SocketChannel socketChannel = serverSocketChannel.accept();
            int readacount=0;
            while (-1!=readacount){
                try {
                    readacount = socketChannel.read(byteBuffer);
                }catch (Exception e){
                    break;
                }
                byteBuffer.rewind();    //将buffer的position置为0,mark作废
            }
        }
    }
}

先运行服务端代码,再运行客户端,发现只用了7ms,而传统IO至少要20ms以上,可用看出零拷贝这种方式是很节约开销的了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值