巨大なテキストファイルから重複している文字列を削除します

Dexxrey:

私は、テキストファイルから重複している文字列を削除したいです。私はHashSetの中で一つ一つのラインを置くことを行い、その後、別のファイルにそれらを書き込むために。そして、それは罰金に動作します。それは大きなファイル(180メガバイト500万ライン)に来るときしかし、それは非常にうまく機能しません。HashSetのか、他のコレクションに500万文字列を格納することができないという事実を仮定すると、私は最初の100の000行を格納するので、私はループを作った、そこまでは再びそれにHashSetのをオフにしてから、ファイルに書き込みますファイルには複数行ではありません。残念ながら、これはすべての重複を削除しませんが、私はそれが彼らの70から90パーセント程度取り除くことができると思います。しかし、それは動作しません。私、5万行と180メガバイトのファイルでそれをテストするとき。私は約300 000重複をカウントし、新しいファイルが300万程度のラインを持っています。300 000 - それは500万程度が必要です。

    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;
}

反復回数をカウント変数がある繰り返し。それは、コードから何かあるか、それはRAMの消費量からでしょうか?そして、それを動作させるための方法はありますか?

ベンジャミン・マウラー

デ - 重複

あなたは、単にそのファイルを重複除外したいことを、一瞬のために仮定しよう。私は、最速、無面倒方法は古き良きUnixのutilsのだろうと思います。

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

あなたのソリューションを改善

TL; DRは、JVMヒープサイズ、初期HashSetのサイズを大きくし、この回答で最後のソリューションを使用します!

ケースでは、必要な、Javaでこれを行うの最初の試みは、これは、より効率的にするようにします。多くの人が言及したように、180メガバイトはすべてのことあまりないです。ただ、全体のファイル、チャンクに必要はありません、それは(プラス、あなたはすべての重複を排除しません)をロードします。たとえば、この行を取ります:

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

これは、初期容量を持つHashSetのを作成します。nは、あなたが行を追加すると、それはオーバー再割り当てメモリおよびコピーすべてに持っているだろうことを意味し、(私は16個の要素を考える?)と0.75の負荷率。ここで読むことが有益なものは、特に「パフォーマンス」であります

それではサイズが割り当てを避けるためにすることを増加してみましょう:

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

であるように私は、負荷率を残したが、それは75%フルです一度その手段は、それが再割り当てされます。あなたは確かにあなたのファイルのサイズがわかっている場合は、それらの設定を調整することができます。

常に最初の測定-さてさて、私はそれを苦労して学ばなければなりませんでした!これは、パフォーマンスの仕事のルール番号1です。私はすべてのことをし、その後は(16ギガバイトのRAMと高速マルチコアCPUでの)私の高速なワークステーション上で、私の独自の実装をテストし、私の編集ですべてのことをまとめ書きました。今、私は(私はすぐに行っているはずです)あなたのソリューションを試して興味がありました。私は自宅で私のノートブック(8ギガバイトRAM、4+歳のCPU)上でそれを再実行しました。

さてさて、ここでは簡略化されたコードは次のようになります。

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();
            }
        }
    }
}

変更点:私は一度にファイル全体をロードし、チャンクを削除しました。私はBufferedReaderの、BCを使用しています。スキャナは、解析するためのより有用である(などの整数を読んで)とオーバーヘッド被る可能性があります。私はまた、最後にファイルの書き込みを追加し、私はBufferedWriterのたびを再作成する必要はありません。また、あなたのチェックは不要であるので、それは、それがなかったかどうか存在し、復帰しない場合File.createNewFileは()のみのファイルを作成することに注意してください。(私は簡潔にするため、適切なエラー処理を省略していることに注意してください)

Iからname.basicsを使用https://datasets.imdbws.com/ 8.837.960行を含む、509メガバイトのファイル(解凍)されていること。これらは実際には一意であるため、最終結果は同じです。

これは、実際に多くの資源の一体を消費し、私のシステムは、むしろ遅くなります。最初は、私ものOutOfMemoryエラーを持って!しかし、より多くのヒープ領域とそれを実行すると、働いていた:time java -Xmx4g SortTest ./name.basics.tsv私を与えます:

