- 今天在开发的过程中,我们在测试二进制文件读取的时候,用了这样一个方法:
public static byte[] readBinaryFile(boolean bIsReadRecode, String strFileName, int iOffset, int iLength) throws IOException {
byte[] byteArrayData = null;
FileInputStream cFileInputStream = null;
BufferedInputStream in = null;
try {
File cFile = new File(strFileName);
if (cFile.exists() == false) {
return byteArrayData;
}
cFileInputStream =new FileInputStream(cFile);
in = new BufferedInputStream(cFileInputStream);
if (bIsReadRecode == true) {
byte[] byteArrayTemp = new byte[iLength];
int iSize = in.read(byteArrayTemp, 0, iLength);
if (iSize == iLength) {
byteArrayData = byteArrayTemp;
}
}
else {
byteArrayData = in.readAllBytes();
}
} catch (FileNotFoundException e) {
logger.error("ReadBinaryFile:" + strFileName + " " + e);
} catch (IOException e) {
logger.error("ReadBinaryFile:" + strFileName + " " + e);
} finally {
if (cFileInputStream != null) {
cFileInputStream.close();
cFileInputStream = null;
}
if (in != null) {
in.close();
in = null;
}
}
return byteArrayData;
}
相信大家都可以看懂这段代码的含义,这段代码是读取一个文件,给定了4个参数,bIsReadRecode是否读取全部内容(false为读取全部内容,true读取部分),strFileName是文件名字,就是读取哪一个文件,iOffset是下标开始的位置,len是读取内容的长度。
下面是测试函数
@Test
public void testReadBinaryFile() throws IOException {
// 定义测试文件名和测试数据
String fileName = "test.bin";
byte[] testData = {0x11, 0x22, 0x33, 0x44, 0x55};
// 写入测试数据到测试文件
FileOutputStream out = null;
try {
out = new FileOutputStream(fileName);
out.write(testData);
} catch (IOException e) {
e.printStackTrace();
fail("Failed to write test data to file");
}finally {
out.close();
}
// 测试读取整个文件内容
byte[] result = readBinaryFile(false, fileName, 0, 0);
assertNotNull(result);
assertArrayEquals(testData, result);
System.out.println("result[0]==========>"+result[0]+"\n"+"result[1]==========>"+result[1]+"\n"+"result[2]==========>"+result[2]+"\n"+"result[3]==========>"+result[3]+"\n"+"result[4]==========>"+result[4]);
// 测试读取部分文件内容
result = readBinaryFile(true, fileName, 1, 3);
for (int i = 0; i < result.length; i++) {
System.out.print(result[i]+"\t");
}
assertNotNull(result);
assertEquals(3, result.length);
assertEquals(testData[1], result[0]);
assertEquals(testData[2], result[1]);
assertEquals(testData[3], result[2]);
// 删除测试文件
File file = new File(fileName);
file.delete();
}
**运行这个测试函数后,我们发现了测试读取全部内容的话是没问题的,但是读取部分内容从下标1开始,读取长度为3的数组时,给我们返回了[0,17,34,51,0],这就很奇怪了,按理说应该是[34,51,68]。
问题出现接下来就是排查问题,我们首先先进入BufferedInputStream的read()源码查看说明。
关于BufferedInputStream
BufferedInputStream类是一个带有缓冲区的输入流,通过缓冲来提高读取数据的效率。
BufferedInputStream的作用是为其它输入流提供缓冲功能。创建BufferedInputStream时,我们会通过它的构造函数指定某个输入流为参数。BufferedInputStream会将该输入流数据分批读取,每次读取一部分到缓冲中;操作完缓冲中的这部分数据之后,再从输入流中读取下一部分的数据。
为什么需要缓冲呢?原因很简单,效率问题!缓冲中的数据实际上是保存在内存中,而原始数据可能是保存在硬盘或NandFlash等存储介质中;而我们知道,从内存中读取数据的速度比从硬盘读取数据的速度至少快10倍以上。
下面我们来看一下源码:
read方法用于从输入流中读取数据到缓冲区中,返回读取的字节数,如果已经到达流末尾,则返回-1。
public synchronized int read(byte b[], int off, int len)
throws IOException
{
getBufIfOpen(); // Check for closed stream
if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int n = 0;
for (;;) {
int nread = read1(b, off + n, len - n);
if (nread <= 0)
return (n == 0) ? nread : n;
n += nread;
if (n >= len)
return n;
// if not closed but no bytes available, return
InputStream input = in;
if (input != null && input.available() <= 0)
return n;
}
}
从源码中我们可以看到,该方法接收一个目标字节数组b及其偏移量off和要读取的字节数len,如果已经到达流末尾则返回-1,否则将缓冲区中的数据复制到目标字节数组中,并返回实际读取的字节数。如果要读取的字节数较小,将只从缓冲区中读取数据,避免频繁地访问输入流。该方法是线程安全的,因为它采用了synchronized关键字保证了同步性。
看过源码之后,我们发现,read()方法在执行的过程中,如果是读取部分代码,还是先会new一个原数组长度的数组,这就导致了会在下标为0和下标为4的地方填充0
知道了这里的问题,Java中为我们提供了一个skip方法,用于解决这个问题
public synchronized long skip(long n) throws IOException {
getBufIfOpen(); // Check for closed stream
if (n <= 0) {
return 0;
}
long avail = count - pos;
if (avail <= 0) {
// If no mark position set then don't keep in buffer
if (markpos <0)
return getInIfOpen().skip(n);
// Fill in buffer to save bytes for reset
fill();
avail = count - pos;
if (avail <= 0)
return 0;
}
long skipped = (avail < n) ? avail : n;
pos += skipped;
return skipped;
}
通过调用这个方法,我们可以直接跳过指定的下标之前的值,直接从指定的下标开始new 新的数组。如下为修改之后的的代码。
public static byte[] readBinaryFile(boolean bIsReadRecode, String strFileName, int iOffset, int iLength) throws IOException {
byte[] byteArrayData = null;
FileInputStream cFileInputStream = null;
BufferedInputStream in = null;
try {
File cFile = new File(strFileName);
if (cFile.exists() == false) {
return byteArrayData;
}
cFileInputStream =new FileInputStream(cFile);
in = new BufferedInputStream(cFileInputStream);
if (bIsReadRecode == true) {
byte[] byteArrayTemp = new byte[iLength];
in.skip(iOffset);
int iSize = in.read(byteArrayTemp, 0, iLength);
if (iSize == iLength) {
byteArrayData = byteArrayTemp;
}
}
else {
byteArrayData = in.readAllBytes();
}
} catch (FileNotFoundException e) {
logger.error("ReadBinaryFile:" + strFileName + " " + e);
} catch (IOException e) {
logger.error("ReadBinaryFile:" + strFileName + " " + e);
} finally {
if (cFileInputStream != null) {
cFileInputStream.close();
cFileInputStream = null;
}
if (in != null) {
in.close();
in = null;
}
}
return byteArrayData;
}
到此问题就解决了,如有不足,请不吝赐教!