Надежный пропуск данных в java.io.InputStream и его подтипах

Я обрабатываю двоичный поток, и мне нужно эффективно пропустить диапазон данных, которые меня не интересуют, к некоторым данным, которые будут обработаны.

InputStream.skip(long) не дает много гарантий:

Пропускает и отбрасывает n байтов данных из этого входного потока. Метод пропуска может по ряду причин привести к пропуску некоторого меньшего количества байтов, возможно, 0. Это может быть результатом любого из ряда условий; достижение конца файла до того, как будут пропущены n байтов, — это только одна возможность. Возвращается фактическое количество пропущенных байтов.

Мне нужно знать, что произошло одно из двух:

  1. Поток закончился
  2. Байты были пропущены

Достаточно просто. Однако снисходительность, предусмотренная в этом описании, означает, что, например, BufferedInputStream может просто пропустить несколько байтов и вернуться. Конечно, он говорит мне, что он пропустил только эти несколько, но не ясно, почему.

Итак, мой вопрос: можете ли вы использовать InputStream.skip(long) таким образом, чтобы вы знали, когда заканчивается поток или успешно завершается пропуск?


person Drew Noakes    schedule 27.12.2012    source источник


Ответы (3)


Я не думаю, что мы сможем получить действительно надежную реализацию, потому что контракт метода skip() довольно странный. Во-первых, поведение в EOF определено нечетко. Если я хочу пропустить 8 байтов, а is.skip(8) возвращает 0, не так просто решить, следует ли мне повторить попытку, существует опасность бесконечного цикла, если какая-то реализация решит вернуть 0 в EOF. И available() тоже нельзя доверять.

Следовательно, я предлагаю следующее:

/**
 * Skips n bytes. Best effort.
 */
public static void myskip(InputStream is, long n) throws IOException {
    while(n > 0) {
        long n1 = is.skip(n);
        if( n1 > 0 ) {
            n -= n1;
        } else if( n1 == 0 ) { // should we retry? lets read one byte
            if( is.read() == -1)  // EOF
                break;
            else 
                n--;
        } else // negative? this should never happen but...
        throw new IOException("skip() returned a negative value. This should never happen");
    }
}

Разве мы не должны вернуть значение, чтобы сообщить количество байтов, «действительно пропущенных»? Или логическое значение, сообщающее о достижении EOF? Мы не можем сделать это надежным способом. Например, если мы вызываем skip(8) для FileInputStream, он вернет 8 даже если мы находимся в EOF, или если файл имеет только 2 байта. Но этот метод надежен в том смысле, что он делает то, что мы хотим: пропустить n байт (если возможно) и позволить мне продолжить его обработку (если мое следующее чтение вернет -1, я буду знать, что было достигнуто EOF).

person leonbloy    schedule 18.01.2013
comment
Ваш ответ конкретно детализирует то, что меня беспокоит. Код, который я разместил, похоже работает на практике, но я не уверен, что он будет работать для всех реализаций InputStream. Ваше расширение выглядит интересно, и я скоро попробую его в класс, где мне это нужно. В настоящее время мой API пытается сообщить, удалось ли пропустить, поэтому мне может потребоваться изменить клиентский код, если нет никаких гарантий. Большое спасибо. - person Drew Noakes; 18.01.2013
comment
Вы можете решить проблему FileInputStream.skip(): используйте цикл while для n-1 байтов; затем, после цикла, вызовите in.read() один раз. Если он возвращает -1, ваш пропуск достиг EOF, в противном случае ваш пропуск был успешным. Кроме того, не забудьте проверить n==0 наверху. - person Kannan Goundan; 31.10.2013
comment
@KannanGoundan Интересное предложение. Недостатком, конечно, является то, что для этого потребуется как минимум два чтения из потока (одно skip плюс одно read), что в некоторых сценариях может повлиять на производительность. - person leonbloy; 31.10.2013
comment
Это выглядит более или менее так же, как метод ByteStreams.skipFully в Guava, так что, вероятно, это правильно. - person Trejkaz; 07.08.2014

Кажется, это работает для пропуска n байтов:

long skippedTotal = 0;
while (skippedTotal != n) {
    long skipped = _stream.skip(n - skippedTotal);
    assert(skipped >= 0);
    skippedTotal += skipped;
    if (skipped == 0)
        break;
}
boolean skippedEnough = skippedTotal == n;

Однако не ясно, будет ли это работать для всех реализаций InputStream, которые можно передать в мою библиотеку. Мне интересно, следует ли реализовать мой собственный метод буферизованного пропуска.

person Drew Noakes    schedule 27.12.2012
comment
Я не понимаю, как какая-либо реализация InputStream может отступить от контракта, в котором говорится, что они возвращают, сколько байтов было действительно пропущено. - person user207421; 08.01.2013
comment
@EJP, я согласен. Меня интересует, было ли пропущено меньше байтов из-за какого-то артефакта ввода-вывода (буферизация или что-то в этом роде) или из-за того, что поток закончился. Если поток не закончился, skip все равно может вернуть ноль. В какой момент вы знаете, что пропуск не работает из-за того, что больше нет байтов, по сравнению с ожиданием байтов по сети? - person Drew Noakes; 08.01.2013
comment
Проблема, которую я вижу в этом, заключается в том, что мы не можем быть уверены, что не должны повторять попытку, когда skipped == 0. Кроме того, логическому значению skippedEnough нельзя доверять. Смотрите мой ответ. - person leonbloy; 18.01.2013

Я опоздал с этим вопросом на 6 лет.

В принципе, нет никакой разницы между skip(int n) и readFully(int n). В случае пропуска вас не интересуют байты.

Для прямого эфира, т.е. tcp или файл, к которому добавлен, skip(n) может заблокировать (ожидать), как только он «пропустит» 0 байтов, в зависимости от предпочтений пользователя для ожидания.

Возврат EOF или -1 указывает на конец потока и должен быть возвращен конечному пользователю, поскольку после этой точки больше ничего не произойдет.

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

person Vortex    schedule 19.09.2018