Ответ 1
Почему бы вам не использовать 2 InputStream
s? Один для чтения заголовка, а другой для тела.
Второй InputStream
должен skip
байты заголовка.
Я читаю данные из файла, который, к сожалению, имеет два типа кодировки символов.
Существует заголовок и тело. Заголовок всегда находится в ASCII и определяет набор символов, в котором тело закодировано.
Заголовок не является фиксированной длиной и должен проходить через анализатор для определения его содержимого/длины.
Файл также может быть довольно большим, поэтому мне нужно избегать переноса всего содержимого в память.
Итак, я начал с одного InputStream. Я сначала переношу его с помощью InputStreamReader с ASCII и декодирует заголовок и извлекаю набор символов для тела. Все хорошо.
Затем я создаю новый InputStreamReader с правильным набором символов, бросаю его за тот же InputStream и начинаю читать тело.
К сожалению, похоже, javadoc подтверждает это, что InputStreamReader может выбрать для чтения для целей эффективности. Таким образом, чтение заголовка жует какое-то/все тело.
Есть ли у кого-нибудь предложения по работе над этой проблемой? Будет ли создание CharsetDecoder вручную и подача в один байт за раз, но хорошая идея (возможно, завернутая в пользовательскую реализацию Reader?)
Спасибо заранее.
EDIT: Моим окончательным решением было написать 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
s? Один для чтения заголовка, а другой для тела.
Второй InputStream
должен skip
байты заголовка.
Вот псевдо-код.
InputStream
, но не
Reader
вокруг него.ByteArrayOutputStream
.ByteArrayInputStream
из
ByteArrayOutputStream
и декодировать
заголовок, на этот раз wrap ByteArrayInputStream
в Reader
с кодировкой ASCII.ByteArrayOutputStream
.ByteArrayInputStream
со второго
ByteArrayOutputStream
и оберните его
с Reader
с кодировкой из
заголовок.Моя первая мысль - закрыть поток и снова открыть его, используя InputStream#skip
, чтобы пропустить заголовок перед тем, как передать поток в новый InputStreamReader
.
Если вы действительно не хотите повторно открывать файл, вы можете использовать файловые дескрипторы, чтобы получить более одного потока для файл, хотя вам, возможно, придется использовать channels, чтобы иметь несколько позиций в файле (так как вы не можете предположить, что можете reset положение с reset
, оно может не поддерживаться).
Я предлагаю перечитать поток с самого начала с помощью нового InputStreamReader
. Возможно, предположим, что поддерживается InputStream.mark
.
Это еще проще:
Как вы сказали, ваш заголовок всегда находится в 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
}
}
Если вы завершаете InputStream и ограничиваете все чтения только одним байтом за раз, он, похоже, отключает буферизацию внутри InputStreamReader.
Таким образом, нам не нужно переписывать логику InputStreamReader.
public class OneByteReadInputStream extends InputStream
{
private final InputStream inputStream;
public OneByteReadInputStream(InputStream inputStream)
{
this.inputStream = inputStream;
}
@Override
public int read() throws IOException
{
return inputStream.read();
}
@Override
public int read(byte[] b, int off, int len) throws IOException
{
return super.read(b, off, 1);
}
}
Чтобы построить:
new InputStreamReader(new OneByteReadInputStream(inputStream));