Java NIO系列-总结(Buffer 、Channel、Selector)

       近期在总结和梳理java的知识点,虽然做了这麽久的c++,但是对Java的设计思想也是想深入了解一下,毕竟语言的思想是相通的,因此基础的必备就很重要了,对于这个NIO对于有些场景,应用还是比较多, 本篇文章主要是作为自我的学习的知识点摘要,希望自己和能够看到的同学都能够从中遇见窥一斑而知全豹的效果,如有错误,请不吝指正,转载请注明出处。

java nio的核心部分    Channels   Buffer   Selectors

Channel:  FileChannel 、DataGramChannel 、SocketChannel 、ServerSocketChannel

Buffer:   ByteBuffer、 CharBuffer 、DoubleBuffer 、FloatBuffer、 IntBuffer 、LongBuffer

ShortBuffer 、MappedByteBuffer

Channel 有点像流,但流是单向的,而数据可以从Channel读到Buffer ,也可以从Buffer写

到Channel中。通道可以异步的读写。通道中的数据只有buffer进行交互。

 

Selector允许单线程处理多个Channel。如果你的应用打开了多个通道,但每个通道的流量都

很低, 使用Selector就会很方便。要使用Selector需要先向Selector注册Channel,然后调

用select()方法。这个方法阻塞到已注册的通道有时间就绪,方法返回后线程就可以处理这

些事件了。

 

RandomAccessFile aFile= new RandomAccessFile("data/noio-data.txt", "rw")

FileChannel inChannek = aFile.getChannel();

ByteBuffer buf  ByteBuffer.allocate(48);

intn bytesRead = inChannel.read(buf);

 

Buffer 读写数据:

1, 写入数据到buffer。2. 调用flip()方法。3.从Buffer中读取数据。4.调用clear()方法

或者compact()方法

当向buffer写入数据时, buffer会记录写下多少数据, 一旦要读取数据, 需要通过flip

()方法将buffer从写模式切换到读模式, 在读模式下, 可以读取之前写入到buffer的所有

数据。

一旦读完所有数据, 就需要清空缓冲区, 让它可以再次被写入, 有两种方式可以清楚缓冲

区,就是这两个方法都是应用在写模式下的:clear()清理整个缓冲区, compact()只会清

除已读过的数据,任何未读取的数据都会被移到缓冲区的其实位置, 新写入的数据将放在为

读的数据的后边。

 

capacity 标识容量,无论是读模式还是写模式,含义都是一样的。

position 用于标记读的位置

limit 标识之前写进多少的数据,现在能读取多少数据。

 

ByteBuffer  MappedByteBuffer CharBuffer DoubleBuffer FloatBuffer IntBuffer

LongBuffer ShortBuffer

每个Buffer都会有一个allocate方法,参数标识buffer存储的数据类型的数量。

 

写数据到buffer有两种形式:

从Channel到Buffer。    通过Buffer的put()方法写到Buffer里。

 

读数据从buffer:

从buffer读取数据到Channel。 使用get()方法熊Buffer中读取数据。

 

flip()方法 将Buffer从次恶魔是切换到读模式调用flip()方法会将position设回0,并将

limit设置成之前的position的值。

 

rewind()方法Buffer.rewind()将position设回0,所以你可以重读Buffer中的所有数据。

limit保持不变,仍然标识能从buffer中读取多少个元素。

 

clear()方法, position将设置为0,limit被设置成capacity的值。Buffer中的数据并没有

被清空,只是标识可以从哪里开始往Buffer中写数据

compact()方法将所有的未读的数据拷贝到buffer的其实处,将position设置到最后一个

一个未读元素的后边, limit设置成为capactiy.

 

mark()标记Buffer中一个特定的position(即一个要被读写的元素的索引)。之后可以通过

调用Buffer.reset()方法恢复到这个position。

 

equal()和compareTo()方法比较两个Buffer。

equal():类型相同, 剩余数据个数相同,剩余数据值相同。只比较剩余,不是比较的整个

CompareTo()标识第一个Buffer比另一个小的判定:

第一个不相等的元素小于另一个Buffer中对应的元素

所有元素都相等,第一个Buffer元素个数比另一个少。

 

分散(scatter)从Channel中读取是指在读操作时将读取的数据写入到多个Buffer。

