一、问题引出
传统文件拷贝过程
它将硬盘上的文件读取到内核空间的缓冲区中,然后再将缓冲区内容拷贝到Socket缓冲区再拷贝到用户缓冲区中,调用写的方法时会从用户缓冲区到内核传冲区到Socket缓冲区再到服务器端的拷贝,总而言之会进行多次拷贝。
显然,会拥有极大的性能损耗。
简化拷贝过程
通过sendfile将用户空间切换到内核空间,从而节省了用户缓冲区的操作,直接由内核和硬件的对话,但是仍然会有拷贝的过程,这时候就要依赖操作系统的操作了。
零拷贝过程
经过操作系统的配合实现了零拷贝的过程,即通过scatter/gather的方法。
二、代码实验
老式拷贝文件
OldeIOServer
package com.demo.zerocopy;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Description:
* @author: HandSomeMaker
* @date: 2019/12/30 15:04
*/
public class OldIoServer {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务器已启动");
while (true) {
Socket socket = serverSocket.accept();
System.out.println("客户端【"+socket.getInetAddress()+"】已连接到服务器");
DataInputStream inputStream = new DataInputStream(socket.getInputStream());
byte[] bytes = new byte[1024];
while (inputStream.read(bytes) > 0) {
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
OldIOClient
package com.demo.zerocopy;
import java.io.*;
import java.net.Socket;
/**
* @Description:
* @author: HandSomeMaker
* @date: 2019/12/30 15:05
*/
public class OldIoClient {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost", 9999);
File file = new File("C:" + File.separator + "Program Files" + File.separator + "CCleaner" + File.separator + "CCleaner64.exe");
InputStream inputStream = new FileInputStream(file);
DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
byte[] bytes = new byte[1024];
long start = System.currentTimeMillis();
while (inputStream.read(bytes, 0, bytes.length) > 0) {
outputStream.write(bytes);
}
System.out.println("传送完成,耗时:" + (System.currentTimeMillis() - start));
outputStream.close();
inputStream.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
新式拷贝文件
NewIOServer
package com.demo.zerocopy;
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;
/**
* @Description:
* @author: HandSomeMaker
* @date: 2019/12/30 15:05
*/
public class NewIoServer {
public static void main(String[] args) {
try {
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
ServerSocket serverSocket=serverSocketChannel.socket();
//开启端口复用
serverSocket.setReuseAddress(true);
serverSocket.bind(new InetSocketAddress(9999));
System.out.println("服务器已启动");
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (true) {
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("客户端已连接到服务器");
//开启阻塞
socketChannel.configureBlocking(true);
while (socketChannel.read(byteBuffer) > 0) {
byteBuffer.clear();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
NewIOClient
package com.demo.zerocopy;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
/**
* @Description:
* @author: HandSomeMaker
* @date: 2019/12/30 15:05
*/
public class NewIoClient {
public static void main(String[] args) {
try {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 9999));
socketChannel.configureBlocking(true);
File file = new File("C:" + File.separator + "Program Files" + File.separator + "CCleaner" + File.separator + "CCleaner64.exe");
FileChannel fileChannel = new FileInputStream(file).getChannel();
long start = System.currentTimeMillis();
//将一个通道和另一个通道直接相连接
fileChannel.transferTo(0, fileChannel.size(), socketChannel);
System.out.println("传送完成,耗时:" + (System.currentTimeMillis() - start));
socketChannel.close();
fileChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
效果演示
老式拷贝文件
新式拷贝文件
总结
显然,新式拷贝文件要比老式拷贝快上成倍的速度。
但是也不绝对,详情请查看https://www.cnblogs.com/interdrp/p/3785164.html