前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >NIO之Buffer缓冲区

NIO之Buffer缓冲区

作者头像
云飞扬
发布2022-04-25 14:19:26
2990
发布2022-04-25 14:19:26
举报
文章被收录于专栏:星汉技术

NIO之Buffer缓冲区

Buffer缓冲区,所谓的缓冲区其实就是在内存中开辟的一段连续空间,用来临时存放数据。

1、标志位

在缓冲区中存在三个基础的游标:capacity(容量)、limit(限制位)、position(当前位)。

当缓冲区刚创建出来时,capacity指向缓冲区的容量即缓冲区的末尾位置,limit等于capacity,position等于0指向最开始的位置。

当向缓冲区写入数据时,会向position指定位置写入数据,并将position+1指向下一个写入位置,为后续接着写入做好准备。而position无论合适都不能大于limit,如果任何写入操作将会导致position大于limit则写入失败抛出异常。

在读取数据时,会将position指向位置中的数据返回,并将position+1指向下一个读取位置,如果任何读取操作造成position大于limit则读取失败,抛出异常。

通常在写完数据要开始读取数据之前要将limit设置为和position相同,指定好边界,再将position设置为0,从头开始读取数据。可以通过flip方法便捷的去实现这个操作。

Mark(标记):下一个要被读或写的元素的索引。位置会自动由相应的get()和put()函数更新。

例如:

代码语言:javascript
复制
初始化缓冲区:
capacity: 10, position: 0, limit: 10
mark: java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]

写入缓冲区9个byte:
capacity: 10, position: 9, limit: 10
mark: java.nio.HeapByteBuffer[pos=9 lim=10 cap=10]

使用flip重置元素位置:
capacity: 10, position: 0, limit: 9
mark: java.nio.HeapByteBuffer[pos=0 lim=9 cap=10]

读取元素:1|读取元素:16|读取元素:12|读取元素:0|读取元素:17|读取元素:5|读取元素:4|读取元素:13|读取元素:18|
使用get读取元素后:
capacity: 10, position: 9, limit: 9
mark: java.nio.HeapByteBuffer[pos=9 lim=9 cap=10]

恢复初始化态clear:
capacity: 10, position: 0, limit: 10
mark: java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]

2、读取数据

利用Buffer读写数据,通常遵循四个步骤:

  • 1、把数据写入buffer;
  • 2、调用flip;
  • 3、从Buffer中读取数据;
  • 4、调用buffer.clear()或者buffer.compact()

从Buffer读数据有两种方式:

  • 1、从buffer读数据到channel。
  • 2、从buffer直接读取数据,调用get方法。

3、Buffer

java.nio.Buffer

Buffer是一个抽象类,不能直接使用,其子类如下:

ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer。

3.1重要方法

此抽象类有以下这些重要的方法。

3.1.1capacity()

返回缓冲区的容量大小。

3.1.2position()

返回缓冲区的当前位置。

3.1.3limit()

返回缓冲区的限制位。

3.1.4mark()

当前position设置标记。

3.1.5flip()

反转缓冲区。将position的值赋给limit,然后position置零。一般用于重新读取数据之前。此方法可以把Buffer从写模式切换到读模式,使用此方法后,position代表的是读取位置,limit标示的是已写入的数据位置。

3.1.6remaining()

判断边界,返回当前位置与限制之间的元素数。

3.1.7hasRemaining()

告知在当前位置和限制之间是否有元素。

3.1.8hasArray()
3.1.9rewind()

重绕缓冲区。只是将position的数值置零。这样可以重复读取buffer中的数据。limit保持不变。

3.1.10reset()

将此缓冲区的位置重置为mark方法标记的位置。

3.1.11clear()

清空缓冲区。这个方法不是真的清空,而是将三个标志位重置到最初的位置。再写入数据,将原有数据覆盖,是逻辑上的清空,不是物理清空。

3.1.12isDirect()

判断当前缓冲区是否是直接缓冲区,如果是直接缓冲区,则返回true,否则返回false。

4、ByteBuffer

以ByteBuffer进行介绍,其他类型的对应类,都具有相同的功能。

4.1重要方法

除了继承父类的重要方法之外,ByteBuffer还具有以下重要的方法。

4.1.1allocate(int capacity)

此方法为一个静态方法,创建一个新的指定空间的间接缓冲区。

4.1.2allocateDirect(int capacity)

此方法为一个静态方法,创建一个新的指定容量的直接缓冲区。

