Pregunta

Nunca he tenido experiencias cercanas con la API de Java IO antes y realmente me siento frustrado ahora. Me resulta difícil de creer lo extraño y complejo que es y lo difícil que puede ser para hacer una tarea sencilla.

Mi tarea: Tengo 2 posiciones (a partir de bytes, que terminan bytes), y pos1 pos2. Necesito leer líneas entre estos dos bytes (incluyendo el de partida, sin incluir el final) y utilizarlos como objetos UTF8 cadena.

Por ejemplo, en la mayoría de lenguajes de script, sería una muy simple 1-2-3-liner como que (en Ruby, pero será esencialmente la misma para Python, Perl, etc):

f = File.open("file.txt").seek(pos1)
while f.pos < pos2 {
  s = f.readline
  # do something with "s" here
}

Se trata rápidamente diablo con Java IO API;) De hecho, veo dos formas de leer líneas (que termina con \n) a partir de archivos locales regulares:

  • RandomAccessFile tiene getFilePointer() y seek(long pos), pero es readLine () lee las cadenas no UTF8 (e incluso no byte arrays), pero muy extrañas cadenas con codificación roto, y que no tiene almacenamiento temporal (lo que probablemente significa que cada llamada read*() se traduciría en una sola read() OS undelying => bastante lento).
  • BufferedReader tiene gran método readLine(), e incluso se puede hacer un poco de búsqueda con skip(long n), pero no tiene manera de determinar el número par de bytes que se ha leído ya, sin mencionar la posición actual en un archivo.

He tratado de usar algo como:

    FileInputStream fis = new FileInputStream(fileName);
    FileChannel fc = fis.getChannel();
    BufferedReader br = new BufferedReader(
            new InputStreamReader(
                    fis,
                    CHARSET_UTF8
            )
    );

... y luego usando fc.position() obtener el archivo actual posición y fc.position(newPosition) a un conjunto de leer, pero no parece funcionar en mi caso: parece que vuelve la posición de un tampón de pre-relleno realizado por BufferedReader, o algo por el estilo -. estos contadores parecen ser redondeado hacia arriba en incrementos de 16 K

¿Realmente tienen que implementar todo por mí mismo, es decir, una interfaz readering fichero que haría:

  • permitir que consiga / posición fija en un archivo
  • amortiguar las operaciones de lectura de archivos
  • permitir la lectura de cadenas UTF8 (o al menos permitir operaciones como "leer todo lo que hasta el próximo \n")

¿Hay una manera más rápida que la implementación de todo yo? Am I supervisar algo?

¿Fue útil?

Solución

import org.apache.commons.io.input.BoundedInputStream

FileInputStream file = new FileInputStream(filename);
file.skip(pos1);
BufferedReader br = new BufferedReader(
   new InputStreamReader(new BoundedInputStream(file,pos2-pos1))
);

Si no se preocupan por pos2, entonces usted necesita WOUNDN'T Apache Commons IO.

Otros consejos

Me escribió el código para leer randomaccessfiles UTF-8 utilizando

//File: CyclicBuffer.java
public class CyclicBuffer {
private static final int size = 3;
private FileChannel channel;
private ByteBuffer buffer = ByteBuffer.allocate(size);

public CyclicBuffer(FileChannel channel) {
    this.channel = channel;
}

private int read() throws IOException {
    return channel.read(buffer);
}

/**
 * Returns the byte read
 *
 * @return byte read -1 - end of file reached
 * @throws IOException
 */
public byte get() throws IOException {
    if (buffer.hasRemaining()) {
        return buffer.get();
    } else {
        buffer.clear();
        int eof = read();
        if (eof == -1) {
            return (byte) eof;
        }
        buffer.flip();
        return buffer.get();
    }
}
}
//File: UTFRandomFileLineReader.java


public class UTFRandomFileLineReader {
private final Charset charset = Charset.forName("utf-8");
private CyclicBuffer buffer;
private ByteBuffer temp = ByteBuffer.allocate(4096);
private boolean eof = false;

public UTFRandomFileLineReader(FileChannel channel) {
    this.buffer = new CyclicBuffer(channel);
}

public String readLine() throws IOException {
    if (eof) {
        return null;
    }
    byte x = 0;
    temp.clear();

    while ((byte) -1 != (x = (buffer.get())) &amp;&amp; x != '\n') {
        if (temp.position() == temp.capacity()) {
            temp = addCapacity(temp);
        }
        temp.put(x);
    }
    if (x == -1) {
        eof = true;
    }
    temp.flip();
    if (temp.hasRemaining()) {
        return charset.decode(temp).toString();
    } else {
        return null;
    }
}

private ByteBuffer addCapacity(ByteBuffer temp) {
    ByteBuffer t = ByteBuffer.allocate(temp.capacity() + 1024);
    temp.flip();
    t.put(temp);
    return t;
}

public static void main(String[] args) throws IOException {
    RandomAccessFile file = new RandomAccessFile("/Users/sachins/utf8.txt",
            "r");
    UTFRandomFileLineReader reader = new UTFRandomFileLineReader(file
            .getChannel());
    int i = 1;
    while (true) {
        String s = reader.readLine();
        if (s == null)
            break;
        System.out.println("\n line  " + i++);
        s = s + "\n";
        for (byte b : s.getBytes(Charset.forName("utf-8"))) {
            System.out.printf("%x", b);
        }
        System.out.printf("\n");

    }
}
}

