web-dev-qa-db-de.com

Wie sortiere ich sehr große Dateien?

Ich habe einige Dateien, die am Anfang jeder Zeile nach ID sortiert werden sollen .. Die Dateien sind ca. 2-3 GB groß 

Ich habe versucht, alle Daten in ein ArrayList einzulesen und zu sortieren. Aber die Erinnerung reicht nicht aus, um sie alle zu behalten. Es funktioniert nicht.

Linien sehen aus wie

0052304 0000004000000000000000000000000000000041 John Teddy 000023
0022024 0000004000000000000000000000000000000041 George Clan 00013

Wie kann ich die Dateien sortieren?

27
Kayser

Das ist nicht gerade ein Java-Problem. Sie müssen einen effizienten Algorithmus zum Sortieren von Daten prüfen, die nicht vollständig in den Speicher eingelesen werden. Einige Anpassungen an Merge-Sort können dies erreichen.

Schauen Sie sich das an: http://en.wikipedia.org/wiki/Merge_sort

und: http://en.wikipedia.org/wiki/External_sorting

Grundsätzlich besteht die Idee hier darin, die Datei in kleinere Teile zu zerlegen, sie zu sortieren (entweder mit Sortierreihenfolge oder einer anderen Methode) und dann die Option Aus Merge-Sortierung verwenden zum Erstellen der neuen sortierten Datei zu verwenden.

37
pcalcao

Dafür benötigen Sie eine externe Zusammenführungsart. Hier ist eine Java-Implementierung davon, die sehr große Dateien sortiert.

18
Ingo Kegel

Da Ihre Datensätze bereits im Flat-File-Textformat vorliegen, können Sie sie an UNIX sort(1) weiterleiten, z. sort -n -t' ' -k1,1 < input > output. Die Daten werden automatisch aufgeteilt und die Zusammenführungssortierung mit dem verfügbaren Speicher und /tmp durchgeführt. Wenn Sie mehr Speicherplatz benötigen, als über Speicherplatz verfügt, fügen Sie dem Befehl -T /tmpdir hinzu.

Es ist ziemlich witzig, dass jeder Ihnen sagt, dass Sie riesige C # - oder Java-Bibliotheken herunterladen oder die Merge-Sortierung selbst implementieren müssen, wenn Sie ein Tool verwenden können, das auf jeder Plattform verfügbar ist und seit Jahrzehnten existiert.

16
rjh

Anstatt alle Daten gleichzeitig in den Speicher zu laden, können Sie nur die Schlüssel und einen Index lesen, an dem die Zeile beginnt (und möglicherweise auch die Länge), z.

class Line {
   int key, length;
   long start;
}

Dies würde ungefähr 40 Bytes pro Zeile benötigen.

Nachdem Sie dieses Array sortiert haben, können Sie mit RandomAccessFile die Zeilen in der Reihenfolge lesen, in der sie angezeigt werden.

Hinweis: Da Sie die Festplatte zufällig treffen, kann dies anstelle des Speichers sehr langsam sein. Ein typischer Datenträger benötigt 8 ms, um wahllos auf Daten zuzugreifen. Wenn Sie 10 Millionen Zeilen haben, dauert dies etwa einen Tag. (Dies ist der absolut schlimmste Fall.) Im Speicher würde es ungefähr 10 Sekunden dauern.

3
Peter Lawrey

Verwenden Sie die Java library big-sorter , mit der sehr große Text- oder Binärdateien sortiert werden können.

So würde Ihr genaues Problem implementiert:

// write the input to a file
String s = "0052304 0000004000000000000000000000000000000041   John Teddy   000023\n"
        + "0022024 0000004000000000000000000000000000000041   George Clan 00013";
File input = new File("target/input");
Files.write(input.toPath(),s.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE);

File output = new File("target/output");


//sort the input
Sorter
    .serializerLinesUtf8()
    .comparator((a,b) -> {
        String ida = a.substring(0, a.indexOf(' '));
        String idb = b.substring(0, b.indexOf(' '));
        return ida.compareTo(idb);
    }) 
    .input(input) 
    .output(output) 
    .sort();

// display the output
Files.readAllLines(output.toPath()).forEach(System.out::println);

ausgabe:

0022024 0000004000000000000000000000000000000041   George Clan 00013
0052304 0000004000000000000000000000000000000041   John Teddy   000023
2
Dave Moten

