2、上述总结
2.1、java.nio.Buffer
中的关键属性
- Buffer 刚建立:
Capacity 与 Limit 相等,均是缓冲区的最大容量
import java.nio.ByteBuffer;
public class Main {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println(buffer.capacity()); // capacity = 10
System.out.println(buffer.position()); // position = 0
System.out.println(buffer.mark()); // limit = 0
}
}
- 向 Buffer 中添加(不越界):
Position 发生变化
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println(buffer.capacity());
System.out.println(buffer.position());
System.out.println(buffer.limit());
buffer.put("Java".getBytes(StandardCharsets.UTF_8)); // 4个字节
System.out.println(buffer.capacity()); // 10
System.out.println(buffer.position()); // 4,区间 [0~3] 已被使用
System.out.println(buffer.limit()); // 10
}
}
- 调用
flip
方法:
Limit 的值先变为 Position 的值,Position 变为 0
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println("init capacity: " + buffer.capacity()); // 10
System.out.println("init position: " + buffer.position()); // 0
System.out.println("init limit: " + buffer.limit()); // 10
buffer.put("Java".getBytes(StandardCharsets.UTF_8));
System.out.println("after add “Java” capacity: " + buffer.capacity()); // 10
System.out.println("after add “Java” position: " + buffer.position()); // 4
System.out.println("after add “Java” limit: " + buffer.limit()); // 10
buffer.flip();
System.out.println("after flip capacity: " + buffer.capacity()); // 10
System.out.println("after flip position: " + buffer.position()); // 0
System.out.println("after flip limit: " + buffer.limit()); // 4
}
}
2.2、使用java.nio.channels.FileChannel
和java.nio.ByteBuffer
实现文件拷贝
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class Main {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("E:\\source.txt");
FileOutputStream fos = new FileOutputStream("E:\\copy.txt");
// 读通道
FileChannel inChannel = fis.getChannel();
// 写通道
FileChannel outChannel = fos.getChannel();
// 缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 利用 NIO 拷贝文件
// Channel 的 read 方法:将数据先放入缓冲区中,会使 Buffer 的 position 发生变化
while (inChannel.read(buffer) != -1) {
// 切换 读模式
buffer.flip();
// Channel 的 write 方法:将缓冲区中的数据放出
outChannel.write(buffer);
// 使用 clear 方法,因为之前的 flip 方法改变了 position 和 limit,需要将其还原会 Buffer 刚创建的状态,便于下一次放数据到缓冲区
buffer.clear();
}
// 关流还是不要忘记的
if (fos != null) {
fos.close();
}
if (fis != null) {
fis.close();
}
}
}
2.3、NIO 网络编程
-
与传统的 IO 相比,NIO 在网络编程方面一个比较大的特点就是非阻塞。传统的 IO 中,服务端需要等待客户端与之建立连接,这个过程中,服务端除了等待其他的什么都做不了,就是“阻塞”;而使用 NIO 的服务端可以在等待的过程中做一些其他的事情,直到有客户端与之建立连接,即“非阻塞”。
-
下面的代码,NIO 实现阻塞式的网络编程(传递一个字符串):
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
// 服务端
public class NIOServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8888));
// 阻塞,等待客户端建立连接
SocketChannel socketChannel = serverSocketChannel.accept();
// 建立缓冲区,等待接收客户端数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 读取客户端发送的数据至 Buffer
int len = socketChannel.read(buffer);
// 字节数组 转换为 字符串
String receiveStr = new String(buffer.array(), 0, len);
System.out.println(receiveStr);
if (socketChannel != null) {
socketChannel.close();
}
if (serverSocketChannel != null) {
serverSocketChannel.close();
}
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
// 客户端
public class NIOClient {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Hello World!".getBytes(StandardCharsets.UTF_8));
// 数据需要向外发,相当于得从 Buffer 中读,所以需要用 flip 方法
buffer.flip();
// 发送数据
socketChannel.write(buffer);
if (socketChannel != null) {
socketChannel.close();
}
}
}
- 下面的代码,NIO 实现阻塞式的网络编程(传递一个文件):
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.UUID;
// 服务端,接收文件
public class NIOServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(8888));
ByteBuffer buffer = ByteBuffer.allocate(1024);
FileOutputStream fos = new FileOutputStream("E:\\" + UUID.randomUUID() + ".txt");
FileChannel fileChannel = fos.getChannel();
SocketChannel sc = ssc.accept();
// 注意 read 和 write 的语义
while (sc.read(buffer) != -1) {
buffer.flip();
fileChannel.write(buffer);
buffer.clear();
}
if (fileChannel != null) {
fileChannel.close();
}
if (fos != null) {
fos.close();
}
if (sc != null) {
sc.close();
}
if (ssc != null) {
ssc.close();
}
}
}
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
// 客户端,发送文件
public class NIOClient {
public static void main(String[] args) throws IOException {
SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
ByteBuffer buffer = ByteBuffer.allocate(1024);
FileInputStream fis = new FileInputStream("E:\\source.txt");
FileChannel fileChannel = fis.getChannel();
// 注意 read 和 write 的语义
while (fileChannel.read(buffer) != -1) {
buffer.flip();
sc.write(buffer);
buffer.clear();
}
if (fileChannel != null) {
fileChannel.close();
}
if (fis != null) {
fis.close();
}
if (sc != null) {
sc.close();
}
}
}
- 下面的代码,NIO 实现非阻塞式的网络编程(传递一个字符串):
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
// 服务端,接收方
public class NIOServer {
public static void main(String[] args) throws IOException, InterruptedException {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(8888));
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel sc = null;
// 设置 非阻塞
ssc.configureBlocking(false);
// 非阻塞 情况下的代码建议使用循环,因为在非阻塞的情况下,accept 方法无法将代码“暂停”
while (true) {
sc = ssc.accept();
// sc 是否为 null 成为客户端与其是否建立连接的依据
if (sc != null) {
int len = sc.read(buffer);
System.out.println(new String(buffer.array(), 0, len, StandardCharsets.UTF_8));
// 假定接收到就结束程序
break;
} else {
// sc == null:没有客户端建立连接,可以进行其他操作
System.out.println("进行其他操作");
// 为了运行效果,防止输出过快
Thread.sleep(1000);
}
}
if (sc != null) {
sc.close();
}
if (ssc != null) {
ssc.close();
}
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
// 客户端,发送方
public class NIOClient {
public static void main(String[] args) throws IOException {
SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("北京明白!".getBytes(StandardCharsets.UTF_8));
buffer.flip();
sc.write(buffer);
if (sc != null) {
sc.close();
}
}
}
3、NIO 网络编程之多路复用
NIO-12 NIO-13 NIO-14 NIO-15 NIO-16 NIO-17 NIO-183.1、如何理解 NIO 的多路复用
-
在传统的 IO 网络编程中,如果想实现一次监听多个端口,需要通过多线程切换来实现。
-
NIO 网络编程,提供了多路复用器,也就是借助这个多路复用器,在一个线程中就能够监听多个端口。这样编程的时候,在一个线程中可以采用轮询的策略,一旦选择器查询到通道处于之前约定好的某种状态,便认为这个通道已经就绪,可以开始通信。
3.2、NIO 网络编程之多路复用举例
- 服务端需一次性监听8887、8888、8889三个端口。客户端向服务端发送数据(一次只发送一个字符串)。
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
// 服务端,监听多个端口,接收数据
public class NIOServer {
public static void main(String[] args) throws IOException {
// 监听端口 8887
ServerSocketChannel ssc1 = ServerSocketChannel.open();
ssc1.bind(new InetSocketAddress(8887));
ssc1.configureBlocking(false);
// 监听端口 8888
ServerSocketChannel ssc2 = ServerSocketChannel.open();
ssc2.bind(new InetSocketAddress(8888));
ssc2.configureBlocking(false);
// 监听端口 8889
ServerSocketChannel ssc3 = ServerSocketChannel.open();
ssc3.bind(new InetSocketAddress(8889));
ssc3.configureBlocking(false);
// 多路复用选择器
Selector selector = Selector.open();
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 将通道注册到选择器上,便于选择器“查询”
ssc1.register(selector, SelectionKey.OP_ACCEPT);
ssc2.register(selector, SelectionKey.OP_ACCEPT);
ssc3.register(selector, SelectionKey.OP_ACCEPT);
// 采用轮询的编程策略,一旦轮询到的通道达到之前约定的状态(这里是 OP_ACCEPT),便认为通道已经就绪,这里来自客户端的可以接收数据
// 一般场景:服务器等待客户端与其建立连接,约定的状态一般设为 OP_ACCEPT 即可
// select 方法返回值大于 0,说明存在就绪的通道
while (selector.select() > 0) {
// 获取保存有 已就绪通道 Key 的 Set 的迭代器
Iterator<SelectionKey> selectionKeyIterator = selector.selectedKeys().iterator();
// 使用迭代器遍历保存有 已就绪通道 Key 的 Set
while (selectionKeyIterator.hasNext()) {
SelectionKey currentReadyChannelKey = selectionKeyIterator.next();
// 开始传输数据
// 获取当前 Key 对应的 ServerSocketChannel
ServerSocketChannel tempSsc = (ServerSocketChannel) currentReadyChannelKey.channel();
SocketChannel sc = tempSsc.accept();
int len = sc.read(buffer);
System.out.println(new String(buffer.array(), 0, len, StandardCharsets.UTF_8));
// 一次传输结束后的必要操作,比如清空缓冲区等
// 注意:ServerSocketChannel tempSsc 不用 close,因为获取到的 ServerSocketChannel 对象与最开始定义的 ServerSocketChannel 对象是同一个。一旦 close 便无法再监听端口
buffer.clear();
if (sc != null) {
sc.close();
}
selectionKeyIterator.remove();
}
}
if (ssc1 != null) {
ssc1.close();
}
if (ssc2 != null) {
ssc2.close();
}
if (ssc3 != null) {
ssc3.close();
}
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
// 客户端,端口 8887
public class NIOClientOne {
public static void main(String[] args) throws IOException {
SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8887));
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Hello!".getBytes(StandardCharsets.UTF_8));
buffer.flip();
sc.write(buffer);
if (sc != null) {
sc.close();
}
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
// 客户端,端口 8887
public class NIOClientThree {
public static void main(String[] args) throws IOException {
SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8887));
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Java!".getBytes(StandardCharsets.UTF_8));
buffer.flip();
sc.write(buffer);
if (sc != null) {
sc.close();
}
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
// 客户端,端口 8888
public class NIOClientTwo {
public static void main(String[] args) throws IOException {
SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Hi!".getBytes(StandardCharsets.UTF_8));
buffer.flip();
sc.write(buffer);
if (sc != null) {
sc.close();
}
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
// 客户端,端口 8889
public class NIOClientFour {
public static void main(String[] args) throws IOException {
SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8889));
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("yyds!".getBytes(StandardCharsets.UTF_8));
buffer.flip();
sc.write(buffer);
if (sc != null) {
sc.close();
}
}
}
网友评论