7

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

假设我不想在发出读取时通过 10 KB/秒。

4

6 回答 6

14

一个简单的解决方案,通过创建一个 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();
    }
}
于 2009-05-16T15:59:43.990 回答
4

粗略的解决方案是一次读取一个块,然后休眠,例如 10k,然后再休眠一秒钟。但我要问的第一个问题是:为什么?有几个可能的答案:

  1. 您不想比完成工作的速度更快;或者
  2. 您不想在系统上造成太大的负载。

我的建议是不要在读取级别控制它。这有点混乱和不准确。而是在工作结束时控制它。Java 有很多很棒的并发工具来处理这个问题。有几种替代方法可以做到这一点。

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

ArrayBlockingQueue这样的东西可用于 (1) 和 (2) 所需的那种节流。由于容量有限,当队列已满时,阅读器最终会阻塞,因此不会很快填满。可以控制工人(消费者)只工作如此之快,以限制速率覆盖(2)。

于 2009-05-16T14:17:02.007 回答
4
  • 而!EOF
    • 将 System.currentTimeMillis() + 1000 (1 sec) 存储在 long 变量中
    • 读取 10K 缓冲区
    • 检查存储的时间是否已过
      • 如果不是,则 Thread.sleep() 用于存储时间 - 当前时间

按照建议创建采用另一个 InputStream 的 ThrottledInputStream 将是一个不错的解决方案。

于 2009-05-16T14:47:26.830 回答
2

如果您使用过 Java I/O,那么您应该熟悉装饰流。我建议使用InputStream另一个子类InputStream并限制流速。(您可以进行子类FileInputStream化,但这种方法极易出错且不灵活。)

您的具体实施将取决于您的具体要求。通常,您需要注意最后一次读取返回的时间 ( System.nanoTime)。在当前读取中,在底层读取之后,wait直到足够的时间用于传输的数据量。更复杂的实现可能会缓冲并(几乎)立即返回(几乎)只有速率指示的数据(请注意,如果缓冲区长度为零,则只应返回读取长度为 0)。

于 2009-05-16T14:29:15.470 回答
2

您可以使用 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);
于 2016-08-23T10:10:37.923 回答
1

这在一定程度上取决于您的意思是“不超过某个比率”还是“接近某个比率”。

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

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

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

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

  • 创建一个 Runnable 来读取
  • 创建一个带有TimerTask的Timer来读取
  • 每秒调度 TimerTask n次。

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

于 2009-05-16T14:17:53.783 回答