Проблема буферизации InputStreamReader

Я считываю данные из файла, который имеет, к сожалению, два типа кодировки символов.

Существует заголовок и тело. Заголовок всегда находится в ASCII и определяет набор символов, в котором кодируется тело.

Заголовок не является фиксированной длиной и должен быть выполнен через синтаксический анализатор для определения его содержания/длины.

Файл может также быть довольно большим, таким образом, я должен избежать, загружают все содержание в память.

Таким образом, я начался с единственным InputStream. Я переношу его первоначально с InputStreamReader с ASCII и декодирую заголовок и извлекаю набор символов для тела. Вся польза.

Затем я создаю новый InputStreamReader с набором правильного символа, отбрасываю его по тому же InputStream и начинаю пытаться считать тело.

К сожалению, это появляется, javadoc подтверждает это, которое InputStreamReader может принять решение считать вперед в целях эффективности. Таким образом, чтение заголовка жует часть/всю тела.

У кого-либо есть какие-либо предложения для работы вокруг этой проблемы? Был бы, создавая CharsetDecoder вручную и подавая один байт за один раз, но хорошую идею (возможно перенесенный в пользовательскую реализацию Читателя?)

Заранее спасибо.

Править: Мое конечное решение состояло в том, чтобы записать InputStreamReader, который не имеет никакой буферизации, чтобы гарантировать, что я могу проанализировать заголовок, не жуя часть тела. Хотя это не ужасно эффективно, я переношу необработанный InputStream с BufferedInputStream, таким образом, это не будет проблема.

// An InputStreamReader that only consumes as many bytes as is necessary
// It does not do any read-ahead.
public class InputStreamReaderUnbuffered extends Reader
{
    private final CharsetDecoder charsetDecoder;
    private final InputStream inputStream;
    private final ByteBuffer byteBuffer = ByteBuffer.allocate( 1 );

    public InputStreamReaderUnbuffered( InputStream inputStream, Charset charset )
    {
        this.inputStream = inputStream;
        charsetDecoder = charset.newDecoder();
    }

    @Override
    public int read() throws IOException
    {
        boolean middleOfReading = false;

        while ( true )
        {
            int b = inputStream.read();

            if ( b == -1 )
            {
                if ( middleOfReading )
                    throw new IOException( "Unexpected end of stream, byte truncated" );

                return -1;
            }

            byteBuffer.clear();
            byteBuffer.put( (byte)b );
            byteBuffer.flip();

            CharBuffer charBuffer = charsetDecoder.decode( byteBuffer );

            // although this is theoretically possible this would violate the unbuffered nature
            // of this class so we throw an exception
            if ( charBuffer.length() > 1 )
                throw new IOException( "Decoded multiple characters from one byte!" );

            if ( charBuffer.length() == 1 )
                return charBuffer.get();

            middleOfReading = true;
        }
    }

    public int read( char[] cbuf, int off, int len ) throws IOException
    {
        for ( int i = 0; i < len; i++ )
        {
            int ch = read();

            if ( ch == -1 )
                return i == 0 ? -1 : i;

            cbuf[ i ] = (char)ch;
        }

        return len;
    }

    public void close() throws IOException
    {
        inputStream.close();
    }
}
11
задан Mike Q 29 June 2010 в 16:36
поделиться

5 ответов

Почему бы вам не использовать 2 InputStream сек? Один для чтения заголовка, другой для тела.

Второй InputStream должен пропустить байты заголовка.

3
ответ дан 3 December 2019 в 10:03
поделиться

Я предлагаю перечитать поток с самого начала с новым InputStreamReader . Предположим, что поддерживается InputStream.mark .

1
ответ дан 3 December 2019 в 10:03
поделиться

Моя первая мысль - закрыть поток и снова открыть его, используя InputStream # skip , чтобы пропустить заголовок перед передачей потока новому InputStreamReader .

Если вы действительно не хотите повторно открывать файл, вы можете использовать файловых дескрипторов , чтобы получить более одного потока в файл, хотя вам, возможно, придется использовать каналов ], чтобы иметь несколько позиций в файле (поскольку вы не можете предположить, что можете сбросить позицию с помощью reset , это может не поддерживаться).

1
ответ дан 3 December 2019 в 10:03
поделиться

Вот псевдокод.

  1. Используйте InputStream , но не оборачивайте его Reader .
  2. Прочитать байты, содержащие заголовок, и сохранить их в ByteArrayOutputStream .
  3. Создайте ByteArrayInputStream из ByteArrayOutputStream и декодируйте заголовок , на этот раз обернув ByteArrayInputStream в Считыватель с кодировкой ASCII.
  4. Вычислить длину входных данных , отличных от ascii, и прочитать это количество байтов в другом ByteArrayOutputStream .
  5. Создайте еще один ByteArrayInputStream из второго ByteArrayOutputStream и оберните его с помощью Reader с кодировкой из заголовка .
3
ответ дан 3 December 2019 в 10:03
поделиться

Это еще проще:

Как вы сказали, ваш заголовок всегда в ASCII. Так что прочтите заголовок непосредственно из InputStream, а когда вы закончите с ним, создайте Reader с правильной кодировкой и прочтите из него

private Reader reader;
private InputStream stream;

public void read() {
    int c = 0;
    while ((c = stream.read()) != -1) {
        // Read encoding
        if ( headerFullyRead ) {
            reader = new InputStreamReader( stream, encoding );
            break;
        }
    }
    while ((c = reader.read()) != -1) {
        // Handle rest of file
    }
}
1
ответ дан 3 December 2019 в 10:03
поделиться
Другие вопросы по тегам:

Похожие вопросы: