NIO简介、缓冲区与Buffer

来源:互联网 发布:怎么看淘宝买家等级 编辑:程序博客网 时间:2024/05/22 16:25
NIO简介

        NIO 提供了一个全新的底层 I/O 模型。与最初的 java.io 包中面向流的概念不同, NIO 中采用面向块的概念。这意味着在尽可能的情况下, I/O 操作以大的数据块为单位进行,而不是一次一个字节或者字符进行。采用这样的操作方式 Java 的 I/O 性能已有很大的提高。当然这样做也牺牲了 Java 操作的简单性。

        NIO 中提供了与平台无关的非阻塞 I/O 。与面向线程的、阻塞式 I/O 方式相比,多道通信、非阻塞 I/O 技术可以使应用程序更有效地处理大量连接的情况。

        IO 的阻塞操作: 在之前的 IO 操作中接收键盘数据的操作时只要执行到 readLine() 方法,程序就要停止而等待用户输入数据;在网络编程中服务器使用 ServerSocket 类的 accept() 方法时,服务器一直处于等待状态,等待客户端连接。这两个操作都是阻塞操作。

        新 IO 并没有在原来的 IO 基础上开发,而是采用了全新的类和接口,除了原有的功能之外还提供了如下新的特性:

  • 多路选择的非封锁式 I/O 设施。
  • 支持文件锁和内存映射。
  • 支持正则表达式的模式匹配设施。
  • 字符集编码器和译码器。

       在 Java 新 IO 中使用 Buffer 和 Channel 支持以上的操作。

缓冲区与 Buffer

       在基本 IO 操作中所有的操作都是直接以流的形式完成的,而在 NIO 中所有的操作都要使用到缓冲区处理,且所有的读写操作都是通过缓冲区完成的。缓冲区是一个线性的、有序的数据集,只能容纳某种特定的数据类型。

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

       Buffer 的基本操作

       java.nio.Buffer 本身是一个抽象类,常用方法如下:

public final int capacity()    // 此缓冲的容量public final int limit()       // 此缓冲的限制public final Buffer limit(int newLimit)     // 设置此缓冲的限制public final int position()    // 缓冲区的操作位置public final Buffer position(int newPosition)     // 设置缓冲区的操作位置public final Buffer clear()    // 清除此缓冲区。将位置设置为 0,将限制设置为容量,并丢弃标记public final Buffer flip()     // 将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。public final Buffer reset()    // 将此缓冲区的位置重置为以前标记的位置public final int remaining()   // 返回当前位置与限制之间的元素数

       在新 IO 中针对每一种基本数据类型都有一种对应的缓冲区操作类,如下:

       ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer

       以上的 7 种数据缓冲操作类,都提供了如下的常用方法:

public static 缓冲区类型 allocate(int capacity)    // 分配缓冲区空间public abstract 基本数据类型 get()   // 取得当前位置的内存public abstract 缓冲区类型 put(基本数据类型 x)     // 写入指定数据类型的数据public final 缓冲区类型 put(基本数据类型[] src)    // 写入一组指定的基本数据类型的数据public 缓冲区类型 put(基本数据类型[] src, int offset, int length)public abstract 缓冲区类型 slice()   // 创建子缓冲区,其中一部分与原缓冲区共享数据public abstract 缓冲区类型 asReadOnlyBuffer()      // 将缓冲区设置为只读缓冲区
       下面以 IntBuffer 类为例演示缓冲区的操作流程,同时观察 position、limit 和 capacity
public class IntBufferDemo {public static void main(String[] args) {// 开辟缓冲区IntBuffer buf = IntBuffer.allocate(10);System.out.println("写入数据之前的position、limit 和 capcity:");System.out.println("position : " + buf.position() + " ------ limit : "+ buf.limit() + " ------ capacity : " + buf.capacity());int[] temp = {1, 3, 5, 7};// 写入数据到buf.put(temp);System.out.println("写入数据之后的position、limit 和 capcity:");System.out.println("position : " + buf.position() + " ------ limit : "+ buf.limit() + " ------ capacity : " + buf.capacity());// 重设缓冲区buf.flip();System.out.println("准备输出数据时的position、limit 和 capcity:");System.out.println("position : " + buf.position() + " ------ limit : "+ buf.limit() + " ------ capacity : " + buf.capacity());// 从缓冲区中读取数据System.out.println("缓冲区中的内容:");while (buf.hasRemaining()) {System.out.print(buf.get() + "\t");}}}

       运行程序观察到:


       注意:如果只开辟了3个缓冲区,但是写4个数据,则操作中就会出现以下错误:

Exception in thread "main" java.nio.BufferOverflowException...

       深入理解缓冲区操作

       在 Buffer 中存在一系列状态变量,这些变量随着写入或读取都有可能被改变,在缓冲区中可以使用 3 个值表示缓冲区的状态。

  • position : 当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1。 
    当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0。当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。
  • limit : 在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position
  • capacity : 作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”.你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。
       下面将逐步分析上面例子的操作步骤:


       创建子缓冲区

       可以使用各个缓冲区类的 slice() 方法从一个缓冲区中创建一个新的缓冲区,子缓冲与原缓冲区中的部分数据可以共享。下面通过例子说明:

public class IntBufferDemo2 {public static void main(String[] args) {// 开辟缓冲区IntBuffer buf = IntBuffer.allocate(10);IntBuffer sub = null;for (int i = 0; i < 10; i++) {buf.put(2 * i + 1);}buf.position(2); // 主缓冲区指针设置在地3个元素上buf.limit(6); // 住缓冲区 limit 为 6sub = buf.slice(); // 开辟自缓冲区for (int i = 0; i < sub.capacity(); i++) {int temp = sub.get(i);sub.put(temp - 1); // 修改自缓冲区中的东西}buf.flip(); // 重设缓冲区buf.limit(buf.capacity()); // 设置 limitSystem.out.println("主缓冲区中的内容:");while (buf.hasRemaining()) {System.out.print(buf.get() + " ");}}}
       运行结果:



       创建只读缓冲区

       如果现在要使用到缓冲区中的内容,但又不希望其内容被修改,则可以通过 asReadOnlyBuffer() 方法创建一个只读缓冲区,但是创建完毕后,此缓冲区不能变为可写状态。

public class IntBufferDemo3 {public static void main(String[] args) {IntBuffer buffer = IntBuffer.allocate(10);IntBuffer read = null;for (int i = 0; i < 10; i++) {buffer.put(2 * i + 1);}read = buffer.asReadOnlyBuffer();read.flip();System.out.println("缓冲区中的内容:");while (read.hasRemaining()) {System.out.print(read.get() + " ");}// read.put(19);}}

       创建直接缓冲区

       在缓冲区操作类中,只有 ByteBuffer 可以创建直接缓冲区,这样 Java 虚拟机将尽最大努力直接对其执行本机的 IO 操作,也就是说,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中。

       创建直接缓冲区时使用 ByteBuffer 类定义如下方法:

public static ByteBuffer allocateDirect(int capacity)
public class ByteBufferDemo {public static void main(String[] args) {ByteBuffer buffer = ByteBuffer.allocate(10); // 开辟直接缓冲区byte[] temp = { 1, 3, 5, 7 };buffer.put(temp);buffer.flip();System.out.println("缓冲区中的内容:");while (buffer.hasRemaining()) {System.out.print(buffer.get() + " ");}}}



0 0
原创粉丝点击