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

    xiaoxiao2022-07-02  109

    NIO nio是non-blocking的简称。 NIO实现高性能处理的原理是使用较少的线程来处理更多的任务。 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
    最新回复(0)