4.1.3wrap(byte[] array)

此方法为一个静态方法,将byte数组包装到间接缓冲区中。

内部调用的是wrap(byte[] array, int offset, int length)方法。

4.1.4wrap(byte[] array, int offset, int length)

此方法为一个静态方法,将byte数组包装到间接缓冲区中。

4.1.5put(byte b)

向缓冲区当前position位置写入数据b。

4.1.6get()

获取当前position位置的数据。

4.1.7equals()

判断两个buffer相对,需满足:

  • 1、类型相同。
  • 2、buffer中剩余字节数相同。
  • 3、所有剩余字节相等

从上面的三个条件可以看出,equals只比较buffer中的部分内容,并不会去比较每一个元素。

4.2方法使用演示

代码语言:javascript
复制
	public static void main(String[] args) {
		//创建指定大小缓冲区
		ByteBuffer buf = ByteBuffer.allocate(5);
		//写入数据
		buf.put("a".getBytes());
		buf.put("b".getBytes());
		buf.put("c".getBytes());
		buf.put("d".getBytes());
		buf.put("e".getBytes());
		//读取数据
		//反转缓冲区 等价于 buf.limit(buf.position()); buf.position(0);
		buf.flip();
		//判断缓冲区是否到了结尾 buf.remaining() > 0
		//--也可以使用  buf.hasRemaining()
		while(buf.hasRemaining()){
			byte[] data = new byte[1];
			buf.get(data);
			String str = new String(data);
			System.out.println(str);
		}
		//重绕缓冲区
		buf.rewind();
		while(buf.hasRemaining()){
			byte[] data = new byte[1];
			buf.get(data);
			String str = new String(data);
			System.out.println(str);
		}
		//mark reset
		buf.rewind();
		byte[] data = new byte[1];
		buf.get(data);
		String str = new String(data);
		System.out.println(str);
		buf.mark();//标记位
		buf.get(data);
		str=new String(data);
		System.out.println(str);
		buf.get(data);
		str=new String(data);
		System.out.println(str);
		buf.reset();//返回标记位
		buf.get(data);
		str=new String(data);
		System.out.println(str);
		//清除数据
		buf.clear();
}

5、MappedByteBuffer

此类继承了ByteBuffer类。

内存映射文件I/O是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的I/O快的多。内存映射文件I/O是通过使文件中的数据出现为内存数组的内容来完成的,不是将整个文件读到内存中,而是只有文件中实际读取或者写入的部分才会映射到内存中。

FileChannel提供了map方法来把文件影射为内存映像文件:

MappedByteBuffer map(int mode,long position,long size);

可以把文件的从position开始的size大小的区域映射为内存映像文件,映射内存缓冲区是个直接缓冲区,继承自ByteBuffer,但相对于ByteBuffer,它有更多的优点:读取快、写入快、随时随地写入。

mode指出了可访问该内存映像文件的方式:

  • 1、READ_ONLY(只读):试图修改得到的缓冲区将导致抛出 ReadOnlyBufferException.(MapMode.READ_ONLY)
  • 2、READ_WRITE(读/写):对得到的缓冲区的更改最终将传播到文件;该更改对映射到同一文件的其他程序不一定是可见的。(MapMode.READ_WRITE)
  • 3、PRIVATE(专用):对得到的缓冲区的更改不会传播到文件,并且该更改对映射到同一文件的其他程序也不是可见的;相反,会创建缓冲区已修改部分的专用副本。(MapMode.PRIVATE)

5.1重要方法

此类出了继承父类的方法之外,还提供了以下这些方法可以使用。

5.1.1fore()

缓冲区是READ_WRITE模式下,此方法对缓冲区内容的修改强行写入文件。

5.1.2load()

将缓冲区的内容载入内存,并返回该缓冲区的引用。

5.1.3isLoaded()

如果缓冲区的内容在物理内存中,则返回真,否则返回假。

5.2案例

代码语言:javascript
复制
	/**
	 * 通过MappedByteBuffer将数据输出到文件中
	 * 
	 * @param context  文件内容
	 * @param filePath 文件名称
	 * @throws IOException
	 */
	public static void outputByMap(String context, String filePath) throws IOException {
		RandomAccessFile raf = new RandomAccessFile(filePath, "rw");
		FileChannel fc = raf.getChannel();
		byte[] msg = context.getBytes();
		MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, msg.length);
		mbb.put(msg);
		raf.close();
	}

6、DirectByteBuffer