Sie können die SQL Lite-Datei db verwenden, die Daten in die db laden und anschließend sortieren und die Ergebnisse für Sie zurückgeben.

Vorteile: Sie müssen sich keine Gedanken über den besten Sortieralgorithmus machen. 

Nachteil: Sie benötigen Festplattenspeicher und verlangsamen die Verarbeitung.

https://sites.google.com/site/arjunwebworld/Home/programming/sorting-large-data-files

2
user2071703

Was Sie tun müssen, ist, die Dateien über einen Stream einzuspalten und separat zu verarbeiten. Dann können Sie die Dateien zusammenführen, da sie bereits sortiert sind. Dies ähnelt der Funktionsweise der Zusammenführungssortierung. 

Die Antwort aus dieser SO - Frage ist von Wert: Große Dateien streamen

1
Woot4Moo

Betriebssysteme verfügen über ein leistungsstarkes Dienstprogramm zur Dateisortierung. Eine einfache Funktion, die ein Bash-Skript aufruft, sollte helfen.

public static void runScript(final Logger log, final String scriptFile) throws IOException, InterruptedException {
    final String command = scriptFile;
    if (!new File (command).exists() || !new File(command).canRead() || !new File(command).canExecute()) {
        log.log(Level.SEVERE, "Cannot find or read " + command);
        log.log(Level.WARNING, "Make sure the file is executable and you have permissions to execute it. Hint: use \"chmod +x filename\" to make it executable");
        throw new IOException("Cannot find or read " + command);
    }
    final int returncode = Runtime.getRuntime().exec(new String[] {"bash", "-c", command}).waitFor();
    if (returncode!=0) {
        log.log(Level.SEVERE, "The script returned an Error with exit code: " + returncode);
        throw new IOException();
    }

}
1
Rishi Dua

Sie müssen eine externe Sortierung durchführen. Das ist die treibende Idee hinter Hadoop/MapReduce, nur dass es keine verteilten Cluster berücksichtigt und auf einem einzelnen Knoten arbeitet.

Für eine bessere Leistung sollten Sie Hadoop/Spark verwenden.

 enter image description here Ändern Sie diese Zeilen entsprechend Ihrem System. fpath ist Ihre einzige große Eingabedatei (getestet mit 20 GB). shared Pfad ist der Ort, an dem das Ausführungsprotokoll gespeichert wird. fdir ist der Ort, an dem die Zwischendateien gespeichert und zusammengeführt werden. Ändern Sie diese Pfade entsprechend Ihrer Maschine.

public static final String fdir = "/tmp/";
    public static final String shared = "/exports/home/schatterjee/cs553-pa2a/";
    public static final String fPath = "/input/data-20GB.in";
    public static final String opLog = shared+"Mysort20GB.log";

