NIO缓冲区

缓冲区

定义:缓冲区是包在一个对象内的基本数据元素数组。

1.1属性

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

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

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

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

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

这四个属性之间的关系

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

1.2缓冲区的API

所有的缓冲区类都实现了Buffer接口。所以在Buffer中定义了缓冲区最重要最基本的方法。

缓冲区的实现类为:

我们来重点看看Buffer接口!

public abstract class Buffer {
    public final int capacity( )
    public final int position( )
    public final Buffer position (int newPositio
    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( );
}	

注意看mark(),reset(),clear()等都是返回Buffer,而不是void,这是因为这里用到了级联调用。该方法直接返回调用者本身,所以可以直接这么写。

例如:buffer.mark( );
           buffer.position(5);
           buffer.reset( );

可以直接写成:buffer.mark().position(5).reset( );

保证代码的可读性的情况下再用这种方式。不要乱用!

1.3创建缓冲区

java中常见的缓冲类:MappedByteBuffer,IntBuffer,DoubleBuffer,ShortBuffer,LongBuffer,FloatBuffer,CharBuffer,和 ByteBuffer。

我们以CharBuffer为例。

有两种创建方式:

1:CharBuffer charBuffer = CharBuffer.allocate(100);

第一种方法,最简单,100就是缓冲区的容量Capacity容量,即最多可以存放100个Char类型的数据

2:char [] myArray = new char [100];
    CharBuffer charbuffer = CharBuffer.wrap (myArray);

第二种是创建一个数组,然后调用wrap方法创建对应大小的缓冲区,此时,数组多大,缓冲区就有多大。注意:存在引用传递,即时char数组有值,创建缓冲区后position依然是o,即所有操作都是从数组的下标0开始。

1.4缓冲区的基本操作

缓冲区的基本操作都是围绕缓冲区的四大参数(容量capaticy,上界limit,位置position,标记mark)操作的。其中容量在创建完缓冲区时指定后就不允许修改,所以会变化的是另外三个属性。

代码实例:

CharBuffer charBuffer = CharBuffer.allocate(100);

刚创建缓冲区时,缓冲区的四大参数为:

capacity=100, position=0 ,因为此时没有指定上界和mark,默认limit=capatity,mark=-1;

1.4.1存取

缓冲区是通过get()和put(value)方法进行存值取值的!

put(value):将value存入缓冲区的position位置上,然后position+1

get();读取缓冲区position位置上的数据,然后position+1;该方法多用于循环遍历(下面会提到,即释放)缓冲区的值,所以position+1,如果只是为了访问值,不想修改缓冲区的信息,那么就使用下面的get(int index);方法

get(int index);读取缓冲区指定位置上的数据

1.4.2 填充

put(int index,Object value):将缓冲区指定index位置上的值改成value。

1.4.3 翻转

当我们已经写满缓冲区时,我们想将其中的所有数据传递给其他对象,我们就需要将缓冲区中的数据一个一个读取出来传给别人。

看代码:

CharBuffer charBuffer = CharBuffer.allocate(10);
charBuffer.put('1');
charBuffer.put('2');
charBuffer.put('3');
//此时我们创建了一个容量为10的缓冲区,并往里面放置了三个值,此时limit=capatity=10,position=3,mark=-1
int position = charBuffer.position();
for (int i = 0; i < position ;i++) {
     System.out.println(charBuffer.get(i));
}
输出记过:
此时的位置:3
1
2
3

或许我们会像上面这样去实现循环遍历。此时我们要注意:当我们的缓冲区按上面的代码循环遍历时,当服务器中途挂了,下一次继续请求缓冲区难道还要重头再读一遍吗。此时就需要有一个变量去记录访问的位置,在异常时持久化到本地,下次请求时从本地读取该变量,继续从该变量的位置开始读取。缓冲区中的这个变量就是position。为了让position可以实现循环遍历,缓冲区提供了一个翻转方法flip(),flip()方法中的操作具体步骤如下

1)将上界属性limit设置为当前位置position,
2)将position设置为0,

此时我们只需要循环limit,每次用get()请求获取数据即可。

完整代码:

CharBuffer charBuffer = CharBuffer.allocate(10);
charBuffer.put('1');
charBuffer.put('2');
charBuffer.put('3');
charBuffer.flip();
for (int i = 0; i < charBuffer.limit() ;i++) {
    System.out.println(charBuffer.get());
}

缓冲区还提供了一个rewind()方法,他和flip()方法相似,区别在于rewind()不影响上界属性,他只是将位置设回0

代码:

public void rewindTest(){
        CharBuffer charBuffer = CharBuffer.allocate(10);
        charBuffer.put('1');
        charBuffer.put('2');
        charBuffer.put('3');
        System.out.println("rewind()前-上界:" + charBuffer.limit());
        System.out.println("rewind()前-位置:" + charBuffer.position());
        charBuffer.rewind();
        System.out.println("rewind()后-上界:" + charBuffer.limit());
        System.out.println("rewind()后-位置:" + charBuffer.position());
    }
运行结果:
rewind()前-上界:10
rewind()前-位置:3
rewind()后-上界:10
rewind()后-位置:0

1.4.4 释放

hasRemaining():返回boolean值,即告诉我们当前position是否为上界。

remaining():告知我们从当前position到上界还剩余的元素数目。

clear():将缓冲区充值为空的状态,注意,它并不改变任何元素数据,只是将上界设置为容量的值,并把position设置为0.

注意:缓冲区操作并不是多线程安全的。如果您想以多线程同时存取特定的缓冲区,您需要在存取缓冲区之前进行同步(例如对缓冲区对象进行跟踪)

1.4.5 压缩

compact()函数:这一缓冲区工具在复制数据时要比您使用 get()和 put()函数高效得多。作用是丢弃已经释放的数据,保留未释放的数据,并使缓冲区对重新填充容量准备就绪。

具体描述:缓冲区现在被定位在缓冲区中最后一个“存活”元素后插入数据的位置。最后,上界属性被设置为容量的值,因此缓冲区可以被再次填满。调用 compact()的作用是丢弃已经释放的数据,保留未释放的数据,并使缓冲区对重新填充容量准备就绪。

CharBuffer charBuffer = CharBuffer.allocate(10);
charBuffer.put('1');
charBuffer.put('2');
charBuffer.put('3');
System.out.println("前容量:"+charBuffer.capacity());
System.out.println("前上界:" + charBuffer.limit());
System.out.println("前位置:" + charBuffer.position());
//现在我们有三个数据,position是3,
charBuffer.compact();
//调用了conmpact()方法后,删除掉positino之前的数据,原来的数据数组往前平移position位置,然后新position=limit-原position
System.out.println("容量:"+charBuffer.capacity());
System.out.println("上限:" + charBuffer.limit());
System.out.println("位置:" + charBuffer.position());

compact的原理图

1.4.6 标记

mark():设置标记

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

一些缓冲区函数会抛弃已经设定的标记(rewind( ),clear( ),以及 flip( )总是抛弃标记)

调用limit(int index)或 position(int index )会抛弃标记

CharBuffer charBuffer = CharBuffer.allocate(100);
charBuffer.put('w');
charBuffer.put('z');
charBuffer.mark();//此时mark为2
charBuffer.put('l');
charBuffer.reset();
charBuffer.limit(1);//将limit设置为1,1小于2,所以mark会被清零,恢复未定义的状态
System.out.println();

1.4.7 比较

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

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

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

简单理解为,position元素前的元素在比较中无所谓,只要剩余的元素的数量和值相等即可。

compareTo():这一函数在缓冲区参数小于,等于,或者大于引用 compareTo( )的对象实例时,分别返回一个负整数,0 和正整数。

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

1.4.8 批量移动

那CharBuffer为例

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)
}

