From 6edbdbd79eb633468dee61995370cbe3623671e6 Mon Sep 17 00:00:00 2001 From: asahi Date: Wed, 28 Aug 2024 00:37:24 +0800 Subject: [PATCH] =?UTF-8?q?=E9=98=85=E8=AF=BBnio=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- java se/nio.md | 126 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/java se/nio.md b/java se/nio.md index fee978b..ceb31fe 100644 --- a/java se/nio.md +++ b/java se/nio.md @@ -251,3 +251,129 @@ public class CopyFile { ``` 在上述示例中,通过`channel.read(buffer)`的返回值来判断是否已经读取到文件末尾,如果返回值为-1,那么代表文件读取已经完成。 + +## File Lock +可以针对整个文件或文件的部分进行加锁。文件锁分为独占锁和共享锁,如果获取了独占锁,那么其他进程将无法获取同一文件加锁;如果获取了共享锁,那么其他进程也可以获取同一文件的共享锁,但是无法获取到独占锁。 + +### 对文件进行上锁 +在获取锁时,需要在FileChannel上调用lock方法: +```java +RandomAccessFile raf = new RandomAccessFile("usefilelocks.txt", "rw"); +FileChannel fc = raf.getChannel(); +FileLock lock = fc.lock(start, end, false); +``` +释放锁方法如下所示: +```java +lock.release(); +``` +## Network和异步IO +通过异步io,可以在没有blocking的情况下读取和写入数据,在通常情况下,调用read方法会一直阻塞到数据可以被读取,而write方法会一直阻塞到数据可以被写入。 + +异步IO通过注册io事件来实现,当例如新的可读数据、新的网络连接到来时,系统会根据注册的io时间监听来发送消息提醒。 + +异步io的好处是,可以在同一线程内处理大量的io操作。在传统的io操作中,如果要处理大量io操作,通常需要轮询、创建大量线程来针对io操作进行处理。 +- 轮询:对io请求进行排队,处理完一个io请求后再处理别的请求 +- 创建大量线程:针对每个io请求,为其创建一个线程进行处理 + +通过nio,可以通过单个线程来监听多个channel的io事件,无需轮询也无需额外的事件。 + +### Selector +nio的核心对象为selector,将channel及其感兴趣的io事件类型注册到selector后,如果io事件触发,那么`selector.select`方法将会返回对应的SelectionKey。 + +Selector创建如下: + +```java +Selector selector = Selector.open(); +``` + +### 开启ServerSocketChannel +为了接收外部连接,需要一个ServerSocketChannel,创建ServerSocketChannel并配置对应ServerSocket的示例如下所示: +```java +ServerSocketChannel ssc = ServerSocketChannel.open(); +ssc.configureBlocking(false); +ServerSocket ss = ssc.socket(); + +InetSocketAddress address = new InetSocketAddress(ports[i]); +ss.bind(address); +``` + +### Selection keys +在创建完SelectableChannel之后,需要将channel注册到selector,可以调用`channel.register`来执行注册操作: +```java +SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT); +``` + +上述方法为ServerSocketChannel注册了OP_ACCEPT的事件监听,OP_ACCEPT事件当新连接到来时将会被触发,OP_ACCEPT是适用于ServerSocketChannel的唯一io事件类型。 + +register方法的返回值SelectionKey代表channel和selector的注册关系,当io事件触发时,selector也会返回事件关联的SelectionKey。 + +除此之外,SelectionKey也可以被用于取消channel对selector的注册。 + +### inner loop +在向selector注册完channel之后,会进入到selector的循环 +```java +while (true) { + selector.select(); + + Set selectedKeys = selector.selectedKeys(); + Iterator it = selectedKeys.iterator(); + + while (it.hasNext()) { + SelectionKey key = it.next(); + // ... deal with I/O event ... + } +} +``` +在调用`selector.select`方法时,改方法会阻塞,直到有至少一个注册的io事件被触发。`selector.select`会返回触发事件的个数。 + +`select.selectedKeys()`方法则是会返回被触发事件关联的SelectionKey集合。 + +对于每个SelectionKey,需要通过`key.readyOps()`来判断事件的类型,并以此对其进行处理。 + +### 为新连接注册 +当新连接到来后,需要将新连接注册到selector中 + +```java +ServerSocketChannel ssc = (ServerSocketChannel)key.channel(); +SocketChannel sc = ssc.accept(); + +sc.configureBlocking(false); +// 监听新连接的READ事件类型 +sc.register(selector, SelectionKey.OP_READ); +``` + +### remove SelectionKey +在完成对io事件的处理之后,必须要将SelectionKey从selectedKeys之中移除,否则selectionKey将会被一直作为活跃的io事件 +```java +it.remove(); +``` + +### 新连接有可读数据 +当注册的新连接传来可读数据之后,可读数据处理方式如下: +```java + else if ((key.readyOps() & SelectionKey.OP_READ) + == SelectionKey.OP_READ) { + + // Read the data + SocketChannel sc = (SocketChannel) key.channel(); +``` +## CharsetEncoder/CharsetDecoder +通过`CharsetEncoder/CharsetDecoder`,可以进行`CharBuffer`和`ByteBuffer`之间的转化。 + +```java +// 获取字符集 +Charset latin1 = Charset.forName("ISO-8859-1"); + +// 通过字符集创建encoder/decoder +CharsetDecoder decoder = latin1.newDecoder(); +CharsetEncoder encoder = latin1.newEncoder(); + +CharBuffer cb = decoder.decode(inputData); + +ByteBuffer outputData = encoder.encode(cb); +``` + + + + +