Я считываю данные из файла, который имеет, к сожалению, два типа кодировки символов.
Существует заголовок и тело. Заголовок всегда находится в 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();
}
}
Почему бы вам не использовать 2 InputStream
сек? Один для чтения заголовка, другой для тела.
Второй InputStream
должен пропустить
байты заголовка.
Я предлагаю перечитать поток с самого начала с новым InputStreamReader
. Предположим, что поддерживается InputStream.mark
.
Моя первая мысль - закрыть поток и снова открыть его, используя InputStream # skip
, чтобы пропустить заголовок перед передачей потока новому InputStreamReader
.
Если вы действительно не хотите повторно открывать файл, вы можете использовать файловых дескрипторов , чтобы получить более одного потока в файл, хотя вам, возможно, придется использовать каналов ], чтобы иметь несколько позиций в файле (поскольку вы не можете предположить, что можете сбросить позицию с помощью reset
, это может не поддерживаться).
Вот псевдокод.
InputStream
, но не оборачивайте его
Reader
. ByteArrayOutputStream
. ByteArrayInputStream
из
ByteArrayOutputStream
и декодируйте заголовок
, на этот раз обернув ByteArrayInputStream
в Считыватель
с кодировкой ASCII. ByteArrayOutputStream
. ByteArrayInputStream
из второго
ByteArrayOutputStream
и оберните его
с помощью Reader
с кодировкой из заголовка
. Это еще проще:
Как вы сказали, ваш заголовок всегда в 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
}
}