首页 > 编程知识 正文

elutionbuffer是什么,bytebuffer写文件

时间:2023-05-03 12:01:02 阅读:134946 作者:1791

初始化文章目录将数据写入ByteBuffer手动写入数据从SocketChannel读取数据从ByteBuffer读取复位从position读取数据确保数据长度字节序处理继续写入

个人资料

在Java套接字编程中,使用块(BIO )时,通过ServerSocket的accept )方法获取客户端套接字,然后使用客户端套接字的输入流和输出流socket.get inputstream.read [ byte ] b ]和socket.get output stream.write [ byte ] b ]方法的所有参数都是字节数组。 显然,这种块式的套接字编程远远不能满足当前并发访问的需要。

因此,我最近学会了在项目中使用Java本机NIO。 在这种情况下,必须通过ServerSocketChannel的accept )方法获取客户端的SocketChannel,并使用客户端的SocketChannel直接读写。 但是,socket channel.read (bytebufferdst )和socket channel.write (bytebuffersrc )方法的参数都是java.nio.ByteBuffer

ByteBuffer有几个基本属性。

position :当前下标位置。 表示进行下一次读写操作时开始位置; limit )结束标记下标表示进行下一个读写操作时的(最大)结束位置; capacity :字节缓冲器的容量; mark:自定义标记位置; 在任何情况下,这四个属性总是满足mark=position=limit=capacity的关系。 目前对mark属性所知不多,在此不再讨论。 剩下的三个属性分别可以在ByteBuffer.position (、ByteBuffer.limit )、ByteBuffer.capacity )中获取。 此处,也可以分别在ByteBuffer.position(intnewpos )、byte buffer.limit (byte buffer.limit ) intnewlim )中设置位置和限制属性

首先,无论读写如何,都必须初始化ByteBuffer容器。 如上所述,ByteBuffer实际上是封装byte数组,因此可以使用静态方法wrap[]data手动封装数组,也可以使用其他静态allocate(int size方法指定长度的byte 初始化后,ByteBuffer的position为0。此处的数据是初始化为0的字节数组。limit=capacity=字节数组的长度。由于用户尚未自定义标记位置,因此标记=-1,即unun 下图显示已初始化16字节的ByteBuffer。 每个字节用两位十六进制数表示。

将数据写入ByteBuffer要手动写入数据,请使用put(byteb )或put(byteb ) ) b )方法手动将字节或字节数组添加到ByteBuffer中。 在ByteBuffer中,putchar(charval )、put short (short val )、putint (intval )、put float (floatval )、putlong )、putflong 以当前位置为起始位置,写入对应长度的数据,写入完成后,将位置向后移动对应长度。 下图显示了将1字节的byte数据和4字节的Integer数据分别写入ByteBuffer的结果。

然而,如果要写入的数据的长度大于ByteBuffer的当前剩馀长度,则会抛出BufferOverflowException异常,该剩馀长度定义为limit和position之间的差,即limit - position 如上述例子所示,如果进一步执行buffer.put(newbyte[12] ); 由于剩下的长度为11,因此会抛出BufferOverflowException异常。 通过调用ByteBuffer.remaining ()检查此ByteBuffer的当前剩馀可用长度。

在实际APP中,通过调用SocketChannel.read (bytebufferdst )将数据从SocketChannel读取到ByteBuffer是通过socket channel指定的ByteBuffer 此方法的返回值是实际读取的字节长度,因为ByteBuffer经常未被阻塞。 如果将实际读取的字节的长度设为n,将ByteBuffer的剩馀可用长度设为r,则两者的关系一定满足0=n=r。 接着上述例子,假设调用read方法,从SocketChannel读取了4个字节的数据

,则buffer的情况如下:

从ByteBuffer中读数据 复位position

现在ByteBuffer容器中已经存有数据,那么现在就要从ByteBuffer中将这些数据取出来解析。由于position就是下一个读写操作的起始位置,故在读取数据后直接写出数据肯定是不正确的,要先把position复位到想要读取的位置。

首先看一个rewind()方法,该方法仅仅是简单粗暴地将position直接复原到0,limit不变。这样进行读取操作的话,就是从第一个字节开始读取了。如下图:

该方法虽然复位了position,可以从头开始读取数据,但是并未标记处有效数据的结束位置。如本例所述,ByteBuffer总容量为16字节,但实际上只读取了9个字节的数据,因此最后的7个字节是无效的数据。故rewind()方法常常用于字节数组的完整拷贝。

实际应用中更常用的是flip()方法,该方法不仅将position复位为0,同时也将limit的位置放置在了position之前所在的位置上,这样position和limit之间即为新读取到的有效数据。如下图:

读取数据

在将position复位之后,我们便可以从ByteBuffer中读取有效数据了。类似put()方法,ByteBuffer同样提供了一系列get方法,从position开始读取数据。get()方法读取1个字节,getChar()、getShort()、getInt()、getFloat()、getLong()、getDouble()则读取相应字节数的数据,并转换成对应的数据类型。如getInt()即为读取4个字节,返回一个Int。在调用这些方法读取数据之后,ByteBuffer还会将position向后移动读取的长度,以便继续调用get类方法读取之后的数据。

