纯净、安全、绿色的下载网站

首页|软件分类|下载排行|最新软件|IT学院

当前位置:首页IT学院IT技术

Java NIO Java Buffer缓冲区(NIO)

阿昌喜欢吃黄桃   2021-09-12 我要评论
想了解Java Buffer缓冲区(NIO)的相关内容吗阿昌喜欢吃黄桃在本文为您仔细讲解Java NIO的相关知识和一些Code实例欢迎阅读和指正我们先划重点:Java,JavaNIO下面大家一起来学习吧

Java NIO(Buffer)

1.1 Buffer 简介

Java NIO 中的 Buffer 用于和 NIO 通道进行交互数据是从通道读入缓冲区从缓冲区写入到通道中的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4NvO2WG0-1631371913192)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210911220012609.png)]

缓冲区本质上是一块可以写入数据然后可以从中读取数据的内存这块内存被包装成 NIO Buffer 对象并提供了一组方法用来方便的访问该块内存

缓冲区实际上是一个容器对象更直接的说其实就是一个数组在 NIO 库中所有数据都是用缓冲区处理的

在读取数据时它是直接读到缓冲区中的; 在写入数据时它也是写入到缓冲区中的;任何时候访问 NIO 中的数据都是将它放到缓冲区中而在面向流 I/O系统中所有数据都是直接写入或者直接将数据读取到 Stream 对象中

在 NIO 中所有的缓冲区类型都继承于抽象类 Buffer最常用的就是 ByteBuffer对于 Java 中的基本类型基本都有一个具体 Buffer 类型与之相对应它们之间的继承关系如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xmz2qs55-1631371913195)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210911220129447.png)]

1.2 Buffer 的基本用法

1、使用 Buffer 读写数据一般遵循以下四个步骤:

(1)写入数据到 Buffer

(2)调用 flip()方法

(3)从 Buffer 中读取数据

(4)调用 clear()方法或者 compact()方法

当向 buffer 写入数据时buffer 会记录下写了多少数据一旦要读取数据需要通过flip()方法将 Buffer 从写模式切换到读模式

在读模式下可以读取之前写入到 buffer的所有数据一旦读完了所有的数据就需要清空缓冲区让它可以再次被写入有两种方式能清空缓冲区:调用 clear()或 compact()方法

clear()方法会清空整个缓冲区

compact()方法只会清除已经读过的数据

任何未读的数据都被移到缓冲区的起始处新写入的数据将放到缓冲区未读数据的后面

2、使用 ByteBuffer的例子

@Test
public void testConect2() throws IOException {
    RandomAccessFile aFile = new RandomAccessFile("d:\\achang/01.txt","rw");
    FileChannel inChannel = aFile.getChannel();
    //创建buffer并指定大小(字节)
    ByteBuffer buf = ByteBuffer.allocate (1024);
    int bytesRead = inChannel.read(buf); //读取buffer
    while (bytesRead != -1) {
        buf.flip(); //读写转换为读模式
        while(buf.hasRemaining()){
            System.out.print((char) buf.get()); // read 1 byte at a time
        }
        buf.clear(); //清空buffer
        //读操作
        bytesRead = inChannel.read(buf);
    }
    aFile.close();
}

3、使用 IntBuffer 的例子

@Test
public void testConect3() throws IOException {
    // 分配新的 int 缓冲区参数为缓冲区容量
    // 新缓冲区的当前位置将为零其界限(限制位置)将为其容量
    // 它将具有一个底层实现数组其数组偏移量将为零
    IntBuffer buffer = IntBuffer.allocate (8);
    for (int i = 0; i < buffer.capacity(); ++i) {
        int j = 2 * (i + 1);
        // 将给定整数写入此缓冲区的当前位置当前位置递增
        buffer.put(j);
    }
    // 重设此缓冲区将限制设置为当前位置然后将当前位置设置为 0
    buffer.flip();
    // 查看在当前位置和限制位置之间是否有元素
    while (buffer.hasRemaining()) {
        // 读取此缓冲区当前位置的整数然后当前位置递增
        int j = buffer.get();
        System.out.print(j + " ");
    }
}

1.3 Buffer 的 capacity、position 和 limit

为了理解 Buffer 的工作原理需要熟悉它的三个属性:

