一、问题引出

传统文件拷贝过程

image.png
它将硬盘上的文件读取到内核空间的缓冲区中,然后再将缓冲区内容拷贝到Socket缓冲区再拷贝到用户缓冲区中,调用写的方法时会从用户缓冲区到内核传冲区到Socket缓冲区再到服务器端的拷贝,总而言之会进行多次拷贝。
显然,会拥有极大的性能损耗。

简化拷贝过程

image.png
通过sendfile将用户空间切换到内核空间,从而节省了用户缓冲区的操作,直接由内核和硬件的对话,但是仍然会有拷贝的过程,这时候就要依赖操作系统的操作了。

零拷贝过程

image.png
经过操作系统的配合实现了零拷贝的过程,即通过scatter/gather的方法。
image.png

二、代码实验

经过理论分析,接下来要用代码实际查看是不是性能拥有差距。

老式拷贝文件

OldeIOServer

  1. package com.demo.zerocopy;
  2. import java.io.DataInputStream;
  3. import java.io.IOException;
  4. import java.net.ServerSocket;
  5. import java.net.Socket;
  6. /**
  7. * @Description:
  8. * @author: HandSomeMaker
  9. * @date: 2019/12/30 15:04
  10. */
  11. public class OldIoServer {
  12. public static void main(String[] args) {
  13. try {
  14. ServerSocket serverSocket = new ServerSocket(9999);
  15. System.out.println("服务器已启动");
  16. while (true) {
  17. Socket socket = serverSocket.accept();
  18. System.out.println("客户端【"+socket.getInetAddress()+"】已连接到服务器");
  19. DataInputStream inputStream = new DataInputStream(socket.getInputStream());
  20. byte[] bytes = new byte[1024];
  21. while (inputStream.read(bytes) > 0) {
  22. }
  23. }
  24. } catch (IOException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }

OldIOClient

  1. package com.demo.zerocopy;
  2. import java.io.*;
  3. import java.net.Socket;
  4. /**
  5. * @Description:
  6. * @author: HandSomeMaker
  7. * @date: 2019/12/30 15:05
  8. */
  9. public class OldIoClient {
  10. public static void main(String[] args) {
  11. try {
  12. Socket socket = new Socket("localhost", 9999);
  13. File file = new File("C:" + File.separator + "Program Files" + File.separator + "CCleaner" + File.separator + "CCleaner64.exe");
  14. InputStream inputStream = new FileInputStream(file);
  15. DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
  16. byte[] bytes = new byte[1024];
  17. long start = System.currentTimeMillis();
  18. while (inputStream.read(bytes, 0, bytes.length) > 0) {
  19. outputStream.write(bytes);
  20. }
  21. System.out.println("传送完成,耗时:" + (System.currentTimeMillis() - start));
  22. outputStream.close();
  23. inputStream.close();
  24. socket.close();
  25. } catch (IOException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. }

新式拷贝文件

NewIOServer

  1. package com.demo.zerocopy;
  2. import java.io.IOException;
  3. import java.net.InetSocketAddress;
  4. import java.net.ServerSocket;
  5. import java.nio.ByteBuffer;
  6. import java.nio.channels.ServerSocketChannel;
  7. import java.nio.channels.SocketChannel;
  8. /**
  9. * @Description:
  10. * @author: HandSomeMaker
  11. * @date: 2019/12/30 15:05
  12. */
  13. public class NewIoServer {
  14. public static void main(String[] args) {
  15. try {
  16. ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
  17. ServerSocket serverSocket=serverSocketChannel.socket();
  18. //开启端口复用
  19. serverSocket.setReuseAddress(true);
  20. serverSocket.bind(new InetSocketAddress(9999));
  21. System.out.println("服务器已启动");
  22. ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
  23. while (true) {
  24. SocketChannel socketChannel = serverSocketChannel.accept();
  25. System.out.println("客户端已连接到服务器");
  26. //开启阻塞
  27. socketChannel.configureBlocking(true);
  28. while (socketChannel.read(byteBuffer) > 0) {
  29. byteBuffer.clear();
  30. }
  31. }
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. }

NewIOClient

  1. package com.demo.zerocopy;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. import java.io.IOException;
  5. import java.net.InetSocketAddress;
  6. import java.nio.channels.FileChannel;
  7. import java.nio.channels.SocketChannel;
  8. /**
  9. * @Description:
  10. * @author: HandSomeMaker
  11. * @date: 2019/12/30 15:05
  12. */
  13. public class NewIoClient {
  14. public static void main(String[] args) {
  15. try {
  16. SocketChannel socketChannel = SocketChannel.open();
  17. socketChannel.connect(new InetSocketAddress("localhost", 9999));
  18. socketChannel.configureBlocking(true);
  19. File file = new File("C:" + File.separator + "Program Files" + File.separator + "CCleaner" + File.separator + "CCleaner64.exe");
  20. FileChannel fileChannel = new FileInputStream(file).getChannel();
  21. long start = System.currentTimeMillis();
  22. //将一个通道和另一个通道直接相连接
  23. fileChannel.transferTo(0, fileChannel.size(), socketChannel);
  24. System.out.println("传送完成,耗时:" + (System.currentTimeMillis() - start));
  25. socketChannel.close();
  26. fileChannel.close();
  27. } catch (IOException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }

效果演示

老式拷贝文件

image.png

新式拷贝文件

image.png

总结

显然,新式拷贝文件要比老式拷贝快上成倍的速度。
但是也不绝对,详情请查看https://www.cnblogs.com/interdrp/p/3785164.html