0

我有以下代码。有没有更有效的方法来完成相同的任务?

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

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

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);
            } 
        }
    }
}
4

4 回答 4

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 ;
}
于 2013-04-26T18:50:53.663 回答
0

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

于 2013-04-26T18:23:52.270 回答
0

我希望读取文件将是操作中最耗时的部分。在许多情况下,尝试在不同的线程上一次读取多个文件会损害而不是提高性能,但拥有一个除了读取文件什么都不做的线程可能会有所帮助,这样它就可以使驱动器尽可能忙碌。

如果文件可能变大(看起来很可能)并且没有任何行超过 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;,并且必须在阅读时查找一次元素,然后在写入时再次查找]。

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

于 2013-04-26T19:07:52.787 回答
0

不确定您是否正在寻找更好的性能或更优雅的代码。如果您更喜欢函数式风格的 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);

结果是以单词为键,单词出现次数为值的字典。

于 2013-04-26T18:39:21.017 回答