NIO的使用

1.为什么要用?

nio比I/O快,nio使用块操作,而不是流,它将最耗时的,填充和缓冲区存取交给OS而提高速度

    (流操作是一个字节一个字节的读,虽然简单,而且可以容易的附加过滤器,但是效率低)

2 nio简介

    buffer和channel是nio的核心,任何对象到(去)任何地方,都要通过channel,而buffer相当于一个容器,任何想去channel的,都要先通过buffer

   nio中的所有数据都要先使用buffer(不论读写),buffer相当于一个数组,提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程

  buffer的各个实现,最常用的Bytebuffer,

每个primate类型都对应一个buffer,Intbuffer,LongBuffer,CharBuffer...

ByteBuffer

CharBuffer

ShortBuffer

IntBuffer

LongBuffer

FloatBuffer

DoubleBuffer

缓冲区的容量 是它所包含的元素的数量。缓冲区的容量永远不会为负并且从不会更改。

缓冲区的限制 是不应读取或写入的第一个元素的索引。缓冲区的限制永远不会为负,并且永远不会大于其容量。

缓冲区的位置 是下一个要读取或写入的元素的索引。缓冲区的位置永远不会为负,并且永远不会大于其限制。 

 

0 <= 标记 <= 位置 <= 限制 <= 容量

 

clear() 使缓冲区做好了新序列信道读取或相对 put 操作的准备:它将限制设置为容量大小,将位置设置为零。

flip() 使缓冲区做好了新序列信道读取或相对 get 操作的准备:它将限制设置为当前位置,然后将该位置设置为零。

rewind() 使缓冲区做好了重新读取已包含的数据的准备:它使限制保持不变,并将位置设置为零。 

 

Channel是一个对象,可以通过它读取和写入数据。拿 NIO 与原来的 I/O 做个比较,通道就像是流。

正如前面提到的,所有数据都通过 Buffer 对象来处理。您永远不会将字节直接写入通道中,相反,您是将数据写入包含一个或者多个字节的缓冲区。同样,您不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。  

通道与流的不同之处在于通道是双向的,而流不是

 

在 NIO 系统中,任何时候执行一个读操作,您都是从通道中读取,但是您不是 直接 从通道读取。因为所有数据最终都驻留在缓冲区中,所以您是从通道读到缓冲区中。

因此读取文件涉及三个步骤:(1) 从 FileInputStream 获取 Channel,(2) 创建 Buffer,(3) 将数据从 Channel 读到 Buffer 中。 

 

第一步是获取通道。我们从 FileInputStream 获取通道:

 

FileInputStream fin = new FileInputStream( "readandshow.txt" );

FileChannel fc = fin.getChannel();

 

下一步是创建缓冲区:

 

ByteBuffer buffer = ByteBuffer.allocate( 1024 );

 

最后,需要将数据从通道读到缓冲区中,如下所示:

 

fc.read( buffer );

clear() 方法重设缓冲区,使它可以接受读入的数据。 flip() 

方法让缓冲区可以将新读入的数据写入另一个通道

状态变量,每次buffer的读/写操作,都会改变buffer的状态,从而能监控buffer

1.position

2.limit

3.capacity

position 指定下一个数据将放到buffer的哪个位置(数组的索引)---从channel读

从channel取---position将表示已经从buffer中读了几个,将要读的哪个位置索引

 

limit变量表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。 

 

position总是小于或者等于limit

 

capacity 底层数组大小,limit<=capacity

 

 

现在我们要将数据写到输出通道中。在这之前,我们必须调用 flip()方法。这个方法做两件非常重要的事: 

它将limit设置为当前position。 

它将 position设置为 0。 

 

读完后,使用clear()方法

1.将limit设的和capacity一样大

2.将position设为0

 

 

 

get() 方法

 

 

 

ByteBuffer类中有四个get() 方法:  

1.byte get();

2.ByteBuffer get(byte dst[]);

3,ByteBuffer get(byte dst[],int offset,int length);

4.byte get(int index)

 

第一个方法获取单个字节。第二和第三个方法将一组字节读到一个数组中。第四个方法从缓冲区中的特定位置获取字节。那些返回 

ByteBuffer的方法只是返回调用它们的缓冲区的this值。 

 

此外,我们认为前三个get()方法是相对的,而最后一个方法是绝对的。 相对 意味着 

get()操作服从limit ,position值 — 

更明确地说,字节是从当前position读取的,而position在 

get之后会增加。另一方面,一个 绝对 方法会忽略limit和 

position值,也不会影响它们。事实上,它完全绕过了缓冲区的统计方法。 

 

上面列出的方法对应于ByteBuffer类。其他类有等价的get()

