mac_girl

NIO流的学习以及Buffer的相关操作

NIO的使用

一)、什么叫NIO?

定义:是一套新的Java I/O标准, 在java1.4中被纳入JDK中。

二)、NIO的实现方法

NIO是基于块的, 以块为基本单位处理数据。

标准的I/O是基于流实现的,以字节为单位处理数据。

三)、NIO的特性

1).为所有的原始类型特供Buffer支持

    ByteBuffer

    CharBuffer

    DoubleBuffer

    FloatBuffer

    IntBuffer

    LongBuffer

    ShortBuffer

2).字符集编码解码解决方案,使用java.nio.Charset

  1. .增加通道(Channel)对象,做为新的原始的I/O抽象

4).支持锁和内存映射文件的文件访问接口

5).提供了基于Selector的异步网络I/O

四)、NIO的两个重要组件

Buffer: 缓冲, 是一块连续的内存块,是NIO中读写数据的中转地。

Channel: 通道, 表示缓冲数据的源头或目的地。

Buffer和Channel的关系:

Channel作为数据的源头:从Channel中写数据到Buffer

Channel    --------->     Buffer

Channel作为数据的目的地:从Buffer中写出数据到Channel

     Channel   <---------      Buffer

五)、NIO的Buffer类族和Channel

Buffer: 是一个抽象类,JDK为每一种Java原生类型都创建了一个Buffer.

注: 除了ByteBuffer外,其它每一种Buffer都具有完全一样的操作。

原因:ByteBuffer多用于绝大多数数标准I/O操作的接口。

Channel: 是一个双向通道,既可读也可写。

注:应用程序中不能直接对Channel进行读写操作,在读取Channel时,需要先将数据读入到相对应的Buffer中,然后在Buffer中进行读取。

使用Buffer读取文件:

public class Nio_Buffer_Channel {
    public static void main(String[] args) throws IOException {
        //获取一个输入流对象
        FileInputStream fin = new FileInputStream("d:/a.txt");
        //获取输入流对象的通道,作为数据的源头
        FileChannel fileChannel = fin.getChannel();
        //创建一个Buffer对象
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //从通道中读取数据到Buffer中
        fileChannel.read(buffer);
        //关闭通道
        fileChannel.close();
        buffer.flip();
    }
}

使用Buffer完成文件的复制:

public class Nio_Buffer_Copy {
    public static void main(String[] args) throws IOException {
        //输出流对象
        FileOutputStream fout = new FileOutputStream("d:/c.txt");
        //输入流对象
        FileInputStream fin = new FileInputStream("d:/a.txt");
        //输出流的通道,数据的目的地
        FileChannel writeChannel = fout.getChannel();
        //输入流的通道,数据的源头
        FileChannel readChannel = fin.getChannel();
        //Buffer对象
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while(true){
            buffer.clear();
            //返回读取数据的大小
            int len = readChannel.read(buffer);
            if(len == -1){
                break;
            }
            buffer.flip();
            writeChannel.write(buffer);
        }
    }
}

结果:

0
11
0
11
0

六)、深入学习Buffer

主要属性:

//标志位
private int mark = -1;
//写模式:当前缓冲区的位置,从Position的下一个位置写数据
//读模式:当前缓冲区的读位置,将从此位置后,读取数据
private int position = 0;
//写模式:  缓冲区的实际上限,总是小于等于容量,通常等于容量
//读模式: 代表可读取的总容量,和上次写入的数据量相等
private int limit;
//写模式: 缓冲区的总容量上限
//读模式: 缓冲区的总容量上限
private int capacity;

对Buffer进行claer()和flip()操作时lmint和position的变化

