[Java]nio(一)——综述

背景

  JDK1.4的java.nio.*包中引入了新的JavaI/O类库,其目的在于 提高速度。实际上,旧的I/O包已经使用nio重新实现过。因此,即使不显式使用nio编写代码,也能从中受益。
  I/O的应用场景分为文件I/O和网络I/O,在这里之研究前者。

组成部分

  nio主要有三大组成部分:通道(Channel)、缓冲器(Buffer)、Selector(选择区)。
  因为nio的数据传输结构更接近于操作系统执行I/O的方式:通道缓冲器,而传统的I/O是面向字节流和字符流,所以nio的速度更快。
  通道用于存放数据,缓冲器用于传输数据,选择区用于监听多个通道的事件(比如:连接打开,数据到达)。

通道和缓冲器

  我们并没有直接和通道交互,我们只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获得数据,要么向缓冲器发送数据。
  唯一直接与通道交互的缓冲器是ByteBuffer,即可以存储未加工字节的缓冲器。它是一个很基础的类:通过告知分配多少存储空间来创建一个ByteBuffer对象,并且还有一个选择集。用于以原始的字节形式或基本数据类型输出和读取数据。但是它不能直接读取或输出对象,即使是字符串对象也不行。虽然这种处理很低级,但是却更贴合操作系统的处理方式。
  旧I/O类库中的FileInputStream、FileOutputStream、RandomAccessFile这三个类被修改了,用以产生FileChannel。这三个类都是字节操纵流,与nio性质一致。像Reader和Writer这种面向字符流的类就不能用于产生通道;但是java.nio.channels.Channels类中提供了方法,可以在通道中产生Reader和Writer。

读写实例

public class GetChannel {

    private static final int BSIZE = 2014;
    public static void main(String[] args) throws IOException {
        /**
         * 写数据,
         * 若已有内容,原内容会被覆盖;
         * 若不存在文件,则会创建新文件并写入新内容
         */
        FileChannel fc = new FileOutputStream("data.txt").getChannel();
        fc.write(ByteBuffer.wrap("some text".getBytes()));
        fc.close();

        /**
         * 添加内容至文件末尾
         */
        fc = new RandomAccessFile("data.txt", "rw").getChannel();
        fc.position(fc.size());     // 移动到文件末尾
        fc.write(ByteBuffer.wrap("Some more".getBytes()));
        fc.close();


        /**
         * 读取文件
         */
        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());
        }
    }
}
  1. channel:对于上述三种流类,getChannel()方法都可以获得一个FileChannel。通道是一种相当基础的东西:可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问
  2. ByteBuffer:将字节存放于ByteBuffer的方法之一是:使用一种“put”方法直接对它们进行填充,填入一个或多个字节,或基本数据类型的值。也可以使用wrap()方法将已存在的字节数组“包装”到ByteBuffer,这样就把一个数组包装为ByteBuffer缓冲器,一旦完成包装,底层数据就可以通过缓冲区或者直接访问。我们称第二种为数组支持的ByteBuffer
  3. position:可以使用FileChannelposition方法来在文件中移动FileChannel。在上例中是将其移动到文件末尾,方便内容的增加。
  4. allocate:对于只读访问,我们要显式使用静态的allocate()方法来分配ByteBuffer,nio的目标就是快速移动大量数据,因此ByteBuffer的大小就显得尤为重要——事实上,这里使用的1K可能比我们通常要使用的小一点(必须通过实际运行程序来找到最佳尺寸)。使用allocateDirect替代allocate,以产生一个与操作系统有更高耦合性的“直接”缓冲器还有可能达到更高的速度。但是,这种分配会增加开支,并且具体的实现也随操作系统的不同而不同。
  5. read()flip():一旦调用read()来告知FileChannelByteBuffer存储字节,就必须调用缓冲器上的flip(),让它做好让别人读取字节的准备。如果打算使用缓冲器执行进一步的read()操作,我们也必须调用clear()来为每个read()做好准备。如下例:
public class ChannelCopy {

    private static final int BSIZE = 2014;

    public static void main(String[] args) throws IOException {
        FileChannel in = new FileInputStream("src/main/resources/in.txt").getChannel();
        FileChannel out = new FileOutputStream("src/main/resources/out.txt").getChannel();

        ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
        while(in.read(buffer) != -1){
            buffer.flip();  // 准备写
            out.write(buffer);
            buffer.clear();  // 准备读
        }

        /**
          * 常用的是直接使用transferTo 或者 transferFrom
          */
//        in.transferTo(0, in.size(), out);
//         或者out.transferFrom(in, 0, in.size());
    }
}

  这里有两个FileChannel,一个用于读,一个用于写。每次read()操作之后,就会将数据输入到缓冲器中,flip()则是准备缓冲器以便它的信息可以由write()提取。write()操作之后,信息仍在缓冲区中,接着clear()操作则对所有的内部指针重新安排,以便缓冲器在另一个read()操作期间能够做好接收数据的准备。
  文件内容复制之类的操作,一般使用transferTo或者transferFrom方法

猜你喜欢

转载自blog.csdn.net/vi_nsn/article/details/78924852