聚集(gather)写入Channel是指在写操作时将多个buffer的数据写入同一个Channel。

ByteBuffer header = ByteBuffer.allocate(128);

ByteBuffer body = ByteBuffer.allocate(1024);

BYteBuffer[] bufferArray = {header, body};

channel.read(bufferArray);//作用于buffer,相对buffer来讲就是读入

 

buffer首先被插入到数组, 然后在将数组作为read方法的参数,read()方法按照buffer数组

中的顺序将从channel中读取的数据写入到buffer, 当一个buffer被写满之后,channel紧接

着就会向另一个buffer中写。就是说在写下一个buffer之前,必须要填满当前的buffer,所

以它不适用于消息大小不固定的动态消息。

 

ByteBuffer header = ByteBuffer.allocate(128);

ByteBuffer body = ByteBuffer.allocate(1024);

 

ByteBuffer[] bufferArray = {header, body};

channel.write(bufferArray);//作用于Buffer,相对于Buffer来讲就是写出

 

write()方法会按照buffer在数组的顺序,将数据写入到channel,只有position和limit之

间的数据才会被写入。就是说如果一个buffer的容量128byte,但是数据只包含58byte,那么这

个写入channel的数据就是58byte.所以聚集Gathering Writes适用于消息大小不固定的消息

 

 

在javanio中如果两个通道中有一个是FileChannel,就可以直接将数据从一个通道传输到另一

个channel.

 

目标通道FileChannel的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);

//参数1表示从position处向目标文件中写数据,count标识传输的字节数,如果目标通道的

剩余空间小于count个字节,则所传输的字节数要小于请求的字节数,如果是SocketChannel

只会传输此刻准备好的数据,可能不足count个字节,因此SocketChannel可能不会将请求的

所有数据全部传输到FileChannel中。

 

源通道FileChannel的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);

//上面的SocketChannel问题在transferTo()方法中同样存在。SocketChannel会一直传输数

据直到buffer被填满。

 

Selector能检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。

Selector selector = Selector.open();  //创建选择器

channel.configureBlocking(false); //与选择器一起使用必须处在非阻塞模式下,意味着

不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式,套接字

通道可以。

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

//第二个参数是个兴趣集合,通过Selector监听Channel时对什么事件感兴趣

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

//第三个参数表示的是附加对象。

 

SelectionKey对象包含interest集合、read集合、Channel、Selector、附加对象(可选)

interest集合包含:int interestSet = selectionKey.interestOps();//返回值相与判断事

件是否在兴趣集合中。

Connect、Accept、Read、Write

SelectionKey.OP_CONNECT

SelectionKey.OP_ACCEPT

SelectionKey.OP_READ

SelectionKey.OP_WRITE

read集合包含:int readySet = selectionKey.readyOps();

selectionKey.isAcceptable();

selectionKey.isConnectable();

selectionKey.isReadable();

selectionKey.isWritable();

 

Channel channel = selectionKey.channel();   //访问通道

Selector selector = selectionKey.selector(); //访问选择器

 

附加对象:可以将一个对象或者更多信息附加到SelectionKey上, 这样就能方便的识别某个

给定的通道。

selectionKey.attach(theObject);

Object attachedObj = selectionKey.attachment();

 

一旦向Selector注册了一个或多个通道,就可以调用几个重载select()方法,返回所感兴趣

事件已经准备就绪的通道。

int select()  //阻塞到至少有一个通道在你注册的事件上就绪了。

int select(long timeout) //同上,最长会阻塞时间。

int selectNow()  //不会阻塞,不管什么通道就绪就立刻返回。如果自从前一次选择操作后

,没有通道变成可选的,则此方法就直接返回零。

 

几个函数返回的int表示的是有多少通道已经就绪。自上次调用select()方法后有多少通道变

成就绪状态。

 

Set selectedKeys = selector.selectedKeys();   //返回的是所有就绪通道的键集

当向Selector注册Channel时,Channel.register()方法会返回一个SelectionKey对象。这个

对象代表了注册到该Selector的通道。可以通过SelectionKey的selsectedKeySet()方法访问

这些对象。Selector不会自己从选择集中移除SelectKey实例。必须在处理玩通道时自己移除

,下次该通道变成就绪时, Selector会再次将其放入已选择集中。

