Java NIO概述

NIO 概述

Java NIO 由以下几个核心部分组成

  • Channels
  • Buffers
  • Selectors

Channel 和 Buffer

基本上, 所有的IO和NIO中都从一个Channel开始. Channel有点像流. 数据可从Channel读到Buffer中, 也可以从Buffer写到Channel中.

Channel和Buffer有好几种类型.

Channel

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

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

Channel的实现

这些是Java NIO中的一些主要Channel的实现:

  • FileChannel : 从文件中读写数据
  • DatagramChannel : 能通过UDP读写网络中的数据
  • SocketChannel : 能通过TCP读写网络中的数据
  • ServerSocketChannel : 可以监听新进来的TCP连接, 像Web服务器那样. 对每一个新进来的连接都会创建一个SocketChannel.

通道涵盖了UDP和TCP网络IO, 以及文件IO.

Buffer

Java NIO 中的Buffer用于和NIO通道进行交互. 如你所知, 数据是从通道读入缓冲区, 从缓冲区写入到通道中的.

缓冲区本质上一块可以写入数据, 然后可以从中读取数据的内存. 这块内存被包装成NIO Buffer 对象, 并提供了一组方法, 用来方便的访问该块内存.

Buffter的基本用法

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

  1. 写入数据到Buffer
  2. 调用flip()方法
  3. 从Buffer中读取数据
  4. 调用clear()方法或者compact()方法

当向Buffer写入数据时, buffer会记录下写了多少数据. 一旦要读取数据, 需要通过flip()方法将Buffer从写模式切换到读模式, 在读模式下, 可以读取之前写入到buffer中的所有数据.

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

  1. 调用clear()方法
  2. 调用compact()方法

clear()方法会清空整个缓冲区. compact()方法只会清楚已经读过的数据. 任何未读的数据都被移到缓冲区的起始处, 新写入的数据将放到缓冲区未读数据的后面.

Buffer的capacity, position和limit

缓冲区本质上是一块可以写入数据, 然后可以从中读取数据的内存. 这块内存被包装成NIO Buffer对象, 并提供了一组方法, 用来方便的访问该块内存.

为了理解Buffer的工作原理, 需要熟悉它的三个属性:

  • capacity
  • position
  • limit

position和limit的含义取决于Buffer处理读模式还是写模式. 不管Buffer处在什么模式, capacity的含义总是一样的.

这里有一个关于capacity, position 和limit在读写模式中的说明, 详细的解释在插图后面.

capacity

作为一个内存块, Buffer有一个固定的大小值, 也叫"capacity". 你只能往里写capacity个byte、long、char等类型. 一旦Buffer满了, 需要将其清空(通过读数据或者清除数据) 才能继续写数据.

position

当你写数据到Buffer中时, position表示当前的位置. 初始的position值为0. 当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元. position最大可为capacity-1.

limit

在写模式下, Buffer的limit表示你最多往Buffer里写多少数据. 写模式下. limit等于Buffer的capacity.

当切换Buffer到读模式时, limit表示你最多能读到多少数据. 因此, 当切换到Buffer到读模式时, limit会被设置成写模式下的position值. 换句话说, 你能读到之前写入的所有数据(limit被设置成已写数据的数量, 这个值在写模式下就是position)

Buffter的类型

以下是Java NIO里关键的Buffer实现:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

这些Buffer覆盖了你能通过IO发送的基本数据类型: byte, short, int, long, float, double 和char.

另外Java NIO还有一个MappedByteBuffer, 用于表示内存映射文件. 这个比较特别

Buffter的分配

要想获得一个Buffer对象首先要进行分配. 每一个Buffer类都有一个allocate方法. 下面是一个分配48字节capacity的ByteBuffer的例子.

ByteBuffer buf = ByteBuffer.allocate(48);

这个分配一个可存储1024个字符的CharBuffer:

CharBuffer buf = CharBuffer.allocate(1024);

向Buffer中写数据

写数据到Buffer有两种方式:

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

从Channel写到Buffer的例子

int byteRead = inChannle.read(buf); // red into buffer.

通过put方法写Buffer的例子:

buf.put(127);

put方法有很多版本, 允许你以不同的方式把数据写入到Buffer中. 例如, 写到一个指定的位置, 或者把一个字节数组写入到Buffer.

flip()方法

flip方法将Buffer从写模式切换到读模式. 调用flip()方法会将position设回0, 并将limit设置成之前的position的值.

换句话说, position现在用于标记读的位置, limit表示之前写进了多少个byte、char等. —现在能读取多少个byte、char等.

从Buffer中读取数据

有两种方式:

  1. 从Buffer读取数据到Channel
  2. 使用get()方法从Buffer中读取数据

从Buffer读取数据到Channel的例子:

// read from buffer into channel
int bytesWritten = inChannel.write(buf);

使用get()方法从Buffer中读取数据的例子

byte aByte = buf.get();

get方法有很多版本, 允许你以不同的方式从Buffer中读取数据. 例如, 从指定position读取, 或者从Buffer中读取数据到字节数组. 更多Buffer实现的细节参考JavaDoc.

rewind()方法

Buffer.rewind()将position设回0, 所以你可以重读Buffer中的所有数据. limit保持不变, 仍然表示能从Buffer中读取多少个元素(byte、char等).

clear()与compact()方法

一旦读完Buffer中的数据, 需要让Buffer准备好再次被写入. 可以通过clear()compact()方法完成.

如果调用的是clear()方法, position将被设回0, limit被设置成capacity的值. 换句话说, Buffer被清空了. Buffer中的数据并未清除, 只是这些标记告诉我们可以从哪里开始往Buffer里写数据.

如果Buffer中有一些未读的数据, 调用clear(), 数据将“被遗忘”, 意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有.

如果Buffer中仍有未读的数据, 且后续还需要这些数据, 但是此时想要先写些数据, 那么使用compact()方法.

compact()方法将所有未读的数据拷贝到Buffer起始处. 然后将position设到最后一个未读元素正后面. limit属性依然clear()方法一样, 设置成capacity. 现在Buffer准备好写数据了, 但是不会覆盖未读的数据.

mark()与reset()方法

通过调用Buffer.mark()方法, 可以标记Buffer中的一个特定position. 之后通过调用Buffer.reset()方法恢复到这个position.

例如:

buffer.mark();
// call buffer.get() a couple of times, e.g. during parsing.
buffer.reset(); // set position back to mark.

equals()

当满足下列条件时, 表示两个Buffer相等:

  1. 有相同的类型(byte、char、int等)
  2. Buffer中剩余的byte、char等的个数相等
  3. Buffer中所有剩余的byte、char等都相同

如你所见, equals只是比较Buffer的一部分, 不是每一个在它里面的元素都比较. 实际上, 它只比较Buffer中的剩余元素.

compareTo()方法

compareTo()方法比较两个Buffer的剩余元素(byte、char等), 如果满足下列条件, 则认为一个Buffer“小于”另一个Buffer.

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

  2. 所有元素都相等, 但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少)

Selector

Selector 允许单线程处理多个Channel. 如果你的应用打开了多个连接(通道), 但每个连接的流量都很低, 使用Selector就会很方便.

要使用Selector, 得向Selector注册Channel, 然后调用它的select()方法. 这个方法会一直阻塞到某个注册的通道有事件就绪. 一旦这个方法返回, 线程就可以处理这些事件, 事件的例子有:如新连接进来, 数据接收等.

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页