【问题标题】:How to loop through and compare millions of values in two text files?如何遍历和比较两个文本文件中的数百万个值?
【发布时间】:2011-09-21 09:54:28
【问题描述】:

我有两个文本文件 (TXT),其中包含超过 200 万个不同的文件名。我想遍历第一个文件中的所有名称并找到第二个文本文件中也存在的名称。

我尝试循环遍历StreamReader,但这需要很多时间。下面的代码我也试过了,还是太费时间了。

StreamReader first = new StreamReader(path);
string strFirst = first.ReadToEnd();
string[] strarrFirst = strFirst.Split('\n');

 bool found = false;

StreamReader second = new StreamReader(path2);
string str = second.ReadToEnd();
string[] strarrSecond = str.Split('\n');

for (int j = 0; j < (strarrFirst.Length); j++)
{
          found = false;

    for (int i = 0; i < (strarrSecond .Length); i++)
    {
        if (strarrFirst[j] == strarrSecond[i])
        {
            found = true;
            break;
        }
    }

    if (!found)
    {
        Console.WriteLine(strarrFirst[j]);
    }
}

比较文件的好方法是什么?

【问题讨论】:

  • 您的第一个错误是将整个文件作为字符串读取并拆分它们。你应该一直在重复调用ReadLine(),直到你读入null(典型的行读循环)。
  • @Jeff - 我想要存储的 TXT 文件大小超过 300 MB,我尝试使用 ReadLine() 但它需要更多时间。

标签: c# arrays performance data-structures loops


【解决方案1】:

这个怎么样:

var commonNames = File.ReadLines(path).Intersect(File.ReadLines(path2));

这是 O(N + M) 而不是您当前的解决方案,它测试第一个文件中的 every 行和第二个文件中的 every 行 - O(N * M )。

假设您使用的是 .NET 4。否则,您可以使用 File.ReadAllLines,但这会将整个文件读入内存。或者你也可以自己写相当于File.ReadLines——这并不难。

最终,当您摆脱当前代码中的 O(N * M) 问题时,您可能会受到文件 IO 的限制 - 没有太多方法可以解决这个问题。

编辑:对于 .NET 2,首先让我们实现类似 ReadLines:

public static IEnumerable<string> ReadLines(string file)
{
    using (TextReader reader = File.OpenText(file))
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            yield return line;
        }
    }
}

现在我们真的想使用HashSet&lt;T&gt;,但那不在.NET 2中 - 所以让我们改用Dictionary&lt;TKey, TValue&gt;:

Dictionary<string, string> map = new Dictionary<string, string>();
foreach (string line in ReadLines(path))
{
    map[line] = line;
}

List<string> intersection = new List<string>();
foreach (string line in ReadLines(path2))
{
    if (map.ContainsKey(line))
    {
        intersection.Add(line);
    }
}

【讨论】:

  • 这可能是吹毛求疵,但是说你的解决方案是 O(N) 而他的解决方案是 O(N^2) 不是更正确吗? ;-)
  • @CPX:涉及到两个文件——你将哪个大小作为 N?这就是我使用 N 和 M 的原因,每个文件一个。
  • @Jon 我很困惑相交或任何其他比较 2 个集合的方法如何是 O(n+m)。在读取第二个集合中的项目时,是否不需要搜索整个第一个集合来寻找匹配项,从而使其成为 O(n*m)
  • 我认为你应该忽略参数的数量,只看参数的力量。 N+M 会线性增加,因此 O(N),而 N*M 会二次增加,因此 O(N^2)
  • @CPX:但是您可能总是知道第一个文件的大小,但第二个文件的大小会有所不同,反之亦然。这里显然有两个变量 - 我认为没有理由将它们结合起来。
【解决方案2】:

尝试这样的方式来加快速度...

            var path = string.Empty;
            var path2 = string.Empty;
            var strFirst = string.Empty;
            var str = string.Empty;
            var strarrFirst = new List<string>();
            var strarrSecond = new List<string>();

            using (var first = new StreamReader(path))
            {
                strFirst = first.ReadToEnd();
            }

            using (var second = new StreamReader(path2))
            {
                str = second.ReadToEnd();
            }


            strarrFirst.AddRange(strFirst.Split('\n'));

            strarrSecond.AddRange(str.Split('\n'));
            strarrSecond.Sort();

            foreach(var value in strarrFirst)
            {
                var found = strarrSecond.BinarySearch(value) >= 0;
                if (!found) Console.WriteLine(value);
            }

【讨论】:

  • ReadToEnd 是邪恶的......真的应该逐行阅读。还要确保对流式阅读器使用“使用”以确保正确处理它们。
【解决方案3】:

只是为了好玩,我尝试了 Jon Skeet 方法并拥有:

    var guidArray = Enumerable.Range(0, 1000000).Select(x => Guid.NewGuid().ToString()).ToList();
        string path = "first.txt";
        File.WriteAllLines(path, guidArray);
        string path2 = "second.txt";
        File.WriteAllLines(path2, guidArray.Select(x=>DateTime.UtcNow.Ticks % 2 == 0 ? x : Guid.NewGuid().ToString()));

        var start = DateTime.Now;

        var commonNames = File.ReadLines(path).Intersect(File.ReadLines(path2)).ToList();

        Console.WriteLine((DateTime.Now - start).TotalMilliseconds);

        start = DateTime.Now;

        var lines = File.ReadAllLines(path);
        var hashset = new HashSet<string>(lines);

        var lines2 = File.ReadAllLines(path2);

        var result = lines2.Where(hashset.Contains).ToList();

        Console.WriteLine((DateTime.Now - start).TotalMilliseconds);
        Console.ReadKey();

而且 Skeet 的方法稍微快一点(1453.0831 与 1488.0851,iDevForFun 方法很慢 - 12791.7316),所以我认为底层应该发生与我尝试手动使用 hashset 相同的事情。

【讨论】:

  • 这很有趣.. 我原以为二进制搜索对原始代码的性能有很大帮助。诚然,乔恩的方法要优雅得多,而且无疑更快。这就是我喜欢这个地方的原因……每天都能学到新东西:)
猜你喜欢
  • 1970-01-01
  • 2014-05-18
  • 2017-01-27
  • 2018-06-28
  • 1970-01-01
  • 2020-03-08
  • 1970-01-01
  • 1970-01-01
  • 2011-08-25
相关资源
最近更新 更多