在Java中以特定速率读取文件

时间:2009-05-16 14:10:50

标签: java file-io inputstream

是否有关于如何以特定速率读取长文件的文章/算法?

说我不想在发出读取时传递10 KB /秒。

6 个答案:

答案 0 :(得分:12)

一个简单的解决方案,通过创建ThrottledInputStream。

这应该像这样使用:

        final InputStream slowIS = new ThrottledInputStream(new BufferedInputStream(new FileInputStream("c:\\file.txt"),8000),300);

300是每秒千字节数。 8000是BufferedInputStream的块大小。

这当然应该通过实现read(byte b [],int off,int len)来概括,这将为您节省大量的System.currentTimeMillis()调用。对于每个读取的字节,都会调用System.currentTimeMillis()一次,这会导致一些开销。还应该可以存储可以节省读取的字节数,而无需调用System.currentTimeMillis()。

确保在它们之间放置一个BufferedInputStream,否则将以单个字节而不是块来轮询FileInputStream。这会将CPU加载形式从10%减少到几乎为0.您将有可能超过数据速率乘以块大小的字节数。

import java.io.InputStream;
import java.io.IOException;

public class ThrottledInputStream extends InputStream {
    private final InputStream rawStream;
    private long totalBytesRead;
    private long startTimeMillis;

    private static final int BYTES_PER_KILOBYTE = 1024;
    private static final int MILLIS_PER_SECOND = 1000;
    private final int ratePerMillis;

    public ThrottledInputStream(InputStream rawStream, int kBytesPersecond) {
        this.rawStream = rawStream;
        ratePerMillis = kBytesPersecond * BYTES_PER_KILOBYTE / MILLIS_PER_SECOND;
    }

    @Override
    public int read() throws IOException {
        if (startTimeMillis == 0) {
            startTimeMillis = System.currentTimeMillis();
        }
        long now = System.currentTimeMillis();
        long interval = now - startTimeMillis;
        //see if we are too fast..
        if (interval * ratePerMillis < totalBytesRead + 1) { //+1 because we are reading 1 byte
            try {
                final long sleepTime = ratePerMillis / (totalBytesRead + 1) - interval; // will most likely only be relevant on the first few passes
                Thread.sleep(Math.max(1, sleepTime));
            } catch (InterruptedException e) {//never realized what that is good for :)
            }
        }
        totalBytesRead += 1;
        return rawStream.read();
    }
}

答案 1 :(得分:4)

原始解决方案只是一次读取一块,然后睡觉,例如10k,然后睡一秒钟。但我要问的第一个问题是:为什么?有几个可能的答案:

  1. 你不想创造比工作更快的工作;或
  2. 您不希望在系统上创建太大的负载。
  3. 我的建议是不要在读取级别控制它。这有点混乱和不准确。而是在工作结束时控制它。 Java有很多很棒的并发工具来处理这个问题。有几种替代方法可以做到这一点。

    我倾向于使用producer consumer模式来解决这类问题。它为您提供了很好的选择,可以通过报告线程等来监控进度,它可以是一个非常干净的解决方案。

    ArrayBlockingQueue这样的东西可以用于(1)和(2)所需的节流类型。由于容量有限,读取器最终会在队列满时阻塞,因此填充速度不会太快。可以控制工人(消费者)只能快速工作以节省覆盖率(2)。

答案 2 :(得分:4)

  • while!EOF
    • 在长变量中存储System.currentTimeMillis()+ 1000(1秒)
    • 读取10K缓冲区
    • 检查存储时间是否已过
      • 如果不是,Thread.sleep()表示存储时间 - 当前时间

按照建议创建另一个InputStream的ThrottledInputStream将是一个很好的解决方案。

答案 3 :(得分:1)

这取决于你的意思是“不要超过一定的比率”或“保持接近一定的比率。”

如果您的意思是“不要超过”,您可以通过简单的循环保证:

 while not EOF do
    read a buffer
    Thread.wait(time)
    write the buffer
 od

等待的时间量是缓冲区大小的简单函数;如果缓冲区大小为10K字节,则需要在读取之间等待一秒钟。

如果你想要比这更接近,你可能需要使用计时器。

  • 创建一个Runnable进行阅读
  • 使用Timer创建TimerTask进行阅读
  • 安排TimerTask n 一秒钟。

如果您担心将数据传递到其他地方的速度,而不是控制读取,请将数据放入数据结构(如队列或循环缓冲区),并控制另一端;定期发送数据。但是,您需要小心,具体取决于数据集大小等,因为如果读取器比编写器快得多,则会遇到内存限制。

答案 4 :(得分:1)

如果您使用过Java I / O,那么您应该熟悉装饰流。我建议InputStream子类接受另一个InputStream并限制流量。 (您可以继承FileInputStream,但这种方法非常容易出错并且不灵活。)

您的具体实施将取决于您的具体要求。通常,您需要记下上次返回的时间(System.nanoTime)。在当前读取之后,在基础读取之后,wait直到已经过了足够的时间来传输数据量。更复杂的实现可以缓冲并立即返回(几乎)只有与速率指令一样多的数据(如果缓冲区的长度为零,则应该只返回读取长度为0)。

答案 5 :(得分:0)

您可以使用RateLimiter。并在InputStream中自己实现读取。下面可以看到一个例子

public class InputStreamFlow extends InputStream {
    private final InputStream inputStream;
    private final RateLimiter maxBytesPerSecond;

    public InputStreamFlow(InputStream inputStream, RateLimiter limiter) {
        this.inputStream = inputStream;
        this.maxBytesPerSecond = limiter;
    }

    @Override
    public int read() throws IOException {
        maxBytesPerSecond.acquire(1);
        return (inputStream.read());
    }

    @Override
    public int read(byte[] b) throws IOException {
        maxBytesPerSecond.acquire(b.length);
        return (inputStream.read(b));
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        maxBytesPerSecond.acquire(len);
        return (inputStream.read(b,off, len));
    }
}

如果要将流量限制为1 MB / s,则可以获得如下输入流:

final RateLimiter limiter = RateLimiter.create(RateLimiter.ONE_MB); 
final InputStreamFlow inputStreamFlow = new InputStreamFlow(originalInputStream, limiter);