Рандомизировать строки действительно огромного текстового файла

Я хотел бы рандомизировать строки в файле, который содержит более 32 миллионов строк из 10 цифр. Я знаю, как это сделать с помощью File.ReadAllLines(...).OrderBy(s => random.Next()).ToArray(), но это не эффективно с точки зрения памяти, поскольку оно загружает все в память (более 1,4 ГБ) и работает только с архитектурой x64.

Альтернативой было бы разбить его и рандомизировать более короткие файлы, а затем объединить их, но мне было интересно, есть ли лучший способ сделать это.

Ответ 1

Это приложение демонстрирует, что вы хотите, используя массив байтов

  • Он создает файл с заполненными номерами от 0 до 32000000.
  • Он загружает файл, затем перетасовывает их в памяти, используя блок-копирующий метод Fisher-Yates.
  • Наконец, он записывает файл в случайном порядке

Пиковое использование памяти составляет около 400 МБ. Выполняется примерно через 20 секунд на моей машине (в основном файл IO).

public class Program
{
    private static Random random = new Random();

    public static void Main(string[] args)
    {
        // create massive file
        var random = new Random();
        const int lineCount = 32000000;

        var file = File.CreateText("BigFile.txt");

        for (var i = 0; i < lineCount ; i++)
        {
            file.WriteLine("{0}",i.ToString("D10"));
        }

        file.Close();

        int sizeOfRecord = 12;

        var loadedLines = File.ReadAllBytes("BigFile.txt");

        ShuffleByteArray(loadedLines, lineCount, sizeOfRecord);

        File.WriteAllBytes("BigFile2.txt", loadedLines);
    }

    private static void ShuffleByteArray(byte[] byteArray, int lineCount, int sizeOfRecord)
    {
        var temp = new byte[sizeOfRecord];

        for (int i = lineCount - 1; i > 0; i--)
        {
            int j = random.Next(0, i + 1);
            // copy i to temp
            Buffer.BlockCopy(byteArray, sizeOfRecord * i, temp, 0, sizeOfRecord);
            // copy j to i
            Buffer.BlockCopy(byteArray, sizeOfRecord * j, byteArray, sizeOfRecord * i, sizeOfRecord);
            // copy temp to j
            Buffer.BlockCopy(temp, 0, byteArray, sizeOfRecord * j, sizeOfRecord);
        }
    }
}

Ответ 2

В вашем текущем подходе будет выделено как минимум 2 больших массива строк (возможно, больше - я не знаю, как OrderBy реализован, но он, вероятно, делает свои собственные распределения).

Если вы рандомизируете данные "на месте", выполняя случайные перестановки между строками (например, используя Fisher-Yates shuffle), это минимизирует использование памяти. Конечно, он по-прежнему будет большим, если файл большой, но вы не будете выделять больше памяти, чем необходимо.


EDIT: если все строки имеют одинаковую длину (*), это означает, что вы можете делать произвольный доступ к данной строке в файле, поэтому вы можете перетасовывать Fisher-Yates непосредственно в файл.

(*) и предполагая, что вы не используете кодировку, в которой символы могут иметь разные длины байтов, например UTF-8