天天看点

NIO与Socket编程之缓冲区的使用

NIO

nio是non-blocking的简称。

NIO实现高性能处理的原理是使用较少的线程来处理更多的任务。

NIO与Socket编程之缓冲区的使用

1.1 NIO概述

1.2 缓冲区介绍

NIO中的Buffer是一个用于存储基本数据类型值的容器,它以类似于数组有序的方式来存储和组织数据。每个基本数据类型都有一个子类与之对应。

1.3 Buffer类的使用

Buffer是抽象类,7个子类也是抽象类。如何创建这些类的对象呢?

使用的方式是将上面7种数据类型的数组包装(wrap)进缓冲区中,此时就需要借助静态方法wrap()进行实现。

warp()方法的作用是将数组放入缓冲区中,来构建存储不同数据类型的缓存区。

(缓存区非线程安全的)

1.3.1 包装数据与获得容量

在NIO技术的缓冲区中,存在4个核心技术点,分别是:

capacity 容量

limit 限制

position 位置

mark 标记

这4个技术点之间值的大小关系如下:

0 <=mark <= position <= limit <= capacity

public class Test {
	public static void main(String[] args) {
	
		byte[] byteArray = new byte[] {1,2,3};
		ByteBuffer bytebuffer = ByteBuffer.wrap(byteArray);
		System.out.println(bytebuffer.getClass().getName());
		System.out.println(bytebuffer.capacity());
	}
}
运行结果:
java.nio.HeapByteBuffer
3
           

看一下wrap()方法的源代码:

public static ByteBuffer wrap(byte[] array) {
        return wrap(array, 0, array.length);
    }
public static ByteBuffer wrap(byte[] array,
                                    int offset, int length)
    {
        try {
            return new HeapByteBuffer(array, offset, length);
        } catch (IllegalArgumentException x) {
            throw new IndexOutOfBoundsException();
        }
    }

           

从源代码可以发现,通过创建HeapByteBuffer类的实例创建ByteBuffer类的实例。因为ByteBuffer与HeapByteBuffer是父子类的关系,所以在将HeapByteBuffer类的对象赋值给数据类型为ByteBuffer的变量时产生多态关系。

ByteBuffer类缓冲区的技术原理就是使用byte[]数组进行数据的保存,在后续使用指定的API来操作这个数组以达到操作缓冲区的目的。

HeapByteBuffer(byte[] buf, int off, int len) { // package-private

        super(-1, off, off + len, buf.length, buf, 0);
        /*
        hb = buf;
        offset = 0;
        */
        this.address = ARRAY_BASE_OFFSET;
    }
           

在HeapByteBuffer类的构造方法中,使用代码super(-1, off, off + len, buf.length, buf, 0)调用父类的构造方法将字节数组buf传给父类ByteBuffer,而且子类HeapByteBuffer还重写了父类Bytebuffer中的大部分方法,因此,在调用HeapByteBuffer类的API时,访问的是父类中的buf字节数组变量,在调用API处理buf字节数组中的数据时,执行的是HeapByteBuffer类中重写的方法。

缓冲区中的capacity其实就是buf.length属性值

1.3.2 限制获取与设置

方法 int limit()的作用:返回此缓冲区的限制。

Buffer limit(int newLimit) 设置缓冲区的限制。

什么是限制呢?缓冲区的限制代表第一个不应该读取或写入元素的index(索引)。缓冲区的限制不应该为负,并且limit不能大于其capacity。

1.3.3 位置获取与设置

方法int position()的作用:返回此缓冲区的位置

Buffer position(int newPosition)的作用:设置此缓冲区新的位置。

什么是位置呢?它代表“下一个”要读取或写入元素的索引。

1.3.4 剩余空间大小获取

方法 int remaining()的作用:返回“当前位置”与limit之间的元素数。

方法 final boolean hasRemaining()方法的作用:判断在当前位置与限制之间是否有元素。

1.3.5 使用Buffer mark()方法处理标记

方法Buffer mark()的作用:在此缓冲区的位置设置标记。

标记有什么作用呢?缓冲区的标记是一个索引,在调用reset()方法时,会将缓冲区的position位置重置为该索引。

1.3.6 判断只读

boolean isReadOnly()方法的作用:告知此缓冲区是否是只读缓冲区

1.3.7 直接缓冲区

boolean isDirect()方法的作用:判断此缓冲区是否为直接缓冲区。

无需经过JVM中间缓冲区存储数据。

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100);
           

