之前在项目中,写过一段对二进制文件进行解析【解析成16进制字符串】的代码。今天回头做总结的时候发现里面有大学问。话不多说,先上代码。
1 public static byte[] loadFile(String fileNm) { 2 File file = new File(fileNm); 3 FileInputStream fis = null; 4 ByteArrayOutputStream baos = null; 5 byte[] data = null; 6 7 try { 8 fis = new FileInputStream(file); 9 // baos = new ByteArrayOutputStream((int)file.length()); 10 baos = new ByteArrayOutputStream(2048); 11 byte[] e = new byte[1024]; 12 13 int len; 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 return data; 37 }
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[]) throws IOException { 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, int len) { if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) - b.length > 0)) { throw new IndexOutOfBoundsException(); } 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(int minCapacity) { // overflow-conscious code if (minCapacity - buf.length > 0) grow(minCapacity); }
这里的buf也是成员变量。
protected byte buf[];
默认是没有数据的。
因此,传入的len【minCapacity】>0;走到了grow(minCaptacity).打开grow()的源码。
private void grow(int minCapacity) { // 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, int newLength) { byte[] copy = new byte[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }
copy为一个1k大小的数组。打开arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, 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大小的字节数组追加到了原来读字节数组的后面。
至此,关于字节流的读取就结束了。