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

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

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

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

  1. 您不希望以比可以完成的更快的速度创建工作; 要么
  2. 您不希望在系统上创建太大的负载。

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

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

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

一个简单的解决方案,通过创建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(); } } 
  • 虽然!EOF
    • 在长变量中存储System.currentTimeMillis()+ 1000(1秒)
    • 读取10K缓冲区
    • 检查存储时间是否已过
      • 如果不是,Thread.sleep()表示存储时间 – 当前时间

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

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

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

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

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

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

  • 创建一个Runnable来进行阅读
  • 使用TimerTask创建一个Timer来进行读取
  • 每秒安排TimerTask n次。

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

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

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