之前在项目中,写过一段对二进制文件进行解析【解析成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     }
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[]) 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大小的字节数组追加到了原来读字节数组的后面
至此,关于字节流的读取就结束了。
01-02 03:58
查看更多