之前在项目中,写过一段对二进制文件进行解析【解析成16进制字符串】的代码。今天回头做总结的时候发现里面有大学问。话不多说,先上代码。
1 public static byte[] loadFile(String fileNm) {2 File file = newFile(fileNm);3 FileInputStream fis = null;4 ByteArrayOutputStream baos = null;5 byte[] data = null;6
7 try{8 fis = newFileInputStream(file);9 //baos = new ByteArrayOutputStream((int)file.length());
10 baos = new ByteArrayOutputStream(2048);11 byte[] e = new byte[1024];12
13 intlen;14 while ((len = fis.read(e)) != -1) {15 baos.write(e, 0, len);16 }17
18 data =baos.toByteArray();19 System.out.println("此时的data is:" +Arrays.toString(data));20 } catch(IOException var15) {21 var15.printStackTrace();22 } finally{23 try{24 if (fis != null) {25 fis.close();26 fis = null;27 }28 if (null !=baos) {29 baos.close();30 baos = null;31 }32 } catch(IOException var14) {33 var14.printStackTrace();34 }35 }36 returndata;37 }
View Code
1.可以看到,先用FileInputStream文件字节流对文件进行了读取。
fis = new FileInputStream(file);
2.然后用ByteArrayOutputStream字节数组输出流将其输出到JVM内存里,供后续程序调用。注意这里用的是输出流,因为是需要将从文件里面读到的字节流输出到内存里【不是输出到控制台】。
baos = new ByteArrayOutputStream(2048);
通过看ByteArrayOutputStream()的构造函数.
public ByteArrayOutputStream(int size) {
if (size < 0) {
throw new IllegalArgumentException("Negative initial size: "
+ size);
}
buf = new byte[size];
}
可以看到,相当于对baos=new byte[2048];也就是baos的初值是一个2k大小的字节数组。
下面就是定义了一个1k大小的字节数组e。
byte[] e = new byte[1024];
3.文件字节流对象fis的read(e)返回的是一个整数【这样设计个人觉得是为了统一】,当返回-1时,代表它读完了。
public int read(byte b[]) throwsIOException {return readBytes(b, 0, b.length);
}
private native int readBytes(byte b[], int off, int len) throws IOException;
可以发现,read调用的还是readBytes(b, 0, b.length)。从该输入流中读取数据的字节,并将其读入字节数组。如果输入不可用,此方法将阻塞。
这个方法会返回读到读入缓冲区的字节总数【但是注意返回长度的有可能小于字节数组的长度】。
继续追踪readBytes,发现此方法被native修饰,没有源码。经查阅得知,native修饰符代表的原生,本地。具体实现不在JDK里面,它通常是有C或者C++实现的。也就意味着,native修饰的方法是java与底层硬件【通常是C或者C++】交互的接口。
在项目代码里,它是一个读1k。
4.然后就是写入内存【JVM】里。
baos.write(e, 0, len);
就是这个方法有大学问。表面上,是往内存里写一个长度为len字节数组e。但是仔细一想,数组是有固定长度的,它是如何保证数据不会覆盖,不会溢出的呢?
打开这个方法的源码。
public synchronized void write(byte b[], int off, intlen) {if ((off < 0) || (off > b.length) || (len < 0) ||((off+ len) - b.length > 0)) {throw newIndexOutOfBoundsException();
}
ensureCapacity(count+len);
System.arraycopy(b, off, buf, count, len);
count+=len;
}
可以看到,当前条件下,if条件是不会走的。
然后我看到是ensureCapacity(count+len);看名字像是扩容,但是count是什么呢?内部具体实现又是什么呢?
首先,在ByteArrayOutputStream源码里看到
protected int count;
count在这个地方被定义。但是并没有赋值,由于count是成员变量,默认初值是0;
于是上面代码就变成了ensureCapacity(len);打开ensureCapacity();
private void ensureCapacity(intminCapacity) {//overflow-conscious code
if (minCapacity - buf.length > 0)
grow(minCapacity);
}
这里的buf也是成员变量。
protected byte buf[];
默认是没有数据的。
因此,传入的len【minCapacity】>0;走到了grow(minCaptacity).打开grow()的源码。
private void grow(intminCapacity) {//overflow-conscious code
int oldCapacity =buf.length;int newCapacity = oldCapacity << 1;if (newCapacity - minCapacity < 0)
newCapacity=minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity=hugeCapacity(minCapacity);
buf=Arrays.copyOf(buf, newCapacity);
}
首先,因为buf.length=0;所以oldCapacity =0;它左移一位的newCapacity还是0;所以newCapacity = minCapacity;打开copyOf(buf,newCapacity);
public static byte[] copyOf(byte[] original, intnewLength) {byte[] copy = new byte[newLength];
System.arraycopy(original,0, copy, 0,
Math.min(original.length, newLength));returncopy;
}
copy为一个1k大小的数组。打开arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));
public static native void arraycopy(Object src, intsrcPos,
Object dest,intdestPos,int length);
这里又是一个被native修饰的方法。同上,就不展开了。直接举例说明arraycopy。
arrayCopy( arr1, 2, arr2, 5, 10);
意思是;将arr1数组里从索引为2的元素开始, 复制到数组arr2里的索引为5的位置, 复制的元素个数为10个.
Int[] arr1 ={1,2,3,4,5};
arrayCopy(arr1, 3, arr1, 2, 2);
意思是:将arr1从数字4开始 拷贝到arr1的数字3的位置, 拷贝2个数, 也就是说将4和5 拷贝到数字3的位置,相当于删除数字3.
此处返回的copy还是它本身的大小。
5.读的细心的朋友可以发现,ensureCapacity下面还有一个
System.arraycopy(b, off, buf, count, len);
由上可知,是将b数组从0开始赋值到buf的count【0】这个位置。并且复制len个元素。然后
count += len;
需要注意的是count是同步改变的,意味着不会被其他地方改变。
6.while这个循环第一遍就结束了,当进行第二遍的时候,由于count已经改变,这个时候的
System.arraycopy(b, off, buf, count, len);
就相当于把 新读到的1k大小的字节数组追加到了原来读字节数组的后面。
至此,关于字节流的读取就结束了。