public class Filp_Clear {
    public static void main(String[] args) {
        //创建具有15个字节的Buffer对象
        ByteBuffer buffer = ByteBuffer.allocate(15);
        System.out.println("存入元素后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
        //向Buffer中存入数据
        for(int i = 0 ; i < 10 ; i++){
            buffer.put((byte)i);
        }
        //存入元素后position和limit的变化
        System.out.println("存入元素后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
        buffer.flip();
        //flip后position和limit的变化
        System.out.println("flip后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
        for(int i = 0 ; i < 5 ; i++){
            System.out.print(buffer.get()+" ");
        }
        System.out.println();
        //读取Buffer元素后position和limit的变化
        System.out.println("读取Buffer元素后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
        buffer.rewind();
        System.out.println("rewind==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
        buffer.flip();
        //第二次flip后position和limit的变化
        System.out.println("第二次flip后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
        buffer.clear();
        //clear后position和limit的变化
        System.out.println("clear后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());

    }
}

运行结果:

存入元素后position和limit的变化==>position:0 limit:15 capacity:15
存入元素后position和limit的变化==>position:10 limit:15 capacity:15
flip后position和limit的变化==>position:0 limit:10 capacity:15
0 1 2 3 4 
读取Buffer元素后position和limit的变化==>position:5 limit:10 capacity:15
rewind==>position:0 limit:10 capacity:15
第二次flip后position和limit的变化==>position:0 limit:0 capacity:15
clear后position和limit的变化==>position:0 limit:15 capacity:15

结果分析:

1).当第一次创建Buffer对象时

    position = 0, capacity = limit = Buffer数组容量大小

2).往Buffer添加数据

   position = 数组所占数据的大小,capacity = limit = Buffer数组容量大小

3).buffer.flip()操作

    position = 0, limit = 数组中所占元素的大小,即原position值, capacity =  Buffer数组容量大小

4).buffer.get()获取元素

   position = 获取元素的个数,limit = 数组中所占元素的大小,capacity =  Buffer数组容量大小

5).再次buffer.flip()

    position = 获取元素的个数,limit = position值,  capacity =  Buffer数组容量大小

    注: 当执行flip操作,limit值总是等于上一次的position值

6).buffer.clear

  position = 0, capacity = limit = Buffer数组容量大小

总结:

   i.  put():  position = 数组元素个数 , limit 和 capacity 不变

   ii.  flip(): position = 0,   limit = 原position值,  capacity 不变

   iii. rewind(): position = 0, limit 和 capacity 不变

   iiii.  claer(): 回到初始状态,position = 0, capacity = limit = Buffer数组容量大小。

七)、Buffer的相关操作

1)、Buffer的创建:

1.从堆中分配

//从堆中分配
ByteBuffer buffer = ByteBuffer.allocation(1024);
//ByteBuffer类
public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }
    
//HeapByteBuffer类 extend ByteBuffer 
 HeapByteBuffer(int cap, int lim) {            // package-private
        super(-1, 0, lim, cap, new byte[cap], 0);
        /*
        hb = new byte[cap];
        offset = 0;
        */
    }
//super(-1, 0, lim, cap, new byte[cap], 0) 
ByteBuffer(int mark, int pos, int lim, int cap,   // package-private
                 byte[] hb, int offset)
    {
        super(mark, pos, lim, cap);
        this.hb = hb;
        this.offset = offset;
    }

2.从既有数组中创建

byte array[] = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap(arry);
public static ByteBuffer wrap(byte[] array) {
        return wrap(array, 0, array.length);
    }
public static ByteBuffer wrap(byte[] array,
                                    int offset, int length)
    {
        try {
            return new HeapByteBuffer(array, offset, length);
        } catch (IllegalArgumentException x) {
            throw new IndexOutOfBoundsException();
        }
    }
 HeapByteBuffer(byte[] buf, int off, int len) { // package-private

        super(-1, off, off + len, buf.length, buf, 0);
        /*
        hb = buf;
        offset = 0;
        */
    }

ByteBuffer(int mark, int pos, int lim, int cap,   // package-private
                 byte[] hb, int offset)
    {
        super(mark, pos, lim, cap);
        this.hb = hb;
        this.offset = offset;
    }

2)、重置和清空缓存区

1.rewind(): 将position置为0,并清除标志位(mark)mark = -1

作用:提取Buffer的有效数据。

2.clear(): 将position重置为0,同时,将limit设置为capacity大小,清除标志位 (mark), msrk = -1

 作用:为重写Buffer做准备。

3.flip(): 将limit设置到position所在位置,将position重置为0,并清除标志位mark = -1

  作用:读写转换时使用。

注:这里的重置是指重置Buffer的各标志位,并不是清空Buffer的内容。

3)、读/写缓冲区

