Java网络编程(5)NIO - Buffer详解

前言

前面已经大概的了解了NIO和NIO的三个组件
Java网络编程(4)NIO的理解与NIO的三个组件
并且编程了一个简单的聊天系统
现在详细的理解Buffer的作用与使用

目录

  1. Buffer
  2. 简单案例
  3. 四个属性
  4. 实例化
  5. 其他重要方法
    5.1. 四个属性的方法
    5.2. 翻转flip()
    5.3. 清除clear()
    5.4. get方法
    5.5. put方法
    5.6. ByteBuffer转换
    5.7. 一些获得Buffer信息的方法
  6. ByteBuffer类型化
  7. Buffer与Channel的交互
  8. 总结

Buffer

首先应该知道NIO的结构:
在这里插入图片描述
在NIO结构中,缓存区Buffer位与通道Channel和客户端之间,本质上是一个读写数据的内存块,是一个容器对象,底层维护着一个数组,该对象有一些属性和方法用于使用该内存块,Channel提供数据流动的渠道,但读写的数据都必须经过Buffer

Buffer类:
在这里插入图片描述
在这里插入图片描述
最顶层的是Buffer抽象类,有四个重要的属性和一些方法,有7个子类,对应着Java的7种基本数据类型(没有Boolean)
能与Channel交互的只有ByteBuffer,所以使用其他的子类最后还要转换为ByteBuffer

简单案例

package com.company.NIOBuffer;

import java.nio.IntBuffer;

public class BasicBuffer {
    public static void main(String[] args) {
        //实例化Buffer
        IntBuffer intBuffer =IntBuffer.allocate(5);
        //capacity()返回Buffer容量
        for (int i=0;i<intBuffer.capacity();i++){
            //put放入
            intBuffer.put(i*2);
        }
        //翻转读写操作
        intBuffer.flip();
        //hasRemaining()返回布尔值告诉是否达到缓冲区上界
        while (intBuffer.hasRemaining()){
            //get()返回当前位置的值
            System.out.println(intBuffer.get());
        }

    }
}

四个属性

在这里插入图片描述

  • 容量( Capacity)

缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能被改变,永远不能为负数。

  • 上界( Limit)

本质是缓冲区底层数组的下标index,缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数,limit永远不能为负数且不能大于Capacity。

  • 位置( Position)

下一个要被读或写的元素的索引。位置会自动由相应的 get( )和 put( )函数更新。

  • 标记( Mark)

一个备忘位置。调用 mark( )来设定 mark = postion。调用 reset( )设定 position = mark。标记在设定前是未定义的(undefined),mark如果在大于position或者limit时,会被丢弃

顺序:mark <= position <= limit <= capacity

Buffer维护底层数组:
在这里插入图片描述

这四个属性在各种方法中都会使用到,可以Debug看看这些属性在方法中的变化

实例化

Buffer、ByteBuffer等类都是抽象类
在这里插入图片描述
抽象类无法实例化,每种子类都提供了四个实例化方法
下面以ByteBuffer为例
ByteBuffer提供了四个静态工厂方法得到ByteBuffer实例
在这里插入图片描述
在这里插入图片描述
这四个方法:

  • allocate(int capacity)
    堆空间中分配一个容量大小为capacity的byte数组作为缓冲区的byte数据存储器(HeapByteBuffer实例)

  • allocateDirect(int capacity)
    是不使用JVM堆栈而是通过操作系统来创建内存块用作缓冲区,它与当前操作系统能够更好的耦合,因此能进一步提高I/O操作速度。但是分配直接缓冲区的系统开销很大,因此只有在缓冲区较大并长期存在,或者需要经常重用时,才使用这种缓冲区

  • wrap(byte[] array)
    这个缓冲区的数据会存放在byte数组中,bytes数组或buff缓冲区任何一方中数据的改动都会影响另一方。其实ByteBuffer底层本来就有一个bytes数组负责来保存buffer缓冲区中的数据,通过allocate方法系统会帮你构造一个byte数组(本质也是HeapByteBuffer实例)

  • wrap(byte[] array, int offset, int length)
    上一个方法的基础上可以指定偏移量和长度,这个offset也就是包装后byteBuffer的position,而length呢就是limit-position的大小,从而我们可以得到limit的位置为length+position(offset)

上面的案例:

IntBuffer intBuffer =IntBuffer.allocate(5);

使用allocate(5)方法创建了容量为5的Buffer
在这里插入图片描述在这里插入图片描述
hb是我们的底层数组,mark标记(没有使用到),position当前位置为0,limit上界为5,capacity数组大小为5

wrap方法实例化:需要一个数组

int[] ints=new int[]{1,2,3,4,5,6};
IntBuffer buffer = IntBuffer.wrap(ints);

在这里插入图片描述
可以看到已经把数组放入Buffer中

wrap方法带参数实例化:

IntBuffer buffer = IntBuffer.wrap(ints,2,2);

在这里插入图片描述
设置的两个参数是限制position和limit的,数组还是全部放进Buffer了

其他重要方法

四个属性的方法

  • capacity()
    返回Buffer容量
  • position()
    获得缓冲区当前位置
  • position(int)
    设置缓冲区的位置
  • limit()
    返回缓冲区限制
  • limit(int)
    设置缓冲区的限制
  • mark()
    在缓冲区当前位置设置标记

翻转flip()

