2009-07-07 7 views
33

Necesito escribir una función que tome algún tipo de flujo de entrada (por ejemplo, un InputStream o un FileChannel) para leer un archivo grande en dos pasadas: una vez precalcular algunas capacidades, y segundo para hacer el trabajo "real". No quiero que todo el archivo se cargue en la memoria a la vez (a menos que sea pequeño).entrada de archivo java con rebobinado()/reset() capacidad

¿Hay una clase Java adecuada que proporcione esta capacidad? FileInputStream no admite mark()/reset(). BufferedInputStream sí, creo, pero no estoy seguro de si tiene que almacenar todo el archivo para hacer esto.

C es tan simple que solo usa fseek(), ftell() y rebobinado(). :-(

+3

Jason, por favor, no-aceptar mi respuesta y tomar [éste.] (Http://stackoverflow.com/a/18665678/3474) Es bueno porque proporciona una implementación eficiente de la API estándar 'InputStream'; cualquier consumidor de 'InputStream' puede usarlo sin cargar todo el archivo. – erickson

Respuesta

22

Creo que las respuestas que hacen referencia a un FileChannel están en la marca.

Aquí hay una implementación de ejemplo de una secuencia de entrada que encapsula esta funcionalidad. Utiliza la delegación, por lo que no es un verdadero FileInputStream, pero es un InputStream, que por lo general es suficiente. Uno podría extender FileInputStream de manera similar si eso es un requisito.

No se ha probado, utilice a su propio riesgo :)

public class MarkableFileInputStream extends FilterInputStream { 
    private FileChannel myFileChannel; 
    private long mark = -1; 

    public MarkableFileInputStream(FileInputStream fis) { 
     super(fis); 
     myFileChannel = fis.getChannel(); 
    } 

    @Override 
    public boolean markSupported() { 
     return true; 
    } 

    @Override 
    public synchronized void mark(int readlimit) { 
     try { 
      mark = myFileChannel.position(); 
     } catch (IOException ex) { 
      mark = -1; 
     } 
    } 

    @Override 
    public synchronized void reset() throws IOException { 
     if (mark == -1) { 
      throw new IOException("not marked"); 
     } 
     myFileChannel.position(mark); 
    } 
} 
+1

Esta es, de lejos, la mejor solución. BufferedInput hace que grandes porciones de, o potencialmente, TODO un archivo tengan doble buffer. Eso es una gran pérdida. Y RandomAccessFile no hereda de I nputStream, por lo tanto, no puede ser un sustituto de dondequiera que ya esté usando las transmisiones. Sin embargo, esta pequeña clase debe ser extremadamente rápida y eficiente desde el punto de vista de la memoria. – Adam

+0

Esto funcionó muy bien para mí. Agregué un 'mark (0);' al constructor porque recibía un error "no marcado" en la primera llamada a 'reset()' y, al menos en mi caso, tiene sentido que la posición predeterminada de restablecimiento ser 0. –

+2

Esta solución funciona muy bien, con un pequeño cambio. Quitaría la "marca = -1" dentro del método de reinicio. Los javadocs para reinicio no dan ninguna indicación de que deba restablecer la marca, solo la posición. Esto permite que se llame a la marca una vez, y luego se restablece para que se la llame varias veces, por ejemplo, cuando se realizan múltiples intentos. –

2

Salida java.io.RandomAccessFile

+0

bien, gracias. parece que puedo usarlo para abrir el archivo y luego usar FileChannel como clase para manipularlo/leerlo/escribirlo. –

+0

Lástima que RandomAccessFile no implemente InputStream con sus métodos mark()/reset(). > :( –

+0

Puede enrollar el suyo con bastante facilidad (si no con elegancia), consulte http://www.coderanch.com/t/277378/Streams/java/InputStream-RandomAccessFile-abest-way para obtener un ejemplo –

7

java.nio.channels.FileChannel tiene un método position(long) para restablecer la posición de nuevo a cero como fseek() en C.

22

BufferedInputStream apoya mark amortiguando el contenido en la memoria. Es mejor reservarlo para look-aheads relativamente pequeños de un tamaño predecible.

En su lugar, RandomAccessFile se puede utilizar directamente, o podría servir como base para un hormigón InputStream, extendido con un método rewind().

Alternativamente, se puede abrir un nuevo FileInputStream para cada pase.

+1

Estoy cambiando a esta respuesta, porque necesito usar una interfaz que pueda compartir entre archivos normales y en -memory buffers. Grrrrr. Estoy escribiendo mi propia interfaz RewindableStream + clases de implementación, una de las cuales envuelve RandomAccessFile. –

2

PushbackInputStream también trabajará, como siempre que se sepa el número de caracteres que desea ser capaz de rebobinar

+0

No es verdad. 'PushbackInputStream # no leído (int b)' no rebobina bytes 'b', pero presiono' b 'en la parte superior de la secuencia. –

2

BufferedInputStream tiene mark(readlimit) y reset(). readlimit debe ser mayor que filesize para que la marca sea válida. file.length()+1 está bien. Esto significa que la marca es válida hasta que se lean readlimit bytes, por lo que puede volver atrás reset().

2

Lo que quiere es RandomAccessFileInputStream - implementa InputStream interfaz con marca/restablecer, a veces buscar basado en RandomAccessFiles. Existen algunas implementaciones que pueden hacer lo que necesita.

Un ejemplo completo con las fuentes está en http://www.fuin.org/utils4j/index.html pero encontrará muchos otros que buscan en Internet y es bastante fácil de codificar si ninguno encaja exactamente.

19

Si obtiene el FileChannel asociado del FileInputStream, puede usar el método de posición para establecer el puntero del archivo en cualquier parte del archivo.

FileInputStream fis = new FileInputStream("/etc/hosts"); 
FileChannel  fc = fis.getChannel(); 


fc.position(100);// set the file pointer to byte position 100; 
Cuestiones relacionadas