分享

java.nio.*

 凤舞天煌 2007-11-12

             JDK 1.4的java.nio.*包中引入了新的JavaI/O类库,其目的在于提高速度。(实际上,旧的I/O包已经使用nio重新实现过,以便充分利用这种速度,即使我们不显式地用nio编写代码,也能从中受益!

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

             唯一直接与通道交互的缓冲器是ByteBuffer,它是java.nio里面的类:通过告知分配多少存储空间来创建一个ByteBuffer对象,并且还有一个方法选择集,用以原始的字节形式或基本数据类型输出和读取数据。但是没办法输出或读取对象,即使是字符串对象也不行;

          ByteBuffer是一个抽象类,不能直接创建实例,可以调用该类的static方法allocate(int capacity)来创建它的一个新缓冲区,该缓冲区的位置将为零,其界限将为其容量,其标记是不确定的。它将具有一个底层实现数组,且其 数组偏移量将为零。

           关于通道,这里介绍一个文件通道FileChannel,它也是一个抽象类,也不能直接创建实例。旧的I/O类库中有本个类被修改过了,可以用它们的getChannel()来返回一个FileChannel对象(更确切的说应该是返回FileChannel的一个匿名子类的对象)。这三个能返回FileChannel的类是:FileInputStream,FileOutputStream,RandomAccessFile 。值得注意的是它们都是字节流的,Reader与Writer这两个处理字符流的类不能用于通道,不过java,nio.channels.Channels类提供了实用方法,用以在通道中产生Reader与Writer(想了解更多请查API文档,这里不介绍)!
===========================================================================================
缓冲区是特定基本类型元素的线性有限序列。除内容外,缓冲区的基本属性还包括容量、限制和位置:

缓冲区的容量 是它所包含的元素的数量。缓冲区的容量不能为负并且不能更改。

缓冲区的限制 是第一个不应该读取或写入的元素的索引。缓冲区的限制不能为负,并且不能大于其容量。

缓冲区的位置 是下一个要读取或写入的元素的索引。缓冲区的位置不能为负,并且不能大于其限制。

  因此,我们从缓冲区读书数据,其实是从此缓冲区的“位置”处一直读到“限制”处!


============================================================================================
           关于缓冲器ByteBuffer,最令人捉摸不清的可能是它的三个方法:flip();clear();remind()
这三个方法一般是和FileChannel的read(ByteBuffer bf)与write(ByteBuffer bf)混合在一起使用的!
   
               FileChannel的read(ByteBuffer bf):将字节序列从FileChannel读入给定的缓冲区ByteBuffer bf 中!读完以后,bf的当前位置就是从FileChannel中读取的字节个数!
              同理,我们应该可以很容易想象出write(ByteBuffer bf)它就是用来将字节序列从给定的缓冲区ByteBuffer bf写入通道FileChannel。
    
              ByteBuffer的flip():反转此缓冲区,将限制设置为当前位置然后将位置设置为 0 !
     
               ByteBuffer的clear();“清除”此缓冲区,将位置设置为 0,将限制设置为容量!
此方法不能实际清除缓冲区中的数据,但从名称来看它似乎能够这样做,这样命名是因为它多数情况下确实是在清除数据时使用。因为调用该方法后,我们一般都会调用FileChannel.read(buff)或者buff.put()来把新的数据放到buff中,此时原来的内容就会被新的内容所覆盖!也不是全部覆盖,而是覆盖掉新数据所包含的字节数!所以看起来好象就是原来的内容被删除一样!

             remind(),重绕此缓冲区,将位置设置为 0 并丢弃标记。此方法与flip()的区别是:remind()不会把限制设置为当前位置,而是保持限制不变!与clear()的区别是:remind()不会把限制设置为容量,而是保持限制不变!

==========================================================================================
讲了这么多,你们不晕,我都晕了,那么现在来点实际的代码,以帮助理解!

//例一:
import java.nio.channels.*;
import java.io.*;

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()); //
将通道位置移动到最后,以便我们在通道后面继续写入新数据
             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中

             //进行此操作后,buff的位置移动了从FileChannel中读取的字节个数!

             buff.flip();           //为下面的buff.get()做准备,将buff的限制设置为当前位置,然后将位置设置为 0
            //因为我们从buff读取,是从此buff的位置处一直读到限制处!
             while(buff.hasRemaining())
               System.out.print((char)buff.get());
           }
} /* Output:
Some text Some more
*///:~
=========================================================================================
//例二:

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

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 用来向FileChannel读取数据

         //其实此循环只会执行一次,因为执行一次就会把FileChannel中的全部数据都读到buffer中
               buffer.flip(); // 为通道写入做准备
               out.write(buffer); //buffer 用来向FileChannel写入数据
               buffer.clear();           // 为通道再次读取做准备
             }
           }
} ///:~

 

============================启示================================

当对flip(),clear(),remind()不是很理解而正在考虑某个地方是否需要这三个方法时,我们还可以调用limit(),position(),remainning()等方法来进行调试。

public final int limit()
返回此缓冲区的限制。
public final int position()
返回此缓冲区的当前位置。
public final int remaining()
返回当前位置与限制之间的元素数。

======================================================================

上面的两个例子,是因为有FileChannel的read()或者write(),以至把ByteBuffer中的位置移动了相应的字节数。然后调用flip时,就把限制设置为当前位置。此时的position就不为0了。下面的例子中,没有FileChannel的参与,当前位置也就没有了变化,此时,若再调用flip,由于当前位置是0,设置为限制后,限制也变成0,若我们要从限制为0的ByteBuffer中读取数据,此时就什么也没读到,可能还会抛出异常。那么解决的方法就是调用rewind(),此方法,只是把位置设置为0,而不会改变其限制,这时,限制就仍为容量。因为用ByteBuffer的static allocated()时,新缓冲区的位置将为零,其限制将为其容量!

另外,调用ByteBuffer的get()与put()时,会把position移动一个字节!而调用这两个方法的带参数版本则不会改变position的值,这里还要区别一下两种情况的put方法:

   情况一:

    ByteBuffer bb = ByteBuffer.allocate(1024);
    CharBuffer cb = bb.asCharBuffer(); //创建ByteBuffer的Char视图

    cb.put("Hwdy!"); ////字符串中的每个字符占1个字节!共占5个字节!所以此句会使position移动5个字节!

 

===================================================================

情况二:

ByteBuffer bb = ByteBuffer.allocate(BSIZE);
    bb.asCharBuffer().put("Howdy"); //用这种形式来创建视图不会移动bb的position

     print(bb.position()); //所以此处的postion值为0

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多