zoukankan      html  css  js  c++  java
  • Nio数据载体-Buffer

    写在前面

    作为NIO核心组件之一,Buffer起着数据容器的作用.那么它和我们传统上使用java.io开发有什么不同呢?

    在java.nio的包描述里面,有这样一段话:

    The central abstractions of the NIO APIs are:

    Buffers, which are containers for data;

    Charsets and their associated decoders and encoders,
    which translate between bytes and Unicode characters;

    Channels of various types, which represent connections
    to entities capable of performing I/O operations; and

    Selectors and selection keys, which together with
    selectable channels define a multiplexed, non-blocking
    I/O facility.

    1
    2
    3
    4
    5
    6
    public void (){
    InputStream is=new FileInputStream("textFileName.txt");



    }

    java.io中,最核心的一个概念是流(Stream),在java中,io编程被称为面向流的编程.流是信息的载体.

    java.nio 是面向块的编程

    ByteBuffer概念

    ByteBuffer对象是固定数量的数据的容器,和List下的ArrayList类似,内核都是一个数组,与ArrayList不同的是,ByteBuffer内部是一个final byte[] hb.

    操纵ByteBuffer,本质上就是在操纵数组,所有的缓冲区都具有四个属性来提供关于其所包含的数据元素的信息:

    • 标记(mark): 一个备忘位置.
    • 位置(position): 下一个要被读或者写的元素的索引.由get或者put自动更新.
    • 上界(limit): 缓冲区现存元素的计数
    • 容量(capacity): 缓冲区的最大容量,在这里,是指的数组的长度.在构造ByteBuffer的必须指定,且永远不能被修改.

    四个属性的关系为:0<= mark <= position <= limit <= capacity

    默认情况下,在创建一个ByteBuffer的时候,limit=capacity,mark=-1,position=0.

    直接缓冲区和非直接缓冲区

    ByteBuffer有三个子类(这里不讨论HeapByteBufferRDirectByteBufferR)

    三个子类中,DirectByteBufferHeapByteBuffer属于包可见的子类,不对外提供服务,MappedByteBuffer是和文件相关的缓冲区.C/C++中存在一个名词,叫做共享内存,java的共享内存,可以由MappedByteBuffer实现.

    ByteBuffer是抽象类,不能直接new,但是其提供了两个工厂方法allocateallocateDirect.

    先看一下allocateDirect:

    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
    public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
    }



    DirectByteBuffer(int cap) { // package-private

    super(-1, 0, cap, cap);
    boolean pa = VM.isDirectMemoryPageAligned();
    int ps = Bits.pageSize();
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    Bits.reserveMemory(size, cap);

    long base = 0;
    try {
    base = unsafe.allocateMemory(size);
    } catch (OutOfMemoryError x) {
    Bits.unreserveMemory(size, cap);
    throw x;
    }
    unsafe.setMemory(base, size, (byte) 0);
    if (pa && (base % ps != 0)) {
    // Round up to page boundary
    address = base + ps - (base & (ps - 1));
    } else {
    address = base;
    }
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;

    }

    再来allocate

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public static ByteBuffer allocate(int capacity) {
    if (capacity < 0)
    throw new IllegalArgumentException();
    return new HeapByteBuffer(capacity, capacity);
    }

    // HeapByteBuffer的内部实现

    HeapByteBuffer(int cap, int lim) { // package-private

    super(-1, 0, lim, cap, new byte[cap], 0);
    /*
    hb = new byte[cap];
    offset = 0;
    */
    }

    现在已经明确了,DirectByteBuffer直接通过Unsafe来分配内存,相当于直接从物理内存中分配内存.由DirectByteBuffer分配的内存,称之为直接缓冲区.

    HeapByteBuffer在其构造函数中,new了一个byte[cap],而且我们都知道,new出来的对象,是分配在堆上的,而堆在虚拟机内.所以,由HeapByteBuffer分配的内存,称之为非直接缓冲区.

    何时使用直接缓冲区? jdk官方文档上描述的很清楚:

    < 大专栏  Nio数据载体-Buffertd class="gutter">
    1
    2
    3
    4
    5
    6
    7
    此方法(allocateDirect)返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区.

    直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显.

    所以,建议将直接缓冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型、持久的缓冲区.

    一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们.

    使用ByteBuffer

    先来看一下ByteBuffer中操作数据的两个方法getput,其重载方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    public abstract byte get();
    public abstract byte get(int index);
    public ByteBuffer get(byte[] dst, int offset, int length);
    public ByteBuffer get(byte[] dst);

    public abstract ByteBuffer put(byte b);
    public abstract ByteBuffer put(int index, byte b);
    public ByteBuffer put(ByteBuffer src);
    public ByteBuffer put(byte[] src, int offset, int length);
    public final ByteBuffer put(byte[] src);

    上述几个方法,分成两类,绝对操作和相对操作.

    相对操作和绝对操作

    绝对操作:public abstract byte get(int index)public abstract ByteBuffer put(int index, byte b).只要参数0<=index<limit,可以无视position,对ByteBuffer进行任意存取操作.

    之所以限制index的范围:0<=index<limit

    1
    2
    3
    4
    5
    final int checkIndex(int i) {                      package-private
    if ((i < 0) || (i >= limit))
    throw new IndexOutOfBoundsException();
    return i;
    }

    对于相对操作,我曾经遇到过一个坑:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public static void main(String[] args) {
    ByteBuffer buffer = bufferGen();

    byte [] tag=new byte[6];
    buffer.get(tag,0,6);

    System.err.println("final result:"+new String(tag, Charset.forName("UTF-8")));

    }
    public static ByteBuffer bufferGen() {
    byte[] bytes = new byte[]{81, 81, 81, 81, 81, 81, 81, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 66, 0, -56, 0, 0, 0, 120, 0, 48, 48, 48, 48, 48, 0, 0, 0, 1, 0, 4, 0, 8, 0, 0, 0, 0, 0, 2, 0, 8, 0, 0, 0, 0, 0, 3, 0, 7, 79, 75, 0};
    ByteBuffer result = ByteBuffer.allocate(1024);
    result.put(bytes, 0, bytes.length);
    return result;
    }

    根据ASCII表,第81个字符是Q.猜测一下,这段代码,会打印什么?如果你猜成了”QQQQQQ”,不怪你,都是学习嘛.如果你认为打印为空,那结果就是为空.

    这就涉及到相对操作的问题了.
    先来看一下,当调用result.put(bytes, 0, bytes.length)时,会发生什么?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //根据上下文语境,这里的length为66
    public ByteBuffer put(byte[] src, int offset, int length) {
    // 1.略过边界检查
    checkBounds(offset, length, src.length);
    // 2. 略过
    if (length > remaining())
    throw new BufferOverflowException();

    //3. 进行数据复制,由于是一个新的ByteBuffer对象,这里position()的结果为0
    // 这段代码执行完毕以后,ByteBuffer中的核心hb数组的长度会变成66
    System.arraycopy(src, offset, hb, ix(position()), length);
    //4. 将重设position
    // 这段代码执行完以后,当前buffer对象的position的值为66
    position(position() + length);
    return this;
    }

    当执行buffer.get(tag,0,6)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 根据上下文语境,这里的length为6
    public ByteBuffer get(byte[] dst, int offset, int length) {
    // 1. 忽略检查
    checkBounds(offset, length, dst.length);
    if (length > remaining())
    throw new BufferUnderflowException();

    // 2. 复制数组从 position开始,而根据前一步的put操作,此时的position为66
    // 所以,此次获取到的数字为 hb中从下标为66到66+6
    System.arraycopy(hb, ix(position()), dst, offset, length);
    // 3. 重设position
    // 这行代码调用完毕后,position由之前的66,变为66+6
    position(position() + length);
    return this;
    }

    由解析得出,此时tag数组中的元素,是ByteBuffer中第66到72下标的元素,而我们put进去的长度为66,所以我们得到的是无效值.

    接着,就可以引申出相对操作和绝对操作的概念:

    limit为参照系的操作,称为绝对操作,绝对操作有两个get(int index)put(int index),之所以以limit作为参照系,是因为参数的值不能大于limit,否则就抛IndexOutOfBoundsException异常.

    position为参照系的操作,称为相对操作,相对操作会改变position的值,无论是get还是put.而且,无论是get还是put,都是以当前position为基点,向后增大.

    现在已经明确了相对操作和绝对操作的概念,现在想要让上面的程序打印”QQQQQQ”,怎办呢?

    只需要让position重置为0,不就行了嘛.

    于是,在返回之前,可以调用buffer.position(0).

    除此之外,ByteBuffer还提供了其他有用的工具.

    rewind

    buffer.get()

    无论是put()还是get(),都会造成position的改变

  • 相关阅读:
    简单爬虫架构解析
    三种urllib实现网页下载,含cookie模拟登陆
    MySQL 从入门到删库
    Python Set
    Python dict
    Python tuple
    Python List
    死锁问题
    线程通信之生产者和消费者案例
    多线程安全和线程同步
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12409788.html
Copyright © 2011-2022 走看看