La eliminación de las cadenas duplicadas de archivos de texto grandes

Dexxrey:

Quiero quitar las cadenas duplicadas de un archivo de texto. Con el fin de hacer que pongo cada línea en un HashSet y luego escribirlos en otro archivo. Y funciona bien. Pero cuando se trata de archivos de gran tamaño (180 MB de 5 millones de líneas) no funciona muy bien. Suponiendo que el hecho de que no es posible almacenar 5 millones de cuerdas en un HashSet o cualquier otra colección, hice un bucle por lo almaceno los primeros 100 000 líneas, a continuación, escribir a archivo, a continuación, desactive la HashSet y otra vez hasta que haya hay más líneas en el archivo. Por desgracia, esto no eliminará todos los duplicados, pero creo que puede eliminar aproximadamente el 70-90% de ellos. Pero no funciona. Cuando la prueba con el archivo de 180MB con 5 millones de líneas. Puedo contar unos 300 000 duplicados y el nuevo archivo tiene alrededor de 3 millones de líneas. Se debe tener alrededor de 5 millones - 300 000.

    public File removeDuplicates(File file) {
    System.out.println("file opened");
    Scanner sc;
    HashSet<String> set = new HashSet<String>();
    JFileChooser chooser = new JFileChooser();
    File createdFile = null;
    int returnVal = chooser.showSaveDialog(parent);
    if (returnVal == JFileChooser.APPROVE_OPTION) {
        BufferedWriter bufferedWriter = null;
        createdFile = chooser.getSelectedFile();
        try {           

            if (!createdFile.exists()) {
                createdFile.createNewFile();
            }
        }catch(Exception e) {
            e.printStackTrace();
        }
    }
    try {
        sc = new Scanner(file);
        boolean hasMore = true;
        while (hasMore) {
            hasMore = false;
            while (sc.hasNextLine() && set.size() < PERIOD) {
                set.add(sc.nextLine());
                repeated++;
            }
            createdFile = this.writeToFile(set,createdFile);
            set.clear();
            hasMore = true;
            if (sc.hasNextLine() == false)
                hasMore = false;
            set.clear();
        }
    } catch (FileNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return createdFile;

}
private File writeToFile(HashSet<String> set, File f) {
        BufferedWriter bufferedWriter = null;
        try {           
            Writer writer = new FileWriter(f, true);
            bufferedWriter = new BufferedWriter(writer);
            for (String str : set) {
                bufferedWriter.write(str);
                bufferedWriter.newLine();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (bufferedWriter != null)
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }


    return f;
}

repite es la variable que cuenta las iteraciones. ¿Es algo a partir del código o se trata del consumo de memoria RAM? Y ¿hay alguna manera de hacer que funcione?

Benjamin Maurer

De-duplicado

Asumamos por un momento, que lo único que desea para de-duplicar ese archivo. Yo diría que el método más rápido y sin problemas sería buenos viejos utilidades UNIX:

cat myfile.txt | sort -u > sorted.txt

La mejora de su Solución

( TL; DR aumentar el tamaño del almacenamiento dinámico de JVM, tamaño HashSet inicializar y utilizar la última solución en esta respuesta! )

En caso de que usted necesita para hacer esto en Java, primero vamos a tratar de hacer esto más eficiente. Al igual que muchas personas han mencionado, 180MB no es todo lo que mucho. Sólo tiene que cargar todo el archivo, sin necesidad de que trozo (más entonces no eliminará todos los duplicados). Tome esta línea, por ejemplo:

HashSet<String> set = new HashSet<String>();

Esto creará un HashSet con una capacidad inicial de n (creo que 16 elementos?) Y un factor de carga de 0,75, lo que significa que a medida que agrega líneas, tendrá que volver a asignar memoria y copiar todos los objetos. Aquí es algo útil para leer, sobre todo "rendimiento"

Así que vamos a aumentar ese tamaño a la asignación evite:

Set<String> set = new HashSet<String>(5000000);

Salí del factor de carga como es, sino que significa que va a reasignar una vez que está 75% lleno. Si se conoce el tamaño de su archivo a ciencia cierta, puede ajustar los ajustes.

Muy bien, he tenido que aprender de la manera difícil - siempre hay que medir primero! Esa es la regla número uno de rendimiento en el trabajo. Escribí todo eso y después se prueba mi propia aplicación en mi estación de trabajo rápido (con 16 GB de RAM y una CPU rápida multi-core) y se suma a todo eso en mi edición. Ahora tenía curiosidad por probar su solución (que debería haber hecho de inmediato). Así que me re-corrió en mi cuaderno en el país (8 GB de RAM, CPU 4+ años de edad).

Muy bien, aquí está el código simplificado:

import java.io.*;
import java.util.*;

public class SortTest {

    public static void main(String[] args) throws IOException {
        if (args.length != 1) {
            System.err.println("Pass filename as argument!");
            System.exit(1);
        }

        Set<String> set = new HashSet<String>();
        File createdFile = new File("./outfile");
        createdFile.createNewFile();

        try (BufferedReader br = new BufferedReader(new FileReader(new File(args[0])))) {
            for (String line = br.readLine(); line != null; line = br.readLine()) {
                set.add(line);
            }
        } catch (IOException ex) {
            throw new RuntimeException("Fatal Error.",  ex);
        }

        try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(createdFile, true))) {
            for (String line : set) {
                bufferedWriter.write(line);
                bufferedWriter.newLine();
            }
        }
    }
}

