Estoy leyendo datos de un archivo que tiene, desafortunadamente, dos tipos de codificación de caracteres.Problema de almacenamiento en búfer de InputStreamReader
Hay un encabezado y un cuerpo. El encabezado está siempre en ASCII y define el conjunto de caracteres en el que está codificado el cuerpo.
El encabezado no es de longitud fija y debe ejecutarse a través de un analizador para determinar su contenido/longitud.
El archivo también puede ser bastante grande, así que debo evitar traer todo el contenido a la memoria.
Así que comencé con un solo InputStream. Lo envuelvo inicialmente con un InputStreamReader con ASCII y decodifico el encabezado y extraigo el conjunto de caracteres para el cuerpo. Todo bien.
Luego creo un nuevo InputStreamReader con el juego de caracteres correcto, lo coloco sobre el mismo InputStream y comienzo a intentar leer el cuerpo.
Desafortunadamente, javadoc confirma esto, que InputStreamReader puede optar por la lectura anticipada para fines de efeciencia. Entonces la lectura del encabezado mastica todo/parte del cuerpo.
¿Alguien tiene alguna sugerencia para solucionar este problema? ¿Crearía un CharsetDecoder manualmente y se alimentaría en un byte a la vez pero sería una buena idea (posiblemente incluido en una implementación personalizada de Reader?)
Gracias de antemano.
EDITAR: Mi solución final fue escribir un InputStreamReader que no tiene buffering para asegurarme de que puedo analizar el encabezado sin masticar parte del cuerpo. Aunque esto no es demasiado eficiente, envuelvo el InputStream sin procesar con un BufferedInputStream para que no sea un problema.
// 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();
}
}
Tal vez me equivoque, pero desde el momento en que pensé que el archivo puede tener un solo tipo de codificación al mismo tiempo. – Roman
@Roman: puede hacer lo que quiera con los archivos; solo son secuencias de bytes. Así que puedes escribir un grupo de bytes que deben interpretarse como ASCII, y luego escribir un grupo de bytes para interpretarlos como UTF-16, e incluso más bytes para ser interpretados como UTF-32. No digo que sea una buena idea, aunque el caso de uso del OP es ciertamente razonable (hay que tener * alguna * forma de indicar qué codificación utiliza un archivo, después de todo). –
@Mike Q - Buena idea, el InputStreamReaderUnbuffered. Sugiero una respuesta por separado: merece la atención :) –