java基础·杂(NIO之ByteBuffer)

本文介绍了Java NIO(New IO)的概念及其与传统IO的区别,重点讲解了通道(Channel)和缓冲区(Buffer)的工作机制。通过实例演示了如何使用FileChannel进行文件读写操作,并详细解释了Buffer的使用流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

接触了NIO的东西,这里记下,方便以后查阅。

1,NIO

Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式。

Channels and Buffers(通道和缓冲区)

    标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读到缓冲区中,或者从缓冲区写入到通道中。

Java NIO的通道类似流,但又有些不同:

    既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。

    通道可以异步地读写。

    通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。

Channel的实现:
   FileChannel 从文件中读写数据。-常用
   DatagramChannel 能通过UDP读写网络中的数据。
   SocketChannel 能通过TCP读写网络中的数据。-常用

  ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

2,案例

例如:

Channels and Buffers的例子:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");//参数 mode 的值可选 "r":可读,"w" :可写,"rw":可读性;
FileChannel inChannel = aFile.getChannel();//返回与此文件关联的唯一 FileChannel 对象。
ByteBuffer buf = ByteBuffer.allocate(48);//create buffer with capacity of 48 bytes
int bytesRead = inChannel.read(buf); //read into buffer.将字节序列从此通道读入给定的缓冲区。读取的字节数,可能为零,如果该通道已到达流的末尾,则返回 -1
while (bytesRead != -1) {
  buf.flip();  //make buffer ready for read
  while(buf.hasRemaining()){//告知在当前位置和限制之间是否有元素。当且仅当此缓冲区中至少还有一个元素时返回 true
      System.out.print((char) buf.get()); // read 1 byte at a time.相对 get 方法。读取此缓冲区当前位置的字节,然后该位置递增。返回:缓冲区当前位置的字节
  }
  buf.clear(); //make buffer ready for writing
  bytesRead = inChannel.read(buf);
}
aFile.close();

通道可以由FileInputStream,FileOutputStream,RandomAccessFile三个类来产生,例如:FileChannel fc = new FileInputStream().getChannel();与通道交互的一般方式就是使用缓冲器,可以把通道比如为煤矿(数据区),而把缓冲器比如为运煤车,想要得到煤一般都通过运煤车来获取,而不是直接和煤矿取煤。

3,Buffer

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

使用Buffer读写数据一般遵循以下四个步骤:
1,写入数据到Buffer
2,调用flip()方法,反转缓冲区,将Buffer从写模式切换到读模式
3,从Buffer中读取数据
4,调用clear()方法或者compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据,任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。

缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。

为了理解Buffer的工作原理,需要熟悉它的三个属性:

capacity,position,limit

position和limit的含义取决于Buffer处在读模式还是写模式。不管Buffer处在什么模式,capacity的含义总是一样的。

capacity

作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”.你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。

position
当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1.

当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。

limit
在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。

当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据。

创建ByteBuffer

使用allocate()静态方法
    ByteBuffer buffer=ByteBuffer.allocate(256);

    以上方法将创建一个容量为256字节的ByteBuffer,如果发现创建的缓冲区容量太小,唯一的选择就是重新创建一个大小合适的缓冲区。

通过包装一个已有的数组来创建
    如下,通过包装的方法创建的缓冲区保留了被包装数组内保存的数据.

    ByteBuffer buffer=ByteBuffer.wrap(byteArray);

    如果要将一个字符串存入ByteBuffer,可以如下操作:
    String sendString="你好,服务器. ";

    ByteBuffer sendBuffer=ByteBuffer.wrap(sendString.getBytes("UTF-16"));



### 可能原因分析 `java.lang.NoSuchMethodError` 的发生通常表明运行时环境中使用的类版本与编译时不同。具体到 `java.nio.ByteBuffer.flip()` 方法引发的错误,可能涉及以下几种情况: 1. **Java 版本不匹配**:如果项目的 JDK 编译环境和实际运行环境的 Java 版本不一致,可能导致某些方法不可用[^1]。 2. **第三方库冲突**:可能存在多个版本的 JAR 文件加载了不同的 `ByteBuffer` 实现,从而覆盖了标准库中的实现[^2]。 3. **模块命名冲突**:类似于 Spring Boot 子模块间的业务类名重复问题,也可能导致类似的异常行为[^3]。 --- ### 解决方案 #### 1. 验证 Java 运行环境 确认当前项目所使用的 JDK 和运行时环境是否一致。可以通过以下命令验证: ```bash java -version javac -version ``` 确保两者均为同一版本(如 Java 8)。如果发现版本不一致,则需统一开发和生产环境的 JDK 版本。 #### 2. 排查依赖冲突 通过 Maven 或 Gradle 工具检查是否存在多个版本的 Servlet API 或其他相关库。以下是基于 Maven 的依赖树查看命令: ```bash mvn dependency:tree ``` 重点关注是否有多个版本的 `javax.servlet-api` 被引入。如果有冲突,可以使用 `<exclusions>` 来排除不必要的依赖项。例如: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> </exclusion> </exclusions> </dependency> ``` 对于 Gradle 用户,可以在构建文件中添加如下配置来强制指定特定版本: ```gradle configurations.all { resolutionStrategy.eachDependency { DependencyResolveDetails details -> if (details.requested.group == 'javax.servlet') { details.useVersion "4.0.1" } } } ``` #### 3. 检查自定义 ByteBuffer 类 有时开发者可能会无意中创建了一个名为 `ByteBuffer` 的类并将其放置在默认包或其他路径下,这会优先于标准库被加载。建议全局搜索代码仓库,查找是否有同名类的存在。如果是这种情况,重命名该类即可解决问题。 #### 4. 清理缓存与重新部署 清理 IDE 中的缓存以及 Tomcat 容器的工作目录,防止旧版字节码残留影响程序正常启动。针对 Tomcat,可修改其扫描跳过设置以减少潜在干扰: ```properties tomcat.util.scan.DefaultJarScanner.jarsToSkip=*,servlet-api.jar,jsp-api.jar ``` 上述属性应写入 `catalina.properties` 文件中[^4]。 --- ### 示例代码调整 假设问题是因依赖冲突引起,在 POM 文件中明确声明所需版本后,测试以下简单代码片段验证修复效果: ```java import java.nio.ByteBuffer; public class TestByteBuffer { public static void main(String[] args) { ByteBuffer buffer = ByteBuffer.allocate(10); System.out.println("Before flip: position=" + buffer.position()); buffer.flip(); System.out.println("After flip: position=" + buffer.position()); } } ``` --- ### 总结 综上所述,`java.nio.ByteBuffer.flip()` 导致的 `NoSuchMethodError` 主要源于运行时环境差异或依赖管理不当。按照以上步骤逐一排查能够有效定位根本原因并实施针对性解决方案。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值