zoukankan      html  css  js  c++  java
  • Java学习笔记--NIO

    参考资料:http://ifeve.com/buffers/

    BIO/NIO/AIO的区别联系

    http://stevex.blog.51cto.com/4300375/1284437
    http://www.cnblogs.com/alipayhutu/archive/2012/05/09/2492037.html

    1. NIO代码示例

    public static void main(String[] args) throws IOException {
        RandomAccessFile aFile = new RandomAccessFile("f:\node.txt","rw");
        FileChannel inChannel = aFile.getChannel();
        ByteBuffer buf = ByteBuffer.allocate(48);
        
        int bytesRead = inChannel.read(buf);
        while (bytesRead != -1) {
            System.out.println("Read,字符数量是:" + bytesRead);
            buf.flip();
            System.out.println("输出:");
            while (buf.hasRemaining()) {
                System.out.print((char) buf.get());
            }
            System.out.println();
            buf.clear();
            bytesRead = inChannel.read(buf);
        }
        aFile.close();
    }

    2. 输出结果:

    Read,字符数量是:48
    输出:
    <node id='-665' lat='38.92025517116' lon='121.58
    Read,字符数量是:48
    输出:
    810979934' />
    <node id='-8' lat='38.90686208459
    //省略

    3. 解释:

    3.1 Buffer的分配
    要想获得一个Buffer对象首先要进行分配。 每一个Buffer类都有一个allocate方法。下面是一个分配48字节capacity的ByteBuffer的例子。

    ByteBuffer buf = ByteBuffer.allocate(48);

    这是分配一个可存储1024个字符的CharBuffer:

    CharBuffer buf = CharBuffer.allocate(1024);

    3.2 向Buffer中写数据

    写数据到Buffer有两种方式:

    1. 从Channel写到Buffer。例:

    buf.put(127);

    2. 通过Buffer的put()方法写到Buffer里。例:

    int bytesRead = inChannel.read(buf); //read into buffer.

    返回值为-1证明读到末尾。put方法有很多版本,允许你以不同的方式把数据写入到Buffer中。例如, 写到一个指定的位置,或者把一个字节数组写入到Buffer。 更多Buffer实现的细节参考JavaDoc。

    3.3 flip()方法

    flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。
    换句话说,position现在用于标记读的位置,limit表示之前写进了多少个byte、char等 —— 现在能读取多少个byte、char等。

    从Buffer中读取数据
    从Buffer中读取数据有两种方式:

    1. 从Buffer读取数据到Channel。例

    //read from buffer into channel.
    int bytesWritten = inChannel.write(buf);

    2. 使用get()方法从Buffer中读取数据。

    byte aByte = buf.get();

    3.4 clear()与compact()方法

    一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成。

    1. clear()
    如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。

    如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。

    2. compact
    如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,那么使用compact()方法。

    compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。

    3.5 mark()与reset()方法
    通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position

    4. Scatter/Gather

    4.1 Scattering Reads
    Scattering Reads是指数据从一个channel读取到多个buffer中。

    代码示例如下:

    ByteBuffer header = ByteBuffer.allocate(128);
    ByteBuffer body = ByteBuffer.allocate(1024);
    ByteBuffer[] bufferArray = { header, body };
    channel.read(bufferArray);

      注意buffer首先被插入到数组,然后再将数组作为channel.read() 的输入参数。read()方法按照buffer在数组中的顺序将从channel中读取的数据写入到buffer,当一个buffer被写满后,channel紧接着向另一个buffer中写。

      Scattering Reads在移动下一个buffer前,必须填满当前的buffer,这也意味着它不适用于动态消息(译者注:消息大小不固定)。换句话说,如果存在消息头和消息体,消息头必须完成填充(例如 128byte),Scattering Reads才能正常工作。

    4.2 Gathering Writes

    Gathering Writes是指数据从多个buffer写入到同一个channel。

    代码示例如下:

    ByteBuffer header = ByteBuffer.allocate(128);
    ByteBuffer body = ByteBuffer.allocate(1024);
    //write data into buffers
    ByteBuffer[] bufferArray = { header, body };
    channel.write(bufferArray);

      buffers数组是write()方法的入参,write()方法会按照buffer在数组中的顺序,将数据写入到channel,注意只有position和limit之间的数据才会被写入。因此,如果一个buffer的容量为128byte,但是仅仅包含58byte的数据,那么这58byte的数据将被写入到channel中。因此与Scattering Reads相反,Gathering Writes能较好的处理动态消息。

    5. 内存映射 MappedByteBuffer 

    FileChannel提供了map方法来把文件影射为内存映像文件: MappedByteBuffer map(int mode,long position,long size); 可以把文件的从position开始的size大小的区域映射为内存映像文件,mode指出了 可访问该内存映像文件的方式:READ_ONLY,READ_WRITE,PRIVATE.
    a. READ_ONLY,(只读): 试图修改得到的缓冲区将导致抛出 ReadOnlyBufferException.(MapMode.READ_ONLY)
    b. READ_WRITE(读/写): 对得到的缓冲区的更改最终将传播到文件;该更改对映射到同一文件的其他程序不一定是可见的。 (MapMode.READ_WRITE)
    c. PRIVATE(专用): 对得到的缓冲区的更改不会传播到文件,并且该更改对映射到同一文件的其他程序也不是可见的;相反,会创建缓冲区已修改部分的专用副本。 (MapMode.PRIVATE) 

    三个方法: 

    a. fore();缓冲区是READ_WRITE模式下,此方法对缓冲区内容的修改强行写入文件
    b. load()将缓冲区的内容载入内存,并返回该缓冲区的引用
    c. isLoaded()如果缓冲区的内容在物理内存中,则返回真,否则返回假 

    三个特性: 

    调用信道的map()方法后,即可将文件的某一部分或全部映射到内存中,映射内存缓冲区是个直接缓冲区,继承自ByteBuffer,但相对于ByteBuffer,它有更多的优点: 

    a. 读取快
    b. 写入快
    c. 随时随地写入

    例: 

    import java.io.RandomAccessFile;
    import java.nio.MappedByteBuffer;
    import java.nio.channels.FileChannel;
    
    public class MemoryMappedFileInJava {
        private static int count = 10485760; // 10 MB
        public static void main(String[] args) throws Exception {
            RandomAccessFile memoryMappedFile = new RandomAccessFile("f:\largeFile.txt", "rw");
            // Mapping a file into memory
            MappedByteBuffer out = memoryMappedFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, count);
            // Writing into Memory Mapped File
            for (int i = 0; i < count; i++) {
                out.put((byte) 'A');
            }
            System.out.println("Writing to Memory Mapped File is completed");
            // reading from memory file in Java
            for (int i = 0; i < 10; i++) {
                System.out.print((char) out.get(i));
            }
            System.out.println("Reading from Memory Mapped File is completed");
            memoryMappedFile.close();
        }
    }

    下面快速总结一下Java内存映射文件和IO

    1). Java语言通过java.nio包支持内存映射文件和IO。
    2). 内存映射文件用于对性能要求高的系统中,如繁忙的电子交易系统
    3). 使用内存映射IO你可以将文件的一部分加载到内存中
    4). 如果被请求的页面不在内存中,内存映射文件会导致页面错误
    5). 将一个文件区间映射到内存中的能力取决于内存的可寻址范围。在32位机器中,不能超过4GB,即2^32比特。
    6). Java中的内存映射文件比流IO要快(译注:对于大文件而言是对的,小文件则未必)
    7). 用于加载文件的内存在Java的堆内存之外,存在于共享内存中,允许两个不同进程访问文件。顺便说一下,这依赖于你用的是direct还是non-direct字节缓存。
    8). 读写内存映射文件是操作系统来负责的,因此,即使你的Java程序在写入内存后就挂掉了,只要操作系统工作正常,数据就会写入磁盘。
    9). Direct字节缓存比non-direct字节缓存性能要好
    10). 不要经常调用MappedByteBuffer.force()方法,这个方法强制操作系统将内存中的内容写入硬盘,所以如果你在每次写内存映射文件后都调用force()方法,你就不能真正从内存映射文件中获益,而是跟disk IO差不多。
    11). 如果电源故障或者主机瘫痪,有可能内存映射文件还没有写入磁盘,意味着可能会丢失一些关键数据。
    12). MappedByteBuffer和文件映射在缓存被GC之前都是有效的。sun.misc.Cleaner可能是清除内存映射文件的唯一选择。

  • 相关阅读:
    Git基本操作二
    Git基本操作一
    Mysql查询一
    接口的token验证
    Laravel模型的一些小技巧
    AOP编程思想实现全局异常处理
    5.4 RegExp类型
    5.4.1 RegExp实例属性
    5.4.2 RegExp实例方法
    5.4.3 RegExp构造函数属性
  • 原文地址:https://www.cnblogs.com/gnivor/p/4579879.html
Copyright © 2011-2022 走看看