Führen Sie dann das folgende Programm aus. Ihre endgültige sortierte Datei wird mit dem Namen op401 im Pfad fdir erstellt. Die letzte Zeile Runtime.getRuntime().exec("valsort " + fdir + "op" + (treeHeight*100)+1 + " > " + opLog); prüft, ob die Ausgabe sortiert ist oder nicht. Entfernen Sie diese Zeile, wenn Sie Valsort nicht installiert haben oder die Eingabedatei nicht mit gensort ( http://www.ordinal.com/gensort.html ) erstellt wird.

Vergessen Sie nicht, int totalLines = 200000000; in die Gesamtzeilen Ihrer Datei zu ändern. und die Anzahl der Threads (int threadCount = 16) sollte immer eine Potenz von 2 haben und groß genug sein, damit sich die Datenmenge (Gesamtgröße * 2/kein Thread) im Speicher befinden kann. Durch das Ändern der Thread-Anzahl wird der Name der endgültigen Ausgabedatei geändert. Wie für 16 wird es op401 sein, für 32 wird es op501 sein, für 8 wird es op301 sein usw.

Genießen.

    import Java.io.*;
    import Java.nio.file.Files;
    import Java.nio.file.Paths;
    import Java.util.ArrayList;
    import Java.util.Comparator;
    import Java.util.stream.Stream;


    class SplitFile extends Thread {
        String fileName;
        int startLine, endLine;

        SplitFile(String fileName, int startLine, int endLine) {
            this.fileName = fileName;
            this.startLine = startLine;
            this.endLine = endLine;
        }

        public static void writeToFile(BufferedWriter writer, String line) {
            try {
                writer.write(line + "\r\n");
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public void run() {
            try {
                BufferedWriter writer = Files.newBufferedWriter(Paths.get(fileName));
                int totalLines = endLine + 1 - startLine;
                Stream<String> chunks =
                        Files.lines(Paths.get(Mysort20GB.fPath))
                                .skip(startLine - 1)
                                .limit(totalLines)
                                .sorted(Comparator.naturalOrder());

                chunks.forEach(line -> {
                    writeToFile(writer, line);
                });
                System.out.println(" Done Writing " + Thread.currentThread().getName());
                writer.close();
            } catch (Exception e) {
                System.out.println(e);
            }
        }
    }

    class MergeFiles extends Thread {
        String file1, file2, file3;
        MergeFiles(String file1, String file2, String file3) {
            this.file1 = file1;
            this.file2 = file2;
            this.file3 = file3;
        }

        public void run() {
            try {
                System.out.println(file1 + " Started Merging " + file2 );
                FileReader fileReader1 = new FileReader(file1);
                FileReader fileReader2 = new FileReader(file2);
                FileWriter writer = new FileWriter(file3);
                BufferedReader bufferedReader1 = new BufferedReader(fileReader1);
                BufferedReader bufferedReader2 = new BufferedReader(fileReader2);
                String line1 = bufferedReader1.readLine();
                String line2 = bufferedReader2.readLine();
                //Merge 2 files based on which string is greater.
                while (line1 != null || line2 != null) {
                    if (line1 == null || (line2 != null && line1.compareTo(line2) > 0)) {
                        writer.write(line2 + "\r\n");
                        line2 = bufferedReader2.readLine();
                    } else {
                        writer.write(line1 + "\r\n");
                        line1 = bufferedReader1.readLine();
                    }
                }
                System.out.println(file1 + " Done Merging " + file2 );
                new File(file1).delete();
                new File(file2).delete();
                writer.close();
            } catch (Exception e) {
                System.out.println(e);
            }
        }
    }

    public class Mysort20GB {
        //public static final String fdir = "/Users/diesel/Desktop/";
        public static final String fdir = "/tmp/";
        public static final String shared = "/exports/home/schatterjee/cs553-pa2a/";
        public static final String fPath = "/input/data-20GB.in";
        public static final String opLog = shared+"Mysort20GB.log";

        public static void main(String[] args) throws Exception{
            long startTime = System.nanoTime();
            int threadCount = 16; // Number of threads
            int totalLines = 200000000;
            int linesPerFile = totalLines / threadCount;
            ArrayList<Thread> activeThreads = new ArrayList<Thread>();

            for (int i = 1; i <= threadCount; i++) {
                int startLine = i == 1 ? i : (i - 1) * linesPerFile + 1;
                int endLine = i * linesPerFile;
                SplitFile mapThreads = new SplitFile(fdir + "op" + i, startLine, endLine);
                activeThreads.add(mapThreads);
                mapThreads.start();
            }
            activeThreads.stream().forEach(t -> {
                try {
                    t.join();
                } catch (Exception e) {
                }
            });

            int treeHeight = (int) (Math.log(threadCount) / Math.log(2));

            for (int i = 0; i < treeHeight; i++) {
                ArrayList<Thread> actvThreads = new ArrayList<Thread>();

for (int j = 1, itr = 1; j <= threadCount / (i + 1); j += 2, itr++) {
                    int offset = i * 100;
                    String tempFile1 = fdir + "op" + (j + offset);
                    String tempFile2 = fdir + "op" + ((j + 1) + offset);
                    String opFile = fdir + "op" + (itr + ((i + 1) * 100));

                    MergeFiles reduceThreads =
                            new MergeFiles(tempFile1,tempFile2,opFile);
                    actvThreads.add(reduceThreads);
                    reduceThreads.start();
                }
                actvThreads.stream().forEach(t -> {
                    try {
                        t.join();
                    } catch (Exception e) {
                    }
                });
            }
            long endTime = System.nanoTime();
            double timeTaken = (endTime - startTime)/1e9;
            System.out.println(timeTaken);
            BufferedWriter logFile = new BufferedWriter(new FileWriter(opLog, true));
            logFile.write("Time Taken in seconds:" + timeTaken);
            Runtime.getRuntime().exec("valsort  " + fdir + "op" + (treeHeight*100)+1 + " > " + opLog);
            logFile.close();
        }
    }
1
sapy