ChannelInputStream跳过方法非常慢



我有以下测试代码:

    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重新启动下载,以跳过我已经下载的字节。

相关内容

最新更新