JDK中的NIO有很多概念,我们先从Buffer开始学习。Buffer可以看做是一个原始数据类型的容器,注意是原始数据类型。它的很多概念和实现我们可以从Buffer这个类中看到。java.nio.Buffer是NIO中各种缓存的最上层抽象类,它定义了Buffer的很多基本操作。
这里我们先描述一下Buffer中的重要属性及其含义。Buffer是一个线性的、有限的原始数据类型的序列。一个Buffer有三个重要的属性,是capacity、limit和position。capacity是Buffer里面元素的数量,capacity永远是非负的并且永远不会改变。limit是Buffer中第一个不能被读或者写的元素的索引,limit永远是非负的并且永远不会大于capacity。position是Buffer中下一个要被读或者写的元素的索引,position永远是非负的并且不会大于capacity。
上面对于capacity、limit和position的解释是基于官方注释的,不太容易理解。通俗地说一下,我们可以把Buffer看做一个数组,capacity就是数组的长度,比如数组的长度是100,此时capacity就是100,但是数组中还没有数据,我们也还没有读取这个Buffer,此时position就是0,limit此时是100,因为数组中的所有元素都是可以读取的,只不过是null。不过Buffer可以让我们更好地控制这些参数,一会我们就会看到我们能通过方法来设置这些参数。
对于每个非boolean类型的原始类型,都有一个对应的Buffer子类,例如IntBuffer、ByteBuffer、CharBuffer、LongBuffer等。
类定义
public abstract class Buffer {}
构造方法
Buffer的构造方法是包私有的:
Buffer(int mark, int pos, int lim, int cap) { // package-private
if (cap < 0)
throw new IllegalArgumentException("Negative capacity: " + cap);
this.capacity = cap;
limit(lim);
position(pos);
if (mark >= 0) {
if (mark > pos)
throw new IllegalArgumentException("mark > position: ("
+ mark + " > " + pos + ")");
this.mark = mark;
}
}
可以看到它需要四个参数来构造,分别是mark、pos、lim和cap。可以看到它首先为capacity赋值,因为capacity在Buffer构建后就不会变。然后分别调用了limit()和position()方法为limit和position赋值,我们来看一下这两个方法:
public final Buffer limit(int newLimit) {
if ((newLimit > capacity) || (newLimit < 0))
throw new IllegalArgumentException();
limit = newLimit;
if (position > limit) position = limit;
if (mark > limit) mark = -1;
return this;
}
这个方法主要是检查limit是否合法,例如limit不能大于capacity,position不能大于limit等。然后会为limit赋值。
public final Buffer position(int newPosition) {
if ((newPosition > limit) || (newPosition < 0))
throw new IllegalArgumentException();
position = newPosition;
if (mark > position) mark = -1;
return this;
}
position和limit一样,是对position进行合法性检查,mark不能大于position。
注意limit和position这两个方法都是公有的,也就是我们可以直接设置一个Buffer的limit和position。
Buffer的构造方法就是为mark、position、limit和capacity进行了合法性检查和赋值,没有进行其他操作。
访问capacity、limit、position和mark
我们可以通过下面两个方法来获取和设置position:
public final int position() {
return position;
}
public final Buffer position(int newPosition) {
if ((newPosition > limit) || (newPosition < 0))
throw new IllegalArgumentException();
position = newPosition;
if (mark > position) mark = -1;
return this;
}
当然在设置时会进行合法性检查,position不能大于limit,也不能为负数。
capacity是在Buffer创建时就固定的,不能设置,只能获取:
public final int capacity() {
return capacity;
}
limit也能获取和设置:
public final int limit() {
return limit;
}
public final Buffer limit(int newLimit) {
if ((newLimit > capacity) || (newLimit < 0))
throw new IllegalArgumentException();
limit = newLimit;
if (position > limit) position = limit;
if (mark > limit) mark = -1;
return this;
}
mark有点特殊,我们需要来讲一下mark的含义,从字面上来理解,它就是一个标记。它不一定要设置。我们可以通过mark方法来设置它,这个方法会将mark设置为当前的position:
public final Buffer mark() {
mark = position;
return this;
}
也就是mark会记住当前的position。然后我们可以通过reset方法来重置:
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
它会将position设置为mark,因为mark是我们之前的position,所以这个动作是重置。从这两个操作就可以看出,mark不能大于position。当我们设置limit和position的值为小于mark的值时,mark会被抛弃,这个从上面limit(int)和position(int)方法可以看出,如果mark大于position,mark会被设置为-1。
清空Buffer
buffer的clear方法用来清空缓存,但是它并不会删除数据,只是设置上面说的几个参数:
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
将position设置为0,limit设置为capacity,mark为-1。
remaining方法
remaining方法返回的是position到limit之间元素的数量。
public final int remaining() {
return limit - position;
}
hasReminging方法
hasRemaining方法判断limit到position之间是否还有元素。
public final boolean hasRemaining() {
return position < limit;
}
hasArray、array方法
这个方法判断这个buffer背后是否有一个可访问的数组。如果这个方法返回true的话,下面要说的array方法就可以安全执行。这是一个抽象方法,需要各个子类自己去给出实现:
public abstract boolean hasArray();
array方法返回这个buffer后面的数组,这也是一个抽象方法,需要子类自己给出实现,注意返回的对象类型是object,但是子类中可能返回更加准确的类型。注意,如果一个buffer后面有一个数组的话,那么对相应数组的修改会反映到buffer上面,反之亦然。
public abstract Object array();
总结
Buffer作为NIO的重要组成部分,这里我们并没有介绍它在NIO编程中的用法,而是只介绍了它的实现以及一些常用的用法,了解了它的实现之后,我们会继续看它在NIO中的使用。
在上面的方法中,我们并没有看到创建buffer的方法,仅有的一个构造方法还是包私有的,也没有对缓存进行存取的方法。实际上,buffer的创建以及存取是在各个子类中给出了实现,因为buffer的各个子类都是对原始数据类型(primary type)的实现,我们不能对原始数据类型使用泛型,所以这里没有通过多态在父类Buffer中实现。