【问题标题】:C# lightweight skeleton parse fileC#轻量级骨架解析文件
【发布时间】:2019-07-25 04:56:39
【问题描述】:

我有一个格式类似于

的文件
{1:[...]}{2:[X:11][Y:78][]...}{3:[...]}{4:[...]}{5:
[]
[]
...
[]}$
{1:[...]}{2:[X:43][Y:13][]...}{3:[...]}{4:[...]}{5:
[]
[]
...
[]}$
...

省略号表示许多重复结构或许多重复行。

因此文件由具有相同格式的段组成,并由管道字符分隔。

仅提取每个段的 X 值的最佳方法是什么?所以我们避免将整个文件加载到内存中。空间和时间上的最佳选择。这可能意味着避免将整个文件加载到内存中。可能我们可以读取每一行和正则表达式以匹配 {2:[X:nn][ 并提取 nn 但这只是行的一小部分。

但也许有更好的方法?

【问题讨论】:

  • 我会使用 StreamReader 一次读取一行文件,以避免将整个文件放入内存。看起来您只需要解析以大括号开头的行。然后使用正则表达式从行中提取 X 值。
  • 您可以读取每个字符,存储您当前所在层次结构的哪个元素,以及extracting only the desired values。这样做的好处是不管每行有多长,这会告诉你你的数据是否无效。
  • @jdweng File.ReadLines 返回一个Enumerable,效果和StreamReader一样
  • 如果这些是大文件并且速度是一个问题,我会用fixed(char* p = yourLine) 解析它,它会执行正则表达式。但是,如果速度不是问题,则正则表达式将更加简洁和易于管理

标签: c# regex file parsing


【解决方案1】:

有很多方法可以解决这个问题,

给定

var lines = File.ReadLines(@"D:\Test.txt");

注意File.ReadLines 返回一个Enumerbale,所以它会延迟加载每一行


选项 1:使用 Positive Look-behind 和模式 (?<=2:\[X:)\d+

的正则表达式
foreach (var line in lines)
{
   var match = Regex.Match(line,@"(?<=2:\[X:)\d+");
   if(match.Success)
      Console.WriteLine(match.Value);  
}

选项2:简单string.Split

foreach (var line in lines)
{
   var results = line.Split(new[] { "2:[X:", "][Y:" }, StringSplitOptions.RemoveEmptyEntries);

   if(results.Length>1)
      Console.WriteLine(results[1]);
}

选项 3:“可能”是一种性能更高的方法,使用 指针 fixedunsafe

public static unsafe (bool found, int value) ParseLine(string line)
{
   const string prefix = "2:[X:"; 
   fixed (char* pLine = line,pPrefix = prefix)
   {
 
      var pLen = line.Length + pLine;
      var found = false;
      var result = 0;
      var i = 0;
      for (char* p = pLine ,pP = pPrefix; p < pLen; p++)
      {
         if (!found )
         {
            if( *p == *(pP+i)) i++;
            if( i ==prefix.Length) found = true;
            continue;
         }
         
         if (*p < '0' || *p > '9')
            break;

         result = result * 10 + *p - '0';

 
      }

      return (found, result);
   }
}
    
...

var results = File.ReadLines(@"D:\Test.txt")
                  .Select(ParseLine)
                  .Where(result => result.found)
                  .Select(result => result.value);

foreach (var result in results)
   Console.WriteLine(result);

注意:这不是关于正则表达式的抨击,只是不同的方法。

我没有对此进行基准测试,但我怀疑 Pointers 将是最快的,split 将紧随其后,Regex 可能是最慢的(即使使用编译的),但它是最易读、最易维护和最健壮的方法(这就是我把它放在第一位的原因)

基准测试

+----------+------------+-----------+-----------+
|  Method  |    Mean    |   Error   |  StdDev   |
+----------+------------+-----------+-----------+
| RegEx    | 3,358.3 us | 65.169 us | 66.923 us |
| Split    | 1,980.9 us | 38.440 us | 48.614 us |
| Pointers | 287.4 us   | 4.396 us  | 4.112 us  |
+----------+------------+-----------+-----------+

测试代码

public class Test
{
   private Regex _regex;

   private string[] data;

   [GlobalSetup]
   public void Setup()
   {
      _regex = new Regex(@"(?<=2:\[X:)\d+", RegexOptions.Compiled);

      data = File.ReadLines(@"D:\Test3.txt")
                 .ToArray();
   }

   [Benchmark]
   public List<int> RegEx()
   {
      return data.Select(line => _regex.Match(line))
                 .Where(x => x.Success)
                 .Select(match => int.Parse(match.Value))
                 .ToList();
   }

   [Benchmark]
   public List<int> Split()
   {
      return data.Select(line => line.Split(new[] { "2:[X:", "][Y:" }, StringSplitOptions.RemoveEmptyEntries))
                 .Where(results => results.Length > 1)
                 .Select(results => int.Parse(results[1]))
                 .ToList();
   }

   [Benchmark]
   public List<int> Pointers()
   {
      return data.Select(ParseLine)
                 .Where(result => result.found)
                 .Select(result => result.value)
                 .ToList();
   }

   public static unsafe (bool found, int value) ParseLine(string line)
   {
      const string prefix = "2:[X:"; 
      fixed (char* pLine = line,pPrefix = prefix)
      {
    
         var pLen = line.Length + pLine;
         var found = false;
         var result = 0;
         var i = 0;
         for (char* p = pLine ,pP = pPrefix; p < pLen; p++)
         {
            if (!found )
            {
               if( *p == *(pP+i)) i++;
               if( i ==prefix.Length) found = true;
               continue;
            }
            
            if (*p < '0' || *p > '9')
               break;

            result = result * 10 + *p - '0';

    
         }

         return (found, result);
      }
   }
}

【讨论】:

  • 在使用正则表达式之前使用 if(line.StartsWith("{")) 效率更高。
  • @jdweng 是的,好点,他们都一样
  • x: 可以出现在 2: 以外的其他块类型中,但是我只对 2: 块中的 X: 感兴趣。
猜你喜欢
  • 2012-10-24
  • 2010-11-03
  • 2011-10-14
  • 2013-07-31
  • 2010-10-06
  • 1970-01-01
  • 2010-09-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多