ByteBuffer使用

NIO,数据的读写操作始终是与缓冲区相关联的.读取时信道(SocketChannel)将数据读入缓冲区,写入时首先要将发送的数据按顺序填入缓冲区.缓冲区是定长的,基本上它只是一个列表,它的所有元素都是基本数据类型.ByteBuffer是最常用的缓冲区,它提供了读写其他数据类型的方法,且信道的读写方法只接收ByteBuffer.因此ByteBuffer的用法是有必要牢固掌握的.

1.创建ByteBuffer
1.1 
使用allocate()静态方法
    ByteBuffer buffer=ByteBuffer.allocate(256);
    
以上方法将创建一个容量为256字节的ByteBuffer,如果发现创建的缓冲区容量太小,唯一的选择就是重新创建一个大小合适的缓冲区.

1.2 
通过包装一个已有的数组来创建
    
如下,通过包装的方法创建的缓冲区保留了被包装数组内保存的数据.
    ByteBuffer buffer=ByteBuffer.wrap(byteArray);

    
如果要将一个字符串存入ByteBuffer,可以如下操作:
    String sendString="
你好,服务器. ";
    ByteBuffer sendBuffer=ByteBuffer.wrap(sendString.getBytes("UTF-16"));

2.
回绕缓冲区
  buffer.flip();
  
这个方法用来将缓冲区准备为数据传出状态,执行以上方法后,输出通道会从数据的开头而不是末尾开始.回绕保持缓冲区中的数据不变,只是准备写入而不是读取.

3.
清除缓冲区
  buffer.clear();
  
这个方法实际上也不会改变缓冲区的数据,而只是简单的重置了缓冲区的主要索引值.不必为了每次读写都创建新的缓冲区,那样做会降低性能.相反,要重用现在的缓冲区,在再次读取之前要清除缓冲区.

4.
从套接字通道(信道)读取数据
  int bytesReaded=socketChannel.read(buffer);
  
执行以上方法后,通道会从socket读取的数据填充此缓冲区,它返回成功读取并存储在缓冲区的字节数.在默认情况下,这至少会读取一个字节,或者返回-1指示数据结束.

5.
向套接字通道(信道)写入数据
  socketChannel.write(buffer);
  
此方法以一个ByteBuffer为参数,试图将该缓冲区中剩余的字节写入信道.

 

例子:

 

# 字节缓冲区
ByteBuffer其实就是一个字节缓冲区, 在这里你可以对缓冲区的数据进行 字节级的操作. 这样的好处在于你可以比较方便的获取到底层的字节操作和字节数据. 比如你有一个int型数据, 而你想获取他的byte[]数组, 你可以这样做:
ByteBuffer buffer = ByteBuffer.allocate(1024); //分配一定的空间,1024
int i = 90;
buffer.putInt(i);
byte[] array = buffer.array(); //获取该buffer的数组,这个数组是跟该buffer一一对应的
for(int j =0; j <4;j++){
System.out.println(Integer.toBinaryString(array[j] & 0xFF));
}
 
 
# public static ByteBuffer wrap(byte[] array)
将array中的数据包装到ByteBuffer中
byte[] array = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap(array);

等效于
ByteBuffer buffer = ByteBuffer.allocate(1024); //分配一定的空间,1024
 
 
 
 
 
 

状态变量

跟踪数据的状态情况使buffer可以自己管理数据资源

position: 其实是指从buffer读取或写入buffer的下一个元素位置。比如,已经写入buffer 3个元素那那么position就是指向第4个位置,即position设置为3(数组从0开始计)。
limit:还有多少数据需要从buffer中取出,或还有多少空间可以放入。postition总是<=limit。
capacity: 表示buffer本身底层数组的容量。limit绝不能>capacity。
filp():作了两件事情:1.将limit指向现在position的位置 2.将position设置为0 (limit=position;position=0)
       这个过程可以使之前buffer写入数据时改变的状态变为可以“准备读取”。因为之前写到buffer中的数据就是position 到 limit-1 两个位置之间(limit指向最后一个数据的后一个位置)。
filp
clear():
    也作了两件事:1. limit=capacity 2.position=0
这个过程可以使buffer读取数据时改变的状态改变为“清空并准备写入”。
clear

访问方法