1.3.8 还原缓冲区的状态

final Buffer clear()方法的作用:还原缓冲区到初始的状态,包含将位置设置为0,将限制设置为容量,并丢弃标记。

但clear()方法,并未清除缓冲区数据。

1.3.9 对缓冲区进行反转

final Buffer flip()方法的作用:反转此缓冲区。

public Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }
           

1.3.10 判断是否有底层实现的数组

final boolean hasArray()方法的作用:判断此缓冲区是否具有可访问的底层实现数组。

public final boolean hasArray() {
        return (hb != null) && !isReadOnly;
    }
           

1.3.11 重绕缓冲区

final Buffer rewind()方法的作用:重绕缓冲区,将位置设置为0并丢弃标记

public Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }
           

rewind()方法的侧重点在“重新”,在重新读取、重新写入时可以使用。

clear()方法的侧重点在“还原一切状态”

flip()方法的侧重点在substring截取

1.3.12 获得偏移量

final int arrayOffset()方法的作用:返回此缓冲区的底层实现数据中的第一个缓冲区元素的偏移量

HeapByteBuffer(byte[] buf, int off, int len) { // package-private

        super(-1, off, off + len, buf.length, buf, 0);
        /*
        hb = buf;
        offset = 0;
        */
        this.address = ARRAY_BASE_OFFSET;
    }
    
ByteBuffer(int mark, int pos, int lim, int cap,   // package-private
                 byte[] hb, int offset)
    {
        super(mark, pos, lim, cap);
        this.hb = hb;
        this.offset = offset;
    }
           

1.4 ByteBuffer类的使用

ByteBuffer类提供了6类操作:

1.以绝对位置和相对位置读写单个字节的get()和put()方法

2.使用相对批量get(byte[] dst)方法可以将缓冲区的连续字节传输到byte[] dst目标数组中

3.使用相对批量put(byte[] src)方法可以将byte[]数组或其他字节缓冲区中的连续字节存储到此缓冲区中。

4.使用绝对和相对getType和putType方法可以按照字节顺序在字节序列中读写其他基本数据类型的值,方法getType和putType可以进行数据类型的自动转换。

5.提供了创建视图缓冲区的方法,这些方法允许将字节缓冲区视为包含其他基本类型值的缓冲区,这些方法有asCharBuffer()、asDoubleBuffer()、asFloatBuffer()、asIntBuffer()、asLongBuffer()和asShortBuffer()

6.提供了对字节缓冲区进行压缩(compacting)、复制(duplicating)和截取(slicing)的方法。

字节缓冲区可以通过allocation()方法创建,此方法为缓冲区的内容分配空间,或者通过wrapping方法将现有的byte[]数组包装到缓冲区来创建。

1.4.1 创建堆缓冲区与直接缓冲区

字节缓冲区分为直接字节缓冲区与非直接字节缓冲区。

allocateDirect()方法创建直接字节缓冲区

allocate()创建非直接字节缓冲区

直接缓冲区的内存释放:1.手动释放空间 2.JVM自动化的处理

public ByteBuffer put(int i, byte x) {

        hb[ix(checkIndex(i))] = x;
        return this;
    }
           

非直接缓冲区的实现类HeapByteBuffer的put(byte)方法的源代码如下:

非直接缓冲区(HeapByteBuffer)在内部直接对byte[]hb字节数组进行操作,而且还是在JVM堆中进行数据处理,因此运行效率相对慢一些。

public ByteBuffer put(int i, byte x) {

        hb[ix(checkIndex(i))] = x;
        return this;
    }
           

直接缓冲区是使用DirectByteBuffer类进行实现的,而非直接缓冲区是使用HeapByteBuffer类实现的。

public ByteBuffer put(byte x) {

        try {
            UNSAFE.putByte(ix(nextPutIndex()), ((x)));
        } finally {
            Reference.reachabilityFence(this);
        }
        return this;
    }
           

直接缓冲区在内部使用sun.misc.Unsafe类进行值的处理。Unsafe类的作用是JVM与操作系统进行直接通信,提高程序员的运行效率。

1.4.2 包装wrap数据的处理

warp(byte[] array)方法的作用:将byte数组包装到缓冲区中。新的缓冲区将由给定的byte数组支持,也就是说,缓冲区修改将导致数组修改。

wrap(byte[] array,int offset,int length)方法的作用:将byte数组包装到缓冲区中。新缓冲区的capacity将为array.length,其position将为offset,其limit将为offset+length。

