【问题标题】:C# - Splitting on a pipe with an escaped pipe in the data?C# - 在数据中带有转义管道的管道上拆分?
【发布时间】:2011-08-14 09:23:13
【问题描述】:

我有一个要拆分的管道分隔文件(我使用的是 C#)。例如:

这|是|一个|测试

但是,某些数据中可能包含管道。如果是这样,它将用反斜杠转义:

This|is|a|pip\|ed|test(这是一个 pip|ed 测试)

我想知道是否有正则表达式或其他方法可以仅在“纯”管道(即前面没有反斜杠的管道)上将其分开。我目前的方法是用自定义的文本位替换转义的管道,在管道上拆分,然后用管道替换我的自定义文本。不是很优雅,我不禁认为有更好的方法。感谢您的帮助。

【问题讨论】:

  • 你见过this (monster) thread。不是直接的答案,但希望能朝着正确的方向前进。
  • 如果你想在其中一个片段的末尾加上一个反斜杠怎么办?

标签: c# regex escaping delimiter


【解决方案1】:

只需使用String.IndexOf() 即可找到下一个管道。如果前一个字符不是反斜杠,则使用String.Substring() 提取单词。或者,您可以使用 String.IndexOfAny() 查找下一个管道或反斜杠的出现。

我做了很多这样的解析,这真的很简单。采用我的方法,如果操作正确,也往往会运行得更快。

编辑

事实上,也许是这样的。看看这在性能方面如何与 RegEx 解决方案进行比较会很有趣。

public List<string> ParseWords(string s)
{
    List<string> words = new List<string>();

    int pos = 0;
    while (pos < s.Length)
    {
        // Get word start
        int start = pos;

        // Get word end
        pos = s.IndexOf('|', pos);
        while (pos > 0 && s[pos - 1] == '\\')
        {
            pos++;
            pos = s.IndexOf('|', pos);
        }

        // Adjust for pipe not found
        if (pos < 0)
            pos = s.Length;

        // Extract this word
        words.Add(s.Substring(start, pos - start));

        // Skip over pipe
        if (pos < s.Length)
            pos++;
    }
    return words;
}

【讨论】:

  • 是的,这样更好,以您自己的方式解析string,而不是使用regex。这运行得更快。 +1
  • 如果您不将单词添加到List&lt;string&gt; 并返回它,手动解析方法的速度比正则表达式方法快约 5 倍。如果你加回管理List&lt;string&gt; 的开销,无论如何,在我的机器上它快了大约 3 倍。
  • 查看我的更新...我更改了我的测试并将正则表达式的实现速度降低了大约 1.6 倍,但是,你仍然赢了!
  • 如果最后一个“单词”为空白/空,我认为这有问题。我有一个包含 37 个标题列名称的文件,但每行的最后一个元素是空白的,所以行以管道“|”结尾但没有额外的空格;在这种情况下,单词只返回 36
  • 我认为当字段末尾有转义的反斜杠时,这也可能会遇到问题。例如“数据\\|更多数据|”。处理来自客户端数据的头痛> .
【解决方案2】:

应该这样做:

string test = @"This|is|a|pip\|ed|test (this is a pip|ed test)";
string[] parts = Regex.Split(test, @"(?<!(?<!\\)*\\)\|");

正则表达式基本上说:在前面没有转义字符的管道上拆分。不过,我不应该为此获得任何功劳,我只是劫持了正则表达式 from this post 并对其进行了简化。

编辑

在性能方面,与本线程提供的手动解析方法相比,我发现此 Regex 实现比使用 OP 提供的较长测试字符串的 Jonathon Wood 实现慢 3 到 5 倍。

话虽如此,如果您不实例化或将单词添加到 List&lt;string&gt; 并返回 void,Jon 的方法比 Regex.Split() 方法快大约 5 倍(0.01 毫秒与 0.002 毫秒)用于纯粹拆分字符串。如果再加上管理和返回 List&lt;string&gt; 的开销,它大约快 3.6 倍(0.01 毫秒对 0.00275 毫秒),平均超过几组一百万次迭代。我没有在这个测试中使用静态 Regex.Split(),而是在我的测试循环之外用上面的表达式创建了一个新的 Regex 实例,然后调用它的 Split 方法。

更新

使用静态 Regex.Split() 函数实际上比重用表达式实例快很多。有了这个实现,正则表达式的使用只比 Jon 的实现慢了大约 1.6 倍(0.0043ms vs. 0.00275ms)

使用我链接到的帖子中的扩展正则表达式得到的结果是相同的。

【讨论】:

  • 假设反斜杠也可以转义(例如"This|is|a|pip\\|ed|test (this is a pip|ed test)"),这是行不通的。您需要使用提到的帖子中的完整内容。
  • @你说得对。这是我决定写一些关于它的代码时想到的第一件事:)