Para @Ken Bloom Una muy rápido ir a una versión de Java 7. Nota: No creo que esta es la manera más eficiente, todavía recibo mi cabeza alrededor NIO.2, Oracle ha comenzado su tutorial aquí

También se nota que esto no está utilizando la nueva sintaxis de Java 7 ARM (que se encarga del manejo de excepciones para el archivo de recursos basados), que no estaba funcionando en la versión más reciente OpenJDK que tengo. Pero si la gente quiere ver la sintaxis, que me haga saber.

/* 
 * Paths uses the default file system, note no exception thrown at this stage if 
 * file is missing
 */
Path file = Paths.get("C:/Projects/timesheet.txt");
ByteBuffer readBuffer = ByteBuffer.allocate(readBufferSize);
FileChannel fc = null;
try
{
    /*
     * newByteChannel is a SeekableByteChannel - this is the fun new construct that 
     * supports asynch file based I/O, e.g. If you declared an AsynchronousFileChannel 
     * you could read and write to that channel simultaneously with multiple threads.
     */
    fc = (FileChannel)file.newByteChannel(StandardOpenOption.READ);
    fc.position(startPosition);
    while (fc.read(readBuffer) != -1)
    {
        readBuffer.rewind();
        System.out.println(Charset.forName(encoding).decode(readBuffer));
        readBuffer.flip();
    }
}

Comenzar con un RandomAccessFile read y utilización o readFully para obtener una matriz de bytes entre pos1 y pos2. Digamos que nos hemos almacenado los datos leídos en una variable denominada rawBytes.

A continuación, crear su BufferedReader utilizando

new BufferedReader(new InputStreamReader(new ByteArrayInputStream(rawBytes)))

A continuación, puede llamar a readLine en el BufferedReader.

Advertencia:. Esto probablemente utiliza más memoria que si se pudiera hacer el BufferedReader buscan a la propia ubicación correcta, ya que todo lo que carga previamente en la memoria

Creo que la confusión se debe a la codificación UTF-8 y la posibilidad de caracteres de doble byte.

UTF8 no especifica cuántos bytes se encuentran en un solo carácter. Estoy asumiendo desde su puesto que está utilizando caracteres de un solo byte. Por ejemplo, 412 bytes significarían 411 caracteres. Pero si la cadena estaban usando caracteres de doble byte, se obtendría el carácter 206.

El paquete java.io original no maneja bien esta confusión de varios bytes. Así que añadieron más clases para ocuparse específicamente de cuerdas. El paquete combina dos tipos diferentes de controladores de archivos (y que puede ser confuso hasta que la nomenclatura se solucionó). La corriente clases proporcionan para los datos de E / S directa sin ninguna conversión. La lector clases de convertir archivos a cadenas con soporte completo para caracteres multi-byte. Esa ayuda puede aclarar parte del problema.

Dado que usted indica que está utilizando caracteres UTF-8, que quiere que las clases de lectores. En este caso, sugiero FileReader. El método de salto () en FileReader le permite pasar por los caracteres X y después de empezar la lectura del texto. Alternativamente, prefiero el método read () sobrecargados ya que le permite captar todo el texto a la vez.

Si usted asume su "bytes" son personajes individuales, intentar algo como esto:

FileReader fr = new FileReader( new File("x.txt") );
char[] buffer = new char[ pos2 - pos ];
fr.read( buffer, pos, buffer.length );
...

llego tarde a la fiesta aquí, pero me encontré con este problema en mi propio proyecto.

Después de mucho recorrido de Javadocs y desbordamiento de pila, creo que he encontrado una solución sencilla.

Después de buscar en el lugar apropiado en su RandomAccessFile, que estoy aquí llamando raFile, haga lo siguiente:

FileDescriptor fd = raFile.getFD();
FileReader     fr = new FileReader(fd);
BufferedReader br = new BufferedReader(fr);

A continuación, debería ser capaz de llamar br.readLine() al contenido de su corazón, que será mucho más rápido que llamar raFile.readLine().

El único que no estoy seguro es si las cadenas UTF8 se manejan correctamente.

El java IO API es muy flexible. Lamentablemente a veces la flexibilidad hace que sea detallado. La idea principal aquí es que hay muchas corrientes, los escritores y los lectores que implementan envoltorio golpeteo. Por ejemplo BufferedInputStream envuelve cualquier otro InputStream. Lo mismo es sobre los flujos de salida.

La diferencia entre las corrientes y los lectores / escritores es que los flujos de trabajo con bytes, mientras que los lectores / escritores trabajan con los personajes.

Afortunadamente algunos arroyos, escritores y lectores tienen constructores convenientes para simplificar la codificación. Si desea leer el archivo sólo hay que decir

    InputStream in = new FileInputStream("/usr/home/me/myfile.txt");
    if (in.markSupported()) {
        in.skip(1024);
        in.read();
    }

No es tan complicado como usted miedo.

Los canales es algo diferente. Es una parte de la llamada "nueva IO" o nio. Nueva IO no está bloqueada - que es su principal ventaja. Usted puede buscar en Internet para cualquier "nio java tutorial" y leer sobre él. Pero es más complicado que IO regular y no es necesario para la mayoría de aplicaciones.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top