Я читаю данные из файла, который имеет, к сожалению, два типа кодировки символов.Ошибка буферизации InputStreamReader
Существует заголовок и корпус. Заголовок всегда находится в 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();
}
}
Может быть, я ошибаюсь, но с того момента я думал, что файл может иметь только один тип кодировки одновременно. – Roman
@Roman: вы можете делать все, что хотите, с файлами; это просто последовательности байтов. Таким образом, вы можете записать кучу байтов, которые должны интерпретироваться как ASCII, а затем выписать еще несколько байтов, предназначенных для интерпретации как UTF-16, и еще больше байтов, предназначенных для интерпретации как UTF-32. Я не говорю, что это хорошая идея, хотя пример использования OP, безусловно, является разумным (у вас должен быть * некоторый * способ указать, что кодирует файл, в конце концов). –
@Mike Q - Хорошая идея InputStreamReaderUnbuffered. Я предлагаю отдельный ответ - он заслуживает внимания :) –