简说Java NIO

什么是NIO

有的人喜欢称作New I/O,原因相对于原来的I/O方式属于新增I/O。也有人称为Non-block I/O(非阻塞I/O),NIO的目标也是让Java拥有非阻塞的I/O,所以就叫它非阻塞的I/O,吼吼。

基本概念

NIO比较核心的三个部分:Buffer,Channel,Selectors。
缓冲区(Buffer)
在面向流的I/O中,可以将数据直接写入,或者读到Stream中。在NIO库中,所有数据都是用缓冲区来处理,在读取数据时直接读到缓冲区,在写入数据时,直接写到缓冲区。可以想象成水管往水池里面蓄水,一旦有数据写入,就像水流一样流到水池里面暂时存储。Buffer的实现有以下几种:

  1. ByteBuffer
  2. CharBuffer
  3. DoubleBuffer
  4. FloatBuffer
  5. IntBuffer
  6. LongBuffer
  7. ShortBuffer

涵盖了io发送的基础数据类型:byte, short, int, long, float, double 和 char。
Buffer的三个属性:
capacity,position,limit,capacity是指定buffer的容量,那么position和limit呢?它们的含义主要取决于buffer是出于读模式还是写模式,对于capacity来说都一样,你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。而position在写数据到buffer时,position表示当前的位置,每一次写入数据position都会移动到下一个位置(最大为capacity-1),当读数据时是从某个位置特定的读,从写模式切换到读模式时position会被重置为0,从buffer读数据时,position会移动到下一个可读的位置。至于limit,在写模式下表示你可以最多写入多少数据,limit等于capacity,切换到读模式时,limit等于写模式的position,也就是你写入了多少,你最大可以读多少。
那么如何分配Buffer?(以ByteBuffer为例)

ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

跟到源码中看一下:allocate是一个静态方法,而ByteBuffer是一个抽象类,最后返回了一个实现类HeapByteBuffer实例

public static ByteBuffer allocate(int capacity) {
   if (capacity < 0)  throw new IllegalArgumentException();
     return new HeapByteBuffer(capacity, capacity);
}

而HeapByteBuffer的构造函数传入了两个capacity,为什么呢?进去看一下就知道了

HeapByteBuffer(int cap, int lim) {            // package-private
 super(-1, 0, lim, cap, new byte[cap], 0);
  /*
   hb = new byte[cap];
   offset = 0;
  */
}

刚刚有一个capacity是当作limit的初始值赋予给limit了,也就是上面提到过的写模式下limit的值是等于capacity的。从这可以看到HeapByteBuffer底层存储使用的是byte数组,而HeapByteBuffer是在堆上实现的ByteBuffer,还有一个实现类是DirectByteBuffer,前面的HeapByteBuffer的内存分配是在jvm之内,而DirectByteBuffer得内存分配是在Jvm之外,使用的是系统本地内存,分配方法用的是c的malloc。
Channel
原话是这么说的:

Java NIO Channels are similar to streams with a few differences:

  • You can both read and write to a Channels. Streams are typically
  • one-way (read or write). Channels can be read and written
  • asynchronously. Channels always read to, or write from, a Buffer.

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

  • 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
  • 通道可以异步地读写。
  • 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。

Channel用法:

RandomAccessFile aFile = new RandomAccessFile("E:\\test.txt", "rw");
// 获取文件通道
 FileChannel inChannel = aFile.getChannel();
 // 申请一个48字节的空间
 ByteBuffer buf = ByteBuffer.allocate(48);
 // 向Buffer中写入
 int bytesRead = inChannel.read(buf);
  while (bytesRead != -1) {
    System.out.println("Read " + bytesRead);
    buf.flip();
    while(buf.hasRemaining()){
       System.out.print((char) buf.get());
    }
    buf.clear();
    bytesRead = inChannel.read(buf);
 }
  aFile.close();

注意 buf.flip() 的调用,首先读取数据到Buffer,然后反转Buffer,接着再从Buffer中读取数据。

Selector
Selector允许单线程处理多个channel,使用selector得向它注册channel,然后调用select方法,这个方法会阻塞到某个通道有事件就绪,一旦这个方法返回,线程就可以处理了。
创建一个selector:

Selector selector = Selector.open();

向selector注册一个channel:

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

值得注意的是和selector一起使用channel必须设置为非阻塞模式,所以FileChannel不可以与selector一起使用,FileChannel不能切换到非阻塞模式。虽好与Socket Channels一起使用。在注册channel时加入了一个事件SelectionKey.OP_READ,意味着对这个通道监听得事件是读事件,一旦通道中有数据写入,select方法就会返回。当然还有其他的事件:

  1. SelectionKey.OP_CONNECT
  2. SelectionKey.OP_ACCEPT
  3. SelectionKey.OP_READ
  4. SelectionKey.OP_WRITE
    对应着connect,accept,read,write,四个事件。如果想要注册多个事件可以这样写:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

好了,NIO得基本概念算是说了一点,后面有时间继续补充吧。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三寸花笺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值