方法,这些方法除了不是处理字节外,其它方面是是完全一样的,它们处理的是与该缓冲区类相适应的类型。 

 

 

put()方法

 

byte put(byte b)

BufferByte put(byte[] src)

ByfferByte put(byte[] src,int offset,int length)

BufferByte put(BufferByte buffer)

byte put (int index,byte b)

 

一个循环使用

while(true){

buffer.clear();

int r = fcin.read(buffer);

if(r == -1)break;

buffer.flip();

fout.write(buffer);

}

 

buffer,分配大小 allocate(1024)

还可以转换一个数组,来做buffer

byte array = new byte[1024];

        ByteBuffer buffer = ByteBuffer.wrap(array);

 

buffer分片共享数据

buffer.position(3)

buffer.limit(7)

ByteBuffer slice = buffer.slice();

创建一个3-6的子缓冲区,共享数据

 

分散和聚集的应用,把数据读入多个buffer...

 

非阻塞的I/O读取,为异步I/O

核心为selector,是注册感兴趣I/O事件,当这些事件发生时,通知你

Selector selector = Selector.open();

然后,对不同的channel对象,registor(),注册对这些channel中I/O事件(如果感兴趣)

registor()的第一个参数总是selector

 

为了接受连接,需要一个ServerSocketChannel(监听每个端口都需要一个)

ServerSocketChannel ssc = ServerSocketChannel.open();

ssc.configureBlocking( false );

//绑定端口

ServerSocket ss = ssc.socket();

InetSocketAddress address = new InetSocketAddress( ports[i] );

ss.bind( address );

 

SelectionKey key = ssc.register( selector, SelectionKey.OP_ACCEPT );

register() 的第一个参数总是这个 Selector。第二个参数是 OP_ACCEPT,这里它指定我们想要监听 accept 事件,也就是在新的连接建立时所发生的事件。这是适用于 ServerSocketChannel 的唯一事件类型。

请注意对 register() 的调用的返回值。 SelectionKey 代表这个通道在此 Selector 上的这个注册。当某个 Selector 通知您某个传入事件时,它是通过提供对应于该事件的 SelectionKey 来进行的。SelectionKey 还可以用于取消通道的注册。 

 

现在已经注册了我们对一些 I/O 事件的兴趣,下面将进入主循环。使用 Selectors 的几乎每个程序都像下面这样使用内部循环: 

 

 

int num = selector.select();

 

Set selectedKeys = selector.selectedKeys();

Iterator it = selectedKeys.iterator();

 

while (it.hasNext()) {

     SelectionKey key = (SelectionKey)it.next();

     // ... deal with I/O event ...

}

 

首先,我们调用 Selector 的 select() 方法。这个方法会阻塞,直到至少有一个已注册的事件发生。当一个或者更多的事件发生时, select() 方法将返回所发生的事件的数量。 

 

接下来,我们调用 Selector 的 selectedKeys() 方法,它返回发生了事件的 SelectionKey 对象的一个 集合 。 

 

我们通过迭代 SelectionKeys 并依次处理每个 SelectionKey 来处理事件。对于每一个 SelectionKey,您必须确定发生的是什么 I/O 事件,以及这个事件影响哪些 I/O 对象。 

 

 

 

 

 

程序执行到这里,我们仅注册了 ServerSocketChannel,并且仅注册它们“接收”事件。为确认这一点,我们对 SelectionKey 调用 readyOps() 方法,并检查发生了什么类型的事件 

if ((key.readyOps() & SelectionKey.OP_ACCEPT)

     == SelectionKey.OP_ACCEPT) {

     // Accept the new connection

     // ...

}

 

可以肯定地说, readOps() 方法告诉我们该事件是新的连接。 

