是否有关于如何以特定速率读取长文件的文章/算法?
说我不想在发出读取时传递10 KB /秒。
答案 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,然后睡一秒钟。但我要问的第一个问题是:为什么?有几个可能的答案:
我的建议是不要在读取级别控制它。这有点混乱和不准确。而是在工作结束时控制它。 Java有很多很棒的并发工具来处理这个问题。有几种替代方法可以做到这一点。
我倾向于使用producer consumer模式来解决这类问题。它为您提供了很好的选择,可以通过报告线程等来监控进度,它可以是一个非常干净的解决方案。
像ArrayBlockingQueue这样的东西可以用于(1)和(2)所需的节流类型。由于容量有限,读取器最终会在队列满时阻塞,因此填充速度不会太快。可以控制工人(消费者)只能快速工作以节省覆盖率(2)。
答案 2 :(得分:4)
按照建议创建另一个InputStream的ThrottledInputStream将是一个很好的解决方案。
答案 3 :(得分:1)
这取决于你的意思是“不要超过一定的比率”或“保持接近一定的比率。”
如果您的意思是“不要超过”,您可以通过简单的循环保证:
while not EOF do
read a buffer
Thread.wait(time)
write the buffer
od
等待的时间量是缓冲区大小的简单函数;如果缓冲区大小为10K字节,则需要在读取之间等待一秒钟。
如果你想要比这更接近,你可能需要使用计时器。
如果您担心将数据传递到其他地方的速度,而不是控制读取,请将数据放入数据结构(如队列或循环缓冲区),并控制另一端;定期发送数据。但是,您需要小心,具体取决于数据集大小等,因为如果读取器比编写器快得多,则会遇到内存限制。
答案 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);