1.4.3 put(byte b)和get()方法的使用与position自增特性

相对位置:从当前位置开始,读取或写入一个或多个元素,position呈递增的状态。

绝对位置操作采用显式元素索引,该操作不影响位置。

1.4.4 put(byte[] src,int offset,int length)和get(byte[] dst,int offset,int length)方法的使用

public class Test {
	public static void main(String[] args) {
	
		byte[] byteArray = new byte[] {1,2,3};
		byte[] src = new byte[] {5,6,7,8};
		ByteBuffer bytebuffer = ByteBuffer.wrap(byteArray);
		bytebuffer.put(0, (byte) 7);
		ByteBuffer buffer1 = ByteBuffer.allocateDirect(10);
		buffer1.put(byteArray, 0, 3);
		buffer1.position(0);
		System.out.println(buffer1.get());
		System.out.println(buffer1.get());
		System.out.println(buffer1.get());
		
	}
}
运行结果:
7
2
3
           

如果没有 buffer1.position(0),则输出结果是 0 0 0

因为buffer1.put(byteArray, 0, 3)操作后,position的位置变为了3,每次get,position值都+1,

3之后的缓冲区值都为0.

1.4.5 put(byte[]src) 和get(byte[]dst)

put(byte[]src) 相当于把src数组所有元素方法缓冲区

get(byte[]dst) 相当于取出目标数组的长度元素。

1.4.6 put(int index,byte b)和get(int index)方法

put(int index,byte b)和get(int index)方法的作用:绝对put方法,将给定字节写入此缓冲区的给定索引位置。

1.4.7 put(ByteBuffer src)方法的使用

将src缓冲区的剩余元素添加进当前缓冲区,然后2个缓冲区position位置都+n.

1.4.8 putType()和 getType()方法的使用

putChar(char value)和 putchar(int index,char value)

getChar() 和getChar(index)

public class Test {
	public static void main(String[] args) {
	
		byte[] byteArray = new byte[] {1,2,3};
		byte[] src = new byte[] {5,6,7,8};
		ByteBuffer bytebuffer = ByteBuffer.wrap(byteArray);
		ByteBuffer buffer1 = ByteBuffer.allocateDirect(10);
		buffer1.putChar('9');
		System.out.println(buffer1.getChar(0));
		
	}
}

           

1.4.9 slice()方法的使用和arrayOffSet()为非0的测试

slice()方法的作用:创建新的字节缓冲区,其内容是此缓冲区内容的共享子序列。新缓冲区的内容将从此缓冲区的当前位置开始。此缓冲区内容的更改在新缓冲区中是可见的,反之亦然;这两个缓冲区的位置、限制和标记值是相互独立的。

arrayOffset():返回此缓冲区的缓冲区第一个元素的偏移量。

1.4.10 转换为CharBuffer字符缓冲区及中文的处理

1.4.11 设置与获得字节顺序

order()方法与字节数据排列的顺序有关,因为不同的CPU在读取字节时的顺序是不一样的

order(ByteOrder bo)设置字节的排列顺序。

1.4.12 比较缓冲区的内容

比较缓冲区的内容有两种方法:equals()和compareTo().

1.4.13 对缓冲区进行扩容

ByteBuffer newBytebuffer = ByteBuffer.allocate(buffer.capacity()+extendsSize);
newByteBuffer.put(buffer);
return newBytebuffer;
           

1.5 CharBuffer类的API使用

1.5.1 append相关方法

public CharBuffer append(char c):将指定字符添加到此缓冲区

append(CharSequence csq)方法的作用:将指定的字符序列添加到此缓冲区。

append(CharSequence csq,int start,int end)

1.5.2 读取添加缓冲区字符相关方法

charAt(int index):读取相对于当前位置的给定索引处的字符。

put(String src):相对批量put方法().

int read(CharBuffer target)方法的作用:试图将当前字符缓冲区中的当前位置开始字符写入指定的缓冲区。

subSequence(int start,int end)方法的作用:创建表示次缓冲区的指定序列、相对于当前位置的新字符缓冲区。

1.5.3 wrap()方法的使用

public static CharBuffer(CharSequence csq,int start,int end)方法的作用:将字符序列包装到缓冲区。新的只读

CharBuffer cb3 = CharBuffer.wrap("abcde",0, 4);
System.out.println(cb3.isReadOnly());

运行结果:
true
           

继续阅读