直接缓冲区DirectByteBuffer:直接在堆外分配一个内存(即,native memory)来存储数据,程序通过JNI直接将数据读/写到堆外内存中。

因为数据直接写入到了堆外内存中,所以这种方式就不会再在JVM管控的堆内再分配内存来存储数据了,也就不存在堆内内存和堆外内存数据拷贝的操作了。这样在进行I/O操作时,只需要将这个堆外内存地址传给JNI的I/O的函数就好了。

底层的数据其实是维护在操作系统的内存中,而不是JVM里,DirectByteBuffer里维护了一个引用address指向了数据,从而操作数据。实现zero copy(零拷贝)。

DirectByteBuffer此类为一个受保护的类,继承了MappedByteBuffer类,不能从外部直接调用,需要通过ByteBuffer的各种子类调用allocateDirect()方法创建对象。

案例:

代码语言:javascript
复制
	public static void main(String[] args) throws Exception {

		String inFile = "E:\\1.mp4";
		String outFile = "D:\\test\\1.mp4";
		outputByDirectBuffer(inFile, outFile);
	}
	/**
	 * 通过直接缓冲区输出内容到文件。
	 * 
	 * @param inFile
	 * @param outFile
	 * @throws Exception
	 */
	public static void outputByDirectBuffer(String inFile, String outFile) throws Exception {
		long start = System.currentTimeMillis();
		FileInputStream is = new FileInputStream(inFile);
		FileOutputStream fos = new FileOutputStream(outFile);

		FileChannel fcIs, fcOut;
		fcIs = is.getChannel();
		fcOut = fos.getChannel();
		// 创建直接内存的缓冲区
		ByteBuffer directByteBuffer = ByteBuffer.allocateDirect(2048);
		while (fcIs.read(directByteBuffer) != -1) {
			directByteBuffer.flip();
			fcOut.write(directByteBuffer);
			directByteBuffer.clear();
		}
		is.close();
		fos.close();
		long end = System.currentTimeMillis();
		System.out.println("DirectByteBuffer需要时间:" + (end - start));
	}

7、HeapByteBuffer

间接缓冲区HeapByteBuffer:数据的分配存储都在JVM堆上,当需要和IO设备打交道的时候,会将JVM堆上所维护的byte[]拷贝至堆外内存,然后堆外内存直接和IO设备交互。

外设之所以要把JVM堆里的数据copy出来再操作,不是因为操作系统不能直接操作JVM内存,而是因为JVM在进行GC(垃圾回收)时,会对数据进行移动,一旦出现这种问题,外设就会出现数据错乱的情况。

HeapByteBuffer此类为一个受保护的类,继承了MappedByteBuffer类,不能从外部直接调用,需要通过ByteBuffer的各种子类调用allocate()方法创建对象。

案例:

代码语言:javascript
复制
	public static void main(String[] args) throws Exception {

		String inFile = "E:\\1.mp4";
		String outFile = "D:\\test\\1.mp4";
		outputByHeapBuffer(inFile, outFile);

	}

	/**
	 * 通过间接缓冲区输出内容到文件。
	 * 
	 * @param inFile
	 * @param outFile
	 * @throws Exception
	 */
	public static void outputByHeapBuffer(String inFile, String outFile) throws Exception {
		long start = System.currentTimeMillis();

		FileInputStream is = new FileInputStream(inFile);
		FileOutputStream fos = new FileOutputStream(outFile);

		FileChannel fcIs, fcOut;
		fcIs = is.getChannel();
		fcOut = fos.getChannel();
		// 创建间接缓冲区
		ByteBuffer directByteBuffer = ByteBuffer.allocate(2048);
		while (fcIs.read(directByteBuffer) != -1) {
			directByteBuffer.flip();
			fcOut.write(directByteBuffer);
			directByteBuffer.clear();
		}
		is.close();
		fos.close();
		long end = System.currentTimeMillis();
		System.out.println("HeapByteBuffer需要时间:" + (end - start));
	}

以上是关于NIO的Buffer的介绍。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022/04/02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • NIO之Buffer缓冲区
    • 1、标志位
      • 2、读取数据
        • 3、Buffer
          • 3.1重要方法
        • 4、ByteBuffer
          • 4.1重要方法
          • 4.2方法使用演示
        • 5、MappedByteBuffer
          • 5.1重要方法
          • 5.2案例
        • 6、DirectByteBuffer
          • 7、HeapByteBuffer
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档