NIO 学习笔记


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/

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值