Java NIO (一)

I/O即输入输出,指的是计算机和外界的接口,或者是单个程序同计算机其他部分的接口。 在Java1.4之前的I/O系统中,提供的都是面向流的I/O系统,系统每次处理一个字节,输入流(input stream)生产一个字节,输出流(output stream)消费一个字节。这种工作模式下,非常容易给流数据创建过滤器(filters),而且也很容易将多个过滤器串起来,每个过滤器针对流过自己的字节做相应处理。另一方面,在这种工作模式下面向流的IO通常很慢。而在Java 1.4中推出了NIO(New I/O),这是一个面向块的I/O系统,系统以块为单位处理数据,每个操作都会生产或者消费一“块”数据,以块为单位处理数据会比以字节(流)为单位处理数据快很多。但是面向块的IO系统同时也损失了一些优雅而简单的操作方式。 

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

 

缓冲区Buffer

Buffer本质上说是一个容器对象。任何发送到Channel的数据都必须先放进Buffer,类似的,任何从Channel中读出的数据都先读进Buffer。

Buffer就是一个装载数据的容器对象,数据从Buffer中读出,或者把数据写入Buffer中。在NIO中添加了Buffer对象,这是NIO和老IO最重要的区别。在面向流的I/O中,你可以把数据直接写入Stream对象,或者直接把数据从Stream中读出来,而不需要任何容器。 

Buffer本质上就是一个数组(array)。通常它是一个字节数组,或者其他种类的可用数组。但是Buffer除了是一个数组之外,它还提供了结构化的访问数据的方法,并且还用来跟踪系统的读/写过程。 

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

 

下面是一个简单的使用IntBuffer的例子:

 

[java] view plain copy

  1. import java.nio.IntBuffer;  
  2.   
  3. public class TestIntBuffer {  
  4.   
  5.     public static void main(String[] args) {  
  6.         // 分配新的int缓冲区,参数为缓冲区容量  
  7.         // 新缓冲区的当前位置position将为零,其界限(限制位置)limit将为其容量。  
  8.         // 它将具有一个底层实现数组,其数组偏移量将为零。  
  9.         IntBuffer buffer = IntBuffer.allocate(8);  
  10.   
  11.         for (int i = 0; i < buffer.capacity(); ++i) {  
  12.             int j = 2 * (i + 1);  
  13.             // 将给定整数写入buffer的当前位置  
  14.             buffer.put(j);  
  15.         }  
  16.   
  17.         // 重设buffer,将limit设置为position,position设置为0  
  18.         buffer.flip();  
  19.   
  20.         // 查看在position和limit之间是否有元素  
  21.         while (buffer.hasRemaining()) {  
  22.             // 读取buffer当前位置的整数  
  23.             int j = buffer.get();  
  24.             System.out.print(j + " ");  
  25.         }  
  26.     }  
  27.   
  28. }  

 

运行后可以看到:

在后面我们还会继续分析Buffer对象,以及它的几个重要的属性。

 

通道Channel

Channel模拟了老IO包中的流的概念。所有去任何地方(或者来自任何地方)的数据都必须通过Channel对象。可以从Channel中读取数据,也可以从Channel中写入数据。NIO与老IO相比而言,Channel就如同Stream。 

所有被NIO处理的数据都必须通过Buffer对象。不能直接将任何字节写入Channel,而是必须先将数据写入Buffer。同样的,也不能直接从Channel中读取任何字节,必须先通过Channel将数据读入Buffer,然后再从Buffer中获取数据。 

Channel与Stream的区别在于:Channel是双向的,而Stream只能是单向的(Stream必须是InputStream或者OutputStream的一个子类,即要么是输入流,要么是输出流,不能即输入又输出)。Channel在被打开之后,即可以读,也可以写,或者同时进行读写操作。 因为Channel是双向的,因此它比Stream更好的反应了底层操作系统IO的实质。特别是在Linux系统中,底层操作系统都是双向的。

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

 

 

