今晚用到 ByteBuffer, 我跟 joy 都是初学 java, 文档里的中文翻译实在是看他母亲不懂, 晕了半天, 作了几个测试, 终于把这个类的用法搞清楚了, 顺便臆想了哈其工作原理.
先列点代码片段:
// ...
//
// 此段代码功能为从 t.txt 里复制所有数据到 out_j.txt:
//
FileChannel fcin = new FileInputStream( "d:/t.txt" ).getChannel();
FileChannel fcout = new FileOutputStream( new File( "d:/out_j.txt" )).getChannel();
ByteBuffer buff = ByteBuffer.allocate( 1024 );
long t1 = System.currentTimeMillis();
while( fcin.read( buff ) != -1 )
{
buff.flip();
fcout.write( buff );
buff.clear();
}
long t2 = System.currentTimeMillis();
long size = fcin.size();
javax.swing.JOptionPane.showMessageDialog( null, size + " 字节, 耗时 " + ( t2 - t1 + 1 ) + " ms." );
SDK 文档里对 ByteBuffer 的说明为:
public abstract class ByteBuffer
extends Buffer
implements Comparable <ByteBuffer>
这说明 ByteBuffer 是继承于 Buffer 的抽象类, 实现了两个接口.
行3 通过 allocate() 分配了一块 1024 字节的缓冲区, 并返回一个 ByteBuffer 对象. (抽象类不能直接 new)
行6 fcin.read() 将数据读入到 buff. 此处的 read() 是 FileChannel 类的一个虚函数.
行8 buff.flip() 这个调用就是开头一直无法理解的部分.
----------------------------------------------------------------------------------------------------
SDK 文档里的对 flip() 的说明是:
public final Buffer flip()
反转此缓冲区。首先对当前位置设置限制,然后将该位置设置为零。如果已定义了标记,则丢弃该标记。
当将数据从一个地方传输到另一个地方时,经常将此方法与 compact 方法一起使用。
我最终的理解是: 文档翻译得太差了, 所有内容都直译为中文, 反而不容易理解.
关键就在以下 3 处:
当前位置: 这个可以直观地理解为缓冲区中的当前数据指针, 或是 SQL 中的游标, 记为 curPointer.
限制: 这个可以理解成实际操作的缓冲区段的结束标记, 记为 endPointer.
反转: 这个完全是对 flip 这个词不负责的翻译, 如果参照 DirectX 里的 flip() 而译为翻转/翻页, 那就好理解得多, 就像写信/看信, 写/看完一页后, 翻到下一页, 眼睛/笔从页底重新移回页首.
这个翻转背后的操作其实就是 "把 endPointer 定位到 curPointer 处, 并把 curPointer 设为 0".
关于标记, 在这里不涉及. 下一句说到常与 compact 方法一起使用, 是可以想像的, 因为 compact 方法对数据进
行了压缩, 有效数据的真实长度发生了变化, 肯定需要用 flip 重新定位结束标记.
在填充, 压缩等数据操作时, curPointer 估计都是自动更新了位置的, 总是指向最后一个有效数据, 所以每次调
用 flip() 后, endPointer 就指向了有效数据的结尾, 而 curPointer 指向了 0 (缓冲起始处).
举个图例:
(c 和 e 分别代表 curPointer 和 endPointer 两个指针)
* 先是一个空的 ByteBuffer (大小为 10 字节)
-------------------
-------------------
c
e
* 然后填充 5 字节数据
-------------------
0 1 2 3 4
-------------------
e c
此时, endPointer 尚在 0 处, curPointer 移到了数据结尾.
经测试, 此时若取数据, 将得到 5 个字节, 内容通常为 0 (也有可能是未知), 因为实际上取到的是从 c 处到缓冲
区实际结束处的 5 个未初始化的字节.
(QZone 字体处理不正确, 此处 c 是在 4 的下面, e 在 0 的下面)
* 调用一次 flip() 后
-------------------
0 1 2 3 4
-------------------
c e
此时, endPointer 先被移到 curPointer, 然后 curPointer 移到 0.
通过测试可见, ByteBuffer 取数据时, 是从 curPointer 起, 到 endPointer 止, 若 curPointer > endPointer, 则取到缓冲区结束.
(QZone 字体处理不正确, 此处 c 是在 0 的下面, e 在 4 的下面)
再看上面代码的关键片段, 行 8 处调用 flip() 即有两个作用, 一是将 curPointer 移到 0, 二是将 endPointer 移到有效数据结尾.
此行可由以下两行代替:
buff.limit( buff.position());
buff.position( 0 );
可见对其工作原理的理解, 应该是正确的.
----------------------------------------------------------------------------------------------------
总结如下:
1. put 数据时, 不会自动清除缓冲区中现有的数据.
2. 每一次 get 或 put 后, curPointer 都将向缓冲区尾部移动, 移动量=操作的数据量.
3. get/put 均是从 curPointer 起, 到 curPointer + 操作的数据长度止.
4. get/put 操作中, 若 curPointer 超过了 endPointer 或缓冲区总长度, 将抛出 java.nio.BufferUnderflowException 异常.
注: curPointer 和 endPointer 只是为文中方便描述命名的, 实际分别对应到 ByteBuffer.position() 和 ByteBuffer.limit() 两个方法.
----------------------------------------------------------------------------------------------------
疑惑:
curPointer 是用 ByteBuffer.position() 取值, 用 ByteBuffer.position( int ) 赋值, 不知道 JDK 为什么要用多态来实现这两个功能, 按我的想法, 设计成 getPosition(), setPosition() 不是要好看好记得多啊.
----------------------------------------------------------------------------------------------------
跟 C++ 的简单比较:
C++ 里面没有类似 ByteBuffer 的现成实现, 实现上述类似的文件复制功能, 通常要自己创建管理缓冲区. C++ 里读写文件通常用 FileRead(), FileWrite() 函数, 在读/写的时候, 可以直接指定读/写的数据长度, 相比下显得
直观方便些, 但 JDK 这个 ByteBuffer 的方式, 确实更方便好用.
ByteBuffer 作为继承自 Buffer 的抽象类, 实现了对 Byte 型缓冲的管理, 同时 JDK 里还有对应其他数据类型的
继承自 Buffer 的抽象类, 分别实现对应类型的缓冲管理. 这种设计减少了编程时的工作. 如果在 C++ 中, 调用
读/写函数时, 还需要考虑传入数据的类型, 通常用传入 sizeof(数据类型) 的方式指定, 除了函数调用时增加耗
费外, 灵活性也更差些.
反过来再想, 为什么 C 要用这种方式? 个人认为, 这是 C 标准库的实现方式, 因为在不同 OS 平台上, 对文件和
设备的访问方法在系统层不一定相同, 同时硬件平台(主要是 CPU)上基本数据类型宽度也有可能不同, 标准库通过
sizeof 这个宏在编译时重新确定了数据宽度, 才使得标准 C 代码得以在不同平台上重新编译运行.
再想 C++ 为什么要用这种方式? 首先, C++ 里沿用 C 标准库的模式是可以理解的, 其次, 也许只是 C++ 标准库
里没有类似的设计, 说不定早就有第三方通过模板实现的了.
个人认为, ByteBuffer 在实现上, 可以算是一种数据结构, 在类设计上, 可以算是一种设计模式了.
分享到:
相关推荐
Java NIO 针对 Java 程序员在处理 I/O 时常见的挑战进行了详细的讲解,使得开发者能够更好地理解并应用这些新特性。例如,书中提到了使用 Java NIO 可以提高应用程序的响应性、可扩展性和可靠性等。 ### Java NIO ...
在探讨Java.nio与Java.io之间的比较时,我们首先需要理解这两个包在Java编程语言中的核心作用和它们各自的优势。Java.io和Java.nio是Java中处理输入/输出操作的两个主要框架,它们各自拥有独特的特性和应用场景。 #...
Java.nio,全称为Java Non-blocking Input/...而压缩包中的"thread"、"noblock"、"block"可能分别对应于线程管理、非阻塞I/O和阻塞I/O的相关示例或讨论,进一步深入研究这些内容,有助于深化对Java.nio的理解和应用。
Java NIO提供了诸如ByteBuffer、CharBuffer、IntBuffer等类型,对应于不同的数据类型。缓冲区具有读写位置,可以通过flip()、clear()和rewind()等方法来管理缓冲区的状态,从而高效地进行数据读写。 3. **选择器...
同时,理解源码可以帮助深入理解Java NIO的工作原理,提升对非阻塞I/O的理解。 总的来说,掌握Java NIO中的ByteBuffer用法对于编写高性能的I/O密集型应用至关重要。通过熟练运用ByteBuffer,开发者可以设计出更高效...
【java.nio.file库详解】 Java 早期版本的文件I/O操作功能相对有限,存在几个显著问题:不支持现代文件系统特性、API设计复杂且冗长、处理大文件和并发性能不足。为了解决这些问题,Java引入了`java.nio.file`库,...
找了好久,终于找到了,java刷新同步获取网络资源
3. `java.nio.charset`:这个包提供了字符编码和解码的相关类,如Charset、CharsetDecoder和CharsetEncoder,用于处理字符串和字节之间的转换。 4. `java.nio.charset.spi`:这是一个服务提供者接口(SPI)包,定义...
标题“nio.rar_NIO_NIO-socket_java nio_java 实例_java.nio”表明这个压缩包包含了一个关于Java NIO的实例,特别是关于NIO套接字(Socket)的编程示例。NIO套接字是Java NIO库中用于网络通信的关键组件,它们允许...
JDK1.7 之 java.nio.file.Files 读取文件仅需一行代码实现 java.nio.file.Files 类是 JDK1.7 中引入的新的文件操作类,该类包含了许多有用的方法来操作文件。其中,Files.readAllBytes(Path) 方法可以将整个文件...
java.io clojure.java.io 的 JK7 java.nio.file.Path 兼容性依赖信息该库托管在 Releases 上。 依赖: [me.moocar/java.io " 0.1.0 " ]用法是 JDK7 中引入的文件路径的抽象。 这个库提供了和 Paths 之间的兼容性。 ...
### Java NIO 详细教程知识点解析 #### 一、Java NIO 概述 ...以上就是Java NIO详细教程的知识点解析,希望对你有所帮助。Java NIO是一种强大的技术,它能够显著提高应用程序在网络IO方面的性能和扩展性。
o Clojure对java.nio的支持。 将clojure.java.io的输入流,输出流和复制功能扩展到java.nio类。 定义新的强制功能缓冲区,字节缓冲区,字符缓冲区,双缓冲区,浮点缓冲区,整数缓冲区,长缓冲区,短缓冲区,通道,可...
《Apress.Pro.Java.7.NIO.2.2011》这本书专注于讲解Java 7中的非阻塞I/O(Non-blocking I/O, NIO)和NIO 2的高级特性,是Java开发者深入理解这一关键领域的重要参考资料。NIO在Java平台中扮演着至关重要的角色,特别...
重新写的Java.nio的socket异步通讯,包含客户端及服务端完整版, 注:解析信息CLASS及进制转换CLASS需要自己去写,项目直接导入,需要自己写一些解析及转换类,这样你才能读懂SOCKET的异步,否则光拿代码没用 ...
java.NIO与Files工具类(处理方案示例).md
Java NIO(New Input/Output)是Java标准库中提供的一种I/O模型,与传统的BIO( Blocking I/O)相比,NIO...对于初学者来说,这些源码实例可以帮助理解Java NIO的基本用法和优势,进一步提升在实际项目中的应用能力。