txt limpiar liberar crear como archivo java buffer character-encoding decode inputstreamreader

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.

  1. Use InputStream , pero no ajuste un Reader a su alrededor.
  2. Lea los bytes que contienen el encabezado y guárdelos en ByteArrayOutputStream .
  3. Crea ByteArrayInputStream desde ByteArrayOutputStream y decodifica el encabezado, esta vez envuelve ByteArrayInputStream en Reader con ASCII charset.
  4. Calcule la longitud de la entrada que no es ascii, y lea esa cantidad de bytes en otra ByteArrayOutputStream .
  5. Cree otro ByteArrayInputStream del segundo ByteArrayOutputStream y envuélvalo con Reader 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));