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设计模式之访问者模式

    深入理解Java设计模式之访问者模式

    这篇文章主要介绍了JAVA设计模式之访问者模式的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解
    2021-11-11
  • SpringCloud Feign远程调用实现详解

    SpringCloud Feign远程调用实现详解

    Feign是Netflix公司开发的一个声明式的REST调用客户端; Ribbon负载均衡、 Hystrⅸ服务熔断是我们Spring Cloud中进行微服务开发非常基础的组件,在使用的过程中我们也发现它们一般都是同时出现的,而且配置也都非常相似
    2022-11-11
  • Java在重载中使用Object的问题

    Java在重载中使用Object的问题

    这篇文章主要介绍了Java在重载中使用Object的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Hibernate Validation自定义注解校验的实现

    Hibernate Validation自定义注解校验的实现

    这篇文章主要介绍了Hibernate Validation自定义注解校验的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • 深入理解hibernate的三种状态

    深入理解hibernate的三种状态

    本篇文章主要介绍了深入理解hibernate的三种状态 ,主要包括了transient(瞬时状态),persistent(持久化状态)以及detached(离线状态),有兴趣的同学可以了解一下
    2017-05-05
  • Java时间轮算法的实现代码示例

    Java时间轮算法的实现代码示例

    本篇文章主要介绍了Java时间轮算法的实现代码示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • idea中怎样创建并运行第一个java程序

    idea中怎样创建并运行第一个java程序

    这篇文章主要介绍了idea中怎样创建并运行第一个java程序问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • Springboot项目保存本地系统日志文件的实现方法

    Springboot项目保存本地系统日志文件的实现方法

    这篇文章主要介绍了Springboot项目保存本地系统日志文件的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • Java 断言 assert的用法详解

    Java 断言 assert的用法详解

    Java assert断言机制是Java5中推出的新特性,它主要用于在程序运行时检查状态或假设的正确性,本篇文章将全面详细地讲解Java assert断言机制,包括断言概述、语法规则、工作原理、使用场景、注意事项以及示例代码等方面,需要的朋友可以参考下
    2023-05-05
  • 解决使用IDEA时跳转到.class的问题

    解决使用IDEA时跳转到.class的问题

    这篇文章主要介绍了解决使用IDEA时跳转到.class的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08

最新评论