.NET中删除空白字符串的10大方法

原创
小哥 2年前 (2023-05-28) 阅读数 9 #大杂烩

转自:http://www.codeceo.com/article/donet-remove-whitespace-string.html

我们有无数种方法可用于删除字符串中的所有空格,但哪一种更快?

介绍

我们有无数种方法可用于删除字符串中的所有空格。它们中的大多数在绝大多数用例中都能很好地工作,但在某些时间敏感的应用程序中,采用最快的方法可能会产生巨大的差异。

如果你问什么是空白,说起来有点混乱。许多人认为空白是 SPACE 字符(UnicodeU+0020,ASCII 32,HTML )但它实际上包括使布局在水平和垂直方向上以空格显示的所有字符。事实上,这是一个定义为Unicode字符数据库中的字符。

本文中提到的空格不仅指其正确的定义,还包括string.Replace(” “, “”)方法。

此处的基准测试方法将删除所有前导、尾随和中间空白。这就是文章标题中“所有空白”的含义。

背景

这篇文章最初是出于我的好奇心而写的。事实上,我不需要使用最快的算法来删除字符串中的空格。

检查空格字符

检查空格字符很简单。所有你需要的代码就是:

char wp = ; char a = a; Assert.True(char.IsWhiteSpace(wp)); Assert.False(char.IsWhiteSpace(a));

但是,当我对删除方法进行手动优化时,我意识到这并不像预期的那么好。微软参考源库中的一些源代码char.cs挖掘发现:

public static bool IsWhiteSpace(char c) { if (IsLatin1(c)) { return (IsWhiteSpaceLatin1(c)); } return CharUnicodeInfo.IsWhiteSpace(c); }

然后CharUnicodeInfo.IsWhiteSpace成了:

internal static bool IsWhiteSpace(char c) { UnicodeCategory uc = GetUnicodeCategory(c); // In Unicode 3.0, U+2028 is the only character which is under the category "LineSeparator". // And U+2029 is th eonly character which is under the category "ParagraphSeparator". switch (uc) { case (UnicodeCategory.SpaceSeparator): case (UnicodeCategory.LineSeparator): case (UnicodeCategory.ParagraphSeparator): return (true); }

return (false);

}

GetUnicodeCategory()方法调用InternalGetUnicodeCategory()该方法实际上非常快,但现在我们按顺序排列了它4方法调用!以下代码由审阅者提供,可用于快速实现自定义版本和JIT默认内联:

// whitespace detection method: very fast, a lot faster than Char.IsWhiteSpace [MethodImpl(MethodImplOptions.AggressiveInlining)] // if its not inlined then it will be slow!!! public static bool isWhiteSpace(char ch) { // this is surprisingly faster than the equivalent if statement switch (ch) { case \u0009: case \u000A: case \u000B: case \u000C: case \u000D: case \u0020: case \u0085: case \u00A0: case \u1680: case \u2000: case \u2001: case \u2002: case \u2003: case \u2004: case \u2005: case \u2006: case \u2007: case \u2008: case \u2009: case \u200A: case \u2028: case \u2029: case \u202F: case \u205F: case \u3000: return true; default: return false; } }

删除字符串的不同方法

我使用各种方法来删除字符串中的所有空格。

分离和合并方法

这是我一直在使用的一种非常简单的方法。根据空格字符分隔字符串,但不包括空项,然后将生成的片段合并回一起。这种方法可能听起来有点傻,但实际上,乍一看,它似乎是一个非常浪费的解决方案:

public static string TrimAllWithSplitAndJoin(string str) { return string.Concat(str.Split(default(string[]), StringSplitOptions.RemoveEmptyEntries)); }

LINQ

这是一种优雅而显式地实现此过程的方法:

public static string TrimAllWithLinq(string str) { return new string(str.Where(c => !isWhiteSpace(c)).ToArray()); }

正则表达式

正则表达式是一种非常强大的力量,任何 程序员 每个人都应该意识到这一点。

static Regex whitespace = new Regex(@"\s+", RegexOptions.Compiled);

public static string TrimAllWithRegex(string str) { return whitespace.Replace(str, ""); }

字符数组就地转换方法

此方法将输入字符串转换为字符数组,然后就地扫描字符串以删除空格字符(不创建中间缓冲区或字符串)。最后,被“修剪”的数组将生成一个新字符串。

public static string TrimAllWithInplaceCharArray(string str) { var len = str.Length; var src = str.ToCharArray(); int dstIdx = 0; for (int i = 0; i < len; i++) { var ch = src[i]; if (!isWhiteSpace(ch)) src[dstIdx++] = ch; } return new string(src, 0, dstIdx); }

字符数组复制方法

此方法类似于字符数组就地转换方法,但它使用Array.Copy复制连续的非空白“字符串”时跳过空格。最后,它将创建一个适当大小的字符数组,并以相同的方式返回一个新字符串。

