From 3c3ab1f41efa67b473101c7dc6d7a1b8c766cd43 Mon Sep 17 00:00:00 2001 From: asahi Date: Sun, 25 Aug 2024 23:38:56 +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 | 120 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/java se/nio.md b/java se/nio.md index 487c13f..c2c74c2 100644 --- a/java se/nio.md +++ b/java se/nio.md @@ -36,3 +36,123 @@ buffer的本质是一个字节数组,buffer提供了对数据的结构化访 上述的每个Buffer类都实现了Buffer接口,除了ByteBuffer之外,其他每个Buffer类型都拥有相同的操作。由于ByteBuffer被绝大多数标准io操作锁使用,故而ByteBuffer除了拥有上述操作外,还拥有一些额外的操作。 +#### Buffer设计 +Buffer是存储指定primitive type数据的容器(Long,Byte,Int,Float,Char,Double,Short),buffer是线性并且有序的。 + +buffer的主要属性包括capacity、limit和position: +- capacity:buffer的capacity是其可以包含的元素数量,capacity不可以为负数并且不可以被改变 +- limit:buffer的limit代表buffer中第一个不应该被读/写的元素下表,`(例如buffer的capacity为5,但是其中只包含3个元素,那么limit则为3)`,limit不能为负数,并且limit不可以比capacity大 +- position:position代表下一个被读/写元素的下标,position部为负数也不应该比limit大 + +#### 传输数据 +buffer的每个子类都定义了get/put类似的操作,相应的read或write操作会从position转移一个或多个元素的数据,如果请求转移的数据比limit大,那么相应的get操作会抛出`BufferUnderflowException`,set操作则是会抛出`BufferOverflowException`;在上述两种情况下,没有数据会被传输。 + +#### mark & reset +buffer的mark操作标识对buffer执行reset操作后position会被重置到的位置。mark并非所有情况下都被定义,当mark被定义时,其应该不为负数并且不应该比position大。如果position或limit被调整为比mark更小的值之后,mark值将会被丢弃。如果在mark没有被定义时调用reset操作,那么会抛出`InvalidMarkException`异常。 + +> #### 属性之间的大小关系 +> +> `0<=mark<=position<=limit<=capacity` + +#### clear/flip/rewind +除了对buffer定义mark/reset操作之外,buffer还定义了如下操作: +- `clear()`:使buffer可以执行新的sequence channel-read操作或relative put操作 + - clear操作会将limit设置为capacity的值并且将position设置为0 +- `flip()`:使buffer可以执行sequence channel-wirte操作和relative get操作: + - flip操作会将limit设置为position,并且将position设置为0 +- `rewind()`:使buffer中的数据可以重新被读取: + - rewind操作会将position设置为0,limit保持不变 + +> 在向buffer写入数据之前,应该调用`clear()`方法,将limit设置为capacity并将position设置为0,从而可以向[0, capacity-1]的范围内写入数据 +> +> 当写入完成之后,想要从buffer中读取数据时,需要先调用`flip()`方法,将limit设置为position并且将position设置为0,从而可以读取[0, position-1]范围内的数据 +> +> 如果想要对buffer中的数据进行重新读取,可以调用`rewind()`方法,其将position设置为0,从而可以对[0, limit-1]范围内的数据重新进行读取。 + +### Channel +Channel为一个对象,可以从channel中读取数据或向channel写入数据。将传统io和nio相比较,channel类似于stream。 + +Channel和Stream不同的是,channel为双向的,而Stream则仅支持单项(InputStream或OutputStream)。一个开启的channel可以被用于读取、写入或同时读取或写入。 + +## Channel读写 +向channel读取或写入数据十分简单: +- 读:仅需创建一个buffer对象,并且访问channel将数据读取到buffer对象中 +- 写:创建一个buffer对象,并将数据填充到buffer对象,之后访问channel并将bufffer中的数据写入到channel中 + +### 读取文件 +通过nio读取文件分为如下步骤: +1. 从FileInputStream中获取Channel +2. 创建buffer对象 +3. 将channel中的数据读取到buffer + +示例如下所示: +```java +FileInputStream fin = new FileInputStream( "readandshow.txt" ); +// 1. 获取channel +FileChannel fc = fin.getChannel(); +// 2. 创建buffer +ByteBuffer buffer = ByteBuffer.allocate(1024); +// 3. 将数据从channel中读取到buffer中 +fc.read( buffer ); +``` + +> 如上述示例所示,在从channel读取数据时,无需指定向buffer中读取多少字节的数据,每个buffer内部中都有一个复杂的统计系统,会跟踪已经读取了多少字节的数据、buffer中还有多少空间用于读取更多数据。 + +### 写入文件 +通过nio写入文件步骤和读取类似,示例如下所示: +```java +FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" ); +// 1. 获取channel +FileChannel fc = fout.getChannel(); +// 2. 创建buffer对象 +ByteBuffer buffer = ByteBuffer.allocate( 1024 ); +for (int i = 0; i < message.length; ++i) { + buffer.put(message[i]); +} + +buffer.flip(); + +// 3. 将buffer中的数据写入到channel中 +fc.write(buffer); +``` + +在向channel写入数据时,同样无需指定需要向channel写入多少字节的数据,buffer内部的统计系统同样会追踪buffer中含有多少字节的数据、还有多少数据待写入。 + +### 同时读取和写入文件 +同时读取和写入文件的示例如下所示: +```java +public class CopyFile { + static public void main(String args[]) throws Exception { + if (args.length < 2) { + System.err.println("Usage: java CopyFile infile outfile"); + System.exit(1); + } + + String infile = args[0]; + String outfile = args[1]; + + try (FileInputStream fin = new FileInputStream(infile); + FileOutputStream fout = new FileOutputStream(outfile)) { + + FileChannel fcin = fin.getChannel(); + FileChannel fcout = fout.getChannel(); + + ByteBuffer buffer = ByteBuffer.allocate(1024); + + while (true) { + buffer.clear(); + int r = fcin.read(buffer); + + if (r == -1) { + break; + } + + buffer.flip(); + fcout.write(buffer); + } + } + } +} +``` + +在上述示例中,通过`channel.read(buffer)`的返回值来判断是否已经读取到文件末尾,如果返回值为-1,那么代表文件读取已经完成。