Usando RandomAccessFile junto con BufferedReader para acelerar la lectura de archivos

Tengo que :-

  • Lea el archivo de texto grande línea por línea.
  • Anote la posición del puntero del archivo después de cada línea leída.
  • Detenga la lectura del archivo si el tiempo de ejecución es superior a 30 segundos.
  • Reanudar desde el último puntero de archivo observado en un nuevo proceso.

Qué estoy haciendo :

  1. Usando RandomAccessFile.getFilePointer () para anotar el puntero del archivo.
  2. Envuelva RandomAccessFile en otro BufferedReader para acelerar el proceso de lectura de archivos según esta respuesta.
  3. Cuando el tiempo es superior a 30 segundos, dejo de leer el archivo. Reiniciando el proceso con el nuevo RandomAccessFile y usando el método RandomAccessFile.seek para mover el puntero del archivo a donde lo dejé.

Problema:

Como estoy leyendo a través de BufferedReader envuelto alrededor de RandomAccessFile, parece que el puntero del archivo se está moviendo mucho más adelante en una sola llamada a BufferedReader.readLine (). Sin embargo, si uso RandomAccessFile.readLine () directamente, el puntero del archivo se mueve correctamente paso a paso hacia adelante.

Usando BufferedReader como envoltorio:

    RandomAccessFile randomAccessFile = new RandomAccessFile("mybigfile.txt", "r");
BufferedReader brRafReader = new BufferedReader(new FileReader(randomAccessFile.getFD()));
while((line = brRafReader.readLine()) != null) {
    System.out.println(line+", Position : "+randomAccessFile.getFilePointer());
}

Salida:

Line goes here, Position : 13040
Line goes here, Position : 13040
Line goes here, Position : 13040
Line goes here, Position : 13040

Usando Direct RandomAccessFile.readLine

    RandomAccessFile randomAccessFile = new RandomAccessFile("mybigfile.txt", "r");
while((line = randomAccessFile.readLine()) != null) {
    System.out.println(line+", Position : "+randomAccessFile.getFilePointer());
}

Salida: (Esto es como se esperaba. El puntero del archivo se mueve correctamente con cada llamada a readline)

Line goes here, Position : 11011
Line goes here, Position : 11089
Line goes here, Position : 12090
Line goes here, Position : 13040

¿Alguien podría decir, qué mal estoy haciendo aquí? ¿Hay alguna manera de acelerar el proceso de lectura usando RandomAccessFile?

Respuesta 1

La razón del comportamiento observado es que, como su nombre lo indica, BufferedReaderestá protegido . Lee una gran cantidad de datos a la vez (en un búfer) y devuelve solo las partes relevantes del contenido del búfer, es decir, la parte hasta el siguiente \nseparador de línea.

Creo que, en términos generales, hay dos enfoques posibles:

  1. Podría implementar su propia lógica de almacenamiento en búfer.
  2. Usando algún truco de reflexión feo para obtener el desplazamiento de búfer requerido

Para 1., ya no lo usarías RandomAccessFile#readLine. En cambio, haría su propio almacenamiento en búfer a través de

byte buffer[] = new byte[8192];
...
// In a loop:
int read = randomAccessFile.read(buffer);
// Figure out where a line break `\n` appears in the buffer,
// return the resulting lines, and take the position of the `\n`
// into account when storing the "file pointer"

Como indica el vago comentario: esto puede ser engorroso y complicado. Básicamente, volverías a implementar lo que hace el readLinemétodo en la BufferedReaderclase. Y en este punto, ni siquiera quiero mencionar los dolores de cabeza que pueden causar los diferentes separadores de línea o conjuntos de caracteres.

Para 2., simplemente puede acceder al campo del BufferedReaderque almacena el desplazamiento del búfer. Esto se implementa en el siguiente ejemplo. Por supuesto, esta es una solución algo cruda, pero mencionada y mostrada aquí como una alternativa simple, dependiendo de qué tan "sostenible" sea la solución y cuánto esfuerzo esté dispuesto a invertir.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class LargeFileRead {
    public static void main(String[] args) throws Exception {

        String fileName = "myBigFile.txt";

        long before = System.nanoTime();
        List<String> result = readBuffered(fileName);
        //List<String> result = readDefault(fileName);
        long after = System.nanoTime();
        double ms = (after - before) / 1e6;
        System.out.println("Reading took " + ms + "ms "
                + "for " + result.size() + " lines");
    }

    private static List<String> readBuffered(String fileName) throws Exception {
        List<String> lines = new ArrayList<String>();
        RandomAccessFile randomAccessFile = new RandomAccessFile(fileName, "r");
        BufferedReader brRafReader = new BufferedReader(
                new FileReader(randomAccessFile.getFD()));
        String line = null;
        long currentOffset = 0;
        long previousOffset = -1;
        while ((line = brRafReader.readLine()) != null) {
            long fileOffset = randomAccessFile.getFilePointer();
            if (fileOffset != previousOffset) {
                if (previousOffset != -1) {
                    currentOffset = previousOffset;
                }
                previousOffset = fileOffset;
            }
            int bufferOffset = getOffset(brRafReader);
            long realPosition = currentOffset + bufferOffset;
            System.out.println("Position : " + realPosition 
                    + " with FP " + randomAccessFile.getFilePointer()
                    + " and offset " + bufferOffset);
            lines.add(line);
        }
        return lines;
    }

    private static int getOffset(BufferedReader bufferedReader) throws Exception {
        Field field = BufferedReader.class.getDeclaredField("nextChar");
        int result = 0;
        try {
            field.setAccessible(true);
            result = (Integer) field.get(bufferedReader);
        } finally {
            field.setAccessible(false);
        }
        return result;
    }

    private static List<String> readDefault(String fileName) throws Exception {
        List<String> lines = new ArrayList<String>();
        RandomAccessFile randomAccessFile = new RandomAccessFile(fileName, "r");
        String line = null;
        while ((line = randomAccessFile.readLine()) != null) {
            System.out.println("Position : " + randomAccessFile.getFilePointer());
            lines.add(line);
        }
        return lines;
    }
}

(Nota: los desplazamientos pueden parecer apagados en 1, pero esto se debe a que el separador de línea no se tiene en cuenta en la posición. Esto podría ajustarse si es necesario)

NOTA: Esto es solo un boceto. Los objetos RandomAccessFile deben cerrarse correctamente cuando finaliza la lectura, pero eso depende de cómo se supone que la lectura se interrumpe cuando se excede el límite de tiempo, como se describe en la pregunta

Respuesta: 2

Estoy tratando de descubrir la complejidad del código dado a continuación en función del tamaño del problema n. suma = 0; if (INCLUSO (n)) para (i = 0; i <n; i ++) if (i% 2 == 0) O (logn) ...

Necesito recuperar algunos datos en la base de datos en tiempo real de Firebase en función de los valores de un niño. Por ejemplo, aquí necesito mostrar solo el hijo (notificación) donde se acepta == verdadero. Transmito mis valores en ...

Estoy tratando de ignorar un carácter unicode (espacio en blanco) de una oración y cualquier ayuda será muy apreciada. Ejemplo: utmctr = google% 20search% 20keyword Salida deseada: palabra clave de búsqueda de google ...

mi java dekstop envía un error como este, ¿qué puedo hacer? getText () en JPasswordField ha quedado en desuso. He intentado reemplazar getText () y cambiar a getPassword (): pst.setString (2, txtPass ...