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

时间:2017-10-12 16:19:37

标签: c# memory-management large-files distinct-values

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

目前,我正在一行读取/处理文件,每行“清理和清理”数据,更新聚合结果,然后将每个处理过的行写入输出文件,然后将其批量插入SQL。迄今为止的表现实际上相当不错。

由于数据最终落在MS SQL中,作为后备,我可以使用SQL来确定不同的值,但我希望能够在登陆SQL之前执行此操作。任何想法或建议都表示赞赏。

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

System.OutOfMemoryException 

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

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

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

4 个答案:

答案 0 :(得分:1)

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

什么没有用?

  • 对于非常大的文件:当我遍历文件时,实时填充的每个字段的哈希表,然后使用hashtable.count。在到达文件末尾之前,哈希表的集合大小会导致SystemOutOfMemoryException。
  • 将数据导入SQL,然后在每列上使用SQL来确定不同的计数。 WAY 太长了。

做了什么工作:

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

通过这种方式,我可以获得每个字段的不同值的计数,而不必同时为每个字段填充和维护内存中的哈希表,这会导致内存不足错误。

答案 1 :(得分:0)

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

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千兆字节的内存消耗(此时正忙于其他事情,对不起)..

如果你要使用数组,你可能需要一个看起来像这样的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>

您应该能够计算出跟踪不同值及其计数所需的记忆。如果您认为它将在数亿美元中,我建议您一次只能处理一列。

只是一个小小的澄清:当我读到“不同值的数量”时,它让我觉得你想要跟踪每个值出现的次数。这就是我使用Dictionary<string, int>的原因 - 字符串是计算的不同值,int是计数

如果您希望将X百万/十亿的值列表重新排序到不同的值,而不需要计算出现次数,那么HashSet可能会减轻重量

答案 2 :(得分:0)

您是否考虑获取值的哈希码(假设它不能大于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兆字节的原始数据。

答案 3 :(得分:0)

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

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来循环数据。 您还在数据视图中进行排序,在处理时不会复制数据。

希望这有帮助