在使用内存映射时,我们创建了单一的缓冲区横跨整个文件或我们感兴趣的文件区域。我们还可以使用更多的缓冲区来读写大小合适的信息块。
这里简要介绍Buffer对象上的基本操作。缓冲区是由具有相同类型的数值构成的数组,Buffer类是一个抽象类,它有众多的具体子类,包括ByteBuffer
、CharBuffer
、DoubleBuffer
、IntBuffer
、LongBuffer
和ShortBuffer
。
注意:
StringBuffer
类与这些缓冲区没有关系。
在实践中,最常用的将是ByteBuffer
和CharBuffer
。如图2-20所示,每个缓冲区都具有:
这些值满足下面的条件:
0<=标记(mark)<=位置(position)<=界限(limit)<=容量(capacity)
使用缓冲区的主要目的是执行“写,然后读入”循环。假设我们有一个缓冲区,在一开始,它的位置为0,界限等于容量。我们不断地调用put将数值添加到这个缓冲区中,当我们耗尽所有的数据或者写出的数据量达到容量大小时,就该切换到读入操作了。
这时调用flip方法将界限设置到当前位置,并把位置复位到0。现在remaining方法返回正数时(它返回的值“界限-位置”),不断地调用get。在我们将缓冲区中所有的值都读入之后,调用clear使缓冲区为下一次写循环做好准备。clear方法将位置复位到0,并将界限复位到容量。
如果你想重读缓冲区,可以使用rewind或mark/reset方法。
要获取缓冲区,可以调用诸如ByteBuffer.allocate或ByteBuffer.wrap这样的静态方法。
然后,可以使用来自某个通道的数据填充缓冲区,或者将缓冲区的内容写出通道中。这是一种飞虫有用的方法,可以替代随机访问文件。
BIO中的阻塞:
非阻塞式NIO:
多方法实现本地文件拷贝:
package top.onefine.demo;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* 文件复制
*
* @author one fine
*/
interface FileCopyRunner {
/**
* 文件复制
* @param source 源文件
* @param target 目标文件
*/
void copeFile(File source, File target);
}
@Slf4j
public class FileCopyDemo {
private static final int ROUNDS = 5;
private static void benchmark(FileCopyRunner test, File source, File target) {
long elapsed = 0L;
boolean b = true;
for (int i = 0; i < ROUNDS; i++) {
long startTime = System.currentTimeMillis();
test.copeFile(source, target);
elapsed += System.currentTimeMillis() - startTime;
b = target.delete();// 删除拷贝的文件
if (!b) {
log.error("文件删除失败.");
break;
}
}
if (b)
log.info("{}: 平均消耗时间{}.", test, elapsed / ROUNDS);
}
private static void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 不带缓冲区的流
FileCopyRunner noBufferStreamCopy = new FileCopyRunner() {
@Override
public void copeFile(File source, File target) {
InputStream fin = null;
OutputStream fout = null;
try {
fin = new FileInputStream(source);
fout = new FileOutputStream(target);
int result;
while ((result = fin.read()) != -1) {
// 返回读取到的内容, 最后返回-1
fout.write(result); // 写到目标文件中
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
close(fout);
close(fin);
}
}
@Override
public String toString() {
return "noBufferStreamCopy";
}
};
// 带有缓冲区的流
FileCopyRunner bufferedStreamCopy = new FileCopyRunner() {
@Override
public void copeFile(File source, File target) {
InputStream fin = null;
OutputStream fout = null;
try {
fin = new BufferedInputStream(new FileInputStream(source));
fout = new BufferedOutputStream(new FileOutputStream(target));
byte[] buffer = new byte[1024];
int result;
while ((result = fin.read(buffer)) != -1) {
// 返回读取到的内容, 最后返回-1
fout.write(buffer, 0, result);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
close(fout);
close(fin);
}
}
@Override
public String toString() {
return "bufferedStreamCopy";
}
};
// 缓冲区 < - > 通道
FileCopyRunner nioBufferCopy = new FileCopyRunner() {
@Override
public void copeFile(File source, File target) {
FileChannel fin = null;
FileChannel fout = null;
try {
fin = new FileInputStream(source).getChannel();
fout = new FileOutputStream(target).getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (fin.read(buffer) != -1) {
// 使用通道将数据读到缓冲区中, 最后返回-1
// 切换模式: 写模式 -> 读模式
buffer.flip();
while (buffer.hasRemaining())
fout.write(buffer);
// 调整模式: 读模式 -> 写模式
buffer.clear();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
close(fout);
close(fin);
}
}
@Override
public String toString() {
return "nioBufferCopy";
}
};
// 通道 < - > 通道
FileCopyRunner nioTransferCopy = new FileCopyRunner() {
@Override
public void copeFile(File source, File target) {
FileChannel fin = null;
FileChannel fout = null;
try {
fin = new FileInputStream(source).getChannel();
fout = new FileOutputStream(target).getChannel();
long transferred = 0L;
// do {
// transferred += fin.transferTo(0, fin.size(), fout);
// } while (transferred == fin.size());
long size = fin.size();
while (transferred != size)
transferred += fin.transferTo(0, size, fout);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
close(fout);
close(fin);
}
}
@Override
public String toString() {
return "nioTransferCopy";
}
};
// 小文件
File smallFile = new File("C:/Users/Lenovo/Desktop/copyTest/file1.docx");
File smallFileCopy = new File("C:/Users/Lenovo/Desktop/copyTest/file1Copy.docx");
log.info("---Coping small file---");
benchmark(noBufferStreamCopy, smallFile, smallFileCopy);
benchmark(bufferedStreamCopy, smallFile, smallFileCopy);
benchmark(nioBufferCopy, smallFile, smallFileCopy);
benchmark(nioTransferCopy, smallFile, smallFileCopy);
// // 中等文件
// File bigFile = new File("C:/Users/Lenovo/Desktop/copyTest/file2.docx");
// File bigFileCopy = new File("C:/Users/Lenovo/Desktop/copyTest/file2Copy.docx");
//
// log.info("---Coping big file---");
//
// benchmark(noBufferStreamCopy, bigFile, bigFileCopy);
// benchmark(bufferedStreamCopy, bigFile, bigFileCopy);
// benchmark(nioBufferCopy, bigFile, bigFileCopy);
// benchmark(nioTransferCopy, bigFile, bigFileCopy);
//
// // 大文件
// File hugeFile = new File("C:/Users/Lenovo/Desktop/copyTest/file3.docx");
// File hugeFileCopy = new File("C:/Users/Lenovo/Desktop/copyTest/file3Copy.docx");
//
// log.info("---Coping huge file---");
//
// benchmark(noBufferStreamCopy, hugeFile, hugeFileCopy);
// benchmark(bufferedStreamCopy, hugeFile, hugeFileCopy);
// benchmark(nioBufferCopy, hugeFile, hugeFileCopy);
// benchmark(nioTransferCopy, hugeFile, hugeFileCopy);
}
}
服务端编写:
package top.onefine.demo.socket.nio;
import lombok.extern.slf4j.Slf4j;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Set;
/**
* @author one fine
*/
@Slf4j
public class ChatServer {
private static final int DEFAULT_PORT = 8888;
private static final String QUIT = "quit";
private static final int BUFFER = 1024;
private ServerSocketChannel server;
private Selector selector;
private final ByteBuffer rBuffer = ByteBuffer.allocate(BUFFER);
private final ByteBuffer wBuffer = ByteBuffer.allocate(BUFFER);
// private final Charset charset = Charset.forName("UTF-8");
private final Charset charset = StandardCharsets.UTF_8;
private final int port;
public ChatServer() {
// this.port = DEFAULT_PORT;
this(DEFAULT_PORT);
}
public ChatServer(int port) {
this.port = port;
}
private void start() {
try {
server = ServerSocketChannel.open();
server.configureBlocking(false); // 设置阻塞状态为false即非阻塞状态
server.socket().bind(new InetSocketAddress(port));
selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT); // 注册监听用户连接事件
log.info("启动服务器, 监听端口: {}...", port);
while (true) {
// 监听
selector.select(); // 阻塞
Set<SelectionKey> selectionKeys = selector.selectedKeys();
// 处理被触发的事件
selectionKeys.forEach(selectionKey -> {
try {
handles(selectionKey);
} catch (IOException e) {
e.printStackTrace();
}
});
selectionKeys.clear(); // 手动清空集合
}
} catch (IOException e) {
e.printStackTrace();
} finally {
close(selector);
}
}
private void handles(SelectionKey selectionKey) throws IOException {
// 触发 accept 事件, 即 和客户端建立连接
if (selectionKey.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
SocketChannel client = server.accept();
client.configureBlocking(false); // 设置为非阻塞
client.register(selector, SelectionKey.OP_READ); // 注册事件
log.info("客户端[{}]已连接.", client.socket().getPort());
}
// 触发 read 事件, 即 客户端发送了消息
if (selectionKey.isReadable()) {
SocketChannel client = (SocketChannel) selectionKey.channel();
String fwdMsg = receive(client);
if (fwdMsg.isEmpty()) {
// 客户端异常
selectionKey.cancel(); // 取消监听
selector.wakeup(); // 通知selector
} else {
// 转发消息
forwardMessage(client, fwdMsg);
// 检查用户是否需要退出
if (readyToQuit(fwdMsg)) {
selectionKey.cancel(); // 取消监听
log.info("客户端[{}]已断开.", client.socket().getPort());
}
}
}
}
private void forwardMessage(SocketChannel client, String fwdMsg) throws IOException {
for (SelectionKey selectionKey : selector.keys()) {
Channel connectedClient = selectionKey.channel();
if (connectedClient instanceof ServerSocketChannel)
continue;
if (selectionKey.isValid() && !client.equals(connectedClient)) {
wBuffer.clear();
wBuffer.put(charset.encode(client.socket().getPort() + ": " + fwdMsg));
wBuffer.flip(); // write -> read
while (wBuffer.hasRemaining())
((SocketChannel) connectedClient).write(wBuffer);
}
}
}
private String receive(SocketChannel client) throws IOException {
rBuffer.clear(); // 清空
while (client.read(rBuffer) > 0) ;
rBuffer.flip();
return String.valueOf(charset.decode(rBuffer));
}
private boolean readyToQuit(String msg) {
return QUIT.equalsIgnoreCase(msg);
}
private void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ChatServer chatServer = new ChatServer(7777);
chatServer.start();
}
}
客户端编写:
package top.onefine.demo.socket.nio;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Set;
/**
* @author one fine
*/
public class ChatClient {
private static final String DEFAULT_SERVER_HOST = "127.0.0.1";
private static final int DEFAULT_SERVER_PORT = 8888;
private static final String QUIT = "quit";
private static final int BUFFER = 1024;
private String host;
private int port;
private SocketChannel client;
private ByteBuffer rBuffer = ByteBuffer.allocate(BUFFER);
private ByteBuffer wBuffer = ByteBuffer.allocate(BUFFER);
private Selector selector;
// private Charset charset = Charset.forName("UTF-8");
private Charset charset = StandardCharsets.UTF_8;
public ChatClient() {
this(DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT);
}
public ChatClient(String host, int port) {
this.host = host;
this.port = port;
}
public boolean readyToQuit(String msg) {
return QUIT.equalsIgnoreCase(msg);
}
private void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void start() {
try {
client = SocketChannel.open();
client.configureBlocking(false); // 非阻塞模式
selector = Selector.open();
// 通道上注册事件
client.register(selector, SelectionKey.OP_CONNECT);
client.connect(new InetSocketAddress(host, port));
while (true) {
selector.select(); // 阻塞
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey selectionKey : selectionKeys) {
handles(selectionKey);
}
selectionKeys.clear();
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClosedSelectorException e) {
// 用户正常退出, 不需要做任何处理
} finally {
close(selector);
}
}
private void handles(SelectionKey selectionKey) throws IOException {
// CONNECT 事件 - 连接就绪事件
if (selectionKey.isConnectable()) {
SocketChannel client = (SocketChannel) selectionKey.channel();
if (client.isConnectionPending()) {
client.finishConnect();
// 处理用户的输入
new Thread(new UserInputHandler(this)).start();
}
client.register(selector, SelectionKey.OP_READ);
}
// READ 事件 - 服务器转发消息
if (selectionKey.isReadable()) {
SocketChannel client = (SocketChannel) selectionKey.channel();
String msg = receive(client);
if (msg.isEmpty()) {
// 服务器异常
close(selector);
} else {
System.out.println(msg);
}
}
}
public void send(String msg) throws IOException {
if (msg.isEmpty()) {
return;
}
wBuffer.clear();
wBuffer.put(charset.encode(msg));
wBuffer.flip();
while (wBuffer.hasRemaining()) {
client.write(wBuffer);
}
// 检查用户是否需要退出
if (readyToQuit(msg))
close(selector);
}
private String receive(SocketChannel client) throws IOException {
rBuffer.clear();
while (client.read(rBuffer) > 0);
rBuffer.flip();
return String.valueOf(charset.decode(rBuffer));
}
}
测试:
package top.onefine.demo.socket.nio;
/**
* @author one fine
*/
public class Client1 {
public static void main(String[] args) {
ChatClient client = new ChatClient("127.0.0.1", 7777);
client.start();
}
}