Buffer

缓冲区基础

概念上,缓冲区是包在一个对象内的基本数据元素数组。Buffer类相比一个简单数组的优点是它将关于数据的数据内容和信息包含在一个单一的对象中。Buffer类以及它专有的子类定义了一个用于处理数据缓冲区的API。

属性

所有的缓冲区都具有四个属性来提供关于其所包含的数据元素的信息。它们是:

  • 容量(Capacity)

缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能被改变

  • 上界(Limit)

缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数。

  • 位置(Position)

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

  • 标记(Mark)

一个备忘位置。调用mark( )来设定mark = postion。调用reset( )设定position = mark。标记在设定前是未定义的(undefined)。

这四个属性之间总是遵循以下关系:

0 <= mark <= position <= limit <= capacity

新建Buffer

位置被设为0,而且容量和上界被设为10,刚好经过缓冲区能够容纳的最后一个字节。标记最初未定义。容量是固定的,但另外的三个属性可以在使用缓冲区时改变。

API

package java.nio;
public abstract class Buffer {public final int capacity()public final int position()public final Buffer position (int newPosition)public final int limit()public final Buffer limit (int newLimit)public final Buffer mark()public final Buffer reset()public final Buffer clear()public final Buffer flip()public final Buffer rewind()public final int remaining()public final boolean hasRemaining()public abstract boolean isReadOnly();
}

对于API还要注意的一点是isReadOnly()函数。所有的缓冲区都是可读的,但并非所有都可写。每个具体的缓冲区类都通过执行isReadOnly()来标示其是否允许该缓存区的内容被修改。一些类型的缓冲区类可能未使其数据元素存储在一个数组中。例如MappedByteBuffer的内容可能实际是一个只读文件。您也可以明确地创建一个只读视图缓冲区,来防止对内容的意外修改。对只读的缓冲区的修改尝试将会导致ReadOnlyBufferException抛出。但是我们要提前做好准备。

存取

public abstract class ByteBuffer
extends Buffer implements Comparable
{// This is a partial API listingpublic abstract byte get();public abstract byte get (int index);public abstract ByteBuffer put (byte b);public abstract ByteBuffer put (int index, byte b);
}

Get和put可以是相对的或者是绝对的。相对方法是不带有索引参数的函数。当相对函数被调用时,位置在返回时前进一。如果位置前进过多,相对运算就会抛出异常。对于put(),如果运算会导致位置超出上界,就会抛出BufferOverflowException异常。对于get(),如果位置不小于上界,就会抛出BufferUnderflowException异常。绝对存取不会影响缓冲区的位置属性,但是如果您所提供的索引超出范围(负数或不小于上界),也将抛出IndexOutOfBoundsException异常。

填充

buffer.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'o');

五次调用put()之后的缓冲区:

注意本例中的每个字符都必须被强制转换为byte。我们不能不经强制转换而这样操做:

buffer.put('H');

因为我们存放的是字节而不是字符。记住在java中,字符在内部以Unicode码表示,每个Unicode字符占16位。本章节的例子使用包含ascii字符集数值的字节。通过将char强制转换为byte,我们删除了前八位来建立一个八位字节值。这通常只适合于拉丁字符而不能适合所有可能的Unicode字符。

翻转

我们已经写满了缓冲区,现在我们必须准备将其清空。我们想把这个缓冲区传递给一个通道,以使内容能被全部写出。但如果通道现在在缓冲区上执行get(),那么它将从我们刚刚插入的有用数据之外取出未定义数据。如果我们将位置值重新设为0,通道就会从正确位置开始获取,但是它是怎样知道何时到达我们所插入数据末端的呢?这就是上界属性被引入的目的。上界属性指明了缓冲区有效内容的末端。我们需要将上界属性设置为当前位置,然后将位置重置为0。我们可以人工用下面的代码实现:

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

但这种从填充到释放状态的缓冲区翻转是API设计者预先设计好的,他们为我们提供了一个非常便利的函数:

Buffer.flip();

flip()函数将一个能够继续添加数据元素的填充状态的缓冲区翻转成一个准备读出元素的释放状态。在翻转之后,缓冲区会变成:

rewind()函数与flip()相似,但不影响上界属性。它只是将位置值设回0。您可以使用rewind()后退,重读已经被翻转的缓冲区中的数据。如果将缓冲区翻转两次会怎样呢?它实际上会大小变为0。按照图2.5的相同步骤对缓冲区进行操作;把上界设为位置的值,并把位置设为0。上界和位置都变成0。尝试对缓冲区上位置和上界都为0的get()操作会导致BufferUnderflowException异常。而put()则会导致BufferOverflowException异常。

释放

布尔函数hasRemaining()会在释放缓冲区时告诉您是否已经达到缓冲区的上界

作为选择,remaining()函数将告知您从当前位置到上界还剩余的元素数目

压缩

public abstract ByteBuffer compact( );

compact()函数调用后:

标记

标记,使缓冲区能够记住一个位置并在之后将其返回。缓冲区的标记在mark( )函数被调用之前是未定义的,调用时标记被设为当前位置的值。reset( )函数将位置设为当前的标记值。如果标记值未定义,调用reset( )将导致InvalidMarkException异常。一些缓冲区函数会抛弃已经设定的标记(rewind( ),clear( ),以及flip( )总是抛弃标记)。如果新设定的值比当前的标记小,调用limit( )或position( )带有索引参数的版本会抛弃标记。

buffer.position(2).mark().position(4);

如果这个缓冲区现在被传递给一个通道,两个字节(“ow”)将会被发送,而位置会前进到6。如果我们此时调用reset( ),位置将会被设为标记,如图2-9所示。再次将缓冲区传递给通道将导致四个字节(“llow”)被发送。

比较

有时候比较两个缓冲区所包含的数据是很有必要的。所有的缓冲区都提供了一个常规的equals( )函数用以测试两个缓冲区的是否相等,以及一个compareTo( )函数用以比较缓冲区。

public abstract class ByteBuffer
extends Buffer implements Comparable
{// This is a partial API listingpublic boolean equals (Object ob)public int compareTo (Object ob)
}

两个缓冲区可用下面的代码来测试是否相等:

if (buffer1.equals (buffer2)) {
doSomething();
}

如果每个缓冲区中剩余的内容相同,那么equals( )函数将返回true,否则返回false。因为这个测试是用于严格的相等而且是可换向的。前面的程序清单中的缓冲区名称可以颠倒,并会产生相同的结果。

两个缓冲区被认为相等的充要条件是:

  • 两个对象类型相同。包含不同数据类型的buffer永远不会相等,而且buffer绝不会等于非buffer对象。
  • 两个对象都剩余同样数量的元素。Buffer的容量不需要相同,而且缓冲区中剩余数据的索引也不必相同。但每个缓冲区中剩余元素的数目(从位置到上界)必须相同
  • 在每个缓冲区中应被Get()函数返回的剩余数据元素序列必须一致。

如果不满足以上任意条件,就会返回false。

两个被认为是相等的缓冲区:

两个被认为不相等的缓冲区:

缓冲区也支持用compareTo( )函数以词典顺序进行比较。这一函数在缓冲区参数小于,等于,或者大于引用compareTo( )的对象实例时,分别返回一个负整数,0和正整数。这些就是所有典型的缓冲区所实现的java.lang.Comparable接口语义。这意味着缓冲区数组可以通过调用java.util.Arrays.sort()函数按照它们的内容进行排序。

与equals( )相似,compareTo( )不允许不同对象间进行比较。但compareTo( )更为严格:如果您传递一个类型错误的对象,它会抛出ClassCastException异常,但equals( )只会返回false。

批量移动

缓冲区的涉及目的就是为了能够高效传输数据。一次移动一个数据元素,如例2-1所示的那样并不高效。如您在下面的程序清单中所看到的那样,buffer API提供了向缓冲区内外批量移动数据元素的函数。