//返回当前position的数据,position后移一位
public byte get();
//读取Buffer的数据到dst,并恰当的移动position
public ByteBuffer get(byte[] dst);
//读取给定index上的数据,不改变position的位置
public byte get(int index);
//在当前位置写入给定数据,position后移一位
public ByteBuffer put(byte b);
//将当前数据写入index位置,position位置不变
public ByteBuffer put(int index, byte b);
//将给定的数据src写入到Buffer中,并恰当的移动position
public final ByteBuffer put(byte[] src);

4)、标志缓冲区

标志(mark)缓冲区:类似于书签,可以随时记录当前位置,在任意时刻,可以回到这个位置。

操作mark的方法:

public final Buffer mark()
public final Buffer reset()

1.设置mark为当前position值

buffer.mark();
public final Buffer mark() {
        //将mark值置为当前的position值
        mark = position;
        return this;
    }

2.获取mark值,将当前的position置为mark值

buffer.reset();
public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
    }

测试:

public class MarkTest {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(15);
        for(int i = 0 ; i < 10 ; i++){
            buffer.put(((byte)i));

        }
        //读写转换
        buffer.flip();
        for(int i = 0 ; i < buffer.limit() ; i++){
            System.out.print(buffer.get());
            if(i == 4){
                //mark = 5,当 i = 4 时,get后,position后移的一位,所以mark = 5
                buffer.mark();
                System.out.print("mark = "+buffer.mark());
            }
        }
        System.out.println();
        System.out.println("原position值 = "+buffer.position());
        //重置position为mark值
        buffer.reset();
        System.out.println("reset后position值 = "+buffer.position());
        while(buffer.hasRemaining()){
            System.out.print(buffer.get());
        }
    }
}

结果:

01234mark = java.nio.HeapByteBuffer[pos=5 lim=10 cap=15]56789
原position值 = 10
reset后position值 = 5
56789

5)、复制缓冲区

复制缓冲区:以原缓冲区为基础,生成一个完全一样的新缓冲区。

新缓冲区的特点:

1.新生成的缓冲区和原缓冲区共享相同的内存数据。

2.缓冲区的任意一方的数据改动都是相互可见的。

3.新生成的缓冲区和原缓冲区独立维护各自的position、limit、mark。

作用:增加了程序的灵活性,为多方同时处理数据提供了可能。

生成复制缓冲区的方法:

public ByteBuffer duplicate()

测试:

public class Duplicate_Buffer {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(15);
        for(int i = 0 ; i < 10 ; i++){
            buffer.put((byte)i);
        }
        //复制当前缓冲区
        ByteBuffer copyBuffer = buffer.duplicate();
        System.out.println("复制后的缓冲区");
        System.out.println(buffer);
        System.out.println(copyBuffer);
        //重置copyBuffer
        copyBuffer.flip();
        //重置copyBuffer后缓冲区变化
        System.out.println("重置copyBuffer后的缓冲区");
        System.out.println(buffer);
        System.out.println(copyBuffer);
        //向copyBuffer缓冲区中存入数据
        copyBuffer.put((byte)100);
        //buffer在相同的位置数据也发生了变化
        System.out.println("buffer.get(0) = "+buffer.get(0));
        System.out.println("copyBuffer.get(0) = "+ copyBuffer.get(0));


    }
}

结果:

复制后的缓冲区
java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
重置copyBuffer后的缓冲区
java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
java.nio.HeapByteBuffer[pos=0 lim=10 cap=15]
buffer.get(0) = 100
copyBuffer.get(0) = 100

结论:在对副本copyBuffer进行put操作后,原Buffer相同所在位置的数据也同样发

         生了变化。

6)、缓冲区分片

定义:在现有的缓冲区中,创建新的子缓冲区。

特点:子缓冲区和父缓冲区共享数据。

创建子缓冲区的方法:slice()

ByteBuffer buffer = ByteBuffer.allocate(15);
        for(int i = 0; i < 10; i++){
            buffer.put((byte)i);
        }
        buffer.position(2);
        buffer.limit(6);
        //生成2-6的缓冲区
        ByteBuffer subBuffer = buffer.slice();

