java - limpiar - Problema de almacenamiento en búfer de InputStreamReader
liberar buffer en java (6)
¿Por qué no usas 2 InputStream
s? Uno para leer el encabezado y otro para el cuerpo.
El segundo InputStream
debe skip
los bytes del encabezado.
Estoy leyendo datos de un archivo que tiene, desafortunadamente, dos tipos de codificación de caracteres.
Hay un encabezado y un cuerpo. El encabezado siempre está en ASCII y define el juego 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 dejo caer sobre el mismo InputStream y empiezo a intentar leer el cuerpo.
Desafortunadamente, parece, javadoc confirma esto, que InputStreamReader puede elegir read-ahead para fines de efeciencia. Entonces la lectura del encabezado mastica todo / parte del cuerpo.
¿Alguien tiene alguna sugerencia para solucionar este problema? ¿Sería la creación de un CharsetDecoder de forma manual y la alimentación en un byte a la vez, pero una buena idea (posiblemente envuelto en una implementación de Reader personalizado?)
Gracias por adelantado.
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();
}
}
Mi primer pensamiento es cerrar la transmisión y volver a InputStream#skip
, usando InputStream#skip
para saltar el encabezado antes de pasar la transmisión al nuevo InputStreamReader
.
Si realmente, realmente no desea volver a abrir el archivo, podría utilizar descriptores de archivos para obtener más de una secuencia en el archivo, aunque es posible que deba usar canales para tener múltiples posiciones dentro del archivo (ya que no puede suponer puede restablecer la posición con reset
, puede que no sea compatible).
Sugiero volver a leer la transmisión desde el principio con un nuevo InputStreamReader
. Tal vez suponga que InputStream.mark
es compatible.
Es aún más fácil:
Como dijiste, tu encabezado está siempre en ASCII. Por lo tanto, lea el encabezado directamente desde InputStream, y cuando haya terminado con él, cree el Reader con la codificación correcta y lea de él
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
}
}
Aquí está el pseudo código.
- Use
InputStream
, pero no ajuste unReader
a su alrededor. - Lea los bytes que contienen el encabezado y guárdelos en
ByteArrayOutputStream
. - Crea
ByteArrayInputStream
desdeByteArrayOutputStream
y decodifica el encabezado, esta vez envuelveByteArrayInputStream
enReader
con ASCII charset. - Calcule la longitud de la entrada que no es ascii, y lea esa cantidad de bytes en otra
ByteArrayOutputStream
. - Cree otro
ByteArrayInputStream
del segundoByteArrayOutputStream
y envuélvalo conReader
con charset desde el encabezado.
Si ajusta el InputStream y limita todas las lecturas a solo 1 byte a la vez, parece deshabilitar el almacenamiento en búfer dentro de InputStreamReader.
De esta forma, no es necesario reescribir la lógica de 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);
}
}
Para construir:
new InputStreamReader(new OneByteReadInputStream(inputStream));