新 I/O

JDK1.4 的java.nio.*包中引入了Java新的I/O类库,其目的在于提高速度。实际上,旧的
I/O包已经使用nio重新实现过,以便充分利用这种速度提高,因此,即使我们不显式地用
nio编写代码,也能从中受益。速度的提高在文件I/O和网络I/O中都有可能发生,我们在这
里只研究前者5,对于后者,将会在Thinking in Enterprise Java中涉及到。


速度的提高来自于所使用的结构更接近于操作系统执行 I/O 的方式:通道和缓冲器。我们
可以把它想象成一个煤矿;通道是一个包含煤层(数据)的矿藏,而缓冲器则我们派送到矿
藏的卡车。卡车载满煤炭而归,我们再从卡车上获得煤炭。也就是说,我们并没有直接和通
道交互;我们只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获得数据,要
么向缓冲器发送数据。


唯一直接与通道交互的缓冲器是 ByteBuffer——也就是说,可以存储未加工字节的缓冲器。
当我们查询 JDK 文档中的 java.nio.ByteBuffer 时,会发现它是相当基础的类:通过告知
分配多少存储空间来创建一个 ByteBuffer 对象,并且还有一个方法选择的集用于以未加工的字节形式或原始的数据类型输出和读取数据。但是,没办法输出或读取对象,即使是字符
串对象也不行。这种处理虽然是低水平但却正好,因为这是大多数操作系统中更有效的映射
方式。


旧 I/O 类库中有三个类被改进了,用以产生 FileChannel,它们是: FileInputStream,
FileOutputStream 以及用于既读又写的 RandomAccessFile。注意这些是字节操纵流,
与低层的 nio 特性一致。Reader  和 Writer 的字符模式类不能用于产生通道,但是
java.nio.channels.Channels 类能提供实用方法在通道中产生 Reader  和 Writer。


下面的简单实例演示了上面三种类型的流,用以产生可写的、可读可写的及可读的通道。


//: c12:GetChannel.java
// Getting channels from streams
// {Clean: data.txt}
import java.io.*;
import java.nio.*;
import java.nio.channels.*;


public class GetChannel {
    private static final int BSIZE = 1024;
    public static void main(String[] args) throws Exception {
    // Write a file:
    FileChannel fc =
      new FileOutputStream("data.txt").getChannel();
    fc.write(ByteBuffer.wrap("Some text ".getBytes()));
    fc.close();
        // Add to the end of the file:
        fc = 
      new RandomAccessFile("data.txt", "rw").getChannel();
        fc.position(fc.size()); // Move to the end
    fc.write(ByteBuffer.wrap("Some more".getBytes()));
    fc.close();
    // Read the file:
    fc = new FileInputStream("data.txt").getChannel();
    ByteBuffer buff = ByteBuffer.allocate(BSIZE);
    fc.read(buff); 
    buff.flip();
    while(buff.hasRemaining())
      System.out.print((char)buff.get());     
    } 
} ///:~


对于这里所展示的任何“流”类,getChannel( )将会产生一个 FileChannel。通道是一种相
当基础的东西:我们可以向它传送用于读写的 ByteBuffer,并且可以锁定文件的某些区域
用于独占式访问(稍后讲述)。
 






将字节存放于 ByteBuffer 的方法之一是:使用一种“put”方法直接对它们进行填充,填入
一或多个字节,或基本数据类型的值。不过,正如你所见,我们也可以使用 warp()方法
将已存在的字节数组  “包装”到 ByteBuffer 中。一旦如此,就不再复制底层的数组,而是
把它作为所产生的 ByteBuffer 的存储器,我们称之为数组支持的 ByteBuffer。


data.txt 文件被 RandomAccessFile 再次打开。注意我们可以在文件内随处移动
FileChannel;在这里,我们把它移到最后以便附加其他的写操作。


对于只读访问,我们必须显式地使用静态的 allocate( )方法来分配 ByteBuffer。nio 的目
标就是要快速移动大量数据,因此 ByteBuffer 的大小就显得尤为重要——实际上,这里使
用的 1K 可能比我们通常应该使用的要小一点(必须通过实际运行我们的应用来找到最佳尺
寸)。


也可以使用 allocateDirect( ) 而不是 allocate( )来获取更快的速度,用于产生一个在更
高层次上耦合操作系统的“直接”缓冲器。但是,这种分配的开支会更大,并且具体实现也随
操作系统的不同而不同,因此必须再次实际运行我们的应用来查看直接缓冲是否可以使我们
获得速度上的优势。


一旦我们调用了 read(),就会告知 FileChannel 向 ByteBuffer 存储字节,我们必须调
用缓冲器上的 flip( ),让它做好让别人读取字节的准备(是的,这似乎有一点拙劣,但是请
记住它是基于很低水平的,适用于获取最大速度)。如果我们打算使用缓冲器执行进一步的
read()操作,我们也必须得调用 clear()来为每个 read()做好准备。这在下面这个
简单文件复制的程序中可以看到:


//: c12:ChannelCopy.java
// Copying a file using channels and buffers 
// {Args: ChannelCopy.java test.txt}
// {Clean: test.txt}  
import java.io.*;
import java.nio.*;
import java.nio.channels.*;


public class ChannelCopy {
    private static final int BSIZE = 1024;
    public static void main(String[] args) throws Exception {
    if(args.length != 2) {
      System.out.println("arguments: sourcefile destfile");
            System.exit(1); 
        } 
    FileChannel  
      in = new FileInputStream(args[0]).getChannel(),
      out = new FileOutputStream(args[1]).getChannel();
    ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
    while(in.read(buffer) != -1) {
      buffer.flip(); // Prepare for writing
      buffer.clear();  // Prepare for reading
        } 
    } 
} ///:~


我们看到一个 FileChannel 被用于读而打开,另一个被用于写而打开。ByteBuffer 被分配
了空间,当 FileChannel.read( )返回-1 时(一个分界符,毋庸置疑,它源于 Unix 和 C),
表示我们已经到达了输入的末尾。每次 read()操作之后,就会将数据输入到缓冲器中,
flip( )则是准备缓冲器以便它的信息可以由 write()提取。Write()操作之后,信息仍
在缓冲器中,接着 clear()操作则对所有的内部指针重新安排,以便缓冲器在另一个 read
()操作期间,能够做好接受数据的准备。


然而,上面那个程序并不是处理此类操作的理想方式。特殊方法 transferTo( )和
transferFrom( )则允许我们将一个通道和另一个通道直接相连:


//: c12:TransferTo.java
// Using transferTo() between channels
// {Args: TransferTo.java TransferTo.txt}
// {Clean: TransferTo.txt}  
import java.io.*;
import java.nio.*;
import java.nio.channels.*;


public class TransferTo {
    public static void main(String[] args) throws Exception {
    if(args.length != 2) {
      System.out.println("arguments: sourcefile destfile");
            System.exit(1); 
        } 
    FileChannel  
      in = new FileInputStream(args[0]).getChannel(),
      out = new FileOutputStream(args[1]).getChannel();
    in.transferTo(0, in.size(), out);
    // Or:
        // out.transferFrom(in, 0, in.size());
    } 
} ///:~


我们并不是经常做这类事情,但是了解这一点还是有好处的。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值