本当の0m44.289s

ユーザー1m23.128s

SYS 0m2.856s

だから、周りに44秒、悪くありません。今度は、割り当てとセットを避けてみましょう:

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

結果:

本当の0m38.443s

ユーザー1m12.140s

SYS 0m2.376s

まあ、ルックスが良いこと。私は現実には、結果は非常に接近している、私はそれらのテストを複数回reranと時間が5秒まで変えることができていること、けれども言わなければなりません。

楽しみのためだけに、私はまた、より近代的かつ簡潔ジャワ(再び、ない適切なエラー処理)を使用して、私自身の小さな実装を紹介します:

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);
    }
}

結果:

本当の0m38.321s

ユーザー1m16.452s

SYS 0m2.828s

以下のコードが、結果はほとんど同じです。注:LinkedHashSetのでHashSetのを交換した場合、それはあなたの行の順序を保持します!これは、可能な限り最も一般的なタイプを使用して、変数と引数を宣言しなければならない理由は良い例です。あなたが使用している場合Set<String> uniqは、変更の実装(LinkedHashSetの対HashSetの)にのみ、その行を変更する必要があります。

私は実際にプロファイラでそれを見ていると思ったが、実行時間はとても短かったプログラムが終了する前に、私も結果を取得できませんでした。

ファイルのあなたのRAMに収まると、あなたが適切な最大ヒープ引数(-Xmx)を使用する場合、それが問題になることはありません。

ところで:私は再テストcat | sort -uバージョン-それは55秒かかりました!

注:より多くのテストの後に重く編集したポスト

EDIT

ユーザーDodgyCodeExceptionの提案および削除余分に続いて.stream()第二版でコールを。

OK、これは最善の解決策™ -私はそれは、ユーザーハルクと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);
        }
    }
}

だけでなく、この解決策は、他の1の速さとして、(おそらくあまりにも多いので)非常に簡潔であるが、すべての最高の、それは順序を保持しますすべてのおかげで.distinct()

代替ソリューション

私は、上記の溶液は、ほとんどのユースケースのために十分なはずだと思うと、かなり単純です。しかし、あなたがRAMに収まらないファイルに対処する必要がある、またはあなたが行の順序を保持する必要があるとしましょう。私たちは、このソリューションの背後にある考え方を取り、少しそれを変更することができます。

平均的な長さのみましょうと言う-あなたは、常にメモリに1つのラインを持っていますので、あなたは、ファイル、行ずつ読みメートルをその後、ストアにいくつかの識別子を必要とし、好ましくは一定のサイズを有する、後で比較K及びK << Mあなたがあるハッシュ関数ではなく、多くの衝突と高速な1が、暗号学的ハッシュ関数を、必要とするので、より多くの衝突耐性(例えば、SHA1、2または3)。しかしノート:より多くの衝突耐性、より大きなハッシュとあなたに配置する必要があり、より大きな計算作業。

  1. 読み込まれた行
  2. 計算ハッシュ
  3. リンクリスト内の値を探します。
    • あなたがより大きなものを見つけた場合、前に挿入
    • あなたは1等しい、廃棄ラインを見つけた場合
  4. 廃棄されていない場合は、出力ファイルに行を書きます

あなたは挿入安価を維持するために、リンクされたリストが必要になります(そのリストは増えています)。リストには、すぐに行を書き込むことによって順序を維持します挿入戦略と出力ファイルが発注保持されます。

これは、約取るだろうn * k + mスペースに、それはハッシュ関数を計算することは計算コストが高くなります。

これは、衝突を扱っていないことに注意してください。あなたは良いハッシュ関数を使用する場合は、ちょうど彼らが(彼らは非常に低いですように)起こることはありませんふりをすることができます。それが重要な場合、あなたは、例えば、確認一意に別のメカニズムを追加し、ハッシュと一緒に行番号を保存し、比較のために、以前に見たの行をフェッチする必要があります。その後、衝突ハッシュを持つ行を格納するためのスキームを見つける必要があります。

おすすめ

転載: http://43.154.161.224:23101/article/api/json?id=138232&siteId=1