以下都以bytebuffer为例
get():
   前三个get方法是相对读取。就是相对于位置状态来读取数据,并且会改变position位置状态。
   byte get();
   ByteBuffer get(byte dst[]);//读取bytebuffer中数据写入 dst[]
   ByteBuffer get(byte dst[],int offset, int length);
   
   该读取数据是绝对读取(一个byte),即会忽略limit和position值。并完全绕过了缓冲区的状态统计方法。
   就是说不会改变buffer内部的位置状态。
   byte get(int index);
 
put();
   与get类似前四个put方法是相对读取。即受position 以及limit影响,并且会改变 position。
   ByteBuffer put( byte b );
   ByteBuffer put( byte src[] ); //从src[]写入bytebuffer
   ByteBuffer put( byte src[], int offset, int length );
   ByteBuffer put( ByteBuffer src );
   最后一个是绝对写入不会影响position等位置状态。
   ByteBuffer put( int index, byte b );
 
除了byte的读写还有其他类型的读写方法。并且他们都存在 相对以及绝对两类。
 
操作的典型使用:
while (true) {
     buffer.clear(); // 准备将数据写入buffer
     int r = fcin.read( buffer ); // channel读取外部系统的数据并写入 buffer
     if (r==-1) {
       break;
     }
     buffer.flip(); //准备将数据读出buffer
     fcout.write( buffer ); // channel读取buffer的数据并写到相应的外部系统
}
 

高级应用

缓存区的分配和包装

ByteBuffer.allocate(int);方法可以分配(创建)一个byte类型的buffer。
ByteBuffer.wrap(byte[]);方法可以将一个已有的byte数组包装出一个新的bytebuffer对象。
后一种方式需要小心处理原来的那个byte数组。因为它可以直接访问了。

缓冲区的分片

分片就是建立“子缓冲区”。子缓冲区共享父缓冲区的一部分底层数组位置。
在某种意义上,子缓冲区就像原来的缓冲区中的一个窗口。
这样当改变子缓冲区的内容时,父缓冲区的相应位置也会被改变。
分片操作是根据当前position以及limit的值来确定的。
buffer.position( 3 );
buffer.limit( 7 );
ByteBuffer slice = buffer.slice();

只读缓冲区

asReadOnlyBuffer()方法可以返回一个与原buffer对象一样的对象,只是新的buffer对象是只读的。

直接缓冲区

sun的定义:给定一个直接字节缓冲区,Java 虚拟机将尽最大努力直接对它执行本机 I/O 操作。也就是说,它会在每一次调用底层操作系统的本机 I/O 操作之前(或之后),尝试避免将缓冲区的内容拷贝到一个中间缓冲区中(或者从一个中间缓冲区中拷贝数据)。
创建directbuffer的方式是用ByteBuffer.allocateDirect( int );方法替代ByteBuffer.allocate(int);

内存影射文件I/O

