参考资料
练习题 icon lost
交流讨论
笔记
img lost

三个月没写博客了,主要也不知道写什么,光顾着向GitHub传题目了。理了下思路,并结合了《Thinking in Java》和API文档,分享了下自己对NIO的理解,当然这只是针对于文件I/O,毕竟其他内容实在太多太多了…

NIO概述(文件I/O)

2002年2月13日,JDK1.4发布,工程代号为Merlin(灰背隼)。JDK1.4发布了很多新的特性,其中就包含NIO。
java.nio全称java non-blocking IO,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。
目的
将java.nio.*引入JavaIO库,是为了提高I/O的速度。速度的提高是因为所使用的结构非常接近于操作系统的I/O操纵方式:Channel(通道)、Buffer(缓冲器)。

Channel

Channel的概念可与I/O中的Stream(流)进行类比。不过Stream只是单向的,分为OutputStream和InputStream;而Channel则是双向的,可读可写。
nio中有一个专门的包,“java.nio.channels”:定义了各种通道,这些通道表示到能够执行 I/O 操作的实体(如文件和套接字)的连接;定义了用于多路复用的、非阻塞 I/O 操作的选择器。
而基本的Channel类都实现了Channel接口:用于I/O连接。
常用的Channel有:
- FileChannel:用于读取、写入、映射和操作文件的通道。
- DatagramChannel:针对面向数据报套接字的可选择通道。
- SocketChannel:针对面向流的连接套接字的可选择通道。

此处只介绍 FileChannel

FileChannel

FileChannel需要通过三个类产生。三个类分别为:FileOutputStreamFileInputStreamRandomAccessFile
例:FileChannel fc=new FileOutputStream("filepathname").getChannel();
FileChannel fc=new RandomAccessFile("filepathname","mode").getChannel();

方法

  • public abstract int read(ByteBuffer dst)throwsIOException
    将此通道中的字节序列读入到缓冲区中。返回读入字节的长度,若达到末尾,则返回-1;
  • public abstract int write(ByteBuffer src)throws IOException
    将字节序列从给定的缓冲区写入此通道。返回写入的字节数。
  • public abstract long size()throws IOException
    返回此通道的文件的当前大小。
  • public abstract long transferTo(long position,long count,WritableByteChannel target)throws IOException
    将字节从此通道的文件传输到给定的可写入字节通道。
  • public abstract long transferFrom(ReadableByteChannel src,long position,long count)throws IOException
    将字节从给定的可读取字节通道传输到此通道的文件中。
    上两方法将两个通道直接进行相连
    例:
public static void main(String[]args)throws Exception{
        FileChannel in=new FileInputStream
                ("./Test_NIO/src/Copy.java").getChannel();
        FileChannel out=new FileOutputStream("./Test_NIO/src/1.txt")
                .getChannel();
        in.transferTo(0,in.size(),out);
}
  • public final void close() throws IOException
    关闭通道
  • public abstract MappedByteBuffer map(FileChannel.MapMode mode,long position,long size)throws IOException
    将此通道的文件区域直接映射到内存中。(下文进行解释)
  • public final FileLock tryLock()throws IOException
    试图获取对此通道的文件的独占锁定

Buffer

缓冲区是特定基本类型元素的线性有限序列。除内容外,缓冲区的基本属性还包括容量、限制和位置:

capacity:是它所包含的元素的数量。缓冲区的容量不能为负并且不能更改。
limit:是第一个不应该读取或写入的元素的索引。缓冲区的限制不能为负,并且不能大于其容量。
position:是下一个要读取或写入的元素的索引。缓冲区的位置不能为负,并且不能大于其限制。

ByteBuffer

ByteBuffer是唯一与通道进行交互的缓冲器
它有两种方式进行创建,分配其缓冲空间:
- public static ByteBuffer allocate(int capacity):分配一个新的字节缓冲区,参数capacity单位为字节。
- public static ByteBuffer wrap(byte[] array):将 byte 数组包装到缓冲区中。

下面演示一个基本的Copy操作(当然可以直接使用Channel.transferTo操作,此处为了解释ByteBuffer的一些常见方法)

public class Copy {
    public static void main(String[]args)throws Exception{
        FileChannel in=new FileInputStream
                ("./Test_NIO/src/Copy.java").getChannel();
        FileChannel out=new FileOutputStream("./Test_NIO/src/1.txt")
                .getChannel();
        ByteBuffer buffer=ByteBuffer.allocate(1024);
        while(in.read(buffer)!=-1){
            buffer.flip();
            out.write(buffer);
            buffer.clear();
        }
        buffer.rewind();
        while(buffer.hasRemaining())
            System.out.print((char)buffer.get());
        in.close();
        out.close();
    }
}