填充值:用缓冲区值填充数组

简单理解成用position后的数据去填充数据!!

有两种形式的 get( )可供从缓冲区到数组进行的数据复制使用。

第一种形式:get(char[] dst):只将一个数组作为参数,将一个缓冲区释放到给定的数组。

第二种形式:get(char[] dst,int offset,int length)使用 offset 和 length 参数来指定目标数组的子区间。

这些批量移动的合成效果与前文所讨论的循环是相同的,但是这些方法可能高效得多.

buffer.get(myArray);等价于buffer.get(myArray,0,myArray.length);

如果您所要求的数量的数据不能被传送,那么不会有数据被传递,缓冲区的状态保持不变,同时抛出 BufferUnderflowException 异常。

因此当您传入一个数组并且没有指定长度,您就相当于要求整个数组被填充。

!!!!如果缓冲区中的数据不够完全填满数组,您会得到一个异常。!!!缓冲区可以比数组多,但是不能比数组少

实例:将一个缓冲区释放到一个大数组中

char [] bigArray = new char [1000];
int length = buffer.remaining( );
buffer.get (bigArrray, 0, length);
processData (bigArray, length);

get前必须检查缓冲区剩余的空间够不够数组放。

获取值:将数组中的值存到缓冲流的position后的位置中,

buffer.put(myArray);
等价于:
buffer.put(myArray,0,myArray.length);

!!!数组多,缓冲区少,不够存会报异常。反之也可以!!!

2 复制缓冲流

Duplicate()函数创建了一个与原始缓冲区相似的新缓冲区。两个缓冲区共享数据元素,拥有同样的容量,但每个缓冲区拥有各自的位置,上界和标记属性。对一个缓冲区内的数据元素所做的改变会反映在另外一个缓冲区上。存在引用传递!更改任何一个对象队徽影响其他两个对象的值。

您 可 以 使 用 asReadOnlyBuffer() 函 数 来 生 成 一 个 只 读 的 缓 冲 区 视 图 。所以不能进行put操作,否则会抛出异常!不过可以曲线救国来改值,

例如:

char[] charArr = {'1','2','3',0,0};
        CharBuffer charBuffer1 = CharBuffer.wrap(charArr);
        CharBuffer charBuffer2 = charBuffer1.asReadOnlyBuffer();
        charArr[4]='9';

我们通过引用传递,修改数组,从而自动修改了只能读操作的缓冲流。

 

如果一个只读的缓冲区与一个可写的缓冲区共享数据,或者有包装好的备份数组,那么对这个可写的缓冲区或直接对这个数组的改变将反映在所有关联的缓冲区上,包括只读缓冲区!
        

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

3 字节缓冲区

字节是操作系统及其 I/O 设备使用的基本数据类型。

3.1 字节顺序

多字节数值被存储在内存中的方式一般称为endian-ness(字节顺序)。

如果数字数值的最高字节——big end(大端),位于低位地址,那么系统就是大端字节顺序。如果最低字节最先保存在内存中,那么小端字节顺序。即数字在内存中的怎么放的,是顺序的还是倒序的。例:8的二进制的1000 ,大端就是1000,小端就是0001

字节顺序很少由软件设计者决定;它通常取决于硬件设计。

在 java.nio 中,字节顺序由 ByteOrder 类封装。

ByteOrder 类定义了决定从缓冲区中存储或检索多字节数值时使用哪一字节顺序的常量。

ByteBuffer 类有所不同:默认字节顺序总是 ByteBuffer.BIG_ENDIAN

public final class ByteOrder
{
public static final ByteOrder BIG_ENDIAN
public static final ByteOrder LITTLE_ENDIAN
public static ByteOrder nativeOrder( )
public String toString( )
}

Java 的默认字节顺序是大端字节顺序

之所以区分字节顺序是因为很多不同的系统底层实现的数据存储是不一样的。

3.2 直接缓冲区