操作缓冲区的很重要的方法flip(),翻转读写操作
上例中:

		//capacity()返回Buffer容量
        for (int i=0;i<intBuffer.capacity();i++){
            //put放入
            intBuffer.put(i*2);
 
        }

        //翻转读写操作
        intBuffer.flip();
        //hasRemaining()返回布尔值告诉是否达到缓冲区上界
        while (intBuffer.hasRemaining()){
            //get()返回当前位置的值
            System.out.println(intBuffer.get());
        }

将i*2放入了Buffer,全部放入Buffer的属性:
在这里插入图片描述
flip后:
在这里插入图片描述

很明显,翻转flip方法就是将position重置
看源代码:
在这里插入图片描述
执行了3步

接下来进行读操作就可以重新开始

清除clear()

package com.company.NIOBuffer;

import java.nio.IntBuffer;

public class BasicBuffer {
    public static void main(String[] args) {
        //实例化Buffer
        IntBuffer intBuffer =IntBuffer.allocate(5);
        //capacity()返回Buffer容量
        for (int i=0;i<intBuffer.capacity();i++){
            //put放入
            intBuffer.put(i*2);

        }
        //翻转读写操作
        intBuffer.flip();
        //hasRemaining()返回布尔值告诉是否达到缓冲区上界
        while (intBuffer.hasRemaining()){
            //get()返回当前位置的值
            System.out.println(intBuffer.get());
        }
        intBuffer.clear();
        while (intBuffer.hasRemaining()){
            //get()返回当前位置的值
            System.out.println(intBuffer.get());
        }

    }
}

在这里插入图片描述
clear方法将缓冲区的各个标记重置到初始位置,不会清除缓冲区数据

清除前:
在这里插入图片描述
清除后:
在这里插入图片描述

清除将position初始化了,所以可以再读一遍数据

看源代码:
在这里插入图片描述
确实是重置了3个标记属性

get方法

不同的子类get的数据类型不同,IntBuffer得到的当然是Int类数据
在这里插入图片描述
get有四种类型:

  • get():相对方法,读取当前位置的数据,然后position +1
  • get(int[] dst):相对体积方法,将此缓冲区传输到给定的目标数组中的数据
  • get(int[] dst, int offset, int length) :从当前位置开始相对读,读length个数据,并写入dst下标从offset到offset+length的区域
  • get(int index) : 绝对方法,从指定下标开始读取

put方法

当然还是不同子类不同类型
在这里插入图片描述

  • put(int i):相对写,向position的位置写入一个数据,并将postion+1,为下次读写作准备
  • put(int index, int i):绝对写,向Buffer底层的数组中下标为index的位置插入数据,不改变position
  • put(int[] src):用相对写,把src数组写入此缓冲区
  • put(int[] src, int offset, int length):从src数组中的offset到offset+length区域读取数据并使用相对写写入此缓冲区
    在这里插入图片描述
  • put(IntBuffer src):将另一个缓冲区的数据写入本缓冲区
    在这里插入图片描述
    3种情况会报错:src为本缓冲区、缓冲区只读、src元素比本缓冲区元素多

ByteBuffer转换

在这里插入图片描述asIntBuffer()等,将ByteBuffer当做转换的Buffer使用

byteBuffer.asIntBuffer().put(5);
System.out.println(byteBuffer.getInt());

在这里插入图片描述

通过这种方法可以将ByteBuffer转换成我们想要的Buffer

一些获得Buffer信息的方法

  • remaining() :int
    返回当前位置与限制之间的元素数
  • hasRemaining() : boolean
    告知当前位置与限制之间是否有元素
  • isReadOnly() : boolean
    此缓冲区是否为只读缓冲区
  • hasArray()
    告知此缓冲区是否有可访问的底层数组
  • array()
    返回底层数组(也就是上面截图的hb)
  • asReadOnlyBuffer()
    将Buffer设置为只读Buffer

等等还有很多

ByteBuffer类型化

ByteBuffer有一些不同,ByteBuffer中可以put不同类型数据

        ByteBuffer byteBuffer = ByteBuffer.allocate(5);
        byteBuffer.putChar('a');
       

但是取出的时候也要设置该类型,不然就会报错

		byteBuffer.flip();
        while (byteBuffer.hasRemaining()){
            System.out.println(byteBuffer.get());
        }

在这里插入图片描述

        byteBuffer.flip();
        System.out.println(byteBuffer.getChar());     

在这里插入图片描述

当放入多个属性时,要按照放入的顺序取出

        ByteBuffer byteBuffer = ByteBuffer.allocate(200);
        byteBuffer.putChar('a');
        byteBuffer.putInt(1);
        byteBuffer.putDouble(22.22);
        byteBuffer.flip();
        System.out.println(byteBuffer.getChar());
        System.out.println(byteBuffer.getInt());
        System.out.println(byteBuffer.getDouble());

在这里插入图片描述

Buffer与Channel的交互

Java网络编程(4)NIO的理解与NIO的三个组件
上一篇有Buffer的使用案例

总结

  1. Buffer是在Channel通道与客户端之间的内存块,读写数据必须经过Buffer
  2. Buffer底层是一个数组,有四个属性操作数组:mark、position、limit、capacity
  3. Buffer是个抽象类,有7个抽象类子类,分别对应Java的7种基本数据类型(没有boolean),其中能与Channel交互的只有ByteBuffer
  4. Buffer实例化有四种方法,还有一些其他重要的方法,这些方法本质上是通过四个属性操作底层数组实现
发布了95 篇原创文章 · 获赞 25 · 访问量 4193

猜你喜欢

转载自blog.csdn.net/key_768/article/details/104656219