【问题标题】:Randomize lines of really huge text file随机化非常大的文本文件的行
【发布时间】:2013-11-20 09:09:33
【问题描述】:

我想随机化包含超过 3200 万行 10 位字符串的文件中的行。我知道如何使用File.ReadAllLines(...).OrderBy(s => random.Next()).ToArray() 执行此操作,但这不是内存效率,因为它将所有内容加载到内存中(超过 1.4GB)并且仅适用于 x64 架构。

另一种方法是拆分它并随机化较短的文件,然后合并它们,但我想知道是否有更好的方法来做到这一点。

【问题讨论】:

  • 所有行的长度都相同吗?这会让事情变得更容易......
  • 如果只执行'ReadAllLines'而不使用OrderBy,会使用多少内存?这已经太多了吗?
  • @ThomasLevesque 是的,所有行的大小都相同。
  • @Baldrick 是的,无论 OrderBy 是什么,都会发生这种情况
  • .OrderBy(s => random.Next()) 不保证会终止。 OrderBy 子句中的函数应该提供总排序。见blogs.msdn.com/b/ericlippert/archive/2011/01/20/…

标签: c# performance memory file-io


【解决方案1】:

您当前的方法将分配至少 2 个大字符串数组(可能更多——我不知道 OrderBy 是如何实现的,但它可能会进行自己的分配)。

如果您通过在行之间进行随机排列(例如使用Fisher-Yates shuffle)来“就地”随机化数据,它将最大限度地减少内存使用量。当然,如果文件很大,它仍然会很大,但您不会分配超过必要的内存。


编辑:如果所有行的长度相同 (*),这意味着您可以随机访问文件中的给定行,因此您可以直接在文件中进行 Fisher-Yates shuffle。

(*) 并假设您没有使用字符可以具有不同字节长度的编码,例如 UTF-8

【讨论】:

  • 为了进行Fisher-Yates shuffle,我不需要将所有行加载到内存中然后进行就地洗牌吗?或者这可以在不将文件加载到内存的情况下完成
  • @idipous,是的,您仍然需要加载内存中的所有行。这并不理想,但最好像之前那样加载它们 2 或 3 次。
  • @idipous,是的,对不起,我修好了
  • 是的,它只是长度相同的数字(例如,一个文件可以有 32M 个 10 位数字,而另一个文件与第一个文件无关,但可以有 3M 个 9 位数字)。我会试一试,让你知道。
【解决方案2】:

此应用程序使用字节数组演示您想要的内容

  1. 它创建一个填充数字 0 到 32000000 的文件。
  2. 它加载文件,然后使用块复制 Fisher-Yates 方法在内存中随机播放它们。
  3. 最后,它以打乱的顺序写回文件

内存使用峰值约为 400 MB。在我的机器上运行大约 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);
        }
    }
}

【讨论】:

  • 虽然你是对的,峰值内存约为 400M,但输出文件不正确......我得到一个文件,里面装满了这样的东西 47 30090968697002 3064902714 34101 307516974214 30
  • @idipous:嗯.. 对我来说是完美的输出。您是否使用我的确切示例看到该问题,或者将其更改为与您的文件一起使用?请记住,如果您的行长度为 10 个字符,则记录大小应为 12。行终止符需要 2 个额外字符。如果您也在运行 Mono,可能会有所不同。
  • 注意:刚刚修复了临时字节数组声明中的错误 - 使其太大(不应该影响您的输出,但值得注意)
  • 为了获得 sizeofrecord 我做了sizeOfRecord = System.Text.ASCIIEncoding.ASCII.GetByteCount(line); 其中行来自读取文件并且所有行的大小相同但数字不同所以我只选择最后一个。
  • 如果你得到的 'line' 的长度是从 'ReadLines' 得到的字符串,那么它将不包括行终止符。那不包括在内。您可能需要在该数字上加 2。
猜你喜欢
  • 1970-01-01
  • 2014-10-22
  • 2013-05-16
  • 2011-07-23
  • 1970-01-01
  • 1970-01-01
  • 2015-11-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多