此处实现了将Copy.java的内容拷贝到了1.txt文件中,并打印内容,涉及到的方法有:
- public final Buffer flip():让ByteBuffer做好让别人读取字节的准备
- public final Buffer clear():清空缓冲区,position = 0;limit = capacity;mark = -1;
- public final Buffer rewind():将ByteBuffer的position置为0。
- public final boolean hasRemaining():position < limit,判断缓冲区是否到末尾
- public abstract ByteBuffer put(byte b):将给定的字节写入此缓冲区的当前位置,然后该位置递增。相应有put方法:
- public abstract byte get():相对 get 方法。读取此缓冲区当前位置的字节,然后该位置递增。
注意:此处能全部打印Copy.java的全部内容是将缓冲区大小设置了1024个字节,能将内容全部放入缓冲区,若设置为32字节,则最后存入缓冲区的内容只有最后一部分

解码与编码

上述将缓冲区的内容输出是通过一个字节一个字节的读取,那么是否有更快的方法去读取缓冲区的内容呢?答案是肯定的!缓冲区容纳的是普通的字节,为了把它们转换成字符,要么在输入它们的时候进行编码,要么再将其从缓冲器输出对他们进行解码
下面通过一个实例进行解释:

public class Transform {
    public static void main(String[]args)throws Exception{
        /*
        解码
        */
        FileChannel in=new FileInputStream("./Test_NIO/src/e.txt")
                .getChannel();
        ByteBuffer buffer=ByteBuffer.allocate(1024);
        in.read(buffer);
        buffer.flip();
        System.out.println(Charset.forName(
                System.getProperty("file.encoding")).
                decode(buffer));
        in.close();
        /*
        编码
         */
        FileChannel out=new FileOutputStream("./Test_NIO/src/2.txt")
                .getChannel();
        buffer=ByteBuffer.wrap("Here".
                getBytes("UTF-16BE"));
        out.write(buffer);
        out.close();
        in=new FileInputStream("./Test_NIO/src/2.txt")
                .getChannel();
        buffer=ByteBuffer.allocate(1024);
        in.read(buffer);
        buffer.flip();
        System.out.print(buffer.asCharBuffer().toString());
    }
}
  • public static String getProperty(“file.encoding”):获取当前的默认字符集,它会产生代表字符集名称的字符串。
  • public static Charset forName(String charsetName):产生一个Charset对象
  • public final CharBuffer decode(ByteBuffer bb):对ByteBuffer进行解码,返回一个CharBuffer对象
  • public byte[] getBytes(String charsetName)throws UnsupportedEncodingException:改变编码方式,”UTF-16BE”可以把文本写到文件中,读取时,再把它转成CharBuffer,就可以产生所期望的文本。

视图缓冲区

视图缓冲区(view buffer)可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer。

视图缓冲区只是其内容受该字节缓冲区(ByteBuffer)支持的另一种缓冲区。字节缓冲区内容的更改在视图缓冲区中是可见的,反之亦然;这两种缓冲区的位置、限制和标记值都是独立的。
视图缓冲区有以下三大主要优势:

视图缓冲区不是根据字节进行索引,而是根据其特定于类型的值的大小进行索引;
视图缓冲区提供了相对批量 get 和 put 方法,这些方法可在缓冲区和数组或相同类型的其他缓冲区之间传输值的连续序列;
视图缓冲区可能更高效,这是因为,当且仅当其支持的字节缓冲区为直接缓冲区时它才是直接缓冲区。
视图缓冲区的字节顺序固定为创建该视图时其字节缓冲区的字节顺序。

例如:CharBuff、IntBuffer、FloatBuffer等

内存映射文件

前文所提到的FileChannel.map()方法就是为了产生MapByteBuffer对象。
MapByteBuffer,直接字节缓冲区,其内容是文件的内存映射区域。由ByteBuffer继承而来。
直接缓冲区与非直接缓冲区
字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则 Java 虚拟机会尽最大努力直接在此缓冲区上执行本机 I/O 操作。也就是说,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。
直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。

简单的来说映射文件允许我们创建和修改那些因为太大而不能放入内存的文件。我们可以假定整个文件都放在内存中,把它当做一个非常大的数组进行访问。

public class LargeMapFiles {
    static int length=0X8FFFFFF;
    public static void main(String[]args)throws Exception{
        MappedByteBuffer buffer=
                new RandomAccessFile("E:/3.txt","rw")
                .getChannel().map(FileChannel.MapMode.READ_WRITE,
                        0,length);
        for(int i=0;i<length;i++){
            buffer.put((byte)'h');
        }
        for(int i=0;i<3;i++)
            System.out.println((char)buffer.get(i));

    }
}

该实例创建了一个128M大小的txt文件,内容将其充满’h’,并打印了前三个字符。

到此分享完毕。

资料来源 Java之NIO概述
博客作者 Blank_spaces
前往答题
我的笔记