[转载请注明作者和出处, 如有谬误, 欢迎在评论中指正. ]
在java NIO中, 通道是IO传输发生时数据通过的入口, 而缓冲区是数据的来源或目标.
Buffer是java NIO中定义的所有缓冲区类的基类.
Buffer的属性
1. 容量(capacity)
缓冲区能够容纳的数据元素的最大数量, capacity在创建缓冲区时指定, 之后无法改变.
2. 上界(limit)
读模式下, limit表示能读取的最后一个字节; 写模式下, limit等于capacity.
3. 位置(position)
position表示当前索引.
4. 标记(mark)
mark表示一个备忘position.
属性之间的关系:0 <= mark <= position <= limit <= capacity
Buffer中与属性相关的API
1. int capacity(). 该方法返回Buffer的容量.
2. clear(). 该方法并不改变Buffer中已有的任何数据, 只是将position设置为0, limit设置为capacity. 一般在向缓冲区写入数据之前调用.
3. flip(). 将Buffer从写模式反转成读模式. 该方法将limit设置为当前position, 然后将position设置为0. 一般在读取缓冲区数据之前调用.
4. boolean hasRemaining(). 当前position和limit之间是否有元素.
5. int limit(). 返回Buffer的limit.
6. limit(int newLimit). 设置Buffer的limit.
7. mark(). 标记当前position.
8. int position(). 返回当前的position.
9. position(int newPosition). 设置position.
10. int remaining(). 返回position和limit之间的元素个数.
11. reset(). 将position设置为之前mark的位置. 如果mark无效, 将抛出InvalidMarkException异常.
12. rewind(). 将position设置为0, 并抛弃mark. 不改变limit的值. 一般用于重新读取缓冲区中的数据.
填充和释放缓冲区
由于Buffer的各个子类的填充和释放缓冲区的方法所需的参数不一致, 所以没有将这些方法抽象到Buffer类中. 以ByteBuffer为例, 其填充和释放缓冲区方法有:
1. byte get(). 相对get操作, 该方法返回当前position处的byte数据, 并将position加1. 如果当前position大于等于limit, get()方法将抛出BufferUnderflowException异常.
2. byte get(int index). 绝对get操作, 该方法返回index处的byte数据, 不position的值. 如果指定的index小于0, 或者大于等于limit, 将方法将抛出IndexOutOfBoundsException异常.
3. get(byte[] dst, int offset, int length). 相对批量get操作, 与多次get操作比较, 该方法具有更高的效率. 如果length大于缓冲区的剩余字节, 将抛出BufferUnderflowException异常. 如果缓冲区存有比数组能容纳的数量更多的数据, 可以使用如下的方式处理:
char[] smallArray = new char[10];
while (buffer.hasRemaining()) {
int length = Math.min(buffer.remaining(), smallArray.length);
buffer.get(smallArray, 0, length);
processData(smallArray, length);
}
4. put(byte b). 相对put操作, 该方法将byte数据存入position处, 并使得position加1. 如果当前position大于等于limit, 该方法将抛出BufferOverflowException异常.
5. put(int index, byte b). 绝对put操作, 该方法将byte数据存入index处, 不改变position的值. 如果指定的index小于0, 或者大于等于limit, 将方法将抛出IndexOutOfBoundsException异常.
6. put(byte[] src, int offset, int length). 相对批量put操作, 与多次put操作比较, 该方法具有更高的效率. 如果length大于缓冲区的剩余字节, 将抛出BufferOverflowException异常.
7. compact(). 该方法将当前position和limit之间的数据拷贝到缓冲区开始部分, 并将position设置为拷贝的数据的个数, limit设置为capacity.
创建缓冲区
Buffer的子类如ByteBuffer, CharBuffer等都是抽象类, 提供静态方法创建缓冲区对象, 以ByteBuffer为例:
1. allocate(int capacity). 创建一个指定capacity的ByteBuffer对象. 该方法返回的ByteBuffer对象底层维护了一个byte数组, 数组的length为指定的capacity.
2. wrap(byte[] array). 使用指定的byte数组创建缓冲区. 调用put操作造成的改动会直接影响这个数组, 对数组的改动也会对该ByteBuffer对象可见.
3. wrap(byte[] array, int offset, int length). 与上个方法类似, 但是使用offset初始化ByteBuffer对象的position, 使用length+offset初始化ByteBuffer对象的limit.
ByteBuffer类提供了获取其底层实现数组的方法:
1. boolean hasArray(). 该方法的返回值表明是否可以获取到ByteBuffer对象的底层实现数组. 如果该方法返回true, 则可以安全的调用array()和arrayOffset()方法. 如果缓冲区为只读缓冲区, 则该方法返回false.
2. byte[] array(). 该方法返回ByteBuffer对象的底层实现数组.
3. int arrayOffset(). 该方法返回缓冲区数据在数组中存储的开始位置的偏移量.
直接缓冲区
通过缓冲区类的allocateDirect(int capacity)方法可以创建相应的直接缓冲区, 缓冲区对象的isDirect方法的返回值表明该缓冲区是否为直接缓冲区. 直接缓冲区具有更好的IO效率, 同时, 创建和销毁直接缓冲区需要更大的代价. 直接缓冲区使用的内存是通过调用本地操作系统的代码分配的, 而不是在JVM堆栈中分配的, 使用直接缓冲区可能引入平台依赖.
总之, 使用直接缓冲区需要谨慎权衡: 如果IO效率不是系统的瓶颈, 最好不要使用直接缓冲区.
字节顺序
多字节数据的存储顺序在不同的平台上可能不同, 有的平台默认使用big-endian(大端字节顺序), 有的平台则默认使用little-endian(小端字节顺序). big-endian的数据的高位存储在低位地址上, little-endian正好与之相反, 平台的默认字节顺序一般是由硬件决定的.
在java中, 字节顺序由ByteOrder类封装. ByteOrder类只有2个对象(其构造函数是private的)--ByteOrder.BIG_ENDIAN和ByteOrder.LITTLE_ENDIAN, 分别代表大端和小端字节顺序. ByteOrder.nativeOrder()方法返回当前平台的字节顺序.
所有缓冲区类都具有ByteOrder order()方法, 返回缓冲区所使用的字节顺序. ByteBuffer之外的缓冲区类的字节顺序是一个只读属性(一旦创建就确定了, 且无法修改): 通过allocate或wrap方法创建的缓冲区的字节顺序和ByteOrder.nativeOrder()方法的返回值相同, 如果缓冲区是另一个缓冲区的视图, 则视图的字节顺序和原始缓冲区的自己顺序相同.
ByteBuffer类则比较特殊, 可以通过order(ByteOrder bo)方法随时改变ByteBuffer对象的字节顺序, 如果没有指定, ByteBuffer的默认字节顺序为ByteOrder.BIG_ENDIAN.
public static void main(String[] args) {
// 默认字节顺序为ByteOrder.BIG_ENDIAN
ByteBuffer bb = ByteBuffer.allocate(4);
bb.putInt(0xabcd1234);
bb.flip();
System.out.println(Integer.toHexString(bb.getInt()));
// 改变其字节顺序
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.flip();
System.out.println(Integer.toHexString(bb.getInt()));
}
缓冲区视图
缓冲区视图和原始缓冲区共享全部或部分数据, 但是具有独立的capacity, limit, position, mark属性.
所有缓冲区类都有以下3个API用于创建其视图, 以CharBuffer为例:
1. CharBuffer duplicate(). 创建一个缓冲区视图, 视图和原始缓冲区共享全部数据, 对一个缓冲区内的数据元素所做的改变会反映在另外一个缓冲区上, 并继承原始缓冲区的direct和readOnly属性. 但是具有独立的capacity, limit, position, mark.
public static void main(String[] args) {
CharBuffer cb = CharBuffer.allocate(100);
cb.put("xing");
CharBuffer view = cb.duplicate();
// 视图的capacity, limit, position等属性初始值和原始缓冲区相同
printProperty(cb);
printProperty(view);
// 视图继承原始缓冲区的direct和readOnly属性
System.out.println(view.isDirect());
System.out.println(view.isReadOnly());
// 但是视图的capacity, limit, position等属性是独立的
view.put(" zhang");
printProperty(cb);
printProperty(view);
// 视图和原始缓冲区共享数据, 修改了视图会影响到原始缓冲区
cb.position(view.position());
cb.flip();
char[] dst = new char[cb.limit()];
cb.get(dst, 0, dst.length);
System.out.println(new String(dst));
}
private static void printProperty(CharBuffer cb) {
System.out.println(cb.position() + "@" + cb.limit() + "@" + cb.capacity());
}
2. CharBuffer asReadOnlyBuffer(). 创建只读缓冲区视图. 只读缓冲区视图不允许使用put操作, 并且其isReadOnly()函数将会返回true. 其余和duplicate()方法返回的缓冲区视图完全相同.
3. CharBuffer slice(). slice方法返回的缓冲区视图和duplicate()方法返回的缓冲区视图区别在于, slice视图只和原始缓冲区共享部分数据元素, slice视图的初始属性值分别为: position是0, limit和capacity为原始缓冲区的limit-position. 其余部分和duplicate视图完全相同.
除了以上3个API, ByteBuffer类提供了其特有的创建视图缓冲区的API:
public abstract CharBuffer asCharBuffer();
public abstract ShortBuffer asShortBuffer();
public abstract IntBuffer asIntBuffer();
public abstract LongBuffer asLongBuffer();
public abstract FloatBuffer asFloatBuffer();
public abstract DoubleBuffer asDoubleBuffer();
由于char, short, int, long, float, double都是多字节数据, 因此ByteBuffer的字节顺序决定了多个字节以怎样的形式组织成一个数据.
分享到:
相关推荐
总结来说,"NIO原生API示例"是一个展示如何使用Java NIO进行非阻塞I/O操作的程序,包括通道、缓冲区和选择器的使用,以及如何处理客户端连接和数据传输。通过分析"ServerImpCopy.java",我们可以学习到NIO在实际项目...
在Java的NIO(New Input/Output)框架中,缓冲区(Buffer)是核心概念之一。本篇文章将深入探讨NIO中的缓冲区特性以及分散/聚集IO操作,这对于理解和优化Java程序的I/O性能至关重要。 缓冲区是NIO中处理数据的主要...
标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。 Java NIO: Non-blocking IO(非阻塞IO) Java NIO...
NIO的API设计考虑到了性能和灵活性,通过使用缓冲区和通道,可以让开发者更好地控制数据的处理过程,同时利用缓冲机制实现数据的批量操作。 在Java NIO中,所有的输入输出操作都围绕着Buffer和Channel这两个核心...
3. **缓冲区(Buffer)**:用于存储数据,是NIO的主要操作对象,减少了数据拷贝的开销。 在本项目中,NIO可能被用于实现以下功能: 1. **高效网络通信**:通过NIO的非阻塞特性,服务器可以同时处理大量客户端请求,...
解决这些问题通常需要深入理解NIO的工作原理,包括缓冲区(Buffer)、通道(Channel)、选择器(Selector)等核心概念,并且需要对Servlet容器的内部机制有一定的了解。 在提供的文件列表中,"src"目录可能包含了源...
2. **缓冲区(Buffer)**:缓冲区是NIO中数据存储的主要对象,它是内存块的抽象,用于在通道和应用程序之间传输数据。Java NIO提供了多种类型的缓冲区,如ByteBuffer、CharBuffer、IntBuffer、FloatBuffer等,每种...
NIO的核心特性包括基于缓冲区的I/O操作和非阻塞I/O,这使得应用程序能够处理更多的连接,同时减少资源的消耗。 NIO API主要分布在以下几个包中: 1. `java.nio`:这个包定义了Buffer及其子类,例如ByteBuffer,...
2. Limit(限制):表示缓冲区第一个不应读取或写入的元素的基于零的索引,即标识了缓冲区中活跃数据的数量。 3. Position(位置):表示下一个可以读取的数据项的基于零的索引,或数据项可以被写入的位置。 4. Mark...
- **缓冲区**:缓冲区是NIO的核心组件,它是一种特殊类型的数组,用于存储各种基本数据类型的数据。Buffer提供了一种高效且可控的方式来读写数据,如get()和put()方法,以及position()、limit()和capacity()属性来...
然而,尽管Java NIO提供了很多优势,但在实际使用中,开发者还需要注意一些潜在的问题,如内存管理(缓冲区过大可能导致内存溢出)和复杂性(相对于传统IO,NIO的API可能更复杂)。 总之,Java NIO为处理大数据文件...
NIO的主要特点是面向缓冲区,非阻塞I/O,以及选择器,这些特性使得NIO在处理大量并发连接时表现出更高的效率。在本篇文章中,我们将深入探讨Java NIO如何读取文件。 一、NIO的基本概念 1. 缓冲区(Buffer):NIO的...
Java NIO_API通过引入基于缓冲区的非阻塞I/O机制,显著提升了I/O处理的性能和灵活性,尤其是在高并发服务器应用中。通过对`Buffer`、`Channel`和`Selector`等核心概念的理解与运用,开发者能够构建出更加高效、响应...
NIO提供了一种基于缓冲区(Buffer)的非阻塞I/O操作机制,极大地提高了I/O处理的性能和效率。本文将详细介绍NIO的主要组成部分及其实现原理,帮助开发者更好地理解和应用NIO技术。 #### 二、NIO API概述 NIO API...
NIO的引入主要是为了解决传统IO(即BIO)中高并发时的性能问题,因为NIO采用的是基于缓冲区和基于通道(Channel)的方式进行操作,可以通过选择器(Selectors)实现对多个通道(Channel)的管理。 NIO和传统的IO...