Java图文并茂详解NIO与零拷贝

 更新时间:2022年11月11日 09:49:07   作者:顽石九变  
零拷贝是网络编程的关键,很多性能优化都离不开。在 Java 程序中,常用的零拷贝有 mmap(memory map,内存映射) 和 sendFile。那么它们在 OS(操作系统) 中,到底是怎么样的一个的设计?另外我们看下NIO 中如何使用零拷贝

零拷贝指的是没有CPU拷贝,并不是不拷贝;减少上下文切换

一、概念说明

1、传统IO

需要4次拷贝,3次上下文切换

2、mmap

mmap 通过内存映射,将文件映射到内存缓冲区,同时用户空间可以共享内存缓冲区的数据,减少内核空间到用户空间的拷贝

需要3次拷贝,3次上下文切换

3、sendfile

Linux 2.4 避免了从内核缓冲区到Socket Buffer的拷贝,直接拷贝到协议栈,从而减少一次数据拷贝

需要2次拷贝,3次上下文切换

4、mmap与sendfile

mmap适合小数据量读写,sendfile适合大文件传输

mmap需要4次上下文切换,3次数据拷贝;sendfile需要3次上下文切换,最少2次数据拷贝

send可用利用DMA方式,减少CPU拷贝,mmap则不能(必须从内核拷贝到Socket缓冲区)

二、传统IO传输文件代码示例

1、服务端代码

import java.io.DataInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class BIOServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(7000);
        while (true) {
            Socket socket = serverSocket.accept();
            DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
            try {
                long total = 0;
                byte[] bytes = new byte[4096];
                FileOutputStream fileOutputStream = new FileOutputStream("d:\\temp\\04.zip");
                while (true) {
                    int read = dataInputStream.read(bytes, 0, bytes.length);
                    if (read == -1) {
                        //文件读取结束,退出循环
                        break;
                    }
                    total += read;
                    fileOutputStream.write(bytes, 0, read);
                }
                System.out.println("收到客户端发送文件,总字节数:" + total);
                fileOutputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

2、客户端代码

import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket;
public class BIOClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 7000);
        FileInputStream fileInputStream = new FileInputStream("d:\\temp\\03.zip");
        DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
        byte[] bytes = new byte[4096];
        long readCount;
        long total = 0;
        long start = System.currentTimeMillis();
        while ((readCount = fileInputStream.read(bytes)) >= 0) {
            total += readCount;
            dataOutputStream.write(bytes);
        }
        System.out.println("发送总字节数:" + total + ", 总耗时:" + (System.currentTimeMillis() - start));
        dataOutputStream.close();
        socket.close();
        fileInputStream.close();
    }
}

3、控制台出输出

测试发送9M的压缩文件,耗时在26ms左右

发送总字节数:9428963, 总耗时:26

三、NIO传输文件代码示例

1、服务端代码

package com.hj.io.nio.zero;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class NIOServerFile {
    public static void main(String[] args) throws IOException {
        InetSocketAddress address = new InetSocketAddress(7000);
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(address);
        //创建buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
        while (true) {
            //等待客户端链接
            SocketChannel socketChannel = serverSocketChannel.accept();
            System.out.println("收到客户端链接");
            int total = 0;
            FileOutputStream fileOutputStream = new FileOutputStream("d:\\temp\\05.zip");
            //循环读取数据,并存储到硬盘
            while (true) {
                try {
                    int readCount = socketChannel.read(byteBuffer);
                    if (readCount == -1) {
                        //文件读取结束
                        break;
                    }
                    total += readCount;
                    fileOutputStream.write(byteBuffer.array(),0,readCount);
                    //将buffer倒带
                    byteBuffer.rewind();
                } catch (IOException e) {
                   break;
                }
            }
            System.out.println("收到客户端发送文件,总字节数:" + total);
            fileOutputStream.close();
        }
    }
}

2、客户端代码

