小红:小明我这边有一个位置大小文件,需要导入到一个byte数组中,但是自己定义的byte数组如果太小,可能导入的文件不全,会丢失部分数据,如果定义的数组大小太大,会导致内存的浪费,我该如何处理?
小明:对于这种未知文件大小的读取,我们需要在读取的时候,对数组进行动态的扩容,以确保文件能完整的读入。
小红:那该如何做?
小明:
总体的思路如下:
- 我们应该先对数组设置一个初始化的大小值A,变量B来记录流当前的读取位置,C表示我们定义的buffer的最大值,D表示buffer的固定值(C>D);
- 我们需要一个死循环,将每一次将流读取的位置赋值于B;
- 我们比较B的值,当流读取完之后,B的值就回小于0,这是我们就知道流以及读完,退出死循环为止;如果流在每一次循环过程中发现都没有读完,那这时我们就需要对当前数组进行比较,看是否扩容;
- 我们需要先比较A与C-A的大小,为什么这样做?主要是我们需要确保我们初始化的值A*2是否比我们自定义buffer的最大值大,为我们二次扩容留下足够的大小空间;
- 当2A大于C时,我们需要比较A是否大于C,如果A大于C我们就需要抛出异常,告诉程序以及超出自定义的内存,否则就直接将最大值bufferC赋值于A;
- 当2A小于C时,表示当前初始化的数组大小还是可以扩容的,我们将A进行<<1处理,并且将处理后的值与D比较,选出最大值,作为A的新值;
- 此时我们已经对新的数组完成了初始化大小的确认,下面就是进行copy处理;
- Arrays.copy这个方法进行copy,通过源码我们可以发现,在copy的过程中,主要是new出来一个新的bety数组,将目标数据进行copy,其中主要的是新的数组copy的长度是,目标数组的大小和A的最小值;
- 退出死循环后,我们只需要比较一下B与A是否一致,如果一致就返回当前数组,如果不一致,我们就将数据进行copy,copy的数组长度时流最后读出的位置。
这就是我对数组动态扩容的思路。
代码源码来源于Files.readAll()方法
private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
private static final int BUFFER_SIZE = 8192;
/**
* 该方法主要是对未知文件大小的流,实现动态扩展byte[]大小
*
* @param source 文件输入流
* @param initialSize byte[]数组初始化大小
* @return 返回文件流的byte[]
* @throws IOException 流读取会产生的IO异常
*/
public static byte[] read(InputStream source, int initialSize) throws IOException {
int capacity = initialSize;
//①定义一个初始化的byte[]
byte[] buf = new byte[capacity];
//②流读取到byte[]的位置
int nread = 0;
int n;
//③死循环,读流
for (; ; ) {
//④将当前流读到初始化的byte[]中,read参数(读取的byte[],byte[]的起始位置,读取的长度),返回的当前读取的流的位置
while ((n = source.read(buf, nread, capacity - nread)) > 0) {
//⑤将当前流读取的位置,赋值与流读取的byte[]的位置
nread += n;
}
//⑥判断n当前流的位置,当n小于0时或者流读取的位置小于0时,表示流以及读完,退出当前死循环
if (n < 0 || (n = source.read()) < 0) break;
//⑦判断当byte[]的大小,当前值小于等于(最大byte的buffer的值-当前byte[]的大小)⑪⑫⑬⑭⑮⑯
if (capacity <= MAX_BUFFER_SIZE - capacity) {
//初始值重新赋值,在当前值二进制值向左移一位返回值和我们自己定义的buffer的大小之前选择最大的值
capacity = Math.max(capacity << 1, BUFFER_SIZE);
} else {
//⑧当前byte[]的大小值,大于(最大byte的buffer的值-当前byte[]的大小)
//⑨先判断当前值是否等于最大byte的buffer的值,等于就抛出内存溢出异常,结束当前进程
if (capacity == MAX_BUFFER_SIZE){
throw new OutOfMemoryError("Required array size too large");
}
//⑩否则将最大值赋予byte[]的大小的值
capacity = MAX_BUFFER_SIZE;
}
//⑪将当前byte[]数组进行copy,copy参数(需要copy的byte[],初始化的大小)
//源码里面查看,在数组copy的过程中,会在出现的大小值和需要copy数组的大小值进行最小选取,选取最小值为返回数组的大小
buf = Arrays.copyOf(buf, capacity);
//⑫在数组的末尾添加最后结束符
buf[nread++] = (byte) n;
}
//⑬判断最终的数组的大小是否和流读取的最后位置一样,如果一致返回当前数组,否则返回copy的新数组
return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
}