### Java NIO 使用教程 Java NIO(New I/O)是 Java 1.4 引入的一组新的 I/O API,相较于传统的 Java IO API,NIO 提供了更高效的 I/O 操作方式,尤其适用于高并发、大数据量的场景。Java NIO 的核心组件包括 **Channel**、**Buffer** 和 **Selector**。 #### Channel 与 Buffer 的基本使用 Java NIO 中的数据读写操作是通过 **Channel** 和 **Buffer** 完成的。Channel 类似于传统的流(Stream),但支持双向数据传输(既可读也可写),而 Buffer 是用于存储数据的容器。 以下是一个使用 NIO 从文件中读取数据的示例: ```java import java.io.FileInputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class TestChannelRead { public static void main(String[] args) throws Exception { FileInputStream fileInputStream = new FileInputStream("D:\\test.txt"); FileChannel fileChannel = fileInputStream.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(1024); fileChannel.read(buffer); buffer.flip(); while (buffer.hasRemaining()) { byte b = buffer.get(); System.out.print((char) b); } fileInputStream.close(); } } ``` 在上述代码中,`FileChannel` 用于从文件中读取数据,`ByteBuffer` 是用于存储数据的缓冲区。通过 `read()` 方法将数据读入缓冲区,随后调用 `flip()` 方法切换缓冲区的读写模式,最后逐字节读取并输出内容[^3]。 #### Buffer 的类型与操作 Java NIO 提供了多种 Buffer 类型,包括 `ByteBuffer`、`CharBuffer`、`DoubleBuffer`、`FloatBuffer`、`IntBuffer`、`LongBuffer` 和 `ShortBuffer`。其中 `ByteBuffer` 是最常用的类型,支持直接内存分配的 `DirectByteBuffer`,在大数据量传输时性能更优,因为它绕过了 JVM 堆内存,减少了数据在用户空间和内核空间之间的复制[^2]。 缓冲区的基本操作包括: - `allocate(int capacity)`:分配缓冲区空间 - `put()`:将数据写入缓冲区 - `flip()`:切换为读模式,设置 `limit` 为当前 `position`,并将 `position` 重置为 0 - `get()`:从缓冲区读取数据 - `clear()` 或 `compact()`:清空或压缩缓冲区,准备下一次写入 #### 文件写入操作 Java NIO 同样支持高效的文件写入操作。以下是一个将字符串写入文件的示例: ```java import java.io.FileOutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class TestChannelWrite { public static void main(String[] args) throws Exception { FileOutputStream fileOutputStream = new FileOutputStream("D:\\output.txt"); FileChannel fileChannel = fileOutputStream.getChannel(); String data = "Hello, Java NIO!"; ByteBuffer buffer = ByteBuffer.wrap(data.getBytes()); fileChannel.write(buffer); fileOutputStream.close(); } } ``` 该代码通过 `FileChannel` 将字符串数据写入指定文件,使用 `ByteBuffer.wrap()` 方法将字符串转换为字节缓冲区,并通过 `write()` 方法完成写入操作[^3]。 #### 文件锁定与并发控制 Java NIO 支持文件锁定功能,可用于多线程或跨进程的并发控制。以下是一个使用文件锁的示例: ```java import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; public class FileLockExample { public static void main(String[] args) throws Exception { RandomAccessFile file = new RandomAccessFile("D:\\lock.txt", "rw"); FileChannel channel = file.getChannel(); // 获取文件锁 FileLock lock = channel.lock(); // 写入数据 String data = "Locked content."; channel.write(ByteBuffer.wrap(data.getBytes())); // 释放锁 lock.release(); file.close(); } } ``` 在此示例中,`FileLock` 用于锁定整个文件,防止其他进程或线程同时修改文件内容,从而确保数据一致性[^4]。 #### 分散与聚集读取 Java NIO 支持分散(Scattering)和聚集(Gathering)读写操作,允许一次从多个 Buffer 读取数据或将数据写入多个 Buffer。这种方式特别适用于处理结构化数据。 以下是一个使用 `ScatteringByteChannel` 的示例: ```java import java.io.FileInputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class ScatteringRead { public static void main(String[] args) throws Exception { FileInputStream fileInputStream = new FileInputStream("D:\\data.txt"); FileChannel channel = fileInputStream.getChannel(); ByteBuffer header = ByteBuffer.allocate(10); ByteBuffer body = ByteBuffer.allocate(20); channel.read(new ByteBuffer[]{header, body}); header.flip(); body.flip(); System.out.println("Header: " + new String(header.array())); System.out.println("Body: " + new String(body.array())); fileInputStream.close(); } } ``` 此示例中,`read()` 方法将数据分散读取到两个缓冲区中,分别用于处理不同的数据段[^4]。 #### 网络编程中的 NIO Java NIO 在网络编程中也提供了强大的支持,尤其是通过 `Selector` 实现的多路复用机制,可以高效地管理多个连接。 以下是一个简单的 NIO TCP 服务器示例: ```java 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.util.Iterator; public class NioServer { public static void main(String[] args) throws IOException { Selector selector = Selector.open(); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress("localhost", 8080)); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { int readyChannels = selector.select(); if (readyChannels == 0) continue; Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isAcceptable()) { ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); SocketChannel clientChannel = serverChannel.accept(); clientChannel.configureBlocking(false); clientChannel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { SocketChannel clientChannel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(256); int bytesRead = clientChannel.read(buffer); if (bytesRead > 0) { buffer.flip(); clientChannel.write(buffer); } else if (bytesRead == -1) { clientChannel.close(); } } keyIterator.remove(); } } } } ``` 该服务器使用 `Selector` 监听多个连接事件,并通过 `SocketChannel` 进行非阻塞读写操作,适用于高并发场景。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值