Cambios: Quité el CHUNKING, cargando todo el archivo a la vez. Estoy usando un BufferedReader, ac. Un escáner es más útil para el análisis (leer números enteros etc.) y podría incurrir en gastos generales. También he añadido la escritura del archivo hasta el final y no necesito volver a crear el BufferedWriter cada vez. También tenga en cuenta que File.createNewFile () sólo va a crear un archivo si no existe y retorno si lo hizo, por lo que su control es superfluo. (Tenga en cuenta que he omitido manejo de errores adecuado por razones de brevedad)

Solía name.basics de https://datasets.imdbws.com/ Eso es un archivo de 509MB (descomprimido), que contiene 8.837.960 líneas. Esos son realmente único, por lo que el resultado final es el mismo.

Se consume realmente una diablos de un montón de recursos y mi sistema se vuelve más lento. En un primer momento, incluso me dio un error OutOfMemory! Pero correr con más espacio de almacenamiento dinámico trabajó: time java -Xmx4g SortTest ./name.basics.tsvme da:

0m44.289s reales

1m23.128s usuarios

0m2.856s sys

Así que alrededor de 44 segundos, no está mal. Ahora vamos a evitar las asignaciones y conjunto:

Set<String> set = new HashSet<String>(9000000, 0.9f);

Resultado:

0m38.443s reales

1m12.140s usuarios

0m2.376s sys

Bueno, que se ve mejor. Tengo que decir sin embargo, que volvió a ejecutar las pruebas varias veces y los tiempos pueden variar hasta 5 segundos, por lo que en la realidad, los resultados están muy cerca.

Sólo por diversión, también a mostrar mi propia aplicación pequeña, que utiliza Java más moderno y sucinto (de nuevo, sin manejo de errores adecuado):

import java.nio.file.*;
import java.util.*;

public class SortTest2 {

    public static void main(String[] args) throws Exception {
        Set<String> uniq = new HashSet<>(100000, 0.9f);
        try (Stream<String> stream = Files.lines(Paths.get(args[0]))) {
            stream.forEach(uniq::add);
        }

        Files.write(Paths.get("./outfile2"), (Iterable<String>) uniq::iterator);
    }
}

resultados:

0m38.321s reales

1m16.452s usuarios

0m2.828s sys

Menos código, pero el resultado es más o menos lo mismo. Nota: si sustituye la HashSet con un LinkedHashSet, se preservará el orden de sus líneas! Este es un ejemplo bueno por las que debe declarar las variables y argumentos con el tipo más genérico posible. Si utiliza Set<String> uniq, usted tiene que cambiar sólo esa línea para la implementación del cambio (HashSet vs LinkedHashSet).

En realidad quería tener una mirada en ella con un perfilador, pero el tiempo de ejecución fue tan corto, que ni siquiera conseguir resultados antes de terminado el programa.

Si los ajustes de archivos en la memoria RAM y se utiliza el argumento de almacenamiento dinámico máximo apropiado (-Xmx), no debería ser un problema.

Por cierto: me re-probado la cat | sort -uversión - tardó 55 segundos!

Nota: después fuertemente editado después de más pruebas

EDITAR

Siguiendo la sugerencia de usuario de DodgyCodeException y superfluo eliminado .stream()llamada en la segunda versión.

OK, esto es la mejor solución ™ - Yo diría que fue un esfuerzo de colaboración, gracias a los usuarios Hulk y vlaz.

import java.nio.file.*;
import java.util.stream.*;

public class SortTest3 {

    public static void main(String[] args) throws Exception {
        try (Stream<String> stream = Files.lines(Paths.get(args[0]))) {
            Files.write(Paths.get("./outfile3"), (Iterable<String>) stream.distinct()::iterator);
        }
    }
}

Esto no sólo es una solución muy breve (posiblemente demasiado así), lo más rápido que el otro, pero lo mejor de todo que preserva el orden . Todo gracias a .distinct().

Soluciones alternativas

Creo que la solución anterior debería ser suficiente para la mayoría de los casos de uso y es bastante simple. Pero digamos que usted necesita para hacer frente a un archivo, que no encaja en la memoria RAM, o que necesitan preservar ordenamiento línea. Podemos tomar la idea detrás de esta solución y cambiarlo un poco.

Puedes leer el archivo, línea por línea, de modo que siempre tenga una línea de la memoria - Digamos que de media longitud m . A continuación, deberá algún identificador para almacenar y comparar después, preferiblemente con un tamaño constante k y k << m . Así que hay una función hash, pero no una mala pasada con muchas colisiones, sino una función hash criptográfica, que es más resistente a la colisión (por ejemplo, SHA1, 2 o 3). Pero nota: la más resistente colisión, cuanto mayor sea el hash y cuanto mayor sea el trabajo computacional que necesita para poner en.

  1. Leer línea
  2. Calcular de hash
  3. Busque el valor de lista enlazada:
    • si encuentras uno más grande, antes de insertar
    • si encuentra uno igual a la línea, descarte
  4. Escriba la línea de archivo de salida si no se descartan

Usted necesitará una lista enlazada para mantener barata de inserción (y que la lista tiene que crecer). La lista se mantendrá ordenado por la estrategia de inserción y el archivo de salida será preservar el orden escribiendo las líneas de inmediato.

Esto tomaría aproximadamente n * k + men el espacio, pero el cálculo de la función hash será costoso computacionalmente.

Tenga en cuenta que esto no se ocupa de las colisiones. Si se utiliza una función de hash buena, sólo puede pretender que no va a ocurrir (ya que son muy poco probable). Si es imprescindible, puede ser necesario añadir otro mecanismo para confirmar la singularidad, por ejemplo, almacenar el número de línea junto con el hash a buscar la línea previamente visto para una comparación. A continuación, tendrá que encontrar un esquema para almacenar las líneas con los hashes colisionaron.

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=138235&siteId=1
Recomendado
Clasificación