我有以下测试代码:
try {
InputStream is;
Stopwatch.start("FileInputStream");
is = new FileInputStream(imageFile.toFile());
is.skip(1024*1024*1024);
is.close();
Stopwatch.stop();
Stopwatch.start("Files.newInputStream");
is = Files.newInputStream(imageFile);
is.skip(1024*1024*1024);
is.close();
Stopwatch.stop();
}
catch(Exception e)
{
}
我有以下输出:
Start: FileInputStream
FileInputStream : 0 ms
Start: Files.newInputStream
Files.newInputStream : 3469 ms
你知道发生了什么事吗?为什么在第二种情况下跳过如此缓慢?
我需要使用从通道获取的InputStreams,因为我的测试表明,对我的任务来说最好的是让两个线程同时从文件中读取(只有当我使用来自通道的Streams时,我才能注意到任何改进)。
在测试中,我发现我可以做这样的事情:
SeekableByteChannel sbc = Files.newByteChannel(imageFile);
sbc.position(1024*1024*1024);
is = Channels.newInputStream(sbc);
这只需要平均28ms,但这对我没有太大帮助,因为要使用它,我必须对API进行重大更改。
我的平台:
Linux galileo 3.11.0-13-generic #20-Ubuntu SMP Wed Oct 23 07:38:26 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)
从源代码来看,skip()
的默认实现实际上是读取(并丢弃)流内容,直到它到达目标位置:
public long skip(long n) throws IOException {
long remaining = n;
int nr;
if (n <= 0) {
return 0;
}
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
byte[] skipBuffer = new byte[size];
while (remaining > 0) {
nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
if (nr < 0) {
break;
}
remaining -= nr;
}
return n - remaining;
}
SeekableByteChannel#position()
方法可能只是更新一个偏移指针,它实际上不需要任何I/O。据推测,FileInputStream
通过类似的优化覆盖了skip()
方法。文件支持这一理论:
此方法跳过的字节数可能超过备份文件中剩余的字节数。这不会产生异常,跳过的字节数可能包括超出支持文件EOF的一些字节数。在跳过末尾之后尝试从流中读取将导致-1指示文件的末尾。
在磁盘或网络存储上,这可能会产生重大影响。
尝试使用GetObjectRequest.setRange
设置范围,使其具有与skip
相同的行为。
GetObjectRequest req = new GetObjectRequest(BUCKET_NAME, "myfile.zip");
req.setRange(1024); // start download skiping 1024 bytes
S3ObjectInputStream in = client.getObject(req).getObjectContent();
// read "in" while not eof
我在实现中使用了这个来避免SocketTimeoutException
。每次我得到SocketTimeoutException
时,我都会使用setRange
重新启动下载,以跳过我已经下载的字节。