它读写要比其他IO快很多.
他使文件或文件的一部分由内存影射。但是只有操作该部分位置的数据才是以内存方式读写的,而不是整个文件读入内存。(并且他是一个os的底层机制。由os底层异步完成内存与物理磁盘上的数据同步)
影射文件可以通过FileChannel对象的map方法得到。
比如以下就是将一个文件的前1024个字节影射到内存,并创建一个MappedByteBuffer对象返回出来。MappedByteBuffer是ByteBuffer的一个子类。
MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE, start, size );
<think>好的,我现在需要帮助用户了解如何使用Java NIO的ByteBuffer类。用户明确要求教程、示例和详解,所以我得先理清ByteBuffer的基本概念和核心组件。首先,我记得ByteBufferJava NIO中的一个关键类,用于高效处理字节数据。根据引用[2],Java NIO提供了非阻塞、高效的I/O操作,而ByteBuffer是其核心之一。 首先,我应该从ByteBuffer的创建方法开始。用户可能需要知道如何分配一个缓冲区,所以需要提到allocate和allocateDirect的区别。引用[3]提到需要弄清楚position、limit、capacity这些变量,所以我得详细解释这些属性。 接下来是缓冲区的读写操作。用户可能需要示例代码来演示如何写入和读取数据。比如put()和get()方法的不同用法,包括绝对定位和相对定位。引用[1]提到put方法有很多版本,可以写入不同数据,这里需要具体说明如何操作。 然后是关于flip()、rewind()、clear()等方法的作用,这些是切换读写模式的关键步骤,容易混淆,必须详细解释。例如,flip()方法用于切换为读模式,调整limit和position的位置,而clear()则重置缓冲区但不清数据,这些都需要明确说明。 还需要涵盖标记(mark)和重置(reset)的用法,以及如何复制缓冲区,比如slice()和duplicate()方法。引用[4]提到的arrayOffset()方法也很重要,如果缓冲区有后备数组的话,用户可能需要知道如何访问底层数组的偏移量。 此外,应该提供一些完整的示例代码,展示从创建缓冲区到读写数据的完整流程,帮助用户理解各个步骤如何衔接。比如,写入字符串到ByteBuffer,然后读取出来,这样的例子比较直观。 最后,要确保回答结构清晰,分步骤讲解,并附上相关问题供用户进一步学习。需要检查所有LaTeX格式是否正确,行内公式用$...$,独立公式用$$...$$,但在这个回答中可能公式较少,主要关注代码块和引用标识。同时引用提供的四个参考文献,适当在相关部分添加引用标记,比如在提到NIO核心概念时引用[^2],在讲解position等变量时引用[^3]等。 总结一下,回答的结构应该是:概述、创建缓冲区、核心属性、读写操作、模式切换、其他方法、示例代码、常见问题,最后是相关问题。这样用户能够逐步学习ByteBuffer使用方法,并通过示例加深理解。</think>### Java NIO ByteBuffer 使用教程详解 #### 1. ByteBuffer 概述 Java NIO 的 $ByteBuffer$ 类是处理字节数据的核心工具,用于高效读写二进制数据(如文件、网络传输)。它通过直接内存访问和缓冲区管理优化 I/O 性能[^2]。 --- #### 2. 创建 ByteBuffer ```java // 分配堆内存缓冲区(JVM 堆内) ByteBuffer heapBuffer = ByteBuffer.allocate(1024); // 分配直接内存缓冲区(操作系统堆外,减少拷贝开销) ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024); // 包装现有数组(数据修改会同步到数组) byte[] arr = new byte[1024]; ByteBuffer wrappedBuffer = ByteBuffer.wrap(arr); ``` --- #### 3. 核心属性与关系 - **capacity**:缓冲区的最大容量(固定值) - **position**:当前读写位置(初始为 0) - **limit**:可读/写的最大位置(初始等于 capacity) - **mark**:临时标记位置(通过 `mark()` 设置) 转换关系如图: ```plaintext 0 ≤ mark ≤ position ≤ limit ≤ capacity ``` --- #### 4. 读写操作 **写入数据**(写模式): ```java byte[] data = "Hello".getBytes(); heapBuffer.put(data); // 相对写入(position 自动后移) heapBuffer.put(2, (byte)0x41); // 绝对写入(指定索引位置) ``` **读取数据**(读模式): ```java heapBuffer.flip(); // 切换为读模式(limit=原position,position=0) while (heapBuffer.hasRemaining()) { byte b = heapBuffer.get(); // 相对读取 } ``` --- #### 5. 模式切换方法 - **`flip()`**:写模式 → 读模式 $$limit = position,\quad position = 0$$ - **`clear()`**:重置缓冲区(不清数据) $$position = 0,\quad limit = capacity$$ - **`rewind()`**:重新读取数据 $$position = 0$$ --- #### 6. 其他重要方法 - **`slice()`**:创建共享数据的子缓冲区 - **`duplicate()`**:创建完全副本(共享底层数据) - **`arrayOffset()`**:获取缓冲区在底层数组的偏移量[^4] ```java if (heapBuffer.hasArray()) { int offset = heapBuffer.arrayOffset() + heapBuffer.position(); } ``` --- #### 7. 完整示例 ```java // 创建并写入数据 ByteBuffer buffer = ByteBuffer.allocate(128); buffer.put("NIO ByteBuffer".getBytes(StandardCharsets.UTF_8)); // 切换读模式 buffer.flip(); // 读取数据到数组 byte[] readData = new byte[buffer.remaining()]; buffer.get(readData); System.out.println(new String(readData)); // 输出 "NIO ByteBuffer" ``` --- #### 8. 常见问题 **Q:堆缓冲区与直接缓冲区的区别?** A:堆缓冲区由 JVM 管理,直接缓冲区通过操作系统本地内存分配(减少拷贝次数,适合高频 I/O 操作)。 **Q:如何避免缓冲区溢出?** A:操作前检查剩余空间:`if (buffer.remaining() >= neededSize) { ... }` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值