NIO

NIO和BIO的区别

IO: 面向流 单向的 阻塞同步IO

NIO:面向缓冲区 通道可以是单向的,也可以是双向的 非阻塞同步IO

NIO的核心在于:通道和缓冲区(Buffer),获取IO设备中的通道以及用于容纳的数据缓冲区,对数据进行处理.缓冲区底层就是数组Channel负责传输,Buffer负责存储。

Buffer属性

buffer作为缓冲区,一次读取一些数据,可以减少磁盘的IO次数和网络的传输次数。而且NIO的buffer在直接内存中,对于文件的IO更加节省了复制一遍的时间。

  • 容量 (capacity) :表示 Buffer 最大数据容量,一旦声明后,不能更改。通过Buffer中的capacity()获取。缓冲区capacity不能为负。

  • 限制 (limit):第一个不应该读取或写入的数据的索引,即位于 limit 后的数据不可读写。通过Buffer中的limit()获取。缓冲区的limit不能为负,并且不能大于其capacity。写模式下,limit等于Buffer的capacity。当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)

  • 位置 (position):当前要读取或写入数据的索引。通过Buffer中的position()获取。缓冲区的position不能为负,并且不能大于其limit。

  • 标记 (mark):标记是一个索引,通过 Buffer 中的 mark() 方法将mark标记为当前position位置。 之后可以通过调用 reset() 方法将 position恢复到标记的mark处。

  • 标记、位置、限制、容量遵守以下不变式: 0 <= mark <= position <= limit <= capacity

  • ByteBuffer

  • CharBuffer

  • DoubleBuffer

  • FloatBuffer

  • IntBuffer

  • LongBuffer

  • ShortBuffer

  1. 调用allocate方法分配Buffer内存大小。ByteBuffer buf = ByteBuffer.allocate(48);此处设置容量。
  2. 写入数据到Buffer,有两种方式:从Channel写到Buffer:inChannel.read(buf)。通过Buffer的put()方法写到Buffer里。put方法有很多版本,允许你以不同的方式把数据写入到Buffer中。例如, 写到一个指定的位置,或者把一个字节数组写入到Buffer。此时limit和capacity相同,position为当前写入的最后一位。
  3. 调用flip()方法,从写模式切换到读模式,将position设回0,并将limit设置成之前position的值。
  4. 从Buffer中读取数据有两种方式:从Buffer读取数据到Channel。int bytesWritten = inChannel.write(buf);使用get()方法从Buffer中读取数据。position不断向后移动,最多不能超出limit。
  5. rewind()可以将position设回0。
  6. 调用clear()方法或者compact()方法。clear()方法会清空整个缓冲区。position归位。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,position将放到缓冲区未读数据的后面。
  7. 通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。

Scatter/Gather

  • Scattering Reads是指数据从一个channel读取到多个buffer中,必须从channel一个个写入buffer,写完一个才能写下一个,比较固定。
  • Gathering Writes是指数据从多个buffer写入到同一个channel,读完一个读下一个,因为每个Buffer中的数据可以不等于容量,比较灵活。
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);
ByeBuffer[] bufferArray = { header, body };
channel.write(bufferArray);

Selector

select作为选择器,可以和多个channel连接,通过循环非阻塞的方式来判断是否有Channel数据,如果有进行处理,多路复用IO。实际上是循环遍历的非阻塞同步IO。

  1. 通过调用Selector.open()方法创建一个Selector
  2. 为了将Channel和Selector配合使用,必须将channel注册到selector上。通过channel.configureBlocking(false);SelectionKey key =channel.register(selector,Selectionkey.OP_READ)方法来实现,与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。
  3. 注意register()方法的第二个参数。这是一个“interest集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:Connect Accept Read Write。通道触发了一个事件意思是该事件已经就绪。所以,某个channel成功连接到另一个服务器称为“连接就绪”。一个server socket channel准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”。对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来。
  4. SelectionKey针对的是添加到select中的那个channel,这里key的意思也是channel的访问的钥匙。可以返回select、channel、此channel中什么东西就绪了等。
  5. 一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件。int select()阻塞到至少有一个通道在你注册的事件上就绪了。selectedKeys() 一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道。
  6. 某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。、
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

表示IO源与目标节点打开的链接

channel本身不能存数据,只能与buffer交互 

  • FileChannel:适用于本地数据传输 
  • SocketChannel:适用于TCP中的网络传输的客户端 
  • ServerSocketChannel:适用于TCP中的网络传输的服务器端 
  • DatagramChannel:适用于UDP中的网络传输 

FileChannel

FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。

在使用FileChannel之前,必须先打开它。但是,我们无法直接打开一个FileChannel,需要通过使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例。

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);

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()) {
    channel.write(buf);
}
  1. position 可以通过调用position()方法获取FileChannel的当前位置
  2. size 返回该实例所关联文件的大小
  3. truncate 截取一个文件。截取文件时,文件将中指定长度后面的部分将被删除
  4. force 尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。要保证这一点,需要调用force()方法。

SocketChannel

Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。可以通过以下2种方式创建:打开一个SocketChannel并连接到互联网上的某台服务器。一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。

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

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()) {
    channel.write(buf);
}

SocketChannel最好配合select一起使用,否则虽然是非阻塞但是为了达到目的也变成阻塞的了。

DatagramChannel

Java NIO中的DatagramChannel是一个能收发UDP包的通道。因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接收的是数据包。这个例子发送一串字符到”jenkov.com”服务器的UDP端口80。 因为服务端并没有监控这个端口,所以什么也不会发生。也不会通知你发出的数据包是否已收到,因为UDP在数据传送方面没有任何保证。

可以将DatagramChannel“连接”到网络中的特定地址的。由于UDP是无连接的,连接到特定地址并不会像TCP通道那样创建一个真正的连接。而是锁住DatagramChannel ,让其只能从特定地址收发数据。

DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
channel.receive(buf);

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));

Pipe

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

Pipe pipe = Pipe.open();

Pipe.SinkChannel sinkChannel = pipe.sink();

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);
}


Pipe.SourceChannel sourceChannel = pipe.source();

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = sourceChannel.read(buf);

在多线程中,每个线程持有两个Pipe,一个用于写入,一个用于读取,能够实现多线程的线程通信。

 

AsynchronousServerSocketChannel用于服务器端,只要三步

1.调用open()静态方法创建AsynchronousServerSocketChannel。

2.调用AsynchronousServerSocketChannel的bind()方法让它在指定的IP地址,指定端口监听。

3.调用AsynchronousServerSocketChannel的accept()方法接受请求。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值