public class Slice_Buffer {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(15);
        for(int i = 0; i < 10; i++){
            buffer.put((byte)i);
        }
        buffer.position(2);
        buffer.limit(6);
        //生成2-6的缓冲区
        ByteBuffer subBuffer = buffer.slice();
        System.out.println(subBuffer);
        System.out.println(subBuffer.get(0));
        System.out.println(subBuffer.get(3));

    }
}

结果:

java.nio.HeapByteBuffer[pos=0 lim=4 cap=4]
2
5

子缓冲区发生了变化,父缓冲区也随即发生变化:

public class Slice_Buffer {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(15);
        for(int i = 0; i < 10; i++){
            buffer.put((byte)i);
        }
        buffer.position(2);
        buffer.limit(6);
        //生成2-6的缓冲区
        ByteBuffer subBuffer = buffer.slice();
        System.out.println(subBuffer);
        System.out.println(subBuffer.get(0));
        System.out.println(subBuffer.get(3));
        for(int i = 0; i < subBuffer.capacity(); i++){
            byte bb = subBuffer.get(i);
            bb*= 10;
            subBuffer.put(bb);
        }
        //重置父缓冲区,若不重置,输出20、30、40、50
        buffer.position(0);
        buffer.limit(buffer.capacity());
        while(buffer.hasRemaining()){
            System.out.print(buffer.get()+" ");
        }
        System.out.println();
    }
}

7)、只读缓冲区

特点:只读,只读缓冲区和原始缓冲区共享内存块,原始缓冲区的修改,只读缓冲区也是可见的。

创建只读缓冲区的方法:

asRreadOnlyBuffer()

public class AsReadOnlyBuffer_Test {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(15);
        for(int i = 0; i < 10; i++){
            buffer.put((byte)i);
        }
        //创建只读缓冲区
        ByteBuffer readBuffer = buffer.asReadOnlyBuffer();
        System.out.println(buffer);
        System.out.println(readBuffer);
        readBuffer.flip();
        while(readBuffer.hasRemaining()){
            System.out.print(readBuffer.get()+" ");
        }
        System.out.println();
        //检测对原始缓冲区进行修改,只读缓冲区可见
        buffer.put(2,(byte)20);
        readBuffer.flip();
        while(readBuffer.hasRemaining()){
            System.out.print(readBuffer.get()+" ");
        }
    }
}

结果:

java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
java.nio.HeapByteBufferR[pos=10 lim=15 cap=15]
0 1 2 3 4 5 6 7 8 9 
0 1 20 3 4 5 6 7 8 9 

8)、文件映射到内存

使用文件映射到内存读取文件中的数据使用RandomAccessFile对象读取

RamdomAcessFile: 随机访问存取对象。

特点: 专门处理文件的类 ,支持"随机访问"方式。

随机:指可以跳转到文件的任意位置处读写数据 。

由FileChannel.map()将文件映射到内存:

//将文件的前1024个字节映射到内存
MappedByteBuffer mbb = channel.map(FileChannel.MapMode.Read_WRITE, 0, 1024)
 

public class MappedFile {
    public static void main(String[] args) throws IOException {
       //随机访问文件类
       RandomAccessFile fin = new RandomAccessFile("d:/a.txt","rw");
        FileChannel channel = fin.getChannel();
        MappedByteBuffer buffer = 
       //将文件的前1024个字节映射到内存
       channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
        while(buffer.hasRemaining()){
            System.out.print((char)buffer.get());
        }
    }
}

结果:

hhhhhhhhhhh

结论:使用文件映射内存的方式,将文本文件通过FileChannel映射到内存中,返回一个Buffer对象,从内存中读取文件的内容,通过修改Buffer,将实际数据写到对应的磁盘中。

9)、处理结构化数据

处理结构化数据的方法:

1.散射:将数据读入到一组Buffer中,即将数据读到Buffer[]中。

由scatteringByteBuffer接口提供操作方法:

public long read(Buffer[] dsts) throws IOException;
public long read(Buffer[] dsts, int offset, int length) throws IOException;

注:通过散射读取数据,通道依次填充每个缓冲区。

2.聚集:将数据写入到一组Buffer中,即将数据写入到Buffer[]中。

 由GatheringByteChannel接口提供操作方法:

public long write(Buffer[] src) throws IOException;
public long write(Buffer[] src, int offset, int length) throws IOException;

