一、什么是缓冲区


  • Java的NIO中Buffer至关重要:buffer是读写的中介,主要和NIO的通道交互。数据是通过通道读入缓冲区和从缓冲区写入通道的。
  • 缓冲区buffer的本质就是一块可以读写的内存块(buffer类内部是一个基本数据类型的数组)。这块内存块被包装成NIO的Buffer对象,并提供了一组方法方便读写。
  • 对于每个非布尔原始数据类型都有一个缓冲区类。



二、Buffer的家族




buf Java是什么意思 buffer在java中什么意思_数据



Buffer是顶层抽象类,另外还有八个基本数据抽象类。

MappedByteBuffer是ByteBuffer的一个具体实现类。




三、Buffer的属性


buf Java是什么意思 buffer在java中什么意思_buf Java是什么意思_02


3.1、容量capacity


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


3.2、上界limit


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


3.3、位置position


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


3.4、标志mark


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




四、Buffer API


buf Java是什么意思 buffer在java中什么意思_buf Java是什么意思_03




4.1、创建一个容量大小为10的字符缓冲区



//新创建的容量为10的ByteBuffer


ByteBuffer bf = ByteBuffer.allocate(10);


buf Java是什么意思 buffer在java中什么意思_nio_04



4.2、存取put() get()


put()和get()方法并没有在Buffer API中,而是在子类中定义的。这是因为每一个Buffer类所采用的参数类型,以及它们返回的数据类型,对每个子类来说都是唯一的,所以它们不能在顶层Buffer类中被抽象地声明。

看看ByteBuffer类的put() get()方法:

public abstract class ByteBuffer extends Buffer implements Comparable{

// This is a partial API listing 

public 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异常。


4.3、填充


往缓冲区中put()五个字节:


bf.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'0');


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

buf Java是什么意思 buffer在java中什么意思_数据_05

put()的绝对方案可以在不丢失位置的情况下进行一些更改:


bf.put(0,(byte)'M').put((byte)'w');


put(0,(byte)'M')将H换位M,且不改变position位置,依旧是5,put((byte)'w')才增加为6。

buf Java是什么意思 buffer在java中什么意思_数据_06


4.4、翻转flip()


如果把已经写满了缓冲区直接传递给一个通道,然后通道在缓冲区上执行get(),那么它将从刚刚插入的有用数据之外取出未定义数据。这时需要将位置值重新设为0,通道就会从正确位置开始获取。

但是它是怎样知道何时到达我们所插入数据末端的呢?这就是上界属性被引入的目的。

上界属性指明了缓冲区有效内容的末端。将上界属性设置为当前位置,然后将位置重置为0。


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


Buffer.flip()就是这个原理。

翻转后的缓冲区:

buf Java是什么意思 buffer在java中什么意思_nio_07


4.5、释放get()


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

以下是一种将数据元素从翻转后的缓冲区释放到一个数组的方法:

for (int i = 0; bf.hasRemaining( ), i++){

myByteArray [i] = buffer.get( );

}

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

也可以通过下面的循环来释放翻转后的缓冲区:

int count = bf.remaining( );

for (int i = 0; i < count, i++) {

myByteArray [i] = buffer.get( );

}

一旦缓冲区对象完成填充并释放,它就可以被重新使用了。Clear()函数将缓冲区重置为空状态。它并不改变缓冲区中的任何数据元素,而是仅仅将上界设为容量的值,并把位置设回0。

/**
 * 填充和释放缓冲区
 * <p>
 * Created by w1992wishes on 2017/6/13.
 */
public class BufferFillDrain {
    private static int index = 0;
    private static String[] strings =
            {
            "A random string value",
            "The product of an infinite number of monkeys",
            "Hey hey we're the Monkees",
            "Opening act for the Monkees: Jimi Hendrix",
            "'Scuse me while I kiss this fly", // Sorry Jimi ;-)
            "Help Me! Help Me!",
            };

    public static void main(String[] args) {
        CharBuffer buffer = CharBuffer.allocate(100);

        while (fillBuffer(buffer)){
            //缓冲区释放前先翻转
            buffer.flip();
            //翻转后再释放
            drainBuffer(buffer);
            //释放后应重置指针
            buffer.clear();
        }
    }

    //释放
    private static void drainBuffer(CharBuffer buffer) {
        while (buffer.hasRemaining())
            System.out.print(buffer.get());
        System.out.println("");
    }