public abstract class CharBuffer
extends Buffer implements CharSequence, Comparable
{// This is a partial API listingpublic CharBuffer get (char [] dst)public CharBuffer get (char [] dst, int offset, int length)public final CharBuffer put (char[] src)public CharBuffer put (char [] src, int offset, int length)public CharBuffer put (CharBuffer src)public final CharBuffer put (String src)public CharBuffer put (String src, int start, int end)
}

当您传入一个数组并且没有指定长度,您就相当于要求整个数组被填充。如果缓冲区中的数据不够完全填满数组,您会得到一个异常。这意味着如果您想将一个小型缓冲区传入一个大型数组,您需要明确地指定缓冲区中剩余的数据长度。

记住在调用get( )之前必须查询缓冲区中的元素数量(因为我们需要告知processData( )被放置在bigArray中的字符个数)。调用get( )会向前移动缓冲区的位置属性,所以之后调用remaining( )会返回0。get( )的批量版本返回缓冲区的引用,而不是被传送的数据元素的计数,以减轻级联调用的困难。

创建Buffer

public abstract class CharBuffer
extends Buffer implements CharSequence, Comparable
{// This is a partial API listingpublic static CharBuffer allocate (int capacity)public static CharBuffer wrap (char [] array)public static CharBuffer wrap (char [] array, int offset, int length)public final boolean hasArray()public final char [] array()public final int arrayOffset()
}

新的缓冲区是由分配或包装操作创建的。

分配操作创建一个缓冲区对象并分配一个私有的空间来储存容量大小的数据元素。

包装操作创建一个缓冲区对象但是不分配任何空间来储存数据元素。它使用您所提供的数组作为存储空间来储存缓冲区中的数据元素。

带有offset和length作为参数的wrap()函数版本则会构造一个按照您提供的offset和length参数值初始化位置和上界的缓冲区。这样做:

CharBuffer charbuffer = CharBuffer.wrap (myArray, 12, 42);

创建了一个position值为12,limit值为54,容量为myArray.length的缓冲区。这个函数并不像您可能认为的那样,创建了一个只占用了一个数组子集的缓冲区。这个缓冲区可以存取这个数组的全部范围;offset和length参数只是设置了初始的状态。调用使用上面代码中的方法创建的缓冲区中的clear()函数,然后对其进行填充,直到超过上界值,这将会重写数组中的所有元素。Slice()函数可以提供一个只占用备份数组一部分的缓冲区。

通过allocate()或者wrap()函数创建的缓冲区通常都是间接的。间接的缓冲区使用备份数组,像我们之前讨论的,您可以通过上面列出的API函数获得对这些数组的存取权。Boolean型函数hasArray()告诉您这个缓冲区是否有一个可存取的备份数组。如果这个函数的返回true,array()函数会返回这个缓冲区对象所使用的数组存储空间的引用。

如果hasArray()函数返回false,不要调用array()函数或者arrayOffset()函数。如果您这样做了您会得到一个UnsupportedOperationException异常。如果一个缓冲区是只读的,它的备份数组将会是超出上界的,即使一个数组对象被提供给wrap()函数。调用array()函数或者arrayOffset()会抛出一个ReadOnlyBufferException异常,来阻止您得到存取权来修改只读缓冲区的内容。如果您通过其它的方式获得了对备份数组的存取权限,对这个数组的修改也会直接影响到这个只读缓冲区。

最后一个函数,arrayOffset(),返回缓冲区数据在数组中存储的开始位置的偏移量(从数组头0开始计算)。如果您使用了带有三个参数的版本的wrap()函数来创建一个缓冲区,对于这个缓冲区,arrayOffset()会一直返回0,像我们之前讨论的那样。然而,如果您切分了由一个数组提供存储的缓冲区,得到的缓冲区可能会有一个非0的数组偏移量。这个数组偏移量和缓冲区容量值会告诉您数组中哪些元素是被缓冲区使用的

复制Buffer

如我们刚刚所讨论的那样,可以创建描述从外部存储到数组中的数据元素的缓冲区对象。但是缓冲区不限于管理数组中的外部数据。它们也能管理其他缓冲区中的外部数据。当一个管理其他缓冲器所包含的数据元素的缓冲器被创建时,这个缓冲器被称为视图缓冲器。大多数的视图缓冲器都是ByteBuffer的视图。在继续前往字节缓冲器的细节之前,我们先将注意力放在所有存储器类型的共同视图上。

