理解java.nio.buffer

NIO包(java.nio.*)引入了四个关键的抽象数据类型,它们共同解决传统的I/O类中的一些问题。

1. Buffer:它是包含数据且用于读写的线形表结构。其中还提供了一个特殊类用于内存映射文件的I/O操作。

2. Charset:它提供Unicode字符串影射到字节序列以及逆影射的操作。

3. Channels:包含socket,file和pipe三种管道,它实际上是双向交流的通道。

4. Selector:它将多元异步I/O操作集中到一个或多个线程中(它可以被看成是Unix中select()函数或Win32中WaitForSingleEvent()函数的面向对象版本)。

其中,Channel(通道)和Buffer(缓冲)是NIO中的两个核心对象

    Channel是对传统的输入/输出系统的模拟,在新IO系统中,所有的数据都需要通过通道传输:Channel与传统的InputStream,OutputStream最大的区别在于它提供了一个map()方法,通过该map()方法可以直接将“一块数据”映射到内存中。如果说传统的输入/输出系统是面向流的处理,则新IO则是面向块的处理。

    Buffer可以被理解成一个容器,它的本质是一个数组,发送到Channel中的所有对象都必须首先发到Buffer中,而从Channel中读取的数据也必须先放到Buffer中。

推荐:

java NIO与传统IO区别,click here

一个nio学习博客,click here

1.Buffer简介

01204650_ly1H.jpg

缓冲区成员:

 private int mark = -1;//一个备忘位置,调用mark()方法时,mark = position;调用reset()方法时,position=mask;
 private int position = 0;//位置,下一个要被读取或写的元素的索引,位置会自动由相应的get(),put()函数更新。
 private int limit;//上界,缓冲区第一个不能被读取或写的元素。或者说缓冲区中现存元素的计数,或者代表有效数据的末端
 private int capacity;//容量,在缓冲区被创建时设定,且永远不能被改变
 long address;

buffer中遵循这样的关系:

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

2.创建Buffer

    Buffer以及其子类都无法直接new,而必须把通过他们提供的工厂方法来创建。通常有两种方式:

1、allocate

CharBuffer charBuffer = CharBuffer.allocate (100);

将在堆上分配一个可以存储100个字符的数组作为backing store。

2、wrap,包装一个已有的数组:

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

注意:这样的方式创建的Buffer,将不会在堆上创建新的数组,而是直接利用myArray做backing store,这意味着任何对myArray或者buffer的修改都将影响到buffer或者myArray。可以通过public final boolean hasArray( )方法来判断是否拥有一个数组,通过array()方法取得这个数组。

3.操作buffer

01203743_v6Yo.jpg

1、填充:

现在将“Hello”填充到buffer:

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

01203745_hfYQ.jpg

2、修改:

将原来的“Hello”,修改为“Mellow”

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

3、翻转:

我们已经写满了缓冲区,现在我们必须将其清空,我们想把这个缓冲区传递给一个通道,以使内容全部被写出,我们可以人工实现代码

b.limit(b.position()).position(0);

或者

b.flip();

flip()一般用于翻转缓冲区,翻转后的效果图为:翻转后的缓冲区无法写入,只能读取

注意:flip()通常用于read状态切换到write状态,或者write状态切换到read状态

int count = buffer.hasRemaining();
for (int i = 0; i<count; i++) { 
    myByteArray [i] = buffer.get( ); 
}

值得注意的是:缓冲区不是线程安全的,在需要并发操作时,需要先同步

使用b.clear()可以将缓冲区重置为空状态,b.clear() 只是将position置为0,limit设置为capacity,并不真正清空数据

4、压缩缓冲区

buffer.compact()

调用compact()方法是丢弃已经释放掉的数据,保留未释放的数据,并使缓冲区对重新填充容量准备就绪

压缩前的缓冲区:

01203746_D78F.jpg

压缩后的缓冲区:

01203747_VIIr.jpg

标记:使缓冲区能记住一个位置并在之后返回

调用mark()时,将当前位置作为标记

调用reset()时,返回到之前标记的地方

注意:

1,rewind( ),clear( ),flip( )总是丢弃标记

2,如果新设定的值小于当前的值,调用limit( )和position( )带有索引参数的版本会抛弃标记

执行下面代码后:设定标记为2

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

01203748_jWXp.jpg

如果此时将缓冲区传递给管道,则“ow”将被发送,而且position会前进到6

如果此时调用reset( );则position后退到2,若此时将缓冲区传递给管道,这"llow"将被发送,而且position会前进到6

5、判断相等:

判断两个缓冲区相等的充要条件是:

1,两个对象类型相同

2,两个缓冲区剩余同样数量的元素

3,每个缓冲区被get( )函数返回的数据元素序列必须一致

