ChannelInputStream跳过方法很慢

时间:2013-11-18 20:04:00

标签: java nio

我有以下测试代码:

    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)

2 个答案:

答案 0 :(得分:1)

查看源代码,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表示文件结束。

在盘片磁盘或网络存储上,这可能会产生重大影响。

答案 1 :(得分:0)

尝试使用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重新开始下载,以跳过我已下载的字节。