定义:直接缓冲区被用于与通道和固有 I/O 例程交互。它们通过使用固有代码来告知操作系统直接释放或填充内存区域,对用于通道直接或原始存取的内存区域中的字节元素的存储尽了最大的努力。

字节缓冲区跟其他缓冲区类型最明显的不同在于,它们可以成为通道所执行的 I/O 的源头和/或目标。通道只接收 ByteBuffer 作为参数。因为只有字节缓冲区有资格参与I/O 操作。

直接字节缓冲区通常是 I/O 操作最好的选择。在设计方面,它们支持 JVM 可用的最高效I/O 机制。
            
 非直接字节缓冲区可以被传递给通道,但是这样可能导致性能损耗。
            
如果您向一个通道中传递一个非直接 ByteBuffer对象用于写入,通道可能会在每次调用中隐含地进行下面的操作:

1.创建一个临时的直接 ByteBuffer 对象。
2.将非直接缓冲区的内容复制到临时缓冲中。
3.使用临时缓冲区执行低层次 I/O 操作。
4.临时缓冲区对象离开作用域,并最终成为被回收的无用数据。

这可能导致缓冲区在每个 I/O 上复制并产生大量对象

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

isDirect()方法返回的布尔值会告诉我们缓冲区是不是直接缓冲区。

byte[] byteArr =new byte[10];
ByteBuffer byteBuffer1 = ByteBuffer.wrap(byteArr);
System.out.println(byteBuffer1.isDirect());

ByteBuffer byteBuffer2 = ByteBuffer.allocate(10);
System.out.println(byteBuffer2.isDirect());

ByteBuffer byteBuffer4 = ByteBuffer.allocateDirect(10);
System.out.println(byteBuffer4.isDirect());

ByteBuffer byteBuffer3 = byteBuffer4.slice();
System.out.println(byteBuffer3.isDirect());

经测试:allccate(),wrap(),都是非直接缓冲区,只有allccateDirect()是直接的。slice方法得出的缓冲区是否为直接缓冲区取决于父缓冲区是否为直接缓冲区。

4 视图缓冲区

定义:视图缓冲区通过已存在的缓冲区对象实例的工厂方法来创建。这种视图对象维护它自己的属性,容量,位置,上界和标记,但是和原来的缓冲区共享数据元素。

 ByteBuffer 类允许创建视图来将 byte 型缓冲区字节数据映射为其它的原始数据类型。例如,asLongBuffer()函数创建一个将八个字节型数据当成一个 long 型数据来存取的视图缓冲区。

public abstract class ByteBuffer
extends Buffer implements Comparable
{
// This is a partial API listing
public abstract CharBuffer asCharBuffer( );
public abstract ShortBuffer asShortBuffer( );
public abstract IntBuffer asIntBuffer( );
public abstract LongBuffer asLongBuffer( );
public abstract FloatBuffer asFloatBuffer( );
public abstract DoubleBuffer asDoubleBuffer( );
}

实例,由字节缓冲区创建字符缓冲区

ByteBuffer byteBuffer = ByteBuffer.allocate(100);
byte[] b ={1,2,3,12,5,4};
byteBuffer.put(b);
ShortBuffer shortBuffer = byteBuffer.asShortBuffer();

因为byte是一个字节,short是两个字节,所以byte的2个数据代表short一个数组。且转化后只会把position后的数据转化给ShortBuffer!

5 数据元素视图

ByteBuffer 类提供了一个不太重要的机制来以多字节数据类型的形式存取 byte 数据组。ByteBuffer 类为每一种原始数据类型提供了存取的和转化的方法