这一系列get方法也都有对应的接收一个int参数的重载方法,参数值表示从指定的位置读取对应长度的数据。如getDouble(2)则表示从下标为2的位置开始读取8个字节的数据,转换为double返回。不过实际应用中往往对指定位置的数据并不那么确定,所以带int参数的方法也不是很常用。get()方法则有两个重载方法:

get(byte[] dst, int offset, int length):表示尝试从 position 开始读取 length 长度的数据拷贝到 dst 目标数组 offset 到 offset + length 位置,相当于执行了 for (int i = off; i < off + len; i++)dst[i] = buffer.get(); get(byte[] dst):尝试读取 dst 目标数组长度的数据,拷贝至目标数组,相当于执行了 buffer.get(dst, 0, dst.length);

此处应注意读取数据后,已读取的数据也不会被清零。下图即为从例子中连续读取1个字节的byte和4个字节的int数据:

此处同样要注意,当想要读取的数据长度大于ByteBuffer剩余的长度时,则会抛出 BufferUnderflowException 异常。如上例中,若再调用buffer.getLong()就会抛出 BufferUnderflowException 异常,因为 remaining 仅为4。

确保数据长度

为了防止出现上述的 BufferUnderflowException 异常,最好要在读取数据之前确保 ByteBuffer 中的有效数据长度足够。在此记录一下我的做法:

private void checkReadLen(long reqLen,ByteBuffer buffer,SocketChannel dataSrc) throws IOException { int readLen; if (buffer.remaining() < reqLen) { // 剩余长度不够,重新读取 buffer.compact(); // 准备继续读取 System.out.println("Buffer remaining is less than" + reqLen + ". Read Again..."); while (true) { readLen = dataSrc.read(buffer); System.out.println("Read Again Length: " + readLen + "; Buffer Position: " + buffer.position()); if (buffer.position() >= reqLen) { // 可读的字节数超过要求字节数 break; } } buffer.flip(); System.out.println("Read Enough Data. Remaining bytes in buffer: " + buffer.remaining()); }} 字节序处理

基本类型的值在内存中的存储形式还有字节序的问题,这种问题在不同CPU的机器之间进行网络通信时尤其应该注意。同时在调用ByteBuffer的各种get方法获取对应类型的数值时,ByteBuffer也会使用自己的字节序进行转换。因此若ByteBuffer的字节序与数据的字节序不一致,就会返回不正确的值。如对于int类型的数值8848,用16进制表示,大字节序为:0x 00 00 22 90;小字节序为:0x 90 22 00 00。若接收到的是小字节序的数据,但是却使用大字节序的方式进行解析,获取的就不是8848,而是-1876819968,也就是大字节序表示的有符号int类型的 0x 90 22 00 00。

JavaNIO提供了java.nio.ByteOrder枚举类来表示机器的字节序,同时提供了静态方法ByteOrder.nativeOrder()可以获取到当前机器使用的字节序,使用ByteBuffer中的order()方法即可获取该buffer所使用的字节序。同时也可以在该方法中传递一个ByteOrder枚举类型来为ByteBuffer指定相应的字节序。如调用buffer.order(ByteOrder.LITTLE_ENDIAN)则将buffer的字节序更改为小字节序。

一开始并不知道还可以这样操作,比较愚蠢地手动将读取到的数据进行字节序的转换。不过觉得还是可以记下来,也许在别的地方用得到。JDK中的 Integer 和 Long 都提供了一个静态方法reverseBytes()来将对应的 int 或 long 数值的字节序进行翻转。而若想读取 float 或 double,也可以先读取 int 或 long,然后调用 Float.intBitsToFloat(int val) 或 Double.longBitsToDouble(long val) 方法将对应的 int 值或 long 值进行转换。当ByteBuffer中的字节序与解析的字节序相反时,可以使用如下方法读取:

int i = Integer.reverseBytes(buffer.getInt()); float f = Float.intBitsToFloat(Integer.reverseBytes(buffer.getInt()));long l = Long.reverseBytes(buffer.getLong());double d = Double.longBitsToDouble(buffer.getLong()); 继续写入数据

由于ByteBuffer往往是非阻塞式的,故不能确定新的数据是否已经读完,但这时候依然可以调用ByteBuffer的compact()方法切换到读取模式。该方法就是将 position 到 limit 之间还未读取的数据拷贝到 ByteBuffer 中数组的最前面,然后再将 position 移动至这些数据之后的一位,将 limit 移动至 capacity。这样 position 和 limit 之间就是已经读取过的老的数据或初始化的数据,就可以放心大胆地继续写入覆盖了。仍然使用之前的例子,调用 compact() 方法后状态如下:

总结

总之ByteBuffer的基本用法就是:
初始化(allocate)–> 写入数据(read / put)–> 转换为写出模式(flip)–> 写出数据(get)–> 转换为写入模式(compact)–> 写入数据(read / put)…

参考资料

java字节序、主机字节序和网络字节序扫盲贴:https://blog.csdn.net/aitangyong/article/details/23204817

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。