如果将同步I/O方式下的数据传输比做数据传输的零星方式(这里的零星是指在数据传输的过程中是以零星的字节方式进行的),那么就可以将非阻塞I/O方式下的数据传输比做数据传输的集装箱方式(在字节和低层数据传输之间,多了一层缓冲区,因此,可以将缓冲区看做是装载字节的集装箱)。
如果将同步I/O方式下的数据传输比做数据传输的零星方式(这里的零星是指在数据传输的过程中是以零星的字节方式进行的),那么就可以将非阻塞I/O方式下的数据传输比做数据传输的集装箱方式(在字节和低层数据传输之间,多了一层缓冲区,因此,可以将缓冲区看做是装载字节的集装箱)。大家可以想象,如果我们要运送比较少的货物,用集装箱好象有点不太合算,而如果要运送上百吨的货物,用集装箱来运送的成本会更低。在数据传输过程中也是一样,如果数据量很小时,使用同步I/O方式会更适合,如果数据量很大时(一般以G为单位),使用非阻塞I/O方式的效率会更高。因此,从理论上说,数据量越大,使用非阻塞I/O方式的单位成本就会越低。产生这种结果的原因和缓冲区的一些特性有着直接的关系。在本节中,将对缓冲区的一些主要特性进行讲解,使读者可以充分理解缓冲区的概念,并能通过缓冲区来提高程序的执行效率。
创建缓冲区
Java提供了七个基本的缓冲区,分别由七个类来管理,它们都可以在java.nio包中找到。这七个类如下所示:
ByteBuffer
ShortBuffer
IntBuffer
CharBuffer
FloatBuffer
DoubleBuffer
LongBuffer
st1":*{behavior:url(#ieooui) }
这七个类中的方法类似,只是它们的返回值或参数和相应的简单类型相对应,如ByteBuffer类的get方法返回了byte类型的数据,而put方法需要一个byte类型的参数。在CharBuffer类中的get和put方法返回和传递的数据类型就是char。这七个类都没有public构造方法,因此,它们不能通过new来创建相应的对象实例。这些类都可以通过两种方式来创建相应的对象实例。
1. 通过静态方法allocate来创建缓冲区。
这七类都有一个静态的allocate方法,通过这个方法可以创建有最大容量限制的缓冲区对象。allocate的定义如下:
ByteBuffer类中的allocate方法:
public static ByteBuffer allocate(int capacity)
IntBuffer类中的allocate方法:
public static IntBuffer allocate(int capacity)
其他五个缓冲区类中的allocate 方法定义和上面的定义类似,只是返回值的类型是相应的缓冲区类。
allocate方法有一个参数capacity,用来指定缓冲区容量的最大值。capacity的不能小于0,否则会抛出一个IllegalArgumentException异常。使用allocate来创建缓冲区,并不是一下子就分配给缓冲区capacity大小的空间,而是根据缓冲区中存储数据的情况来动态分配缓冲区的大小(实际上,在低层Java采用了数据结构中的堆来管理缓冲区的大小),因此,这个capacity可以是一个很大的值,如1024*1024(1M)。allocate的使用方法如下:
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
IntBuffer intBuffer = IntBuffer.allocate(1024);
在使用allocate创建缓冲区时应用注意,capacity的含义随着缓冲区的不同而不同。如创建字节缓冲区时,capacity指的是字节数。而在创建整型(int)缓冲区时,capacity指的是int型值的数目,如果转换成字数,capacity的值应该乘4。如上面代码中的intBuffer缓冲区最大可容纳的字节数是1024*4 = 4096个。
2. 通过静态方法wrap来创建缓冲区。
使用allocate方法可以创建一个空的缓冲区。而wrap方法可以利用已经存在的数据来创建缓冲区。wrap方法可以将数组直接转换成相应类型的缓冲区。wrap方法有两种重载形式,它们的定义如下:
ByteBuffer类中的wrap方法:
public static ByteBuffer wrap(byte[] array)
public static ByteBuffer wrap(byte[] array, int offset, int length)
IntBuffer类中的wrap方法:
public static IntBuffer wrap(byte[] array)
public static IntBuffer wrap(byte[] array, int offset, int length)
其他五个缓冲区类中的wrap 方法定义和上面的定义类似,只是返回值的类型是相应的缓冲区类。
在wrap方法中的array参数是要转换的数组(如果是其他的缓冲区类,数组的类型就是相应的简单类型,如IntBuffer类中的wrap方法的array就是int[]类型)。offset是要转换的子数组的偏移量,也就是子数组在array中的开始索引。length是要转换的子数组的长度。利用后两个参数可以将array数组中的一部分转换成缓冲区对象。它们的使用方法如下:
byte[] myByte = new byte[] { 1, 2, 3 };
int[] myInt = new int[] { 1, 2, 3, 4 };
ByteBuffer byteBuffer = ByteBuffer.wrap(myByte);
IntBuffer intBuffer = IntBuffer.wrap(myInt, 1, 2);
可以通过缓冲区类的capacity方法来得到缓冲区的大小。capacity方法的定义如下:
public final int capacity()
如果使用allocate方法来创建缓冲区,capacity方法的返回值就是capacity参数的值。而使用wrap方法来创建缓冲区,capacity方法的返回值是array数组的长度,但要注意,使用wrap来转换array的字数组时,capacity的长度仍然是原数组的长度,如上面代码中的intBuffer缓冲区的capacity值是4,而不是2。
除了可以将数组转换成缓冲区外,也可以通过缓冲区类的array方法将缓冲区转换成相应类型的数组。IntBuffer类的array方法的定义方法如下(其他缓冲区类的array的定义类似):
public final int[] array()
下面的代码演示了如何使用array方法将缓冲区转换成相应类型的数组。
int[] myInt = new int[] { 1, 2, 3, 4, 5, 6 };
IntBuffer intBuffer = IntBuffer.wrap(myInt, 1, 3);
for (int v : intBuffer.array())
System.out.print(v + " ");
在执行上面代码后,我们发现输出的结果是1 2 3 4 5 6,而不是2 3 4。这说明在将子数组转换成缓冲区的过程中实际上是将整个数组转换成了缓冲区,这就是用wrap包装子数组后,capacity的值仍然是原数组长度的真正原因。在使用array方法时应注意,在以下两种缓冲区中不能使用array方法:
只读的缓冲区
如果使用只读缓冲区的array方法,将会抛出一个ReadOnlyBufferException异常。
使用allocateDirect方法创建的缓冲区。
如果调用这种缓冲区中的array方法,将会抛出一个UnsupportedOperationException异常。
可以通过缓冲区类的hasArray方法来判断这个缓冲区是否可以使用array方法,如果返回true,则说明这个缓冲区可以使用array方法,否则,使用array方法将会抛出上述的两种异常之一。
注意: 使用array方法返回的数组并不是缓冲区数据的副本。被返回的数组实际上就是缓冲区中的数据,也就是说,array方法只返回了缓冲区数据的引用。当数组中的数据被修改后,缓冲区中的数据也会被修改,返之也是如此。关于这方面内容将在下一节“读写缓冲区中的数据”中详细讲解。
在上述的七个缓冲区类中,ByteBuffer类和CharBuffer类各自还有另外一种方法来创建缓冲区对象。
l ByteBuffer类
可以通过ByteBuffer类的allocateDirect方法来创建ByteBuffer对象。allocateDirect方法的定义如下:
public static ByteBuffer allocateDirect(int capacity)
使用allocateDirect方法可以一次性分配capacity大小的连续字节空间。通过allocateDirect方法来创建具有连续空间的ByteBuffer对象虽然可以在一定程度上提高效率,但这种方式并不是平台独立的。也就是说,在一些操作系统平台上使用allocateDirect方法来创建ByteBuffer对象会使效率大幅度提高,而在另一些操作系统平台上,性能会表现得非常差。而且allocateDirect方法需要较长的时间来分配内存空间,在释放空间时也较慢。因此,在使用allocateDirect方法时应谨慎。
通过isDirect方法可以判断缓冲区对象(其他的缓冲区类也有isDirect方法,因为,ByteBuffer对象可以转换成其他的缓冲区对象,这部分内容将在后面讲解)是用哪种方式创建的,如果isDirect方法返回true,则这个缓冲区对象是用allocateDirect方法创建的,否则,就是用其他方法创建的缓冲区对象。
l CharBuffer类
我们可以发现,上述的七种缓冲区中并没有字符串缓冲区,而字符串在程序中却是最常用的一种数据类型。不过不要担心,虽然java.nio包中并未提供字符串缓冲区,但却可以将字符串转换成字符缓冲区(就是CharBuffer对象)。在CharBuffer类中的wrap方法除了上述的两种重载形式外,又多了两种重载形式,它们的定义如下:
public static CharBuffer wrap(CharSequence csq)
public static CharBuffer wrap(CharSequence csq, int start, int end)
其中csq参数表示要转换的字符串,但我们注意到csq的类型并不是String,而是CharSequence。CharSequence类Java中四个可以表示字符串的类的父类,这四个类是String、StringBuffer、StringBuilder和CharBuffer(大家要注意,StringBuffer和本节讲的缓冲区类一点关系都没有,这个类在java.lang包中)。也就是说,CharBuffer类的wrap方法可以将这四个类的对象转换成CharBuffer对象。
另外两个参数start和end分别是子字符串的开始索引和结束索引的下一个位置,如将字符串"1234"中的"23" 转换成CharBuffer对象的语句如下:
CharBuffer cb = CharBuffer.wrap("1234", 1, 3);
下面的代码演示了如何使用wrap方法将不同形式的字符串转换成CharBuffer对象。
StringBuffer stringBuffer = new StringBuffer("通过StringBuffer创建CharBuffer对象");
StringBuilder stringBuilder = new StringBuilder("通过StringBuilder创建CharBuffer对象");
CharBuffer charBuffer1 = CharBuffer.wrap("通过String创建CharBuffer对象");
CharBuffer charBuffer2 = CharBuffer.wrap(stringBuffer);
CharBuffer charBuffer3 = CharBuffer.wrap(stringBuilder);
CharBuffer charBuffer4 = CharBuffer.wrap(charBuffer1, 1, 3);
本文出自 “软件改变整个宇宙” 博客,请务必保留此出处
分享到:
相关推荐
非直接缓冲区是在Java堆中分配的,数据传输需要经过JVM堆。虽然它没有直接缓冲区那么高效,但在处理小数据量时,由于避免了额外的内存管理开销,可能更合适。非直接缓冲区可以通过`allocate()`方法创建,如下: ``...
在实际应用中,你可能还需要将生成的缓冲区坐标串进行可视化,这通常可以通过将这些坐标数据传递给GIS软件或Web地图服务(如OpenLayers、Leaflet等)来实现。为了在Eclipse中直接运行此代码,确保已经正确地添加了...
在这个特定的案例"SuperMap Objects Java线对象缓冲区分析"中,我们将深入探讨如何在Java环境下利用SuperMap Objects进行线对象的缓冲区分析。 首先,我们要理解什么是缓冲区分析。缓冲区分析是GIS中的一个基本操作...
- **未使用缓冲区的Java实现**:直接使用Java的基本流类,如`FileInputStream`和`FileOutputStream`。 - **手动缓冲的Java实现**:开发者自定义缓冲逻辑。 - **使用Java自带缓冲类的实现**:采用`BufferedReader`和`...
在Java编程语言中,我们可以利用线缓冲区生成算法来有效地处理大量的线条绘制任务,尤其是在2D图形渲染中。本工程提供的代码示例是基于Java实现的线缓冲区生成算法,特别是采用了平行双线法,这使得程序能够在内存中...
### Java入门缓冲区溢出编程心得 ...对于Java程序员来说,虽然本例使用的是C语言,但是缓冲区溢出的概念同样适用于其他语言,尤其是那些允许直接操作内存的语言。了解并掌握这一知识点对于提高软件安全性具有重要意义。
当使用BufferedReader时,数据会被一次性读取到缓冲区中,而不是每次读取一个字符,这样可以减少对底层物理设备的访问次数,从而提升性能。例如,我们通常会用以下方式创建一个BufferedReader对象,从键盘读取输入:...
通过在内存中创建一个额外的缓冲区,可以先在缓冲区中完成所有绘制,然后再一次性将缓冲区内容绘制到屏幕上,从而避免了频繁的屏幕刷新,显著提升了动画的视觉效果。无论是通过覆写`update()`还是直接在`paint()`中...
6. **编程接口**:在编程中,程序员通常通过标准输入流(如C语言的`stdin`,Java的`System.in`)访问键盘缓冲区。一些高级编程语言提供了高级API,如`readline`函数,允许更灵活地处理用户输入。 7. **多线程环境下...
2. **缓冲区类型**:如直接缓冲区(Direct Buffer)与非直接缓冲区(Heap Buffer),它们在Java中是如何实现的,以及各自的优缺点。 3. **缓冲区管理**:如何创建、使用和释放缓冲区,以及在多线程环境下的同步问题...
7. `ByteBufferUtil.java`:ByteBuffer工具类,提供了一些帮助方法,如读写数据、清空缓冲区等,以方便操作ByteBuffer。 总的来说,这个示例通过NIO实现了客户端与服务器之间的高效通信,解决了TCP沾包问题,同时...
这种方法的优势在于可以直接利用Java提供的API,而不需要手动管理额外的缓冲区。 ##### 4.2 在`paint(Graphics g)`中实现双缓冲 在`paint(Graphics g)`中实现双缓冲相对更简单,通常涉及创建一个额外的图像对象...
尽管x86架构是讨论缓冲区溢出的典型场景,但其他处理器架构(如ARM、MIPS等)也会遇到类似问题。不同架构的内存管理机制和指令集可能会带来不同的挑战。例如,某些架构可能没有显式的返回地址,或者其指针大小与x86...
2. **生产者**:负责将任务放入缓冲区,通常在主线程或其他非阻塞线程中执行。 3. **消费者**:从缓冲区取出任务并在后台线程中执行,可能使用多线程池来并发处理多个任务。 4. **同步机制**:确保生产者和消费者...
总结一下,Java双缓冲技术是通过在内存中创建一个临时缓冲区,先在缓冲区内完成所有绘图,然后一次性将缓冲区内容显示到屏幕上,以此减少屏幕闪烁。在Swing中,通过重写`paintComponent()`方法并调用`super....
在Java中,它主要应用于Swing和JavaFX等图形库。本教程将深入探讨Java双缓冲技术的原理,并通过实例源码进行讲解。 双缓冲的核心思想是创建一个后台缓冲区,用于绘制所有图形操作,然后再一次性将完成的图像显示到...
4. **写入数据**:处理完缓冲区中的数据后,我们可能会选择将其写入另一个位置,如输出文件。这里同样可以使用`fwrite`或`std::ofstream`的`write`方法。 5. **缓冲区管理**:在处理过程中,我们需要管理缓冲区的...
本文将深入探讨Buffer的基本原理,其在应用缓冲区中的使用,以及在Socket通信中的具体实践。 首先,理解Buffer的基本概念。Buffer是内存块,用于临时存储数据,它允许程序以一种高效的方式处理数据流。在数据处理...
在Java编程语言中,"画图(双缓冲)"是一个重要的技术概念,它...这个应用可能包含了各种图形操作,如线条绘制、填充、旋转、缩放等,所有这些操作都在后台缓冲区完成,确保最终呈现给用户的是一副完整、无瑕疵的画面。