public abstract class ByteBuffer
extends Buffer implements Comparable
{
public abstract char getChar( );
public abstract char getChar (int index);
public abstract short getShort( );
public abstract short getShort (int index);
public abstract int getInt( );
public abstract int getInt (int index);
public abstract long getLong( );
public abstract long getLong (int index);
public abstract float getFloat( );
public abstract float getFloat (int index);
public abstract double getDouble( );
public abstract double getDouble (int index);
public abstract ByteBuffer putChar (char value);
public abstract ByteBuffer putChar (int index, char value);
public abstract ByteBuffer putShort (short value);
public abstract ByteBuffer putShort (int index, short value);
public abstract ByteBuffer putInt (int value);
public abstract ByteBuffer putInt (int index, int value);
public abstract ByteBuffer putLong (long value);
public abstract ByteBuffer putLong (int index, long value);
public abstract ByteBuffer putFloat (float value);
public abstract ByteBuffer putFloat (int index, float value);
public abstract ByteBuffer putDouble (double value);
public abstract ByteBuffer putDouble (int index, double value);
}

例如:如果 getInt()函数被调用,从当前的position位置开始的四个字节会被包装成一个 int 类型的变量然后作为函数的返回值返回。

同理,如果使用putInt(int value)函数,则会把value转换成Buffer,即4字节的的byte,然后存到缓冲区里。

如果您试图获取的原始类型需要比缓冲区中存在的字节数更多的字节,会抛出BufferUnderflowException。

这样方法存取值是低效的,但是它允许对一个字节流中的数据进行随机的放置。

6. 存取无符号数据

Java 编程语言对无符号数值并没有提供直接的支持(除了 char 类型)。ByteBuffer 类的 API 对此并没有提供直接的支持,但是可以使用Unsigned工具类,自己在本地创建这个工具类,我是没搜到nio哪个包有这个类。

public class Unsigned
{
public static short getUnsignedByte (ByteBuffer bb)
{
return ((short)(bb.get( ) & 0xff));
}
public static void putUnsignedByte (ByteBuffer bb, int value)
{
bb.put ((byte)(value & 0xff));
}
public static short getUnsignedByte (ByteBuffer bb, int position)
{
return ((short)(bb.get (position) & (short)0xff));
}
public static void putUnsignedByte (ByteBuffer bb, int position,
int value)
{
bb.put (position, (byte)(value & 0xff));
}
public static int getUnsignedShort (ByteBuffer bb)
{
return (bb.getShort( ) & 0xffff);
}
public static void putUnsignedShort (ByteBuffer bb, int value)
{
bb.putShort ((short)(value & 0xffff));
}
public static int getUnsignedShort (ByteBuffer bb, int position)
{
return (bb.getShort (position) & 0xffff);
}
public static void putUnsignedShort (ByteBuffer bb, int position,
int value)
{
bb.putShort (position, (short)(value & 0xffff));
}
public static long getUnsignedInt (ByteBuffer bb)
{
return ((long)bb.getInt( ) & 0xffffffffL);
}
public static void putUnsignedInt (ByteBuffer bb, long value)
{
bb.putInt ((int)(value & 0xffffffffL));
}
public static long getUnsignedInt (ByteBuffer bb, int position)
{
return ((long)bb.getInt (position) & 0xffffffffL);
}
public static void putUnsignedInt (ByteBuffer bb, int position,
long value)
{
bb.putInt (position, (int)(value & 0xffffffffL));
}
}

向 ByteBuffer 对象中获取和存放无符号值的工具类。这里所有的函数都是静态的,并且带有一个 ByteBuffer 参数。由于 java 不提供无符号原始类型,每个从缓冲区中读出的无符号值被升到比它大的下一个基本数据类型中。因为没有比long更大的基本数据类型,所以没有getUnsignedLong( )方法,

7. 内存映射缓冲区(MappedByteBuffer)

映射缓冲区是带有存储在文件,通过内存映射来存取数据元素的字节缓冲区。映射缓冲区通常是直接存取内存的,只能通过 FileChannel 类创建。映射缓冲区的用法和直接缓冲区类似,但是 MappedByteBuffer 对象可以处理独立于文件存取形式的的许多特定字符。在通道文章里会继续讨论该缓冲区

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值