java nio中的ByteBuffer扩展问题
作者:ะัี潪ิื
前言
在jdk1.4之前对于输入输出只能使用InPutStream和outPutSream这类传统io模型,在jdk1.4之后新增了nio,什么是nio?
nio是new input/output 的简称,nio的效率要比传统io效率高,主要原因就是nio利用了系统底层的零拷贝技术和多路复用技术。
NIO核心知识
NIO有三个核心概念
- 1、Channal通道
- 2、Buffer缓冲
- 3、Selector选择器
以上三者之间的关系是,一个线程拥有一个selector选择器,一个selector选择器管理多个channel通道,每个channel通道具有一个Buffer缓冲。为了更好的理解nio这三者之间的关系。举一个实际生活中遇到的例子。
公司一般每年都会提供免费的体检,一般都是和爱康国宾合作的,去体检了几次发现一个有趣的事情:
1、客户很多、体检项目也很多。但是客户不知道自己应该去哪个房间体检。
2、体检中心提供一个中转中心,中转中心四周都是体检房间,所有的人在每项体检后都要经过这个中转中心。
3、每个体检者手里都要握着一个体检单。体检单上会有自己的体检项目。
4、中转中心有一个工作人员(引导员),每个体检人员来到中转中心,引导员会结合体检房间的空闲情况和体检者的体检单来指定具体要去的体检场所。
5、体检速度快,不会产生太长的队伍,比较高效的方式。
那这个这里里面有几个角色完全可以对应nio中的三个概念,分别是,引导员=selector, 体检者手上的体检单=Buffer, 每个体检者=channel;
ByteBuffer
ByteBuffer是Buffer的一个子类,实现方式类似byte[], 该类有个重要的概念就是指针,指针标志的位置代表下次操作的位置。
ByteBuffer分为读模式和写模式,当然了这种称谓都是人们习惯性的称呼,其实只是指针指向的区别,比如读模式指针指向数组下标为0的位置,写模式指针指向数组最后一位。
在实际使用中一个byteBuffer可能需要被反复读取多次,于是byteBuffer提供了mark()方法,mark方法标记的下标供reset方法使用。
如果当前byteBuffer中存储的是[a,b,c,d,e]这五个字符,读取的时候读取到c的时候调用了mark()方法添加了标记,那么当读取到e时,又想从标记的地方重新读取时,只需要调用reset()即可将指针指向c的位置,ByteBuffer提供的方法虽然很好,但是在日常使用的时候还是不太方便,比如在b处打了标记,当读取到c时又打了标记,目的时要从c处重新读取一遍,然后再从b处重新读取一遍,这是byteBuffer就显得有些不太好使了。
笔者写了一个扩展byteBuffer的类
代码如下:
package pers.cz.tools; import java.nio.Buffer; import java.nio.ByteBuffer; import java.util.Arrays; /** * @program: Reids * @description: 扩展ByteBuffer,提供多次标记的功能。 * @author: Cheng Zhi * @create: 2023-04-13 20:23 **/ public class JefByteBuffer { /** * 保存所有的标记点 */ private int[] markPocket = new int[16]; ByteBuffer byteBuffer; private int index = 0; public JefByteBuffer(int capacity, boolean isUseLocalMem) { if (isUseLocalMem) { byteBuffer = ByteBuffer.allocateDirect(capacity); } else { byteBuffer = ByteBuffer.allocate(capacity); } } public JefByteBuffer(ByteBuffer byteBuffer) { this.byteBuffer = byteBuffer; } /** * 切换模式 */ public Buffer flip() { return byteBuffer.flip(); } public char read() { return (char) byteBuffer.get(); } public void unread() { int position = byteBuffer.position(); byteBuffer.position(position - 1); } public byte get() { return byteBuffer.get(); } public void put(byte[] src) { byteBuffer.put(src); } public void put(byte bi) { byteBuffer.put(bi); } public void clear() { byteBuffer.clear(); } public boolean isEnd() { int current = byteBuffer.position(); int end = byteBuffer.limit(); if (current == end) { return true; } return false; } /** * 打标记 */ public void mark() { int mark = byteBuffer.position(); ensureCapacityInternal(); markPocket[index] = mark; index ++; } /** * 去除标记 */ public void unmark() { index --; markPocket[index] = 0; } /** * 重置 */ public void reset() { index --; if (index < 0) { return; } int mark = markPocket[index]; if (mark < 0) { return; } byteBuffer.position(mark); } /** * 为markPocket扩容 */ private void ensureCapacityInternal() { int oldCapacity = markPocket.length; if (index >= oldCapacity) { int newCapacity = oldCapacity + (oldCapacity >> 1); // minCapacity is usually close to size, so this is a win: markPocket = Arrays.copyOf(markPocket, newCapacity); } } }
使用方式:
public static void test2() { JefByteBuffer jefByteBuffer = new JefByteBuffer(100); jefByteBuffer.put(new byte[] {'1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t'}); jefByteBuffer.flip(); for (int i=1; i<21; i++) { if (i == 1) { jefByteBuffer.mark(); } if (i == 3) { jefByteBuffer.mark(); } if (i == 5) { jefByteBuffer.mark(); } if (i == 7) { jefByteBuffer.mark(); } if (i == 9) { jefByteBuffer.mark(); } if (i == 10) { jefByteBuffer.unmark(); } if (i == 11) { //jefByteBuffer.unmark(); // 这里将回到i=7的标记点 jefByteBuffer.reset(); } /* if (i ==8) { jefByteBuffer.reset(); }*/ char b = (char) jefByteBuffer.read(); jefByteBuffer.unread(); System.out.println(b); } }
这样在使用byteBuffer的时候就可以灵活的读取,方法名更是见名知意,比如read读取了一个字节之后,调用unread后还可以重新读取。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。