web-dev-qa-db-fra.com

Suppression de lignes en double dans un fichier à l'aide de Java

Dans le cadre d'un projet sur lequel je travaille, j'aimerais nettoyer un fichier que je génère d'entrées de ligne en double. Cependant, ces doublons ne se produiront souvent pas l'un près de l'autre. Je suis venu avec une méthode de le faire en Java (qui a essentiellement fait une copie du fichier, puis utilisé une instruction while imbriquée pour comparer chaque ligne d'un fichier avec le reste de l'autre). Le problème, c'est que mon fichier généré est assez volumineux et lourd (environ 225 000 lignes de texte et environ 40 Mo). J'estime que mon processus actuel prend 63 heures! Ce n'est certainement pas acceptable.

J'ai besoin d'une solution intégrée pour cela, cependant. De préférence en Java. Des idées? Merci!

24
Monster

Hmm ... 40 Mo semble assez petit pour que vous puissiez construire une Set des lignes et ensuite les imprimer toutes en arrière. Ce serait beaucoup, beaucoup plus rapide que de faire O (n2) Travail I/O.

Ce serait quelque chose comme ceci (en ignorant les exceptions):

public void stripDuplicatesFromFile(String filename) {
    BufferedReader reader = new BufferedReader(new FileReader(filename));
    Set<String> lines = new HashSet<String>(10000); // maybe should be bigger
    String line;
    while ((line = reader.readLine()) != null) {
        lines.add(line);
    }
    reader.close();
    BufferedWriter writer = new BufferedWriter(new FileWriter(filename));
    for (String unique : lines) {
        writer.write(unique);
        writer.newLine();
    }
    writer.close();
}

Si l'ordre est important, vous pouvez utiliser un LinkedHashSet au lieu d'un HashSet. Étant donné que les éléments sont stockés par référence, la surcharge d'une liste liée supplémentaire doit être insignifiante par rapport à la quantité réelle de données.

Edit: Comme Workshop Alex l'a fait remarquer, si vous voulez bien créer un fichier temporaire, vous pouvez simplement imprimer les lignes au fur et à mesure que vous les lisez. Cela vous permet d’utiliser une simple HashSet au lieu de LinkedHashSet. Mais je doute que vous remarquiez la différence sur une opération liée aux entrées/sorties comme celle-ci.

36
Michael Myers

D'accord, la plupart des réponses sont un peu ridicules et lentes car elles impliquent l'ajout de lignes à un hachage ou quoi que ce soit, puis à ce qu'elles soient à nouveau déplacées. Me laisser montrer la solution la plus optimale en pseudocode:

Create a hashset for just strings.
Open the input file.
Open the output file.
while not EOF(input)
  Read Line.
  If not(Line in hashSet)
    Add Line to hashset.
    Write Line to output.
  End If.
End While.
Free hashset.
Close input.
Close output.

S'il vous plaît, ne rendez pas la tâche plus difficile que nécessaire. :-) Ne vous souciez même plus du tri, vous n'en avez pas besoin.

15
Wim ten Brink

Une approche similaire

