是否有更有效的方法来迭代文件集合并构建文件内容的字典

本文关键字:文件 合并 集合 构建 字典 迭代 有效 方法 是否 | 更新日期: 2023-09-27 18:11:07

我有以下代码。是否有更有效的方法来完成同样的任务?

  1. 给定一个文件夹,遍历文件夹中的文件。
  2. 在每个文件中,跳过头四行,
  3. 基于空格分隔行后,如果生成的数组包含少于7个元素,则跳过它,
  4. 检查指定的元素是否已经在字典中。如果是,则增加计数。如果没有,请添加。

这不是个复杂的过程。有更好的方法吗?LINQ吗?

string sourceDirectory = @"d:'TESTDATA'";
string[] files = Directory.GetFiles(sourceDirectory, "*.log", 
    SearchOption.TopDirectoryOnly);
var dictionary = new Dictionary<string, int>();
foreach (var file in files)
{
    string[] lines = System.IO.File.ReadLines(file).Skip(4).ToArray();
    foreach (var line in lines)
    {
        var elements = line.Split(' ');
        if (elements.Length > 6)
        {
            if (dictionary.ContainsKey(elements[9]))
            {
                dictionary[elements[9]]++;
            }
            else
            {
                dictionary.Add(elements[9], 1);
            } 
        }
    }
}

是否有更有效的方法来迭代文件集合并构建文件内容的字典

Linqy应该做的事。怀疑它是否更有效率。而且,几乎可以肯定的是,它的调试更加麻烦。但是现在很流行:

static Dictionary<string,int> Slurp( string rootDirectory )
{
  Dictionary<string,int> instance = Directory.EnumerateFiles(rootDirectory,"*.log",SearchOption.TopDirectoryOnly)
                                             .SelectMany( fn => File.ReadAllLines(fn)
                                                                    .Skip(4)
                                                                    .Select( txt => txt.Split( " ".ToCharArray() , StringSplitOptions.RemoveEmptyEntries) )
                                                                    .Where(x => x.Length > 9 )
                                                                    .Select( x => x[9])
                                                        )
                                             .GroupBy( x => x )
                                             .ToDictionary( x => x.Key , x => x.Count()) 
                                             ;
  return instance ;
}

一种更有效(性能方面)的方法是使用Parallel并行化外部foreach。Foreach方法。你还需要一个ConcurrentDictionary对象,而不是一个标准的字典。

不确定您是否正在寻找更好的性能或更优雅的代码。如果你喜欢函数式linq,可以这样写:

var query= from element in
                       (
                           //go through all file names
                           from fileName in files
                           //read all lines from every file and skip first 4
                           from line in File.ReadAllLines(fileName).Skip(4)
                           //split every line into words
                           let lineData = line.Split(new[] {' '})
                           //select only lines with more than 6 words
                           where lineData.Count() > 6
                           //take 6th element from line
                           select lineData.ElementAt(6)
                       )
                   //outer query will group by element
                   group element by element
                   into g
                   select new
                       {
                           Key = g.Key,
                           Count = g.Count()
                       };
  var dictionary =  list.ToDictionary(e=>e.Key,e=>e.Count);

结果是一个以单词为键并以单词重叠次数为值的字典。

我认为读取文件将是操作中最耗费精力的部分。在许多情况下,尝试在不同的线程上一次读取多个文件将损害而不是帮助性能,但是有一个线程除了读取文件什么都不做可能是有帮助的,这样它可以使驱动器尽可能繁忙。

如果文件变得很大(看起来很有可能),并且如果没有行超过32K字节(8,000-32,000个Unicode字符),我建议您以大约32K或64K字节(不是字符)的块读取它们。以字节读取文件并将其细分为行可能比以行读取要快,因为细分可能发生在与物理磁盘访问不同的线程上。

我建议从一个线程开始,一个线程用于磁盘访问,一个线程用于解析和计数,它们之间有一个阻塞队列。磁盘访问线程应该将包含32K字节数组的数据项放在队列中,指示有多少字节是有效的[可能在文件末尾少于32K],并指示它是否是文件的最后一条记录。解析线程应该读取这些项,将它们解析成行,并更新相应的计数。

为了提高计数性能,定义

可能会有所帮助
class ExposedFieldHolder<T> {public T Value; }

则有一个Dictionary<string, ExposedFieldHolder<int>>。必须为每个字典槽创建一个新的ExposedFieldHolder<int>,但dictionary[elements[9]].Value++;可能比dictionary[elements[9]]++;快,因为后者语句翻译为dictionary[elements[9]] = dictionary[elements[9]]+1;,并且在读取时必须查找一次元素,在写入时必须再次查找]。

如果有必要在多个线程上进行解析和计数,我建议每个线程都有自己的队列,并且磁盘读取线程在每个文件之后切换队列[文件的所有块应该由同一个线程处理,因为一个文本行可能跨越两个块]。此外,虽然可以使用ConcurrentDictionary,但让每个线程拥有自己独立的Dictionary并在最后合并结果可能会更有效。