CapacityPositionlimit

position 和 limit 的含义取决于 Buffer 处在读模式还是写模式不管 Buffer 处在什么模式capacity 的含义总是一样的

这里有一个关于 capacityposition 和 limit 在读写模式中的说明

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A0u57DWf-1631371913198)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210911214306000.png)]

(1)capacity

作为一个内存块Buffer 有一个固定的大小值也叫“capacity”.

你只能往里写capacity 个 byte、longchar 等类型

一旦 Buffer 满了需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据

(2)position

写数据到 Buffer 中时position 表示写入数据的当前位置position 的初始值为0

当一个 byte、long 等数据写到 Buffer 后 position 会向下移动到下一个可插入数据的 Buffer 单元position 最大可为 capacity – 1(因为 position 的初始值为0).

读数据到 Buffer 中时position 表示读入数据的当前位置如 position=2 时表示已开始读入了 3 个 byte或从第 3 个 byte 开始读取

通过 ByteBuffer.flip()切换到读模式时 position 会被重置为 0当 Buffer 从 position 读入数据后position 会下移到下一个可读入的数据 Buffer 单元

(3)limit

写数据时limit 表示可对Buffer 最多写入多少个数据写模式下limit 等于Buffer 的 capacity读数据时limit 表示 Buffer 里有多少可读数据(not null 的数据)因此能读到之前写入的所有数据(limit 被设置成已写数据的数量这个值在写模式下就是position)(剩余未读的数据)

1.4 Buffer 的类型

Java NIO 有以下 Buffer 类型

ByteBufferMappedByteBufferCharBufferDoubleBufferFloatBufferIntBufferLongBufferShortBuffer

这些 Buffer 类型代表了不同的数据类型

换句话说就是可以通过 charshortintlongfloat 或 double 类型来操作缓冲区中的字节

1.5 Buffer 分配和写数据

1、Buffer 分配

要想获得一个 Buffer 对象首先要进行分配 每一个 Buffer 类都有一个 allocate 方法

下面是一个分配 48 字节 capacity 的 ByteBuffer 的例子

ByteBuffer buf = ByteBuffer.allocate(48);

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

CharBuffer buf = CharBuffer.allocate(1024);

2、向 Buffer 中写数据

写数据到 Buffer 有两种方式:

(1)从 Channel 写到 Buffer

(2)通过 Buffer 的 put()方法写到 Buffer 里

从 Channel 写到 Buffer 的例子

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

通过 put 方法写 Buffer 的例子:

buf.put(127);

put 方法有很多版本允许你以不同的方式把数据写入到 Buffer 中例如 写到一个指定的位置或者把一个字节数组写入到 Buffer

3、flip()方法

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

换句话说position 现在用于标记读的位置limit 表示之前写进了多少个 byte、char 等 (现在能读取多少个 byte、char 等)

1.6 从 Buffer 中读取数据

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

(1)从 Buffer 读取数据到 Channel

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

从 Buffer 读取数据到 Channel 的例子:

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

使用 get()方法从 Buffer 中读取数据的例子

byte aByte = buf.get();

get 方法有很多版本允许你以不同的方式从 Buffer 中读取数据例如从指定position 读取或者从 Buffer 中读取数据到字节数组

1.7 Buffer 几个方法

1、rewind()方法

Buffer.rewind()将 position 设回 0所以你可以重读 Buffer 中的所有数据limit 保持不变仍然表示能从 Buffer 中读取多少个元素(byte、char 等)

2、clear()与 compact()方法

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

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

如果 Buffer 中有一些未读的数据调用clear()方法数据将“被遗忘”意味着不再有任何标记会告诉你哪些数据被读过哪些还没有
如果 Buffer 中仍有未读的数据且后续还需要这些数据但是此时想要先先写些数据那么使用 compact()方法

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

3、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.8、缓冲区操作

1、缓冲区分片

在 NIO 中除了可以分配或者包装一个缓冲区对象外还可以根据现有的缓冲区对象来创建一个子缓冲区即在现有缓冲区上切出一片来作为一个新的缓冲区但现有的缓冲区与创建的子缓冲区在底层数组层面上是数据共享的也就是说子缓冲区相当于是现有缓冲区的一个视图窗口调用 slice()方法可以创建一个子缓冲区