public void stripDuplicatesFromFile(String filename) {
    IOUtils.writeLines(
        new LinkedHashSet<String>(IOUtils.readLines(new FileInputStream(filename)),
        "\n", new FileOutputStream(filename + ".uniq"));
}
9
Peter Lawrey

Quelque chose comme ça, peut-être:

BufferedReader in = ...;
Set<String> lines = new LinkedHashSet();
for (String line; (line = in.readLine()) != null;)
    lines.add(line); // does nothing if duplicate is already added
PrintWriter out = ...;
for (String line : lines)
    out.println(line);

LinkedHashSet conserve l'ordre d'insertion, par opposition à HashSet qui (tout en étant légèrement plus rapide pour la recherche/insertion) réorganisera toutes les lignes.

4
gustafc

Vous pouvez utiliser Définir dans la bibliothèque Collections pour stocker des valeurs uniques et visibles lors de la lecture du fichier.

Set<String> uniqueStrings = new HashSet<String>();

// read your file, looping on newline, putting each line into variable 'thisLine'

    uniqueStrings.add(thisLine);

// finish read

for (String uniqueString:uniqueStrings) {
  // do your processing for each unique String
  // i.e. System.out.println(uniqueString);
}
3
brabster
  • Lire dans le fichier, en enregistrant le numéro de ligne et la ligne: O (n)
  • Triez-le en ordre alphabétique: O (n log n)
  • Supprimer les doublons: O (n)
  • Triez-le dans son ordre de numéro de ligne d'origine: O (n log n)
2
Simon Nickerson

Si l'ordre n'a pas d'importance, le le moyen le plus simple consiste à utiliser des scripts Shell :

<infile sort | uniq > outfile
2
phihag

Essayez un simple HashSet qui stocke les lignes que vous avez déjà lues. Puis parcourez le fichier. Si vous rencontrez des doublons, ils sont simplement ignorés (un ensemble ne pouvant contenir que chaque élément une fois).

2
Kevin Dungs

L'approche de hachage est correcte, mais vous pouvez l'ajuster pour ne pas avoir à stocker toutes les chaînes en mémoire, mais un pointeur logique vers l'emplacement dans le fichier afin que vous puissiez revenir à la lecture de la valeur réelle au cas où vous en auriez besoin.

Une autre approche créative consiste à ajouter à chaque ligne le numéro de la ligne, puis à trier toutes les lignes, à supprimer les doublons (en ignorant le dernier jeton qui devrait être le numéro), puis à trier à nouveau le fichier par le dernier jeton et en le rayant. dans la sortie.

1
fortran

Il existe deux solutions évolutives, où évolutif, je veux dire disque et non basé sur la mémoire, selon que la procédure doit être stable ou non, où stable, je veux dire que l'ordre après la suppression des doublons est le même. si l'évolutivité n'est pas un problème, utilisez simplement la mémoire pour le même type de méthode.

Pour la solution non stable, commencez par trier le fichier sur le disque. Pour ce faire, divisez le fichier en fichiers plus petits, triez les plus petits morceaux en mémoire, puis fusionnez les fichiers dans un ordre de tri, la fusion ignorant les doublons. 

La fusion elle-même peut être réalisée en utilisant presque pas de mémoire, en comparant uniquement la ligne en cours dans chaque fichier, car la ligne suivante est garantie d'être plus grande.

La solution stable est légèrement plus délicate. Commencez par trier le fichier en morceaux comme avant, mais indiquez dans chaque ligne le numéro de la ligne d'origine. Ensuite, pendant la "fusion", ne stockez pas Le résultat, seulement les numéros de ligne à supprimer.

Copiez ensuite le fichier original ligne par ligne, en ignorant les numéros de ligne que vous avez stockés ci-dessus.

0
user44242

L'importance de l'ordre des lignes et le nombre de doublons comptez-vous voir? 

Sinon, et si vous comptez sur beaucoup de dupes (c'est-à-dire beaucoup plus de lecture que d'écriture), je penserais également à paralléliser la solution de hachage, avec le hachage comme ressource partagée. 

0
mikek
void deleteDuplicates(File filename) throws IOException{
    @SuppressWarnings("resource")
    BufferedReader reader = new BufferedReader(new FileReader(filename));
    Set<String> lines = new LinkedHashSet<String>();
    String line;
    String delims = " ";
    System.out.println("Read the duplicate contents now and writing to file");
    while((line=reader.readLine())!=null){
        line = line.trim(); 
        StringTokenizer str = new StringTokenizer(line, delims);
        while (str.hasMoreElements()) {
            line = (String) str.nextElement();
            lines.add(line);
            BufferedWriter writer = new BufferedWriter(new FileWriter(filename));
            for(String unique: lines){
                writer.write(unique+" ");               
            }
            writer.close();
        }
    }
    System.out.println(lines);
    System.out.println("Duplicate removal successful");
}
0
Anit Chaudhary

J'ai fait deux hypothèses pour cette solution efficace:

  1. Il y a un équivalent de ligne de Blob ou on peut le traiter en binaire
  2. Nous pouvons enregistrer le décalage ou un pointeur au début de chaque ligne.

Sur la base de ces hypothèses, la solution est la suivante: 1. Lisez une ligne, enregistrez la longueur dans la table de hachage en tant que clé pour obtenir une table de hachage plus claire. Enregistrez la liste en tant qu’entrée dans hashmap pour toutes les lignes ayant cette longueur mentionnée dans key. La construction de cette table de hachage est O (n). Lors du mappage des décalages pour chaque ligne de la table de hachage, comparez les blobs de ligne avec toutes les entrées existantes dans la liste de lignes (décalages) pour cette longueur de clé à l'exception de l'entrée -1. as offset.if duplicate found supprime les deux lignes et enregistre l'offset -1 dans ces emplacements de la liste.

Alors considérez la complexité et l'utilisation de la mémoire:

Mémoire de hachage, complexité de l'espace = O(n) où n est le nombre de lignes

Complexité temporelle - s'il n'y a pas de doublons mais que toutes les lignes de longueur égale, en tenant compte de la longueur de chaque ligne = m, considérons le nombre de lignes = n, alors ce serait, O (n). Puisque nous supposons que nous pouvons comparer blob, le m n'a pas d'importance. C'était le pire des cas.

Dans d'autres cas, nous économisons sur les comparaisons bien que nous aurons peu d'espace supplémentaire requis dans hashmap.

De plus, nous pouvons utiliser mapreduce côté serveur pour scinder l'ensemble et fusionner les résultats ultérieurement. Et en utilisant la longueur ou le début de la ligne comme clé de mappage.

0
AAW

Si vous pouviez utiliser les commandes UNIX Shell, vous pourriez effectuer les opérations suivantes:

for(i = line 0 to end)
{
    sed 's/\$i//2g' ; deletes all repeats
}

Cela parcourrait tout votre fichier et transmettrait chaque occurrence unique une fois par appel. De cette façon, vous ne ferez pas un tas de recherches que vous avez faites auparavant.

0
samoz