视图存储器总是通过调用已存在的存储器实例中的函数来创建。使用已存在的存储器实例中的工厂方法意味着视图对象为原始存储器的内部实现细节私有。数据元素可以直接存取,无论它们是存储在数组中还是以一些其他的方式,而不需经过原始缓冲区对象的get()/put() API。如果原始缓冲区是直接缓冲区,该缓冲区的视图会具有同样的效率优势。映像缓冲区也是如此。

public abstract class CharBuffer
extends Buffer implements CharSequence, Comparable
{// This is a partial API listingpublic abstract CharBuffer duplicate();public abstract CharBuffer asReadOnlyBuffer();public abstract CharBuffer slice();
}

duplicate()函数创建了一个与原始缓冲区相似的新缓冲区。两个缓冲区共享数据元素,拥有同样的容量,但每个缓冲区拥有各自的位置,上界和标记属性。对一个缓冲区内的数据元素所做的改变会反映在另外一个缓冲区上。这一副本缓冲区具有与原始缓冲区同样的数据视图。如果原始的缓冲区为只读,或者为直接缓冲区,新的缓冲区将继承这些属性。

duplicate

您可以使用asReadOnlyBuffer()函数来生成一个只读的缓冲区视图。这与duplicate()相同,除了这个新的缓冲区不允许使用put(),并且其isReadOnly()函数将会返回true。对这一只读缓冲区的put()函数的调用尝试会导致抛出ReadOnlyBufferException异常。

分割缓冲区与复制相似,但slice()创建一个从原始缓冲区的当前位置开始的新缓冲区,并且其容量是原始缓冲区的剩余元素数量(limit - position)。这个新缓冲区与原始缓冲区共享一段数据元素子序列。分割出来的缓冲区也会继承只读和直接属性

slice

ByteBuffer

字节顺序

非字节类型的基本类型,除了布尔型3都是由组合在一起的几个字节组成的。这些数据类型及其大小如下:

每个基本数据类型都是以连续字节序列的形式存储在内存中。例如,32位的int值0x037fb4c7(十进制的58,700,999)可能会如图所显示的那样被塞入内存字节中(内存地址从左往右增加)

大端顺序

小端顺序

多字节数值被存储在内存中的方式一般被称为endian-ness(字节顺序)。如果数字数值的最高字节——big end(大端),位于低位地址,那么系统就是大端字节顺序。如果最低字节最先保存在内存中,那么小端字节顺序。

package java.nio;
public final class ByteOrder
{public static final ByteOrder BIG_ENDIANpublic static final ByteOrder LITTLE_ENDIANpublic static ByteOrder nativeOrder()public String toString()
}

直接Buffer

直接ByteBuffer是通过调用具有所需容量的ByteBuffer.allocateDirect()函数产生的,就像我们之前所涉及的allocate()函数一样。注意用一个wrap()函数所创建的被包装的缓冲区总是非直接的。

public abstract class ByteBuffer
extends Buffer implements Comparable
{// This is a partial API listingpublic static ByteBuffer allocate (int capacity)public static ByteBuffer allocateDirect (int capacity)public abstract boolean isDirect();
}