散射/聚集的使用:

对于一个固定格式的文件的读写,在已知文件具体结构情况下,可以构建若干个符合文件结构的Buffer,Buffer的大小恰好符合各段结构的大小。

Jdk提供了各种通道,使用散射和聚集读写结构化数据:

例:DatagramChannel、 FileChannel、SocketChannel

注:这3个通道都实现了ScatteringByteChannel和GatheringByteChannel.

通过聚集创建文本文件,格式为 书名作者:

public class GatheringBuffer {
    public static void main(String[] args) throws IOException {
        //通过聚集创建文本文件,格式为 书名作者
        ByteBuffer bookBuf = ByteBuffer.wrap("java性能优化技巧".getBytes("Utf-8"));
        ByteBuffer autBuf = ByteBuffer.wrap("葛一鸣".getBytes("utf-8"));
        int booklen = bookBuf.capacity();
        int autlen = autBuf.capacity();
        ByteBuffer buffer[] = new ByteBuffer[]{bookBuf ,autBuf};
        File file = new File("d:/gather.txt");
        //文件不存在时,创建一个新文件
        if(!file.exists()){
            file.createNewFile();
        }
        FileOutputStream fou = new FileOutputStream(file);
        FileChannel channel = fou.getChannel();
        //聚集写文件
        channel.write(buffer);
        channel.close();

    }
}

通过散射读取固定格式的文本文件:

前提:知道文件对应的格式长度,精确的构造Buffer

public class GatheringBuffer {
    public static void main(String[] args) throws IOException {
        //通过聚集创建文本文件,格式为 书名作者
        ByteBuffer bookBuf = ByteBuffer.wrap("java性能优化技巧".getBytes("utf-8"));
        ByteBuffer autBuf = ByteBuffer.wrap("葛一鸣".getBytes("utf-8"));
        int booklen = bookBuf.capacity();
        int autlen = autBuf.capacity();
        ByteBuffer[] buffer = new ByteBuffer[]{bookBuf ,autBuf};
        File file = new File("d:/gather.txt");
        //文件不存在时,创建一个新文件
        if(!file.exists()){
            file.createNewFile();
        }
        FileOutputStream fou = new FileOutputStream(file);
        FileChannel channel = fou.getChannel();
        //聚集写文件
        channel.write(buffer);
        channel.close();
        //通过散射读取文件,格式:书名作者
        //构造精确的Buffer
        ByteBuffer scatter_bookBuffer = ByteBuffer.allocate(booklen);
        ByteBuffer scatter_autBuffer = ByteBuffer.allocate(autlen);
        ByteBuffer[] scatter_Buffer = new ByteBuffer[]{scatter_bookBuffer, scatter_autBuffer};
        FileInputStream fin = new FileInputStream("d:/gather.txt");
        FileChannel fileChannel = fin.getChannel();
        fileChannel.read(scatter_Buffer);
        String bookName = new String(scatter_Buffer[0].array(),"utf-8");
        String authorName = new String(scatter_Buffer[1].array(),"utf-8");
        System.out.println(bookName+authorName);
    }
}

结果:

java性能优化技巧葛一鸣

八)、MappedByteBuffer性能评估

性能比较:

传统的基于流的I/O操作 < NIO的ByteBuffer < NIO的MappedByteBuffer(将文件映射到内存)。

九)、直接内存访问

DirectBuffer: Buffer提供的直接访问系统物理内存的类。

普通的ByteBuffer: 在堆上分配内存,其最大内存,受最大堆限制。

DirectBuffer: 直接分配在物理内存中,并不占用堆空间。

创建DirectBuffer对象:

ByteBuffer.allocateDirect()

DirectBuffer和ByteBuffer的区别:

1.DirectBuffer的内存访问速度比ByteBuffer的快

2.创建和销毁DirectBuffer的花费远比ByteBuffer的高

DirectBuffer的使用:

在需要频繁的创建和销毁Buffer时,不宜使用DirectBuffer, 但如果能将DirectBuffer进行复用,那么,在读写的情况下,可以大幅度的改善系统的性能。

posted on 2019-10-13 12:23  宇宙美少女  阅读(240)  评论(1编辑  收藏  举报

导航