SelectionKey.channel()方法返回的通道需要转型成你要处理的类型,如

ServerSocketChannel或SocketChannel等。

 

wakeUp()某个线程调用select()方法后阻塞了,即没有通道已经就绪,只要让其他线程在

第一个线程调用select()方法的那个对象上调用Selector.wakeUp()方法,阻塞在select(

)方法上的线程会立刻返回。如果有其他线程调用wakeUp()方法。但没有线程阻塞在

select()方法上, 下个调用select()方法的线程会立刻“醒来”。

 

close()用完Selector后调用其close(),关闭选择器。并使注册到该选择器上的多有

SelectionKey失效,通道本身并不会关闭。

 

使用FileChannel时先打开通道, 通过使用一个InputStream、OutPutStream或者

RandomAccesssFile来获取一个FileChannel实例。

 

通过position()方法获取FileChannel的当前位置。也可以通过调用position(long pos)方法

设置FileChannel的当前位置。如果将位置这只在文件结束符之后,然后试图从文件通道中读

取数据, 读方法将返回 -1 --文件结束符。如果将位置设置在文件结束符之后,然后向通道

中写数据,文件将撑大到当前位置并写入数据。这可能导致“文件空洞”,磁盘上物理文件

中写入的数据间有空隙。

 

FileChannel的size()方法将返回该实例所关联文件的大小。

FileChannel的truncate()方法截取一个文件,截取文件时,文件当中指定长度后面的部分将

被删除。

 

FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。此方法有一个布

尔值参数,指明是否同时将文件元数据(权限信息)写到磁盘上。

 

SocketChannel是一个来连接到TCP网络套接字的通道。创建方式:

打开一个SocketChannel并连接到互联网上的某台服务器。

SocketChannel socketChannel = SocketChannel.open();

socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

一个新连接到达ServerSocketChannel时,会创建一个SocketChannel.

 

connect() 如果在非阻塞模式下,此时调用次函数connect(),该方法可能在连接建立之前就

返回了。为了确定连接是否建立,使用finishConnect()方法。

 

write()在非阻塞模式下,此方法在没有写出任何内容时可能就返回了,所以需要在循环中

调用。

 

read()在非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了, 所以需要关

注它的int类型返回值,这个值说明了读取了多少个字节。

 

ServerSocketChannel是一个可以监听新进来的TCP连接的通道。

ServerSocketChannel.open()方法来打开通道,当然也有其他通道的close()方法关闭通道

通过ServerSocketChannel.accept()方法监听新进来的连接。方法返回一个包含新进来连接

的SocketChannel。因此,accept()方法会一直阻塞到有新连接到达。通常放在循环中。

 

ServerSocketChannel可以设置成非阻塞模式。在非阻塞模式下,accept()方法会立刻返回

,如果还没有新进来的连接,返回的将是null.因此需要再循环中判断返回值。

 

DatagramChannel是一个能收发UDP包的通道。

DatagramChannel channel = DatagramChannel.open();

channel.socket().bind(new InetSocketAddress(9999));

通过receive()方法从DatagramChannel接收数据。会将接收到的数据包内容复制到指定的

buffer,如果buffer容不下收到的数据,多出的数据将被丢弃。

通过send()方法从DatagramChannel发送数据,

通过connect()方法将DatagramChannel连接到网络中的特定地址,由于UDP是无连接的,连接

到特定地址并不会像TCP通道那样创建一个真正的连接。而是锁住DatagramChannel,让其只能

从特定地址收发数据。当连接之后也可以像传统通道一样使用read()和write()方法。只是

在数据传送发面没有任何保证。

 

Pipe管道是两个线程之间的单向连接。有一个source通道和一个sink通道。数据会从一个线

程被写到sink通道,另一个线程在从source通道读取这些数据。

 

Pipe pipe = Pipe.open();//打开管道

 

向管道中写数据,需要访问sink通道:

Pipe.SinkChannel sinkChannel = pipe.sink();

通过调用SinkChannel的write()方法,将数据写入到SinkChannel。

 

从管道中读取数据,需要访问source通道:

Pipe.SourceChannel sourceChannel = pipe.source();

通过调用SourceChannel的read()方法来读取数据,返回的int值标识从管道读取了多少字

节的数据。

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值