import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
public class NIOClientFile {
    public static void main(String[] args) throws IOException {
        //打开一个SocketChannel并链接到服务器端
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 7000));
        //打开一个文件
        FileChannel fileChannel = new FileInputStream("d:\\temp\\03.zip").getChannel();
        //发送文件到服务器
        long start = System.currentTimeMillis();
        //在linux下,一次transferTo调用就可以完成传输
        //在windows下,一次transferTo调用最多只能传8M,大文件需要分段传输,需要注意传输位置
        //transferTo底层使用零拷贝
        long total = fileChannel.size();
        long sended = 0;
        while (sended < total) {
            //从上一次传输位置继续发送
            long lenth = fileChannel.transferTo(sended, fileChannel.size(), socketChannel);
            sended += lenth;
        }
        System.out.println("发送总字节数:" + sended + ",总耗时:" + (System.currentTimeMillis() - start));
        //关闭channel
        fileChannel.close();
        socketChannel.close();
    }
}

3、控制台出输出

测试发送9M的压缩文件,耗时在16ms左右

发送总字节数:9428963,总耗时:16

四、总结

使用零拷贝传输,性能明显高于传统IO传输

到此这篇关于Java图文并茂详解NIO与零拷贝的文章就介绍到这了,更多相关Java NIO与零拷贝内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java实现ip地址和int数字的相互转换

    Java实现ip地址和int数字的相互转换

    这篇文章主要介绍了Java实现ip地址和int数字的相互转换,帮助大家更好的利用Java处理数据,感兴趣的朋友可以了解下
    2020-09-09
  • Spring中SmartLifecycle的用法解读

    Spring中SmartLifecycle的用法解读

    这篇文章主要介绍了Spring中SmartLifecycle的用法解读,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • Eclipse 2022 设置中文汉化的超详细图文教程

    Eclipse 2022 设置中文汉化的超详细图文教程

    这篇文章主要介绍了Eclipse 2022 设置中文汉化的超详细图文教程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-03-03
  • Java设计模式之代理模式(Proxy模式)介绍

    Java设计模式之代理模式(Proxy模式)介绍

    这篇文章主要介绍了Java设计模式之代理模式(Proxy模式)介绍,本文讲解了为什么要使用代理模式、如何使用代理模式等内容,需要的朋友可以参考下
    2015-03-03
  • Java文件读写IO/NIO及性能比较详细代码及总结

    Java文件读写IO/NIO及性能比较详细代码及总结

    这篇文章主要介绍了Java文件读写IO/NIO及性能比较详细代码及总结,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • Java中常用的9种文件下载方法总结

    Java中常用的9种文件下载方法总结

    下载文件在我们项目很常见,有下载视频、文件、图片、附件、导出Excel等,所以本文为大家整理了9中Java中常用的文件下载方式,希望对大家有所帮助
    2023-09-09
  • Java正则表达式API Matcher类方法

    Java正则表达式API Matcher类方法

    这篇文章主要介绍了Java正则表达式API Matcher类方法,对Matcher类的一些有用方法进行功能对它们进行分组展开介绍,需要的朋友可以参考一下
    2022-06-06
  • Java泛型T,E,K,V,N,?与Object区别和含义

    Java泛型T,E,K,V,N,?与Object区别和含义

    Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。本文将详细讲讲Java泛型T、E、K、V、N、?和Object区别和含义,需要发可以参考一下
    2022-03-03
  • IDEA中实现springboot热部署方式

    IDEA中实现springboot热部署方式

    在IDEA中实现SpringBoot的热部署可以通过修改设置来完成,首先在设置中搜索Compiler,并勾选Build project automatically,然后进入Advanced Settings,勾选Allow auto-make to start even if developed application is currently running
    2024-09-09
  • JAVA偏向锁的原理与实战

    JAVA偏向锁的原理与实战

    这篇文章主要为大家详细介绍了JAVA偏向锁的原理与实战,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03

最新评论