Чтение файла с определенной скоростью в Java - PullRequest
5 голосов
/ 16 мая 2009

Есть ли статья / алгоритм, как я могу читать длинный файл с определенной скоростью?

Скажите, что я не хочу пропускать 10 КБ / с при выдаче чтения.

Ответы [ 6 ]

12 голосов
/ 16 мая 2009

Простое решение путем создания 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();
    }
}
4 голосов
/ 16 мая 2009
  • пока! EOF
    • хранить System.currentTimeMillis () + 1000 (1 сек) в длинной переменной
    • чтение 10K буфера
    • проверить, прошло ли сохраненное время
      • , если это не так, Thread.sleep () для сохраненного времени - текущего времени

Создание ThrottledInputStream, которое принимает другой InputStream, как предложено, было бы хорошим решением.

4 голосов
/ 16 мая 2009

Необработанное решение - просто прочитать порцию за раз, а затем уснуть, например, 10 Кб, затем поспать секунду. Но первый вопрос, который я должен задать: почему? Есть несколько вероятных ответов:

  1. Вы не хотите создавать работу быстрее, чем это возможно; или
  2. Вы не хотите создавать слишком большую нагрузку на систему.

Мое предложение не контролировать его на уровне чтения. Это немного грязно и неточно. Вместо этого контролируйте это в конце работы. В Java есть множество отличных инструментов для параллелизма. Есть несколько альтернативных способов сделать это.

Мне нравится использовать шаблон производитель-потребитель для решения этой проблемы. Это дает вам широкие возможности для отслеживания прогресса благодаря наличию потока отчетов и т. Д., И это может быть действительно чистым решением.

Что-то наподобие ArrayBlockingQueue можно использовать для регулирования, необходимого как для (1), так и (2). При ограниченной емкости считыватель в конечном итоге блокируется, когда очередь заполнена, поэтому не заполняется слишком быстро. Работников (потребителей) можно контролировать так, чтобы они работали так быстро, а также ограничивали покрытие ставки (2).

1 голос
/ 16 мая 2009

Если вы использовали Java I / O, то вы должны быть знакомы с декорированием потоков. Я предлагаю InputStream подкласс, который берет другой InputStream и регулирует скорость потока. (Вы можете создать подкласс FileInputStream, но этот подход очень подвержен ошибкам и негибок.)

Ваша точная реализация будет зависеть от ваших точных требований. Как правило, вы хотите отметить время последнего чтения (System.nanoTime). При текущем чтении, после базового чтения, wait до тех пор, пока не пройдет достаточное время для объема переданных данных. Более сложная реализация может буферизовать и возвращать (почти) сразу же только с таким количеством данных, сколько требует скорость (будьте осторожны, что вы должны возвращать только длину чтения 0, если буфер нулевой длины).

1 голос
/ 16 мая 2009

Это немного зависит от того, имеете ли вы в виду «не превышать определенный уровень» или «оставаться рядом с определенным уровнем».

Если вы имеете в виду «не превышать», вы можете гарантировать это простым циклом:

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

Количество времени ожидания является простой функцией размера буфера; если размер буфера составляет 10 Кбайт, вы хотите подождать секунду между чтениями.

Если вы хотите приблизиться к этому, вам, вероятно, нужно использовать таймер.

  • создать Runnable для чтения
  • создайте таймер с TimerTask для чтения
  • расписание TimerTask n раз в секунду.

Если вас беспокоит скорость, с которой вы передаете данные чему-то другому, вместо того, чтобы контролировать чтение, поместите данные в структуру данных, такую ​​как очередь или циклический буфер, и управляйте другим концом; отправлять данные периодически. Вы должны быть осторожны с этим, хотя, в зависимости от размера набора данных и тому подобного, потому что вы можете столкнуться с ограничениями памяти, если читатель намного быстрее, чем писатель.

0 голосов
/ 23 августа 2016

Вы можете использовать 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 МБ / с, вы можете получить входной поток следующим образом:

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