【解决方案3】:

我遇到了类似的情况,对我来说,管道的数量是固定的(不是带有 "\|" 的管道)。我就是这样处理的。

string sPipeSplit = "This|is|a|pip\\|ed|test (this is a pip|ed test)";
string sTempString = sPipeSplit.Replace("\\|", "¬"); //replace \| with non printable character
string[] sSplitString = sTempString.Split('|');
//string sFirstString = sSplitString[0].Replace("¬", "\\|"); //If you have fixed number of fields and you are copying to other field use replace while copying to other field.
/* Or you could use a loop to replace everything at once
foreach (string si in sSplitString)
{
    si.Replace("¬", "\\|");
}
*/

【讨论】:

    【解决方案4】:

    这是另一种解决方案。

    编程最美妙的地方之一就是为同一个问题提供解决方案的几种方法:

    string text = @"This|is|a|pip\|ed|test"; //The original text
    string parsed = ""; //Where you will store the parsed string
    
    bool flag = false;
    foreach (var x in text.Split('|')) {
        bool endsWithArroba = x.EndsWith(@"\");
        parsed += flag ? "|" + x + " " : endsWithArroba ? x.Substring(0, x.Length-1) : x + " ";
        flag = endsWithArroba;
    }
    

    【讨论】:

    • 这很漂亮,但如果您关心性能,这不是一个很好的选择。
    • @Jonathan 正如我所说,这只是另一种方式。粘贴与您提供的代码类似的代码是没有意义的。我同意你的看法,虽然性能可能不是我在这个问题上真正重要的东西。
    • 我并没有批评你发布它。事实上,我提到它很光滑。我只是在评论这种方法的性能。
    • 我认为这个和@Jonathan 都非常漂亮,所以对两者都 +1。
    【解决方案5】:

    Cory 的解决方案非常好。但是,如果您不想使用正则表达式,那么您可以简单地搜索“\|”并用其他字符替换它,然后进行拆分,然后再次用“\ |”替换它。

    另一种选择是进行拆分,然后检查所有字符串,如果最后一个字符是 \,则将其与下一个字符串连接。

    当然,如果您需要在管道之前使用转义的反斜杠,所有这些都会忽略会发生什么......比如“\\|”。

    总的来说,我倾向于正则表达式。

    坦率地说,我更喜欢使用FileHelpers,因为即使这不是逗号分隔的,但它基本上是一样的。他们有一个关于why you shouldn't write this stuff yourself 的精彩故事。

    【讨论】:

      【解决方案6】:

      您可以使用正则表达式来做到这一点。一旦决定使用反斜杠作为转义字符,您需要考虑两种转义情况:

      • 转义管道:\|
      • 转义要按字面解释的反斜杠。

      这两者都可以在同一个正则表达式中完成。转义的反斜杠将始终是两个 \ 字符在一起。连续的转义反斜杠将始终是偶数个 \ 字符。如果您在管道之前找到一个奇数序列的\,这意味着您有几个转义的反斜杠,然后是一个转义的管道。所以你想使用这样的东西:

      /^(?:((?:[^|\\]|(?:\\{2})|\\\|)+)(?:\||$))*/
      

      也许令人困惑,但它应该有效。说明:

      ^              #The start of a line
      (?:...
          [^|\\]     #A character other than | or \ OR
          (?:\\{2})* #An even number of \ characters OR
          \\\|       #A literal \ followed by a literal |
      ...)+          #Repeat the preceding at least once
      (?:$|\|)       #Either a literal | or the end of a line
      

      【讨论】:

      • @Justin 由于某种原因它无法在我的计算机上运行。此外,缺少)。
      • @Oscar - 嵌套的括号太多,很难跟踪。立即尝试。
      • @Justin 现在可以工作了,尽管@Cory 解决方案的情况相同:A\\|b 应该变成 A\|b而不是 A\\ 和 b。第一个 \\ 是一个和其他字符一样的字符,第二个是转义 |,所以第二个将被删除,句子将保持原样。
      • @Oscar - 如果你输入A\\|b,你已经转义了反斜杠字符本身,所以它应该被解释为A` plus b. To get A\|b, you would input A\\\ |b. That's how I would expect it to work, myself, and it's consistent with most escape schemes I've seen. In C#, for example, the string \\\n` 将是一个文字 `` 和一个回车符。
      • @Justin 这取决于你如何看待它。当有人告诉你:I want to parse the string ABC\DE,你应该假设 \ 已经被转义了。否则原来的例子没有意义,因为如果你写“\|”,C#本身就会报错因为你在这里什么也没有逃脱。为了恢复,我认为要解析的字符串是文字(已经转义)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-02
      • 1970-01-01
      • 2018-03-27
      • 1970-01-01
      • 1970-01-01
      • 2023-04-07
      相关资源
      最近更新 更多