java nio(一)--Buffer基础

来源:互联网 发布:星际淘宝主微盘 编辑:程序博客网 时间:2024/05/18 02:46

一、什么是缓冲区

  • Java的NIO中Buffer至关重要:buffer是读写的中介,主要和NIO的通道交互。数据是通过通道读入缓冲区和从缓冲区写入通道的。

  • 缓冲区buffer的本质就是一块可以读写的内存块(buffer类内部是一个基本数据类型的数组)。这块内存块被包装成NIO的Buffer对象,并提供了一组方法方便读写。

  • 对于每个非布尔原始数据类型都有一个缓冲区类。


二、Buffer的家族


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

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


三、Buffer的属性

3.1、容量capacity

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

3.2、上界limit

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

3.3、位置position

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

3.4、标志mark

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


四、Buffer API


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

//新创建的容量为10的ByteBuffer
ByteBuffer bf = ByteBuffer.allocate(10);


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字符。

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

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

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

4.4、翻转flip()

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

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

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

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

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

翻转后的缓冲区:

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 listingpublic abstract ByteBuffer compact( );}

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


bf.compact();

这里发生了几件事。

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

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

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

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

4.7、标记mark

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

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

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

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


bf.reset()

4.8、比较equlas()和compareTo()

所有的缓冲区都提供了一个常规的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()函数返回的剩余数据元素序列必须一致。

两个相等的缓冲区

两个不相等的缓存区

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

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

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

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

char [] bigArray = new char [1000];// Get count of chars remaining in the bufferint length = buffer.remaining( );// Buffer is known to contain < 1,000 charsbuffer.get (bigArrray, 0, length);

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

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

原创粉丝点击