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