@Test
public void testConect3() throws IOException {
    ByteBuffer buffer = ByteBuffer.allocate (10);
    // 缓冲区中的数据 0-9
    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());
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2SY6Ci9G-1631371913206)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210911223844959.png)]

2、只读缓冲区

只读缓冲区非常简单可以读取它们但是不能向它们写入数据

可以通过调用缓冲区的 asReadOnlyBuffer()方法将任何常规缓冲区转 换为只读缓冲区这个方法返回一个与原缓冲区完全相同的缓冲区并与原缓冲区共享数据只不过它是只读的

如果原缓冲区的内容发生了变化只读缓冲区的内容也随之发生变化:

@Test
public void testConect4() throws IOException {
    ByteBuffer buffer = ByteBuffer. allocate (10);
    // 缓冲区中的数据 0-9
    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());
    }
}

如果尝试修改只读缓冲区的内容则会报 ReadOnlyBufferException 异常

只读缓冲区对于保护数据很有用在将缓冲区传递给某个 对象的方法时无法知道这个方法是否会修改缓冲区中的数据创建一个只读的缓冲区可以保证该缓冲区不会被修改

只可以把常规缓冲区转换为只读缓冲区而不能将只读的缓冲区转换为可写的缓冲区

3、直接缓冲区

直接缓冲区是为加快 I/O 速度使用一种特殊方式为其分配内存的缓冲区JDK 文档中的描述为:给定一个直接字节缓冲区Java 虚拟机将尽最大努力直接对它执行本机I/O 操作

也就是说它会在每一次调用底层操作系统的本机 I/O 操作之前(或之后)尝试避免将缓冲区的内容拷贝到一个中间缓冲区中 或者从一个中间缓冲区中拷贝数据要分配直接缓冲区需要调用 allocateDirect()方法而不是 allocate()方法使用方式与普通缓冲区并无区别

拷贝文件示例:

@Test
public void testConect5() throws IOException {
    //读取
    String infile = "d:\\achang\\01.txt";
    FileInputStream fin = new FileInputStream(infile);
    FileChannel fcin = fin.getChannel();
    //输出
    String outfile = "d:\\achang\\02.txt";
    FileOutputStream fout = new FileOutputStream(outfile);
    FileChannel fcout = fout.getChannel();
    // 使用 allocateDirect而不是 allocate
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
    while (true) {
        buffer.clear();
        int r = fcin.read(buffer);
        if (r == -1) {
            break;
        }
        buffer.flip();//转为写模式
        fcout.write(buffer);
    }
}

4、内存映射文件 I/O

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

内存映射文件 I/O 是通过使文件中的数据出现为 内存数组的内容来完成的这其初听起来似乎不过就是将整个文件读到内存中但是事实上并不是这样一般来说只有文件中实际读取或者写入的部分才会映射到内存中

示例代码:MappedByteBuffer

static private final int start = 0;
static private final int limit = 1024;
static public void main(String args[]) throws Exception {
    RandomAccessFile raf = new RandomAccessFile("d:\\achang\\01.txt","rw");
    FileChannel fc = raf.getChannel();
    MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE,start,limit);
    mbb.put(0,(byte) 97);
    mbb.put(1023, (byte) 122);
    raf.close();
}

总结·

本篇文章就到这里了希望能够给你带来帮助也希望您能够多多关注的更多内容!


相关文章

猜您喜欢

  • js输入框组件 JavaScript实现一个输入框组件

    想了解JavaScript实现一个输入框组件的相关内容吗Aaron_1127在本文为您仔细讲解js输入框组件的相关知识和一些Code实例欢迎阅读和指正我们先划重点:js输入框组件,js输入组件,js输入框下面大家一起来学习吧..
  • Java面试题 Java面试题冲刺第二十九天--JVM3

    想了解Java面试题冲刺第二十九天--JVM3的相关内容吗_陈哈哈在本文为您仔细讲解Java面试题的相关知识和一些Code实例欢迎阅读和指正我们先划重点:Java面试题,JavaJVM下面大家一起来学习吧..

网友评论

Copyright 2020 www.sopisoft.net 【绿软下载站】 版权所有 软件发布

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 点此查看联系方式