您的位置:首页 > 编程语言 > Java开发

Java NIO使用及原理分析

2016-01-11 20:17 543 查看
转载自:http://blog.csdn.net/wuxianglong/article/details/6604817

在NIO中有几个核心对象需要掌握:缓冲区(Buffer)、通道(Channel)、选择器(Selector)。

缓冲区Buffer

       缓冲区实际上是一个容器对象,更直接的说,其实就是一个数组,在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,它也是写入到缓冲区中的;任何时候访问 NIO 中的数据,都是将它放到缓冲区中。而在面向流I/O系统中,所有数据都是直接写入或者直接将数据读取到Stream对象中。

       在NIO中,所有的缓冲区类型都继承于抽象类Buffer,最常用的就是ByteBuffer,对于Java中的基本类型,基本都有一个具体Buffer类型与之相对应,它们之间的继承关系如下图所示:

     


       Buffer对象最重要的属性有下面三个,它们一起合作完成对缓冲区内部状态的变化跟踪:

       position:指定了下一个将要被写入或者读取的元素索引,它的值由get()/put()方法自动更新,在新创建一个Buffer对象时,position被初始化为0。

       limit:指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。

      capacity:指定了可以存储在缓冲区中的最大数据容量,实际上,它指定了底层数组的大小,或者至少是指定了准许我们使用的底层数组的容量。

      以上三个属性值之间有一些相对大小的关系:0 <= position <= limit <= capacity。如果创建一个新的容量大小为10的ByteBuffer对象,在初始化的时候,position设置为0,limit和 capacity被设置为10,在以后使用ByteBuffer对象过程中,capacity的值不会再发生变化,而其它两个将会随着使用而变化。

     如果向缓冲区中写入4个数据后,则此时position的值为4,即下一个将要被写入的字节索引为4,而limit仍然是10,如下图所示:

    


     下一步把读取的数据写入到输出通道中,相当于从缓冲区中读取数据,在此之前,必须调用flip()方法,该方法将会完成两件事情:

     1. 把limit设置为当前的position值

     2. 把position设置为0

     由于position被设置为0,所以可以保证在下一步输出时读取到的是缓冲区中的第一个字节,而limit被设置为当前的position,可以保证读取的数据正好是之前写入到缓冲区中的数据,如下图所示:   



     现在调用get()方法从缓冲区中读取数据写入到输出通道,这会导致position的增加而limit保持不变,但position不会超过limit的值,所以在读取我们之前写入到缓冲区中的4个自己之后,position和limit的值都为4,如下图所示:

     


     在从缓冲区中读取数据完毕后,limit的值仍然保持在我们调用flip()方法时的值,调用clear()方法能够把所有的状态变化设置为初始化时的值,如下图所示:

 


简单示例:
import java.nio.IntBuffer;

public class IntBufferTest {

public static void main(String[] args) {
// 分配新的int缓冲区,参数为缓冲区容量
// 新缓冲区的当前位置将为零,其limit将为其capacity。它将具有一个底层实现数组,其数组偏移量将为零。
IntBuffer buffer = IntBuffer.allocate(10);
System.out.print("初始化: ");
System.out.print("position: " + buffer.position() + ", ");
System.out.print("limit: " + buffer.limit() + ", ");
System.out.println("capacity: " + buffer.capacity());

for (int i = 0; i < 5; ++i) {
// 将给定整数写入此缓冲区的当前位置,当前位置递增
buffer.put(i);
}
System.out.print("put值后: ");
System.out.print("position: " + buffer.position() + ", ");
System.out.print("limit: " + buffer.limit() + ", ");
System.out.println("capacity: " + buffer.capacity());

// 重设此缓冲区,将limit为当前位置position,然后将当前位置position设置为0
buffer.flip();
System.out.print("flip()后: ");
System.out.print("position: " + buffer.position() + ", ");
System.out.print("limit: " + buffer.limit() + ", ");
System.out.println("capacity: " + buffer.capacity());

// 查看在当前位置和限制位置之间是否有元素
while (buffer.hasRemaining()) {
// 读取此缓冲区当前位置的整数,然后当前位置递增
int j = buffer.get();
System.out.print(j + "  ");
}
System.out.println();

buffer.clear();
System.out.print("clear()后: ");
System.out.print("position: " + buffer.position() + ", ");
System.out.print("limit: " + buffer.limit() + ", ");
System.out.println("capacity: " + buffer.capacity());
}
}
运行结果:
初始化: position: 0, limit: 10, capacity: 10
put值后: position: 5, limit: 10, capacity: 10
flip()后: position: 0, limit: 5, capacity: 10
0  1  2  3  4
clear()后: position: 0, limit: 10, capacity: 10


通道Channel

       通道是一个对象,通过它可以读取和写入数据,当然了所有数据都通过Buffer对象来处理。我们永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区。同样不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。

       在NIO中,提供了多种通道对象,而所有的通道对象都实现了Channel接口。它们之间的继承关系如下图所示:

      


使用NIO读取数据

      在前面我们说过,任何时候读取数据,都不是直接从通道读取,而是从通道读取到缓冲区。所以使用NIO读取数据可以分为下面三个步骤:

      1. 从FileInputStream获取Channel

      2. 创建Buffer

      3. 将数据从Channel读取到Buffer中

     下面是一个简单的使用NIO从文件中读取数据的例子:
static void read() throws Exception {
FileInputStream fin = new FileInputStream("build.gradle");

FileChannel fc = fin.getChannel(); // 获取通道

ByteBuffer buffer = ByteBuffer.allocate(1024);// 创建缓冲区

for (; fc.read(buffer) > 0/* 读取数据到缓冲区 */;) {
buffer.flip();

while (buffer.remaining() > 0) {
byte b = buffer.get();
System.out.print(((char) b));
}
buffer.clear();
}
fin.close();
}
使用NIO写入数据

      使用NIO写入数据与读取数据的过程类似,同样数据不是直接写入通道,而是写入缓冲区,可以分为下面三个步骤:

       1. 从FileInputStream获取Channel

       2. 创建Buffer

       3. 将数据从Channel写入到Buffer中

      下面是一个简单的使用NIO向文件中写入数据的例子:
static void write() throws Exception {
FileOutputStream fout = new FileOutputStream("c:\\test.txt");

FileChannel fc = fout.getChannel();

ByteBuffer buffer = ByteBuffer.allocate(1024);
for (int i = 0; i < 10; ++i) {
buffer.put((byte) i);
}
buffer.flip();

fc.write(buffer);

fout.close();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: