NIO学习记录

1.NIO总体介绍(来源: http://ifeve.com/java-nio-all/  作者:Jakob Jenkov)

Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式。

Java NIO: Channels and Buffers(通道和缓冲区)

标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。

Java NIO: Non-blocking IO(非阻塞IO)

Java NIO可以让你非阻塞的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。

Java NIO: Selectors(选择器)

Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。

2.Channels学习

Java NIO的通道类似流,但又有些不同:

  • 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。

  • 通道可以异步地读写。

  • 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。

Channel的实现

这些是Java NIO中最重要的通道的实现:

  • FileChannel:从文件中读写数据。

  • DatagramChannel:能通过UDP读写网络中的数据。

  • SocketChannel:能通过TCP读写网络中的数据。

  • ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

FileChannel文件的操作(FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下?):transferFrom(),transferTo()实现通道之间的数据交换

//最后两行代码实现的结果是一样的
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();
//transferFrom
toChannel.transferFrom(position, count, fromChannel);
//transferTo
fromChannel.transferTo(position, count, toChannel);

FileChannel的方法:

    read(buf):从fileChannel读取数据到buffer.

    write(buf):从buffer向FileChannel写数据.

  close():用完FileChannel后必须将其关闭.

    size():获得关联文件的大小

    truncate(Int):截取一个文件的部分字段。

    force(bool):将文件数据(原来存在内存中)和元数据强制写到磁盘上.

  position():获取FileChannel的当前位置

    position(int):设置FileChannel的当前位置

SocketChannel两个创建方式 : 打开一个SocketChannel并连接到互联网上的某台服务器。一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。

    阻塞模式和非阻塞模式:阻塞模式等到相应的事件之后返回。非阻塞模式:连接就立即放回(可能在连接未完成就返回了

SocketChannel的方法:

    open():直接通过SocketChannel.open()打开一个通道。其他都是使用在实例上的方法

  connnect():建立连接

    read(buf):从SocketChannel读取数据到buffer.

    write(buf):从buffer向SocketChannel写数据.

  close():用完SocketChannel后将其关闭.

ServerSocketChannel:监听新进来的TCP连接的通道

例子:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.socket().bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false);

while(true){
    SocketChannel socketChannel = serverSocketChannel.accept();
    if(socketChannel != null){
        //do something with socketChannel...
    }
}

DatagramChannel:收发UDP包的通道

例子:

//发送数据
String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();

int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));
//接收数据:
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
channel.receive(buf);


3. Buffer

缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。数据是从通道读入缓冲区,从缓冲区写入到通道中的。

Buffer的基本用法

使用Buffer读写数据一般遵循以下四个步骤:

  1. 写入数据到Buffer

  2. 调用flip()方法(filp()切换写到模式)

  3. 从Buffer中读取数据

  4. 调用clear()方法或者compact()方法(clear()清空所有数据,compact()方法只会清除已经读过的数据,并移动数据到开始位置

Buffer的工作原理

三个属性capacity(容量),position(读/写指针的位置),limit(本次读/写大小
NIO学习记录

buffer方法

  • read():从Channel写数据到Buffer

  • put()  :放入数据

  • wirte() : 从Buffer读取数据到Channel,和read对应

  • get()    : 从Buffer中读取数据

  • flip()    :  将Buffer从写模式切换到读模式

  • rewind() :重新读取数据

  • mark()     : 标记buffer位置

  • reset()     : 回到标记buffer位置

注:支持批量读取/写入数据(通过buffer数组来操作

4.Selector

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

selector使用的简单步骤:

  • 打开一个selector

  • 注册通道(channel)到selector

  • 监听四种事件(接受,连接,读,写),并书写相关逻辑代码。

代码:

Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
  int readyChannels = selector.select();
  if(readyChannels == 0) continue;
  Set selectedKeys = selector.selectedKeys();
  Iterator keyIterator = selectedKeys.iterator();
  while(keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.
    } else if (key.isConnectable()) {
        // a connection was established with a remote server.
    } else if (key.isReadable()) {
        // a channel is ready for reading
    } else if (key.isWritable()) {
        // a channel is ready for writing
    }
    keyIterator.remove();//必须移除操作完成的channel事件
  }
}

SelectionKey

    当向Selector注册Channel时,register()方法会返回一个SelectionKey对象。这个对象包含了许多属性:

  • interest集合(就是四种事件[ 接受,连接,读,写 ]的集合[int])

  • ready集合(就是四种事件[ 接受,连接,读,写 ]是否可用的集合[int]

  • Channel

  • Selector

interest集合就是一个整数interestSet),使用二进制操作(& |)

int interestSet = selectionKey.interestOps();

boolean isInterestedInAccept  = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE;

ready 集合是通道已经准备就绪的操作的集合,也是一个整数。

int readySet = selectionKey.readyOps();//获取整个集合
//单个获取
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

selector和channel:

Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector();

附加对象:自己添加对象,标识通道

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();    
//还可以在用register()方法向Selector注册Channel的时候附加对象。如:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

wakeUp()

某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。只要让其它线程在第一个线程调用 select()方法的那个对象上调用Selector.wakeup()方法即可。阻塞在select()方法上的线程会立马返回。

如果有其它线程调用了wakeup()方法,但当前没有线程阻塞在select()方法上,下个调用select()方法的线程会立即“醒来(wake up)”。

close()

用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。

通过Selector选择通道

一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件(如连接、接受、读或写)已经准备就绪的那些通道。换句话说,如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。

下面是select()方法:

  • int select()

  • int select(long timeout)

  • int selectNow()

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

select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。

selectNow()不会阻塞,不管什么通道就绪都立刻返回

5.Pipe

Java NIO 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。

这里是Pipe原理的图示:

NIO学习记录

创建管道

通过Pipe.open()方法打开管道。例如:

Pipe pipe = Pipe.open();

向管道写数据

要向管道写数据,需要访问sink通道。像这样:

Pipe.SinkChannel sinkChannel = pipe.sink();

通过调用SinkChannel的write()方法,将数据写入SinkChannel,像这样:

String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());

buf.flip();

while(buf.hasRemaining()) {
    sinkChannel.write(buf);
}

从管道读取数据

从读取管道的数据,需要访问source通道,像这样:

Pipe.SourceChannel sourceChannel = pipe.source();

调用source通道的read()方法来读取数据,像这样:

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = sourceChannel.read(buf);

read()方法返回的int值会告诉我们多少字节被读进了缓冲区。

你可能感兴趣的:(并发,nio)