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等。

类定义

  1. public abstract class Buffer {}

构造方法

Buffer的构造方法是包私有的:

  1. Buffer(int mark, int pos, int lim, int cap) { // package-private
  2. if (cap < 0)
  3. throw new IllegalArgumentException("Negative capacity: " + cap);
  4. this.capacity = cap;
  5. limit(lim);
  6. position(pos);
  7. if (mark >= 0) {
  8. if (mark > pos)
  9. throw new IllegalArgumentException("mark > position: ("
  10. + mark + " > " + pos + ")");
  11. this.mark = mark;
  12. }
  13. }

可以看到它需要四个参数来构造,分别是mark、pos、lim和cap。可以看到它首先为capacity赋值,因为capacity在Buffer构建后就不会变。然后分别调用了limit()和position()方法为limit和position赋值,我们来看一下这两个方法:

  1. public final Buffer limit(int newLimit) {
  2. if ((newLimit > capacity) || (newLimit < 0))
  3. throw new IllegalArgumentException();
  4. limit = newLimit;
  5. if (position > limit) position = limit;
  6. if (mark > limit) mark = -1;
  7. return this;
  8. }

这个方法主要是检查limit是否合法,例如limit不能大于capacity,position不能大于limit等。然后会为limit赋值。

  1. public final Buffer position(int newPosition) {
  2. if ((newPosition > limit) || (newPosition < 0))
  3. throw new IllegalArgumentException();
  4. position = newPosition;
  5. if (mark > position) mark = -1;
  6. return this;
  7. }

position和limit一样,是对position进行合法性检查,mark不能大于position。
注意limit和position这两个方法都是公有的,也就是我们可以直接设置一个Buffer的limit和position。
Buffer的构造方法就是为mark、position、limit和capacity进行了合法性检查和赋值,没有进行其他操作。

访问capacity、limit、position和mark

我们可以通过下面两个方法来获取和设置position:

  1. public final int position() {
  2. return position;
  3. }
  4. public final Buffer position(int newPosition) {
  5. if ((newPosition > limit) || (newPosition < 0))
  6. throw new IllegalArgumentException();
  7. position = newPosition;
  8. if (mark > position) mark = -1;
  9. return this;
  10. }

当然在设置时会进行合法性检查,position不能大于limit,也不能为负数。
capacity是在Buffer创建时就固定的,不能设置,只能获取:

  1. public final int capacity() {
  2. return capacity;
  3. }

limit也能获取和设置:

  1. public final int limit() {
  2. return limit;
  3. }
  4. public final Buffer limit(int newLimit) {
  5. if ((newLimit > capacity) || (newLimit < 0))
  6. throw new IllegalArgumentException();
  7. limit = newLimit;
  8. if (position > limit) position = limit;
  9. if (mark > limit) mark = -1;
  10. return this;
  11. }

mark有点特殊,我们需要来讲一下mark的含义,从字面上来理解,它就是一个标记。它不一定要设置。我们可以通过mark方法来设置它,这个方法会将mark设置为当前的position:

  1. public final Buffer mark() {
  2. mark = position;
  3. return this;
  4. }

也就是mark会记住当前的position。然后我们可以通过reset方法来重置:

  1. public final Buffer reset() {
  2. int m = mark;
  3. if (m < 0)
  4. throw new InvalidMarkException();
  5. position = m;
  6. return this;
  7. }

它会将position设置为mark,因为mark是我们之前的position,所以这个动作是重置。从这两个操作就可以看出,mark不能大于position。当我们设置limit和position的值为小于mark的值时,mark会被抛弃,这个从上面limit(int)和position(int)方法可以看出,如果mark大于position,mark会被设置为-1。

清空Buffer

buffer的clear方法用来清空缓存,但是它并不会删除数据,只是设置上面说的几个参数:

  1. public final Buffer clear() {
  2. position = 0;
  3. limit = capacity;
  4. mark = -1;
  5. return this;
  6. }

将position设置为0,limit设置为capacity,mark为-1。

remaining方法

remaining方法返回的是position到limit之间元素的数量。

  1. public final int remaining() {
  2. return limit - position;
  3. }

hasReminging方法

hasRemaining方法判断limit到position之间是否还有元素。

  1. public final boolean hasRemaining() {
  2. return position < limit;
  3. }

hasArray、array方法

这个方法判断这个buffer背后是否有一个可访问的数组。如果这个方法返回true的话,下面要说的array方法就可以安全执行。这是一个抽象方法,需要各个子类自己去给出实现:

  1. public abstract boolean hasArray();

array方法返回这个buffer后面的数组,这也是一个抽象方法,需要子类自己给出实现,注意返回的对象类型是object,但是子类中可能返回更加准确的类型。注意,如果一个buffer后面有一个数组的话,那么对相应数组的修改会反映到buffer上面,反之亦然。

  1. public abstract Object array();

总结

Buffer作为NIO的重要组成部分,这里我们并没有介绍它在NIO编程中的用法,而是只介绍了它的实现以及一些常用的用法,了解了它的实现之后,我们会继续看它在NIO中的使用。
在上面的方法中,我们并没有看到创建buffer的方法,仅有的一个构造方法还是包私有的,也没有对缓存进行存取的方法。实际上,buffer的创建以及存取是在各个子类中给出了实现,因为buffer的各个子类都是对原始数据类型(primary type)的实现,我们不能对原始数据类型使用泛型,所以这里没有通过多态在父类Buffer中实现。