    //填充
    private static boolean fillBuffer(CharBuffer buffer) {
        if (index >= strings.length)
            return false;

        String str = strings[index++];
        Arrays.asList(str.toCharArray())
                .stream()
                .forEach( c -> buffer.put(c) );
        return true;
    }
}




4.6、压缩compact()


public abstract class ByteBuffer extends Buffer implements Comparable {

// This is a partial API listing

public abstract ByteBuffer compact( );

}

如果只想从缓冲区中释放一部分数据,而不是全部,然后重新填充。为了实现这一点,未读的数据元素需要下移以使第一个元素索引为0。尽管重复这样做会效率低下,但这有时非常必要,而API提供了一个compact()函数。这一缓冲区工具在复制数据时要比使用get()和put()函数高效得多。

buf Java是什么意思 buffer在java中什么意思_nio_08


bf.compact();


buf Java是什么意思 buffer在java中什么意思_nio_09

这里发生了几件事。

数据元素2-5被复制到0-3位置。

位置4和5不受影响,但现在正在或已经超出了当前位置,因此是“死的”。它们可以被之后的put()调用重写。

还要注意的是,位置已经被设为被复制的数据元素的数目。也就是说,缓冲区现在被定位在缓冲区中最后一个“存活”元素后插入数据的位置。

最后,上界属性被设置为容量的值,因此缓冲区可以被再次填满。调用compact()的作用是丢弃已经释放的数据,保留未释放的数据,并使缓冲区对重新填充容量准备就绪。


4.7、标记mark


标记,使缓冲区能够记住一个位置并在之后将其返回。

缓冲区的标记在mark( )函数被调用之前是未定义的,调用时标记被设为当前位置的值。

reset( )函数将位置设为当前的标记值。如果标记值未定义,调用reset( )将导致InvalidMarkException异常。

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

buf Java是什么意思 buffer在java中什么意思_buf Java是什么意思_10


bf.reset()


buf Java是什么意思 buffer在java中什么意思_数组_11


4.8、比较equlas()和compareTo()


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

public abstract class ByteBuffer extends Buffer implements Comparable {

// This is a partial API listing

public boolean equals (Object ob)

public int compareTo (Object ob)

}

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

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

如果每个缓冲区中剩余的内容相同,那么equals( )函数将返回true,否则返回false。

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

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

两个相等的缓冲区

buf Java是什么意思 buffer在java中什么意思_java_12

两个不相等的缓存区

buf Java是什么意思 buffer在java中什么意思_java_13

缓冲区也支持用compareTo( )函数以词典顺序进行比较。这一函数在缓冲区参数小于,等于,或者大于引用compareTo( )的对象实例时,分别返回一个负整数,0和正整数。

这些就是所有典型的缓冲区所实现的java.lang.Comparable接口语义。

这意味着缓冲区数组可以通过调用java.util.Arrays.sort()函数按照它们的内容进行排序。

比较是针对每个缓冲区内剩余数据进行的,与它们在equals( )中的方式相同,直到不相等的元素被发现或者到达缓冲区的上界。如果一个缓冲区在不相等元素发现前已经被耗尽,较短的缓冲区被认为是小于较长的缓冲区。


4.9、批量移动


public abstract class CharBuffer extends Buffer implements CharSequence, Comparable {

// This is a partial API listing

public 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( )可供从缓冲区到数组进行的数据复制使用。

第一种形式只将一个数组作为参数,将一个缓冲区释放到给定的数组。

第二种形式使用offset和length参数来指定目标数组的子区间。

如果要求的数量的数据不能被传送,那么不会有数据被传递,缓冲区的状态保持不变,同时抛出BufferUnderflowException异常。因此当传入一个数组并且没有指定长度,就相当于要求整个数组被填充。如果缓冲区中的数据不够完全填满数组,会得到一个异常。这意味着如果想将一个小型缓冲区传入一个大型数组,需要明确地指定缓冲区中剩余的数据长度。

char [] bigArray = new char [1000];

// Get count of chars remaining in the buffer

int length = buffer.remaining( );

// Buffer is known to contain < 1,000 chars

buffer.get (bigArrray, 0, length);

Put()的批量版本工作方式相似,但以相反的方向移动数据,从数组移动到缓冲区。

如果缓冲区有足够的空间接受数组中的数据(buffer.remaining()>myArray.length),数据将会被复制到从当前位置开始的缓冲区,并且缓冲区位置会被提前所增加数据元素的数量。如果缓冲区中没有足够的空间,那么不会有数据被传递,同时抛出一个BufferOverflowException异常。