Как отсортировать очень большие файлы - PullRequest
27 голосов
/ 27 октября 2011

У меня есть несколько файлов, которые должны быть отсортированы по id в начале каждой строки.Файлы около 2-3 ГБ.

Я попытался прочитать все данные в ArrayList и отсортировать их.Но памяти недостаточно, чтобы сохранить их всех.Это не работает.

Линии выглядят как

0052304 0000004000000000000000000000000000000041 John Teddy 000023
0022024 0000004000000000000000000000000000000041 George Clan 00013

Как мне отсортировать файлы ??

Ответы [ 9 ]

37 голосов
/ 27 октября 2011

Это не совсем проблема Java.Вам необходимо изучить эффективный алгоритм сортировки данных, которые не полностью считываются в память.Несколько адаптаций к сортировке слиянием могут достичь этого.

Взгляните на это: http://en.wikipedia.org/wiki/Merge_sort

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

По сути, идея состоит в том, чтобы сломатьфайл на более мелкие части, сортируйте их (с помощью сортировки слиянием или другим методом), а затем используйте команду «Объединить из сортировки слиянием» для создания нового отсортированного файла.

18 голосов
/ 27 октября 2011

Для этого вам нужна внешняя сортировка слиянием. Здесь - это Java-реализация, которая сортирует очень большие файлы.

16 голосов
/ 17 августа 2016

Поскольку ваши записи уже находятся в текстовом формате плоских файлов, вы можете передать их в UNIX sort(1) например. sort -n -t' ' -k1,1 < input > output. Он автоматически разделит данные на части и выполнит сортировку слиянием, используя доступную память и /tmp. Если вам нужно больше места, чем доступно для памяти, добавьте -T /tmpdir к команде.

Довольно забавно, что все говорят вам, что нужно загружать огромные библиотеки C # или Java или самостоятельно выполнять сортировку слиянием, когда вы можете использовать инструмент, который доступен на каждой платформе и существует уже десятилетия.

3 голосов
/ 15 апреля 2018

Вам необходимо выполнить внешнюю сортировку.Это хорошая движущая идея Hadoop / MapReduce, просто она не учитывает распределенный кластер и работает на одном узле.

Для повышения производительности следует использовать Hadoop / Spark.

enter image description here Измените эти строки в соответствии с вашей системой.fpath - это ваш один большой входной файл (протестирован с 20 ГБ).shared путь - это место, где хранится журнал выполнения.fdir - это место, где промежуточные файлы будут храниться и объединяться.Измените эти пути в соответствии с вашей машиной.

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

Затем запустите следующую программу.Ваш окончательно отсортированный файл будет создан с именем op401 в пути fdir.последняя строка Runtime.getRuntime().exec("valsort " + fdir + "op" + (treeHeight*100)+1 + " > " + opLog); проверяет, отсортирован вывод или нет.Удалите эту строку, если у вас не установлен valsort или входной файл не создан с помощью gensort (http://www.ordinal.com/gensort.html).

Также не забудьте изменить int totalLines = 200000000; на общее количество строк в вашем файле.count (int threadCount = 16) должен всегда иметь степень 2 и быть достаточно большим, чтобы (общий размер * 2 / без потока) объем данных мог находиться в памяти. Изменение количества потоков изменит имя конечного выходного файла.16, это будет op401, для 32 это будет op501, для 8 это будет op301 и т. Д.

Наслаждайтесь.

    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();
        }
    }
3 голосов
/ 27 октября 2011

Вместо того, чтобы загружать все данные в память одновременно, вы можете прочитать только ключи и указатель, где начинается строка (и, возможно, также длина), например,

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

Это будет использовать около40 байтов на строку.

После того, как вы отсортировали этот массив, вы можете использовать RandomAccessFile для чтения строк в порядке их появления.

Примечание: поскольку вы будете случайно ударять по диску, вместо этогоиспользования памяти это может быть очень медленным.Типичный диск занимает 8 мс для произвольного доступа к данным, и если у вас есть 10 миллионов строк, это займет около одного дня.(Это наихудший случай). В памяти это займет около 10 секунд.

2 голосов
/ 29 мая 2019

Используйте библиотеку Java big-sorter , которая может использоваться для сортировки очень больших текстовых или двоичных файлов.

Вот как будет реализована ваша точная задача:

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

выход:

0022024 0000004000000000000000000000000000000041   George Clan 00013
0052304 0000004000000000000000000000000000000041   John Teddy   000023
2 голосов
/ 14 февраля 2013

Вы можете использовать файл SQL Lite db, загрузить данные в базу данных и затем разрешить ей сортировку и вернуть результаты за вас.

Преимущества: не нужно беспокоиться о написании лучшего алгоритма сортировки.

Недостаток: Вам понадобится место на диске, медленная обработка.

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

1 голос
/ 19 июля 2014

Операционные системы поставляются с мощной утилитой сортировки файлов.Простая функция, которая вызывает скрипт bash, должна помочь.

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 голос
/ 27 октября 2011

Что вам нужно сделать, это разделить файлы на части через поток и обработать их отдельно.Затем вы можете объединить файлы вместе, так как они уже будут отсортированы, это похоже на то, как работает сортировка слиянием.

Ответ на этот вопрос SO будет иметь значение: Поток больших файлов

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...