NIO--Buffer相关推荐

  1. java.nio.Buffer flip()方法

    碰到java.nio.Buffer flip()方法,不明白是干什么用的,于是就赶快查看中文API,API上面翻译的是:"反转此缓冲区.首先对当前位置设置限制,然后将该位置设置为零.如果已定 ...

  2. java nio.Buffer的属性变化

    java nio.Buffer的属性变化 认识Buffer Channel 提供从文件.网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer.Buffer,实际上是一个连续数组. 常用的 ...

  3. mappedbytebuffer_Java NIO Buffer【MappedByteBuffer】概述与FileChannel的联系

    " NIO[Non-blocking IO非阻塞式IO],可以让你非阻塞的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情.当数据被写入到缓冲区时,线程可以继续处理它 ...

  4. java.nio.Buffer 中的 flip()方法

    在Java NIO编程中,对缓冲区操作常常需要使用  java.nio.Buffer中的 flip()方法. Buffer 中的 flip() 方法涉及到 Buffer 中的capacity.posi ...

  5. JAVA NIO - Buffer Channel

    2019独角兽企业重金招聘Python工程师标准>>> Buffer和Channel是NIO中的基本对象,凡是涉及到I/O的操作都都会用到Buffer和Channel. Channe ...

  6. 【Java 网络编程】NIO Buffer 简介 ( 概念 | 数据传输 | 标记 | 位置 | 限制 | 容量 | 标记 | 重置 | 清除 | 翻转 | 重绕 | 链式操作 )

    文章目录 I. Buffer 简介 II. Buffer 属性 III. Buffer 数据读写 IV. Buffer 标记 mark() 和重置 reset() V. Buffer 清除 翻转 重绕 ...

  7. 关于java.nio.Buffer的API

    Buffer操作是Java NIO应用开发的基础,以下介绍Buffer操作的相关参数以及操作说明. 浏览全文 转载于:https://www.cnblogs.com/ungshow/archive/2 ...

  8. Java NIO ———— Buffer 缓冲区详解

    引言 缓冲区是一个用于特定基本类型的容器.由java.nio 包定义,所有缓冲区都是 Buffer 抽象类的子类. Java NIO 中的 Buffer ,主要用于与NIO 通道进行交互.数据从通道存 ...

  9. java rewind()_Java NIO Buffer的clear()、reset()、rewind()、flip()方法的区别

    Buffer是一个抽象类,位于java.nio包中,主要用作缓冲区.注意:Buffer是非线程安全类.capacity一旦初始化后就不会改变,其值一直为常量.在使用中我们一般使用Buffer的抽象子类 ...

  10. NIO Buffer

    使用Buffer读写数据一般遵循以下四个步骤: 写入数据到Buffer 调用flip()方法 从Buffer中读取数据 调用clear()方法或者compact()方法 RandomAccessFil ...

最新文章

  1. 团队冲刺(二)个人工作总结9
  2. python类型提示模块包_(任何)python模块的类型提示是什么?
  3. 谷歌开源预训练新范式BiT,准确率提高近25%!网友评价:CV界的BERT
  4. Python的enumerater
  5. 为什么要划分物料组_SAP
  6. 使用用户自定义类型作为map的key
  7. 欧拉函数(Euler_Function)
  8. 【面试必备!Mac版本】一文打通Git教程!史上最硬核解析!——双非上岸阿里巴巴系列
  9. Delphi 与 DirectX 之 DelphiX(80): TDIB.BlendPixel();
  10. 苹果决定不修复 Big Sur 和 Catalina 中的这两个0day
  11. 检查丢失的软件包并安装它们的优雅方法?
  12. 强悍的 vim 实用功能
  13. linux禁用别人ping自己,linux /etc/sysctl.conf 禁止别人ping自己
  14. 一个计算机软件学生的求职简历,计算机学生求职的个人简历模板
  15. MMD原神动画制作(学习教程一)
  16. Excel— 撤销工作表保护密码 的破解并获取原始密码
  17. 手机软件测试sim卡流程,传统SIM卡:该说再见了
  18. FCC算法和数据结构 项目实战:罗马数字转换器
  19. 关于IDEA控制台中文乱码问题的解决
  20. Qt 实现PC端网易云音乐界面

热门文章

  1. GDB调试程序系列 (3)
  2. kinmall分析百度亮剑区块链能否险中求胜?
  3. java 中Lock的使用
  4. iOS程序员也要学点算法吧 简单排序之插入排序
  5. 决定将本博客技术知识从VS.Net转型SuperMap产品动态与开发
  6. 配置sql server 2000以允许远程访问
  7. scala数据结构之Maps和Tuples
  8. 什么是OpenCL?面向FPGA的OpenCL有何优点?
  9. 理解统计信息(3/6):谁创建和管理统计信息?在性能调优中,统计信息的作用。...
  10. RHEL5 RHEL6 差异 1