使用NIO读取数据

在第一个练习中,我们首先从文件中读取一些数据。如果使用老的IO,只需要简单的创建FileInputStream,然后从中读取数据。在NIO中,事情变得有些不同了,首先需要从FileInputStream中获取Channel对象,然后使用这个Channel去读文件。 

在NIO系统中任何时刻执行一个读操作时,都要从Buffer中读,但不是直接从Channel中读。由于所有的数据都需要通过Buffer承载,所以需首先从Channel中把数据读进Buffer。

因此,从文件中读数据一共有三步:

1. 从FileInputStream中获取Channel

2. 创建Buffer

3. 从Channel中把数据读入Buffer

下面是一个简单的使用NIO从文件中读取数据的例子:

 

[java] view plain copy

  1. import java.io.FileInputStream;  
  2. import java.nio.ByteBuffer;  
  3. import java.nio.channels.FileChannel;  
  4.   
  5. public class TestChannelRead {  
  6.   
  7.     public static void main(String[] args) throws Exception {  
  8.         FileInputStream fileInputStream = new FileInputStream("D:\\test.txt");  
  9.         // 获取通道  
  10.         FileChannel fileChannel = fileInputStream.getChannel();  
  11.   
  12.         // 创建缓冲区  
  13.         ByteBuffer buffer = ByteBuffer.allocate(1024);  
  14.   
  15.         // 读取数据到缓冲区  
  16.         fileChannel.read(buffer);  
  17.   
  18.         // 重设buffer,将limit设置为position,position设置为0  
  19.         buffer.flip();  
  20.   
  21.         // 查看在position和limit之间是否有元素  
  22.         while (buffer.hasRemaining()) {  
  23.             // 读取buffer当前位置的整数  
  24.             byte b = buffer.get();  
  25.             System.out.print((char) b);  
  26.         }  
  27.   
  28.         fileInputStream.close();  
  29.     }  
  30.   
  31. }  

你一定发现,在这里,并没有告诉Channel把多少内容读进Buffer。在每个Buffer中都有一套完整的内部计数系统来跟踪已经读了多少数据了,Buffer中还剩多少空间。关于Buffer的计数系统在随后的“Buffer内部原理”中讲解。

 

 

使用NIO写入数据

使用NIO写入数据与读取数据的过程类似,同样数据不是直接写入Channel,而是先将数据写入Buffer,可以分为下面三个步骤:

1. 从FileOutputStream获取Channel

2. 创建Buffer并且把数据放到Buffer中

3. 将Buffer中的数据写入Channel

下面是一个简单的使用NIO向文件中写入数据的例子:

 

[java] view plain copy

  1. import java.io.FileOutputStream;  
  2. import java.nio.ByteBuffer;  
  3. import java.nio.channels.FileChannel;  
  4.   
  5. public class TestChannelWrite {  
  6.   
  7.     private static byte message[] = { 831111091013298121116101,  
  8.             11546 };  
  9.   
  10.     public static void main(String[] args) throws Exception {  
  11.         FileOutputStream fileOutputStream = new FileOutputStream("D:\\test.txt");  
  12.         // 获取通道  
  13.         FileChannel fileChannel = fileOutputStream.getChannel();  
  14.   
  15.         // 创建缓冲区  
  16.         ByteBuffer buffer = ByteBuffer.allocate(1024);  
  17.         // 数据存入缓冲区  
  18.         for (int i = 0; i < message.length; ++i) {  
  19.             buffer.put(message[i]);  
  20.         }  
  21.         // 重设buffer,将limit设置为position,position设置为0  
  22.         buffer.flip();  
  23.   
  24.         // 将buffer中的数据写入  
  25.         fileChannel.write(buffer);  
  26.   
  27.         fileOutputStream.close();  
  28.     }  
  29.   
  30. }  

再次注意,我们没有必要告诉Channel总共要写多少数据,Buffer的内部计数系统会跟踪已经写了多少数据了,还剩多少空间可以使用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值