public static string TrimAllWithCharArrayCopy(string str) { var len = str.Length; var src = str.ToCharArray(); int srcIdx = 0, dstIdx = 0, count = 0; for (int i = 0; i < len; i++) { if (isWhiteSpace(src[i])) { count = i - srcIdx; Array.Copy(src, srcIdx, src, dstIdx, count); srcIdx += count + 1; dstIdx += count; len--; } } if (dstIdx < len) Array.Copy(src, srcIdx, src, dstIdx, len - dstIdx); return new string(src, 0, len); }

循环交换法

在代码中实现循环并使用 StringBuilder 类,依靠StringBuilder内部优化以创建新字符串。为了避免任何其他因素干扰此实现,不调用其他方法,并且通过缓存到局部变量来避免访问类成员。最后,通过设置StringBuilder.Length将缓冲区调整为适当的大小。

// Code suggested by http://www.codeproject.com/Members/TheBasketcaseSoftware public static string TrimAllWithLexerLoop(string s) { int length = s.Length; var buffer = new StringBuilder(s); var dstIdx = 0; for (int index = 0; index < s.Length; index++) { char ch = s[index]; switch (ch) { case \u0020: case \u00A0: case \u1680: case \u2000: case \u2001: case \u2002: case \u2003: case \u2004: case \u2005: case \u2006: case \u2007: case \u2008: case \u2009: case \u200A: case \u202F: case \u205F: case \u3000: case \u2028: case \u2029: case \u0009: case \u000A: case \u000B: case \u000C: case \u000D: case \u0085: length--; continue; default: break; } buffer[dstIdx++] = ch; } buffer.Length = length; return buffer.ToString();; }

循环字符法

此方法与以前的循环交换方法几乎相同,但它使用if要调用的语句isWhiteSpace()而不是凌乱 switch 伎俩 :)。

public static string TrimAllWithLexerLoopCharIsWhitespce(string s) { int length = s.Length; var buffer = new StringBuilder(s); var dstIdx = 0; for (int index = 0; index < s.Length; index++) { char currentchar = s[index]; if (isWhiteSpace(currentchar)) length--; else buffer[dstIdx++] = currentchar; } buffer.Length = length; return buffer.ToString();; }

就地更改字符串方法(不安全)

此方法使用不安全的字符指针和指针操作来就地更改字符串。我不推荐这种方法,因为它破坏了.NET生产中框架的基本约定是字符串是不可变的。

public static unsafe string TrimAllWithStringInplace(string str) { fixed (char pfixed = str) { char dst = pfixed; for (char p = pfixed; p != 0; p++) if (!isWhiteSpace(p)) dst++ = *p;

    /*// reset the string size
        * ONLY IT DIDNT WORK! A GARBAGE COLLECTION ACCESS VIOLATION OCCURRED AFTER USING IT
        * SO I HAD TO RESORT TO RETURN A NEW STRING INSTEAD, WITH ONLY THE PERTINENT BYTES
        * IT WOULD BE A LOT FASTER IF IT DID WORK THOUGH...
    Int32 len = (Int32)(dst - pfixed);
    Int32* pi = (Int32*)pfixed;
    pi[-1] = len;
    pfixed[len] = \0;*/
    return new string(pfixed, 0, (int)(dst - pfixed));
}

}

就地更改字符串方法V2(不安全)

此方法与上一个方法几乎相同,但此处我们使用类似于数组的指针访问。我很好奇,我不知道哪种存储访问会更快。

public static unsafe string TrimAllWithStringInplaceV2(string str) { var len = str.Length; fixed (char* pStr = str) { int dstIdx = 0; for (int i = 0; i < len; i++) if (!isWhiteSpace(pStr[i])) pStr[dstIdx++] = pStr[i]; // since the unsafe string length reset didnt work we need to resort to this slower compromise return new string(pStr, 0, dstIdx); } }

String.Replace("","")

这种实现方法是幼稚的,因为它只替换空格字符,因此它没有使用正确的空格定义,导致缺少许多其他空格字符。尽管它应该被认为是本文中最快的方法,但它的功能不如其他方法。

但如果只需要去掉实空格字符,就很难用纯.NET写出胜过string.Replace的代码。大多数字符串方法将回退到本地手动优化C ++代码。而String.Replace本身将用comstring.cpp调用C ++方法:

FCIMPL3(Object, COMString::ReplaceString, StringObject thisRefUNSAFE, StringObject oldValueUNSAFE, StringObject newValueUNSAFE)

以下是基准测试套件方法:

public static string TrimAllWithStringReplace(string str) { // This method is NOT functionaly equivalent to the others as it will only trim "spaces" // Whitespace comprises lots of other characters return str.Replace(" ", ""); }

许可证

本文以及任何相关的源代码和文件基于The Code Project Open License (CPOL)的许可。

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除