java NIO之buffer

Posted 爱上口袋的天空

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java NIO之buffer相关的知识,希望对你有一定的参考价值。

1、简介

  • 通道进行交互。数据是从通道读入缓冲区,从缓冲区写入到通道中
  • 在 NIO 库中,所有数据都是用缓冲区处理的

1.1、基本用法

使用 Buffer 读写数据,四个步骤:

  1. 写入数据到 Buffer
  2. 调用 flip()方法
  3. 从 Buffer 中读取数据
  4. 调用 clear()方法或者 compact()方法 

读数据的完整例子

@Test
public void buffer01() throws Exception {
    //FileChannel
    RandomAccessFile aFile =
            new RandomAccessFile("b://1.txt","rw");
    FileChannel channel = aFile.getChannel();

    //创建buffer大小
    ByteBuffer buffer = ByteBuffer.allocate(1024);

    //读
    int bytesRead = channel.read(buffer);

    while(bytesRead != -1) {
        //read模式
        buffer.flip();

        while(buffer.hasRemaining()) {
            System.out.println((char)buffer.get());
        }
        buffer.clear();
        bytesRead = channel.read(buffer);
    }

    aFile.close();
}

写数据的完整例子:

//创建buffer
IntBuffer buffer = IntBuffer.allocate(8);

//buffer放
for (int i = 0; i < buffer.capacity(); i++) {
    int j = 2*(i+1);
    buffer.put(j);
}

//重置缓冲区
buffer.flip();

//获取
while(buffer.hasRemaining()) {
    int value = buffer.get();
    System.out.println(value+" ");
}


1.2、三个重要属性

Buffer还有三个属性:Capacity、Position、limit

  • capacity 内存块固定大小值
    一旦 Buffer 满了,需要将其清空,才能写入 
  • position
    写入数据的时候,初始值为0,慢慢会往下移动,最大值为-1表示满了
    读入数据的时候, position=2 时表示已开始读入了 3 个 byte。ByteBuffer.flip()切换到读模式时 position 会被重置为 0
  • limit
    limit 表示可对 Buffer 最多写入或者读取多少个数据

1.3、方法详解

分配字节数据
要想获得一个 Buffer 对象首先要进行分配。 每一个 Buffer 类都有一个 allocate 方法
比如:

ByteBuffer buf = ByteBuffer.allocate(48);


写数据的两种方式:

(1)从 Channel 写到 Buffer。
(2)通过 Buffer 的 put()方法写到 Buffer 里。

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

buf.put(127);

读写模式转换 flip()

flip 方法将 Buffer 从写模式切换到读模式。调用 flip()方法会将 position 设回 0,并将 limit 设置成之前 position 的值


从 Buffer 中读取数据

有两种方式:
(1)从 Buffer 读取数据到 Channel。
(2)使用 get()方法从 Buffer 中读取数据

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

byte aByte = buf.get();

1.4、其他方法

rewind()将 position 设回 0

clear()与 compact()都是清空数据,但有所区别:

一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用 clear()或 compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面

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

buffer.mark();
//call buffer.get() a couple of times, e.g. during parsing.
buffer.reset(); //set position back to mark

1.5、缓冲区

缓冲区可以分为四种类型
分别为缓冲区分片、只读缓冲区、直接缓冲区、内存映射文件 I/O

缓冲区分片

        根据现有的缓冲区对象来创建一个子缓冲区,即在现有缓冲区上切出一片来作为一个新的缓冲区,但现有的缓冲区与创建的子缓冲区在底层数组层面上是数据共享的,也就是说,子缓冲区相当于是现有缓冲区的一个视图窗口。调用 slice()方法可以创建一个子缓冲区

完整代码:

//缓冲区分片
@Test
public void b01() {
    ByteBuffer buffer = ByteBuffer.allocate(10);

    for (int i = 0; i < buffer.capacity(); i++) {
        buffer.put((byte)i);
    }

    //创建子缓冲区
    buffer.position(3);
    buffer.limit(7);
    ByteBuffer slice = buffer.slice();

    //改变子缓冲区内容
    for (int i = 0; i <slice.capacity() ; i++) {
        byte b = slice.get(i);
        b *=10;
        slice.put(i,b);
    }

    buffer.position(0);
    buffer.limit(buffer.capacity());

    while(buffer.remaining()>0) {
        System.out.println(buffer.get());
    }
}

只读缓冲区

可以读取它们,但是不能向它们写入数据。可以通过调用缓冲区的 asReadOnlyBuffer()方法
与原缓冲区共享数据,只不过它是只读的。如果原缓冲区的内容发生了变化,只读缓冲区的内容也随之发生变化

完整代码

//只读缓冲区
@Test
public void b02() {
    ByteBuffer buffer = ByteBuffer.allocate(10);

    for (int i = 0; i < buffer.capacity(); i++) {
        buffer.put((byte)i);
    }

    //创建只读缓冲区
    ByteBuffer readonly = buffer.asReadOnlyBuffer();

    for (int i = 0; i < buffer.capacity(); i++) {
        byte b = buffer.get(i);
        b *=10;
        buffer.put(i,b);
    }

    readonly.position(0);
    readonly.limit(buffer.capacity());

    while (readonly.remaining()>0) {
        System.out.println(readonly.get());
    }
}

直接缓冲区

加快 I/O 速度
要分配直接缓冲区,需要调用 allocateDirect()方法,而不是 allocate()方法,使用方式与普通缓冲区并无区别

下面展示缓冲区的复制代码
完整代码

//直接缓冲区
@Test
public void b03() throws Exception {
    String infile = "b://1.txt";
    FileInputStream fin = new FileInputStream(infile);
    FileChannel finChannel = fin.getChannel();

    String outfile = "b://2.txt";
    FileOutputStream fout = new FileOutputStream(outfile);
    FileChannel foutChannel = fout.getChannel();

    //创建直接缓冲区
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

    while (true) {
        buffer.clear();
        int r = finChannel.read(buffer);
        if(r == -1) {
            break;
        }
        buffer.flip();
        foutChannel.write(buffer);
    }
}

内存映射文件 I/O

内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快的多

static private final int start = 0;
static private final int size = 1024;

//内存映射文件io
 @Test
 public void b04() throws Exception {
     RandomAccessFile raf = new RandomAccessFile("b://1.txt", "rw");
     FileChannel fc = raf.getChannel();
     
     MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, start, size);

     mbb.put(0, (byte) 97);
     mbb.put(1023, (byte) 122);
     raf.close();
 }

以上是关于java NIO之buffer的主要内容,如果未能解决你的问题,请参考以下文章

《Java源码分析》:Java NIO 之 Buffer

java NIO之buffer

Java IO之NIO原理解析以及代码解析

JAVA基础知识之NIO——Buffer

Java NIO之Buffer

Java NIO 之 Buffer(缓冲区)