ByteArrayInputStream和DataInputStream的源码分析和使用方法详细分析

ByteArrayInputStream和DataInputStream的源码分析和使用方法详细分析
ByteArrayInputStream的源码——零拷贝Zero-Copy的一种字节流在传统的磁盘 I/O比如FileInputStream.class、BufferedInputStream.class...等 中使用者都需要将磁盘的数据先复制到内存中来使用而无法实现零拷贝Zero-Copy而ByteArrayInputStream .class可以从内存中直接读取数据因此ByteArrayInputStream .class这个类正是为了解决内存数据读取而存在的ByteArrayInputStream 的核心价值在于零拷贝Zero-Copy它允许将内存中的byte[]字节数组当作输入流来读取是处理内存数据的常用工具如下所示ByteArrayInputStream .class的UML关系图如下所示ByteArrayInputStream .class的源码如下package java.io; public class ByteArrayInputStream extends InputStream { //byte[] buf变量直接指向了内存中的byte[]字节数组不会向BufferedInputStream源码中的那样先将数据复制到自己内部的byte[]字节数组上再进行数据处理 protected byte buf[]; //准备从当前内存中的byte[]数组直接引用读取字节的索引位置取值范围为[0,count) protected int pos; //标记某个索引位置0markpos //该变量只会在 构造函数和mark()函数中赋值 protected int mark 0; //byte[] buf变量指向的内存中的byte[]字节数组的长度 protected int count; //构造函数 public ByteArrayInputStream(byte buf[]) { this.buf buf;//byte[] buf变量直接指向了内存中的byte[]字节数组 this.pos 0; this.count buf.length; } //构造函数准备从byte buf[]字节数组中读取字节的索引位置从int offset索引位置开始 public ByteArrayInputStream(byte buf[], int offset, int length) { this.buf buf;//byte[] buf变量直接指向了内存中的byte[]字节数组 this.pos offset; //读取数组的右边界索引位置 内存中的byte[]字节数组的长度 this.count Math.min(offset length, buf.length); this.mark offset;//读取数组时候的左边界索引位置offset } //线程同步的函数从内存中的byte[]字节数组读取1个字节 public synchronized int read() { return (pos count) ? (buf[pos] 0xff) : -1; } //线程同步的函数从内存中的byte[]字节数组读取len个字节到指定的byte[]数组b中这len个字节被放到byte[]数组b的[off,offlen)索引位置。 public synchronized int read(byte b[], int off, int len) { if (b null) { //如果指定的内存中的byte[]数组b为null抛出一个NullPointerException throw new NullPointerException(); //范围检测off和len必须是非负数b.length - off是内存中的byte[]数组还可以放的字节ASCII码值的数量 } else if (off 0 || len 0 || len b.length - off) { throw new IndexOutOfBoundsException(); } //poscount时返回-1表示内存中的byte[]数组已经读取完毕 if (pos count) { return -1; } //int avail表示本次可以从内存中的byte[]数组中读取到的字节数量 int avail count - pos; if (len avail) { len avail; } if (len 0) { return 0; } //从内存中的byte[]字节数组读取[pos,poslen)索引位置的字节到指定的byte[]数组b中这len个字节被放到byte[]数组b的[off,offlen)索引位置。 System.arraycopy(buf, pos, b, off, len); pos len;//跟新int pos变量的值 return len; } //线程同步的函数 public synchronized long skip(long n) { //尝试更新右指针int pos的值对于不同的n值有以下2种可能 //a、如果ncount - pos并且n0时才更新int pos posn //b、当n0或者ncount - pos时更新int pos count long k count - pos; if (n k) { k n 0 ? 0 : n; } pos k; return k; } //线程同步的函数返回内存中的byte[]字节数组还可以读取的字节数量 public synchronized int available() { return count - pos; } public boolean markSupported() { return true; } //更新int mark变量为pos的值 public void mark(int readAheadLimit) { mark pos; } //线程同步的函数重置int pos变量的值为mark的值 public synchronized void reset() { pos mark; } public void close() throws IOException { } }1.1、ByteArrayInputStream.class从内存中读取数据的过程ByteArrayInputStream.class本质就是通过直接引用字节数组来实现的零拷贝并通过双指针int mark是左指针和int pos是右指针来实现的read()函数、skip()函数、reset()函数...等ByteArrayInputStream.class从内存中读取数据的过程如下所示import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; public class ByteArrayInputStreamTest { private static final int LEN 5; // 对应英文字母abcdefghijklmnopqrstuvwxyzASCII编码 private static final byte[] ArrayLetters { 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A }; public static void main(String[] args) { //step1创建ByteArrayInputStream字节流引用的是内存中的ArrayLetters数组 ByteArrayInputStream bais new ByteArrayInputStream(ArrayLetters); //step2 从ByteArrayInputStream指向的内存数组中读取5个字节 for (int i0; iLEN; i) { // 若能继续读取下一个字节则读取下一个字节 if (bais.available() 0) { // 读取ByteArrayInputStream指向的内存数组中的下一个字节 int tmp bais.read(); System.out.printf(%d : 0x%s\n, i, Integer.toHexString(tmp)); } } //step3标记ByteArrayInputStream中下一个被读取的索引位置即--标记0x66因为因为前面已经读取了5个字节所以下一个被读取的位置是第6个字节 // ①、 ByteArrayInputStream中的mark(0)函数中的“参数0”是没有实际意义的。 // ②、 mark()与reset()是配套的reset()会将字节流中右指针int pos指向的索引重置为mark()函数中左指针int mark指向的索引 bais.mark(0); //step4 跳过5个字节。跳过5个字节后下一个被读取的索引位置应该是ByteArrayInputStream指向的内存数组中“0x6B”。 bais.skip(5); //step5从ByteArrayInputStream指向的内存数组中读取5个字节到byte[] buf数组的[0,5)索引位置。即读取“0x6B, 0x6C, 0x6D, 0x6E, 0x6F” byte[] buf new byte[LEN]; bais.read(buf, 0, LEN); String str1 new String(buf); System.out.printf(str1%s\n, str1); //step6、重置ByteArrayInputStream中的右指针int pos为mark()函数中左指针int mark指向的索引即0x66。 bais.reset(); //step7、从“重置后的ByteArrayInputStream”中读取5个字节到buf中。即读取“0x66, 0x67, 0x68, 0x69, 0x6A” bais.read(buf, 0, LEN); String str2 new String(buf); System.out.printf(str2%s\n, str2); } }上述代码的执行过程为①、step1创建ByteArrayInputStream对象引用的是内存中的byte[] ArrayLetters数组如下所示int mark 0,int pos 0②、step2从内存中的byte[] ArrayLetters数组中依次读取5个字节读取完成后int pos5int mark0如下所示int mark 0,int pos 5③、step3标记ByteArrayInputStream中下一个被读取的索引位置即--标记0x66因为因为前面已经读取了5个字节所以下一个被读取的位置是第6个字节如下所示int mark 5,int pos 5mark(0)函数需要注意以下2点a、 ByteArrayInputStream中的mark(0)函数中的“入参0”是没有实际意义的b、 mark()函数与reset()函数是配套的reset()函数会将ByteArrayInputStream中右指针int pos指向的索引重置为mark()函数中左指针int mark指向的索引。④、step4 跳过5个字节。跳过5个字节后下一个被读取的索引位置应该是ByteArrayInputStream指向的内存数组中0x6B如下所示int mark 5,int pos 10⑤、step5 从ByteArrayInputStream指向的内存数组中读取5个字节到byte[] buf数组的[0,5)索引位置。即读取“0x6B, 0x6C, 0x6D, 0x6E, 0x6F”如下所示int mark 5,int pos 15⑥、step6 重置ByteArrayInputStream中的右指针int pos为step3中调用mark()函数时左指针int mark指向的索引位置即0x66如下所示int mark 5,int pos 5⑦、step7 从“重置后的ByteArrayInputStream”中读取5个字节到buf中。即读取“0x66, 0x67, 0x68, 0x69, 0x6A”如下所示int mark 5,int pos 10二、DataInputStream的源码——Java原生8种数据类型解析和UTF-8 解码算法实现的一种装饰器流DataInputStream.class是 Java I/O 体系中实现了 DataInput.interface 接口的核心类自 JDK 1.0 起就为从机器无关的二进制数据中读取基本 Java 数据类型提供了标准实现同时也继承了FilterInputStream.class因此也可以用来装饰其它输入流 。其中DataInputStream.class::readUTF()函数是UTF-8 解码算法的精妙实现这是 Java 序列化和网络协议的基石。DataInputStream.class的UML关系图如下所示public class DataInputStream extends FilterInputStream implements DataInput { //构造函数需要传入一个被装饰的输入流 public DataInputStream(InputStream in) { super(in); } private byte bytearr[] new byte[80]; private char chararr[] new char[80]; //实际调用的是InputStream.class::read()函数 public final int read(byte b[]) throws IOException { return in.read(b, 0, b.length); } //实际调用的是InputStream.class::read()函数 public final int read(byte b[], int off, int len) throws IOException { return in.read(b, off, len); } public final void readFully(byte b[]) throws IOException { readFully(b, 0, b.length); } public final void readFully(byte b[], int off, int len) throws IOException { if (len 0) throw new IndexOutOfBoundsException(); int n 0; while (n len) { int count in.read(b, off n, len - n); if (count 0) throw new EOFException(); n count; } } //当前这个DataInputStream对象可以从它装饰的输入流中可以理解为一个大型的字节数组中跳过n个字节n被装饰的输入流中可用的字节数量再进行后续操作比如通过read()函数读取等 public final int skipBytes(int n) throws IOException { int total 0;//累计跳过的字节总数量 int cur 0;//本次通过InputStream.class::skip()函数跳过的字节总数量