Czytam dane z pliku, który ma niestety dwa typy kodowania znaków.Problem z buforowaniem InputStreamReader
Istnieje nagłówek i treść. Nagłówek zawsze znajduje się w ASCII i definiuje zestaw znaków, w którym kodowane jest ciało.
Nagłówek nie ma ustalonej długości i musi być uruchamiany przez parser w celu ustalenia jego zawartości/długości.
Plik może być dość duży, więc należy unikać umieszczania całej zawartości w pamięci.
Więc zacząłem od jednego InputStream. Zawijam go początkowo za pomocą InputStreamReader z ASCII i dekoduję nagłówek i wypakowuję zestaw znaków dla ciała. Wszystko dobrze.
Następnie tworzę nowy InputStreamReader z poprawnym zestawem znaków, upuszczam go na tym samym InputStream i zaczynam próbować czytać ciało.
Niestety wygląda na to, że javadoc potwierdza to, że InputStreamReader może zdecydować się na odczyt z wyprzedzeniem dla celów efektywności. Czytanie nagłówka przeżuwa część/całe ciało.
Czy ktoś ma jakieś sugestie dotyczące obejścia tego problemu? Czy ręczne tworzenie CharsetDecoder i karmienie w jednym bajcie, ale dobry pomysł (ewentualnie w niestandardowej implementacji Reader?)
Z góry dziękuję.
EDYCJA: Moje ostateczne rozwiązanie było napisanie InputStreamReader, który nie ma buforowanie, aby upewnić się, że mogę parsować nagłówek bez żucia części ciała. Chociaż nie jest to bardzo wydajne, zawijam surowy InputStream za pomocą BufferedInputStream, więc nie będzie problemu.
// 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();
}
}
Może się mylę, ale od tej chwili myślałem, że plik może mieć tylko jeden typ kodowania w tym samym czasie. – Roman
@Roman: Możesz robić cokolwiek chcesz z plikami; są po prostu ciągami bajtów. Możesz więc zapisać grupę bajtów, które mają być interpretowane jako ASCII, a następnie wypisać kilka bajtów, które mają być interpretowane jako UTF-16, a jeszcze więcej bajtów powinno być interpretowanych jako UTF-32. Nie mówię, że to dobry pomysł, chociaż przypadek użycia OP jest z pewnością uzasadniony (musisz mieć * jakiś * sposób wskazania, jakie kodowanie pliku używa, mimo wszystko). –
@Mike Q - Dobry pomysł InputStreamReaderUnbuffered. Proponuję osobną odpowiedź - zasługuje na uwagę :) –