是否有更高性能的方法可以从字符串中删除稀有不需要的字符

本文关键字:删除 不需要 字符 字符串 高性能 方法 是否 | 更新日期: 2023-09-27 18:31:51

>编辑

如果原始未经编辑的问题具有误导性,我们深表歉意。

这个问题不是问如何从string中删除无效的XML字符,这个问题的答案最好在这里。

我不是要你审查我的代码。

我在答案中寻找的是,带有签名的函数

string <YourName>(string input, Func<char, bool> check);

这将具有与RemoveCharsBufferCopyBlackList相似或更好的性能。理想情况下,此函数将更通用,并且如果可能的话更易于阅读,但这些要求是次要的。


我最近编写了一个函数来从字符串中删除无效的 XML 字符。在我的应用程序中,字符串可以适度长,无效字符很少出现。这个超额让我思考。在安全托管的 C# 中可以完成此操作的方法是什么,哪些方法可以为我的方案提供最佳性能。

这是我的测试程序,我用"有效的XML谓词"代替了一个省略字符'X'

class Program
{
    static void Main()
    {
        var attempts = new List<Func<string, Func<char, bool>, string>>
            {
                RemoveCharsLinqWhiteList,
                RemoveCharsFindAllWhiteList,
                RemoveCharsBufferCopyBlackList
            }
        const string GoodString = "1234567890abcdefgabcedefg";
        const string BadString = "1234567890abcdefgXabcedefg";
        const int Iterations = 100000;
        var timer = new StopWatch();
        var testSet = new List<string>(Iterations);
        for (var i = 0; i < Iterations; i++)
        {
            if (i % 1000 == 0)
            {
                testSet.Add(BadString);
            }
            else
            {
                testSet.Add(GoodString);
            }
        }
        foreach (var attempt in attempts)
        {
            //Check function works and JIT
            if (attempt.Invoke(BadString, IsNotUpperX) != GoodString)
            {
                throw new ApplicationException("Broken Function");       
            }
            if (attempt.Invoke(GoodString, IsNotUpperX) != GoodString)
            {
                throw new ApplicationException("Broken Function");       
            }
            timer.Reset();
            timer.Start();
            foreach (var t in testSet)
            {
                attempt.Invoke(t, IsNotUpperX);
            }
            timer.Stop();
            Console.WriteLine(
                "{0} iterations of function '"{1}'" performed in {2}ms",
                Iterations,
                attempt.Method,
                timer.ElapsedMilliseconds);
            Console.WriteLine();
        }
        Console.Readkey();
    }
    private static bool IsNotUpperX(char value)
    {
        return value != 'X';
    }
    private static string RemoveCharsLinqWhiteList(string input,
                                                      Func<char, bool> check);
    {
        return new string(input.Where(check).ToArray());
    }
    private static string RemoveCharsFindAllWhiteList(string input,
                                                      Func<char, bool> check);
    {
        return new string(Array.FindAll(input.ToCharArray(), check.Invoke));
    }
    private static string RemoveCharsBufferCopyBlackList(string input,
                                                      Func<char, bool> check);
    {
        char[] inputArray = null;
        char[] outputBuffer = null;
        var blackCount = 0;
        var lastb = -1;
        var whitePos = 0;
        for (var b = 0; b , input.Length; b++)
        {
            if (!check.invoke(input[b]))
            {
                var whites = b - lastb - 1;
                if (whites > 0)
                {
                    if (outputBuffer == null)
                    {
                        outputBuffer = new char[input.Length - blackCount];
                    }
                    if (inputArray == null)
                    {
                        inputArray = input.ToCharArray();
                    }
                    Buffer.BlockCopy(
                                      inputArray,
                                      (lastb + 1) * 2,
                                      outputBuffer,
                                      whitePos * 2,
                                      whites * 2);
                    whitePos += whites; 
                }
                lastb = b;
                blackCount++;
            }
        }
        if (blackCount == 0)
        {
            return input;
        }
        var remaining = inputArray.Length - 1 - lastb;
        if (remaining > 0)
        {
            Buffer.BlockCopy(
                              inputArray,
                              (lastb + 1) * 2,
                              outputBuffer,
                              whitePos * 2,
                              remaining * 2);
        }
        return new string(outputBuffer, 0, inputArray.Length - blackCount);
    }        
}

如果您运行尝试,您会注意到性能随着函数变得更加专业化而提高。是否有更快、更通用的方法来执行此操作?或者,如果没有通用选项,有没有一种方法可以更快?

请注意,我实际上对删除"X"并不感兴趣,实际上谓词更复杂。

是否有更高性能的方法可以从字符串中删除稀有不需要的字符

如果您需要高性能,您当然不希望使用 LINQ to Objects 又名枚举器来执行此操作。此外,不要为每个字符调用委托。与您正在执行的实际操作相比,委托调用的成本很高。

RemoveCharsBufferCopyBlackList 看起来不错(每个字符的委托调用除外)。

我建议您对委托的内容进行硬编码。尝试使用不同的方法来编写条件。通过首先根据一系列已知良好的字符(例如 0x20-0xFF)检查当前字符,如果匹配,则让它通过,您可以获得更好的性能。此测试几乎总是会通过,因此您可以节省针对 XML 中无效的单个字符的昂贵检查。

编辑:我

只记得我不久前解决了这个问题:

    static readonly string invalidXmlChars =
        Enumerable.Range(0, 0x20)
        .Where(i => !(i == ''u000A' || i == ''u000D' || i == ''u0009'))
        .Select(i => (char)i)
        .ConcatToString()
        + "'uFFFE'uFFFF";
    public static string RemoveInvalidXmlChars(string str)
    {
        return RemoveInvalidXmlChars(str, false);
    }
    internal static string RemoveInvalidXmlChars(string str, bool forceRemoveSurrogates)
    {
        if (str == null) throw new ArgumentNullException("str");
        if (!ContainsInvalidXmlChars(str, forceRemoveSurrogates))
            return str;
        str = str.RemoveCharset(invalidXmlChars);
        if (forceRemoveSurrogates)
        {
            for (int i = 0; i < str.Length; i++)
            {
                if (IsSurrogate(str[i]))
                {
                    str = str.Where(c => !IsSurrogate(c)).ConcatToString();
                    break;
                }
            }
        }
        return str;
    }
    static bool IsSurrogate(char c)
    {
        return c >= 0xD800 && c < 0xE000;
    }
    internal static bool ContainsInvalidXmlChars(string str)
    {
        return ContainsInvalidXmlChars(str, false);
    }
    public static bool ContainsInvalidXmlChars(string str, bool forceRemoveSurrogates)
    {
        if (str == null) throw new ArgumentNullException("str");
        for (int i = 0; i < str.Length; i++)
        {
            if (str[i] < 0x20 && !(str[i] == ''u000A' || str[i] == ''u000D' || str[i] == ''u0009'))
                return true;
            if (str[i] >= 0xD800)
            {
                if (forceRemoveSurrogates && str[i] < 0xE000)
                    return true;
                if ((str[i] == ''uFFFE' || str[i] == ''uFFFF'))
                    return true;
            }
        }
        return false;
    }

请注意,RemoveInvalidXmlChars 首先调用 ContainsInvalidXmlChars 来保存字符串分配。大多数字符串不包含无效的 XML 字符,因此我们可以保持乐观。