6、复制缓冲区

//缓冲区复制
CharBuffer buffer = CharBuffer.allocate(10);
buffer.position(3).limit(6).mark().position(5);
CharBuffer duBuffer = buffer.duplicate();
buffer.clear();

01203749_89I6.jpg

7、分割缓冲区

//缓冲区分割
char [] myBuff = new char[100];
CharBuffer cb = CharBuffer.wrap(myBuff);
cb.position(12).limit(21);
CharBuffer sliceBuffer = cb.slice();

01203751_BzjR.jpg

转载于:https://my.oschina.net/OQKuDOtsbYT2/blog/204277

<think> 我们遇到的问题是将一个`DirectShortBufferU`对象强制转换为`ByteBuffer`时出现了类型转换异常。根据错误信息,这两个类都在`java.base`模块中,但属于不同的类型,因此不能直接转换。 ### 错误原因分析 1. **类型不匹配**:`DirectShortBufferU`是`ShortBuffer`的一种(直接内存分配的短整型缓冲区),而`ByteBuffer`是字节缓冲区。它们虽然都是缓冲区,但存储的数据类型不同(`short` vs `byte`),因此不能直接强制转换。 2. **常见场景**:这种错误通常发生在试图将其他类型的缓冲区(如`ShortBuffer`、`IntBuffer`等)当作`ByteBuffer`使用时,尤其是在使用Java NIO进行音视频处理、网络通信或文件I/O时。 ### 解决方案 #### 方法1: 使用`asByteBuffer()`方法转换 如果原始数据是`short`类型,但需要以字节形式处理,可以先将`ShortBuffer`转换为`ByteBuffer`: ```java ShortBuffer shortBuffer = ...; // 你的ShortBuffer对象 ByteBuffer byteBuffer = ByteBuffer.allocate(shortBuffer.remaining() * 2); // 每个short占2个字节 byteBuffer.asShortBuffer().put(shortBuffer); byteBuffer.flip(); // 准备读取 ``` #### 方法2: 重新设计数据流 确保在数据处理的整个链路上使用统一的缓冲区类型: ```java // 错误示例:将ShortBuffer强制转换为ByteBuffer ShortBuffer shortBuf = ...; ByteBuffer byteBuf = (ByteBuffer) shortBuf; // 这里会抛出ClassCastException // 正确做法:创建新的ByteBuffer并放入数据 ByteBuffer byteBuf = ByteBuffer.allocate(shortBuf.remaining() * 2); for (int i = 0; shortBuf.hasRemaining(); i++) { short s = shortBuf.get(); byteBuf.putShort(s); // 将每个short拆分为两个byte } byteBuf.flip(); // 翻转以便读取 ``` #### 方法3: 使用`ByteBuffer.wrap()`封装数组 如果数据量不大,可以先将`ShortBuffer`转为`short[]`数组,再封装为`ByteBuffer`: ```java ShortBuffer shortBuffer = ...; short[] shortArray = new short[shortBuffer.remaining()]; shortBuffer.get(shortArray); ByteBuffer byteBuffer = ByteBuffer.wrap( new byte[shortArray.length * 2] // 预先分配字节空间 ); for (short s : shortArray) { byteBuffer.putShort(s); // 按顺序写入 } byteBuffer.flip(); ``` ### 原理说明 - **数据存储差异**: - `ByteBuffer`:存储单元为1字节(8位) - `ShortBuffer`:存储单元为2字节(16位) - **转换必要性**: 当需要将多字节数据类型(如short)写入字节流(如网络传输或文件存储)时,必须将每个`short`拆分为两个`byte`,遵循大端序或小端序。 ### 字节序处理 在转换过程中要注意字节序(Endianness): ```java byteBuffer.order(ByteOrder.BIG_ENDIAN); // 默认大端序 // 或 byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // 小端序 ``` ### 案例:音频处理中的转换 在音频编程中,经常需要将16位音频数据(每个样本占2字节)转换为字节数组: ```java // 假设audioData是包含音频样本的short数组 short[] audioData = ...; ByteBuffer byteBuf = ByteBuffer.allocate(audioData.length * 2); byteBuf.order(ByteOrder.LITTLE_ENDIAN); // 音频常用小端序 for (short sample : audioData) { byteBuf.putShort(sample); } byteBuf.flip(); ``` ### 相关问题 1. 如何将`IntBuffer`转换为`ByteBuffer`? 2. `ByteBuffer`的大小端设置对数据存储有什么影响? 3.NIO编程中如何高效处理不同类型缓冲区的转换? 4. 直接缓冲区(`DirectBuffer`)和非直接缓冲区的性能差异是什么? [^1]: Java版本兼容性问题可能导致方法签名变化 [^2]: `ByteBuffer`是NIO中处理二进制数据的基础类 [^3]: 第三方库的版本升级可能修复了底层兼容性问题 [^4]: 正确理解缓冲区状态(position, limit, capacity)对数据处理至关重要 </think>### 解决 `class java.nio.DirectShortBufferU cannot be cast to class java.nio.ByteBuffer` 错误的方法 #### 错误原因分析 该错误发生在试图将 `ShortBuffer` 类型(具体实现为 `DirectShortBufferU`)强制转换为 `ByteBuffer` 时。两者虽然都是 `java.nio.Buffer` 的子类,但属于不同的数据类型容器: - `ByteBuffer`:存储字节数据(8位) - `ShortBuffer`:存储短整型数据(16位) - 强制转换违反了 Java 类型系统规则,因为它们是不同的缓冲区类型[^2][^4] #### 解决方案(代码示例) ##### 方法1:显式创建目标类型的缓冲区(推荐) ```java // 原始代码(错误) ShortBuffer shortBuffer = ShortBuffer.allocate(1024); ByteBuffer byteBuffer = (ByteBuffer) shortBuffer; // 抛出ClassCastException // 修正方案 ShortBuffer shortBuffer = ShortBuffer.allocate(1024); ByteBuffer byteBuffer = ByteBuffer.allocate(shortBuffer.capacity() * 2); // 容量换算:每个short=2字节 // 数据迁移 shortBuffer.flip(); while (shortBuffer.hasRemaining()) { short s = shortBuffer.get(); byteBuffer.putShort(s); // 按字节写入 } byteBuffer.flip(); // 准备读取 ``` ##### 方法2:使用视图缓冲区(高效转换) ```java ShortBuffer shortBuffer = ShortBuffer.allocate(1024); ByteBuffer byteBuffer = ByteBuffer.allocateDirect(shortBuffer.capacity() * 2); // 建立视图映射 ByteBuffer byteView = byteBuffer.asReadOnlyBuffer(); ShortBuffer shortView = byteView.asShortBuffer(); // 数据交互 shortBuffer.put(new short[]{1, 2, 3}); // 操作原始ShortBuffer shortBuffer.flip(); shortView.put(shortBuffer); // 通过视图写入ByteBuffer ``` ##### 方法3:使用第三方库统一处理(如Guava) ```java import com.google.common.io.ByteStreams; ShortBuffer shortBuffer = ShortBuffer.allocate(1024); shortBuffer.put(new short[]{1, 2, 3}); shortBuffer.flip(); // 转换为字节数组 byte[] bytes = new byte[shortBuffer.remaining() * 2]; ByteBuffer.wrap(bytes).asShortBuffer().put(shortBuffer); // 再包装为ByteBuffer ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); ``` #### 核心原理 - **类型安全**:Java 禁止直接转换不同类型缓冲区 - **内存结构差异**: - `ByteBuffer` 内存布局:`[byte1][byte2][byte3]...` - `ShortBuffer` 内存布局:`[short1(2字节)][short2(2字节)]...` - **转换公式**: 对于包含 $n$ 个元素的 `ShortBuffer`,所需 `ByteBuffer` 容量为: $$ \text{capacity}_{\text{byte}} = n \times 2 $$ #### 最佳实践 1. **始终显式创建目标类型缓冲区** 2. **使用视图转换代替强制转换**: ```java // 安全转换方向 ByteBuffer -> 通过 .asShortBuffer() 获得 ShortBuffer 视图 // 反向转换需要数据复制 ``` 3. **统一使用ByteBuffer作为基础容器**: ```java ByteBuffer baseBuffer = ByteBuffer.allocateDirect(1024); ShortBuffer shortView = baseBuffer.asShortBuffer(); // 安全访问 ``` #### 常见错误场景修复 **场景:Canal 1.1.6 兼容性问题 (引用[3])** 升级到 1.1.7+ 版本或修改源码: ```diff // 原始错误代码 - ShortBuffer buffer = (ShortBuffer) byteBuffer; // 修正为 + ShortBuffer buffer = byteBuffer.asShortBuffer(); ``` [^1]: JDK版本差异可能导致ByteBuffer方法签名变化 [^2]: ByteBuffer与其他类型缓冲区的转换需要显式处理 [^3]: 第三方库版本升级可修复类型转换兼容性问题 [^4]: 理解缓冲区状态管理(position/limit/capacity)是正确操作的前提 --- ### 相关问题 1. 如何正确地将 `IntBuffer` 转换为 `ByteBuffer`? 2. `DirectBuffer` 和普通缓冲区在性能上有何差异? 3. 为什么 `asShortBuffer()` 可以安全转换而强制转换会失败? 4.NIO编程中如何处理不同字节序(Endianness)的转换?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值