前言
一直想把NIO系列的文章更新下去,只不过发现在入职支付宝后工作实在是忙,所以一直拖到现在。直接从一个学生过狗成为一名加班狗,好吧,这就是互联网公司的现状吧,但是每天都是充实的,而且发现其他的员工也基本非常乐意加班,难道这就是阿里的文化熏陶?!
废话不多说,还是进入今天的正题,在前面的文章中,我们已经对Java的NIO有了一个粗浅的认识——主要之为了支持非阻塞I/O的操作,之前的BIO则是阻塞的。因为异步性的引入,大大提高了Java在处理异步任务的效率。下面认识下NIO中的缓冲区:
认识缓冲区
缓冲区可以理解为存储固定数量的容器,这些数据可以在存储后被检索,缓冲区的数据可以被写满或者被释放,而这些操作的实现需要认识缓冲区的属性:容量(capacity)、上界(limit)、位置(position)和标记(mark)。这四个属性的关系可以用下图来表示:

理解好这张图,缓冲区的精髓就掌握的四五分了,还有四五分从哪里来呢?当然是实践中来了。这张图传达了如下的信息:
0 <= mark <= position <= limit <= capacity
容量
容量是指缓冲区可以容纳的最大元素个数,这一容量在缓冲区被创建的时候就被指定了。
上界
就是缓冲区现存元素的计数
位置
下一个要被读取的元素的索引
标记
由于在读取过程中position的值是动态变化,如果在读到某一个位置的时候需要做一个标记以便下次继续读,就可以做一个“书签”,让mark的值等于当前读到的position的值。
在java.nio包中Buffer类是缓冲区的顶层类,其他的缓冲区类都是基于这个类展开的。一个缓冲区类的主要方法如下:
public abstract class Buffer {
public final int capacity( );
public final int position( );
public final Buffer position (int newPositio);
public final int limit( );
public final Buffer limit (int newLimit);
public final Buffer mark( );
public final Buffer reset( );
public final Buffer clear( );
public final Buffer flip( );
public final Buffer rewind( );
public final int remaining( );
public final boolean hasRemaining( );
public abstract boolean isReadOnly( );
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
作为一个缓冲区,肯定是需要存取数据的,但是在Buffer类中并没有get和put类似的方法,实际上Java 设计者将get和put方法放到子类实现了。这样设计主要是考虑到在只需要关心缓冲区钟部分数据的情况,因为在这种情况可能有不同的存取策略。
缓冲区实战
现在已经对缓冲区有了一个基本认识,缓冲区的操作主要涉及以下几个部分:
存取
翻转
释放
压缩
标记
比较
批量移动
存取、翻转和释放
在缓冲区存取元素,需要创建缓冲区,存取操作的两个核心方法是:get和put方法,完成翻转只需要调用flip方法。下面是一个简单示例:
package com.rhwayfun.patchwork.nio.buffer;
import java.nio.CharBuffer;
/**
* @description: Buffer缓冲区演示
* @author: rhwayfun
* @since: 2016-05-21
*/
public class BufferFillDrain {
private static int index = 0;
private static String[] strings = new String[]{
"Hello, this is a buffer",
"I am learning buffer",
"It is a wonderful world"
};
public static void main(String[] args) {
CharBuffer buffer = CharBuffer.allocate(100);
while (fillBuffer(buffer)){
buffer.flip();
drainBuffer(buffer);
buffer.clear();
}
}
private static void drainBuffer(CharBuffer buffer) {
int count = buffer.remaining();
for (int i = 0; i < count; i++){
System.out.println(i + "--->" + buffer.get());
}
}
private static boolean fillBuffer(CharBuffer buffer) {
if (index >= strings.length) {
return false;
}
String s = strings[index++];
for (int i = 0; i < s.length(); i++){
buffer.put(s.charAt(i));
}
return true;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
压缩
压缩使用在需要释放缓冲区的空间的场景,执行压缩需要调用compact方法。该方法的会首先将position位置之后的元素(如果没有调用flip方法的话)复制从0开始的位置上,而position位置及之后的元素保持不变,在进行元素复制的过程中position的值也会发生改变。压缩的过程可以用如下的图表示:

下面的代码有演示了这个过程:
package com.rhwayfun.patchwork.nio.buffer;
import java.nio.CharBuffer;
/**
* @description: 使用compact方法释放一部分数据
* @author: rhwayfun
* @since: 2016-05-21
*/
public class BufferCompact {
public static void main(String[] args) {
String s = "hello";
CharBuffer buffer = CharBuffer.allocate(10);
for (int i = 0; i < s.length(); i++){
buffer.put(s.charAt(i));
}
buffer.put(0,'M').put('w');
buffer.flip();
System.out.println("release char " + buffer.get());
System.out.println("release char " + buffer.get());
buffer.compact();
buffer.put('a').put('b');
buffer.flip();
while (buffer.hasRemaining()){
System.out.println("print output " + buffer.get());
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
比较
缓冲区的比较可以使用equals方法,进行缓冲区的比较的意义在于检测缓冲区包含的数据是否相同。当然,缓冲区的比较也支持使用compareTo方法进行比较。如果buffer1.equals(buffer2)要返回true,那么需要满足以下条件才可以:
- 两个对象类型相同。包含不同数据类型的 buffer 永远不会相等,而且 buffer绝不会等于非 buffer 对象。
- 两个对象都剩余同样数量的元素。Buffer 的容量不需要相同,而且缓冲区中剩余数据的索引也不必相同。但每个缓冲区中剩余元素的数目(从位置到上界)必须相同。
- 在每个缓冲区中应被 Get()函数返回的剩余数据元素序列必须一致。
下面的代码演示了进行缓冲区比较的过程:
package com.rhwayfun.patchwork.nio.buffer;
import java.nio.CharBuffer;
/**
* @description: 使用equals方法比较两个缓冲区是否相等
* @author: rhwayfun
* @since: 2016-05-21
*/
public class BufferCompareWithEquals {
public static void main(String[] args) {
String s1 = "two com";
String s2 = "com";
CharBuffer buffer1 = CharBuffer.allocate(10);
CharBuffer buffer2 = CharBuffer.allocate(10);
for (int i = 0; i < s1.length(); i++){
buffer1.put(s1.charAt(i));
}
buffer1.flip();
buffer1.put('A');
buffer1.position(4);
for (int i = 0; i < s2.length();i++){
buffer2.put(s2.charAt(i));
}
buffer2.flip();
buffer2.limit(4);
buffer2.put(3,'B');
buffer2.position(0);
buffer2.limit(3);
System.out.println(buffer1.equals(buffer2));
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
批量操作
在之前的get和put方法,一次只能支持操作一个元素,从效率的角度讲还是很低的,那么有没有批量操作的方法呢?在Buffer API中可以使用重载的get和put方法进行批量的操作。下面的代码演示了如何进行缓冲区的批量操作:
package com.rhwayfun.patchwork.nio.buffer;
import java.nio.CharBuffer;
/**
* @description: 批量操作缓冲区
* @author: rhwayfun
* @since: 2016-05-21
*/
public class BufferBatchOperation {
public static void main(String[] args) {
copyToBigArray();
copyToSmallArray();
System.out.println();
putStringToBuffer();
}
private static void copyToBigArray() {
String s = "hello,rhwayfun";
CharBuffer buffer = CharBuffer.allocate(20);
for (int i = 0; i < s.length(); i++){
buffer.put(s.charAt(i));
}
buffer.flip();
char[] array = new char[100];
int remaining = buffer.remaining();
buffer.get(array,0,remaining);
for (char c : array){
System.out.print(c + " ");
}
}
private static void copyToSmallArray(){
String s = "hello,rhwayfun";
CharBuffer buffer = CharBuffer.allocate(20);
for (int i = 0; i < s.length(); i++){
buffer.put(s.charAt(i));
}
buffer.flip();
char[] array = new char[10];
while (buffer.hasRemaining()){
int length = Math.min(array.length,buffer.remaining());
buffer.get(array,0,length);
}
}
private static void putStringToBuffer(){
String s = "Good morning";
CharBuffer buffer = CharBuffer.allocate(20);
buffer.put(s);
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print(buffer.get() + " ");
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
小结
通过以上的叙述以及针对缓冲区的也也演示操作,我们对缓冲区的掌握也更加深入了,其实对缓冲区的理解最关键的还是缓冲区的模型图。这个图理解了,掌握Buffer类的API也就驾轻就熟了。