NIO 学习笔记

    xiaoxiao2025-09-20  130

    NIO 学习笔记

    1. 缓冲区-Buffer1.1 概述1.2 基本属性1.3 创建缓冲区1.4 复制缓冲区1.5 缓冲区的读取1.6 常见的方法1.7 字节缓冲区1.7.1 字节顺序1.7.2 直接缓冲区1.7.3 内存映射缓冲区 1.8 总结 2 通道-Channel2.1 打开通道2.2 使用通道2.3 关闭通道2.4 Scatter/Gather2.5 文件通道2.6 通道直接数据传输2.7 Socket 通道 3 选择器-Selector3.1 基础操作3.2 方法 NIO 主要有三大块:Buffer、Channel 和 Selector

    1. 缓冲区-Buffer

    1.1 概述

    缓冲区的作用是一个存储器,或者分段运输区,在这里数据可被存储并在之后用于检索,主要有以下几种: 可以看到,针对每种基本的数据类型都有对应的缓冲区。

    1.2 基本属性

    容量(Capacity): 最大能存多少元素,一旦创建,大小就固定了。 上界(Limit):缓冲区第一个不能读或写的元素,或者说现存元素的计数。 位置(Position):下一次读或写元素的位置 标记(Mark):备忘的位置

    有如下关系: 0 <= mark <= position <= limit <= capacity

    1.3 创建缓冲区

    创建一个Buffer,位置被设为0,而且容量和上界被设为11,刚好经过缓冲区能够容纳的最后一个字节。标记最初未定义。容量是固定的,但另外的三个属性可以在使用缓冲区时改变。

    CharBuffer charBuffer = CharBuffer.allocate (11);

    也可以通过数组来创建:

    char [] myArray = new char [11]; CharBuffer charbuffer = CharBuffer.wrap (myArray);

    如果对charbuffer 操作, 可以直接影响到数组myArray,wrap 也有其他的构造方法。

    1.4 复制缓冲区

    CharBuffer buffer = CharBuffer.allocate (8); buffer.position (3).limit (6).mark( ).position (5); CharBuffer dupeBuffer = buffer.duplicate( ); buffer.clear( );

    这里通过duplicate 复制了缓冲区,通过图片,可以看出,产生了一个新的dupeBuffer 对象,但原始缓冲区和副本都操作同样的数据元素。可以通过asReadOnlyBuffer方法,创建一个只读缓冲区。

    CharBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();

    调用readOnlyBuffer.put(‘X’);,会抛ReadOnlyBufferException异常。

    分割缓冲区

    char [] myBuffer = new char [10]; CharBuffer cb = CharBuffer.wrap (myBuffer); cb.position(2).limit(6); CharBuffer sliced = cb.slice( );

    可以看到分隔后的数组sliced大小就只有4了。 只有ByteBuffer字节缓冲区可以与通道共同使用。

    1.5 缓冲区的读取

    写数据到Buffer有两种方式:

    1、从Channel写到Buffer。

    int bytesRead = inChannel.read(buf);

    2、通过Buffer的put()方法写到Buffer里。

    buf.put(127);

    从Buffer中读数据的两种方式: 1、从Buffer读取数据到Channel的例子

    int bytesWritten = inChannel.write(buf);

    2、使用get()方法从Buffer中读取数据的例子

    byte aByte = buf.get();

    1.6 常见的方法

    rewind()方法:会将position设回0,意味着可以重读Buffer中的所有数据 clear()方法:会将position设回0,limit被设置成 capacity的值,清空所有数据。 compact()方法:将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面,换句话就是清空已经读取过的数据,未读取的拷贝到起始处。 mark()与reset()方法:可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。 equals()方法:当满足下列条件时,表示两个Buffer相等: 1.有相同的类型(byte、char、int等)。 2.Buffer中剩余的byte、char等的个数相等。 3.Buffer中所有剩余的byte、char等都相同。 compareTo()方法:compareTo()方法比较两个Buffer的剩余元素(byte、char等), 如果满足下列条件,则认为一个Buffer“小于”另一个Buffer: 1.第一个不相等的元素小于另一个Buffer中对应的元素 。 2.所有元素都相等,但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少)。

    1.7 字节缓冲区

    所有的数据类型,都有对应的缓冲区。但字节缓冲区比较特殊,字节是操作系统及其I/O设备使用的基本数据类型。所以也是至关重要的一个缓冲区。

    1.7.1 字节顺序

    左边是低位,右边是高位 大端:数字的最高字节位于低位地址。那么系统就是大端字节顺序。 如: 1-6-8-A-B 小端:数字的最高字节位于高位地址。那么系统就是小端字节顺序。 B-A-8-6-1 Java的默认字节顺序是大端字节顺序。

    1.7.2 直接缓冲区

    因为字节缓冲区才能与系统进行I/O操作,操作系统的内存是连续的,是相连的字节序列。 但是在jvm中,可能不会在内存中连续,或者无用存储单元收集可能随时对其进行移动。所以引进了直接缓冲区的概念。 创建:

    ByteBuffer buffer = ByteBuffer.allocateDirect(10);

    如果你不知道缓冲区是否是直接缓冲区,可以通过isDirect 方法获得。true表示直接缓冲区。

    1.7.3 内存映射缓冲区

    映射缓冲区是带有存储在文件,通过内存映射来存取数据元素的字节缓冲区。映射缓冲区通常是直接存取内存的,只能通过FileChannel类创建。映射缓冲区的用法和直接缓冲区类似,但是MappedByteBuffer对象可以处理独立于文件存取形式的的许多特定字符。RocketMQ 存储就是用了内存映射缓冲区。

    1.8 总结

    如果缓冲区写入状态变成读取状态,可以通过flip函数实现。读取完数据,需要清空缓冲区,准备下一次的读取,可以通过clean() 或compact() 。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。

    2 通道-Channel

    从通道读取数据到Buffer, 从Buufer 写数据到通道。 Channel的实现类,有一个FileChannel类和三个socket通道类:SocketChannel、ServerSocketChannel和DatagramChannel

    2.1 打开通道

    SocketChannel sc = SocketChannel.open(); sc.connect(new InetSocketAddress("127.0.0.1", 9090)); ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.socket().bind (new InetSocketAddress (9090)); DatagramChannel dc = DatagramChannel.open( ); RandomAccessFile raf = new RandomAccessFile ("somefile", "r"); FileChannel fc = raf.getChannel( );

    2.2 使用通道

    FileInputStream input = new FileInputStream (fileName); FileChannel channel = input.getChannel( ); channel.write (buffer);

    2.3 关闭通道

    FileInputStream input = new FileInputStream (fileName); FileChannel channel = input.getChannel( ); channel.close();

    2.4 Scatter/Gather

    Gather 聚合多个缓冲区到一个缓冲区 Scatter 一个缓冲区分散到多个缓冲区

    2.5 文件通道

    FileChannel类可以实现常用的read,write以及scatter/gather操作,但文件通道总是阻塞的,因此不能被置于非阻塞模式。 jdk1.4 开始,提供了文件锁的功能。但文件锁定特性在很大程度上依赖本地的操作系统实现。并非所有的操作系统和文件系统都支持共享文件锁。对于那些不支持的,对一个共享锁的请求会被自动提升为对独占锁的请求。这可以保证准确性却可能严重影响性能

    2.6 通道直接数据传输

    transferFrom():将数据从源通道传输到FileChannel中

    RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw"); FileChannel fromChannel = fromFile.getChannel(); RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw"); FileChannel toChannel = toFile.getChannel(); long position = 0; long count = fromChannel.size(); toChannel.transferFrom(position, count, fromChannel);

    transferTo():将数据从FileChannel传输到其他的channel中

    RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw"); FileChannel fromChannel = fromFile.getChannel(); RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw"); FileChannel toChannel = toFile.getChannel(); long position = 0; long count = fromChannel.size(); fromChannel.transferTo(position, count, toChannel);

    2.7 Socket 通道

    新的socket通道类可以运行非阻塞模式并且是可选择的

    SocketChannel sc = SocketChannel.open( ); sc.configureBlocking (false); // nonblocking ... if ( ! sc.isBlocking( )) { doSomething (cs); }

    3 选择器-Selector

    Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

    3.1 基础操作

    创建选择器 Selector selector = Selector.open(); 向Selector注册通道,注意:channel必须注册到selector上才能使用。

    channel.configureBlocking(false); SelectionKey key = channel.register(selector,SelectionKey.OP_READ);

    与Selector一起使用时,Channel必须处于非阻塞模式下, FileChannel 不能与Selector一起使用,因为FileChannel 是阻塞的。 事件类型: OP_CONNECT: 连接 OP_ACCEPT: 接收 OP_WRITE: 读 OP_READ: 写

    通道触发了一个事件意思是该事件已经就绪 对多个事件感兴趣通过|连接起来

    SelectionKey key = channel.register(selector,SelectionKey.OP_READ | SelectionKey.OP_ACCEPT);

    3.2 方法

    interestOps() 方法:你所选择的感兴趣的事件集合。 readyOps() 方法:就绪的结合。 attach() 方法:附件对象,可以将一个对象或者更多信息附着到SelectionKey上,这样就能方便的识别某个给定的通道。

    selectionKey.attach(theObject); Object attachedObj = selectionKey.attachment();

    select()方法:你注册过,感兴趣并已经就绪的事件。 selectedKeys()方法:你注册过,感兴趣并已经就绪的事件的key集合。 wakeUp()方法: select方法是阻塞的,如果一直没有通道就绪,将一直阻塞。通过这个方法,可以在让阻塞在select上的线程立即返回。 close()方法:用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。

    参考: 1、《Java+NIO+中文版.pdf》 2、http://ifeve.com/channels/

    最新回复(0)