需要在非常大的文件中的每一列中找到不同值的数量

Need to find number of distinct values in each column in very large files

我正在用 C# 处理大型文件(希望如此),我需要一种方法来确定文件每一列中不同值的数量。我已经阅读了所有我能找到的与使用 C# 确定不同值相关的问题。挑战在于,由于某些文件的大小很大,并且一列中可能有数千万个不同的值(并且可能有数百列——各种数据类型),因此要创建列表、字典或数组等用于每列——然后使用先前回答的问题中描述的技术——会使我面临达到 2 GB 内存限制的危险。

目前,我 reading/processing 文件一次一行,每一行 "cleaning and sanitizing" 数据,更新聚合结果,然后将每个处理过的行写入输出文件,然后批量输出插入到 SQL。到目前为止的表现实际上相当不错。

由于数据最终登陆 MS SQL,作为后备,我可以使用 SQL 来确定不同的值,但理想情况下我希望能够在登陆 SQL。如有任何想法或建议,我们将不胜感激。

更新: 我为每个字段创建了一个哈希 Table 并为每个字段添加了新的不同值。在处理结束时,我使用 myDistinctValues.Count 获得计数。这适用于小文件,但正如我担心的那样,对于大文件我得到

System.OutOfMemoryException 

抛出。根据建议,我确实尝试添加

<runtime>
    <gcAllowVeryLargeObjects enabled="true"/>
</runtime>

到我的应用程序配置,但这没有帮助。

您期望有多少个不同的值?我使用了以下简单的应用程序:

using System;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        Dictionary<string, int> ds = new Dictionary<string, int>;
        Random r = new Random();
        for (int i = 0; i < 100000000; i++) {
            string s = Guid.NewGuid().ToString();

            d[s] = r.Next(0, 1000000);

            if (i % 100000 == 0)
            {
                Console.Out.WriteLine("Dict size: " + d.Count);
            }
        }

    }
}

连同 .net 4.6.1,x64 构建目标,在我 运行 我的机器内存不足之前,我消耗了 4000 万个唯一对象和 5.5 GB 内存(它正忙于处理其他事情抱歉,抱歉)..

如果您要使用数组,您可能需要一个 app.config,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
    </startup>
    <runtime>
        <gcAllowVeryLargeObjects enabled="true"/>
    </runtime>
</configuration>

您应该能够算出您需要什么样的内存来跟踪不同的值及其计数。如果您认为它会达到数亿,我建议您一次处理一个专栏..

也只是一个小的澄清:当我阅读 "the number of distinct values" 时,它让我觉得您想跟踪每个值出现的次数。这就是我使用 Dictionary<string, int> 的原因 - 字符串是正在计算的不同值,而 int 是计数

如果您希望将 X million/billion 值的列表重复删除为不同的值,而不需要计算出现次数,那么 HashSet 可能更轻量级

您是否考虑获取一个值的哈希码(假设它不能大于 128 字节),创建一个哈希集并执行如下操作:

static void Main(string[] args)
{
    List<object> vals = new List<object> {1, 'c', "as", 2, 1};

    foreach(var v in vals)
        Console.WriteLine($"Is uniques: {IsUniq(v)}");

    Console.ReadKey();
}

private static HashSet<object> _hashes = new HashSet<object>();
private static bool IsUniq(object v)
{
    return _hashes.Add(v);
}

对于 100 万个元素,它应该是 100-150 兆字节的原始数据。

虽然我的解决方案并不优雅而且肯定有更好的解决方案(BTree?),但我发现了一些有效的方法并认为我会分享它。我不可能是唯一一个想要确定超大文件中字段的不同计数的人。也就是说,我不知道这将如何扩展到数亿或数十亿条记录。在某个时候,如果数据足够多,单个数组将达到 2GB 的大小限制。

什么不起作用:

  • 对于非常大的文件:在我遍历文件时实时填充的每个字段的散列 table,然后使用 hashtable.count。散列的总大小 tables 在到达文件末尾之前导致 SystemOutOfMemoryException。
  • 将数据导入 SQL,然后在每一列上使用 SQL 来确定非重复计数。 WAY 太长了。

什么起作用了:

  • 对于具有数千万行的大文件,我首先对前 1000 行进行分析,其中我为每个字段创建一个散列 table 并填充不同的值。
  • 对于 1000 个不同值中超过 50 个不同值的任何字段,我用布尔标志标记该字段 HasHighDensityOfDistinctValues = true。
  • 对于 HasHighDensityOfDistinctValues == true 的任何此类字段,我创建一个单独的文本文件,并在遍历主文件时,将该字段的值写入特定于字段的文本文件。
  • 对于不同值密度较低的字段,我为每个字段维护散列 table 并向其写入不同值。
  • 我注意到在许多高密度字段中,多个连续行出现重复值(例如 PersonID),因此,为了减少字段特定文本文件的条目数,我将字段的前一个值,如果当前值不等于前一个值,则只写入文本文件。这显着减少了特定于字段的文本文件的总大小。
  • 完成对正在处理的主文件的迭代后,我会迭代我的 FieldProcessingResults class 并且对于每个字段,如果 HasHighDensityOfDistinctValues==true,我会读取字段特定文本文件中的每一行并填充具有不同值的字段特定散列 table,然后使用 HashTable.Count 确定不同值的计数。
  • 在继续下一个字段之前,我存储与该字段关联的计数,然后使用 myHashTable.Clear() 清除散列 table。在继续下一个字段之前,我关闭并删除了特定于字段的文本文件。

以这种方式,我能够获得每个字段的不同值的计数,而不必同时为每个字段填充和维护内存中的哈希 table,这导致了内存错误。

您是否尝试过将文件加载到数据表中,然后通过数据视图进行不同的选择(而不是创建副本)? 查看

https://social.msdn.microsoft.com/Forums/vstudio/en-US/fccda8dc-4515-4133-9022-2cb6bafa8ad9/how-does-a-dataview-act-in-memory?forum=netfxbcl

这是一些伪代码

Read from File into Datatable
Create DataView with sort on the column you want
UniqueCount = 0
var CurrentValue="<some impossible value>"
For each ViewRow in DataView
    If CurrentValue <> ViewRow["MyColumn"]
        UniqueCount ++

UniqueCount should give me my result

这会很有效,因为您只使用 2 个变量 UniqueCount 和 CurrentValue 来循环数据。 您还在数据视图中排序,它在处理时不会复制数据。

希望对您有所帮助