在字符串中去除空格的最快方法

28

我正在尝试从数据库表中获取由","分隔的多个电子邮件地址字符串,但它也会返回空格,我想快速地去除这些空格。

下面的代码确实可以去掉空格,但是当我尝试从一个包含30000个电子邮件地址的字符串中删除它们之间的空格时,它也变得很慢。去除这些空格需要四到五分钟。

 Regex Spaces =
        new Regex(@"\s+", RegexOptions.Compiled);
txtEmailID.Text = MultipleSpaces.Replace(emailaddress),"");

请问有什么方法可以快速移除大量电子邮件地址中的空格吗?


空格符号(Whitespace)!= 空格(spaces)(后者更广泛,包括例如换行符)。 - user395760
https://dev59.com/73NA5IYBdhLWcg3wBpDs - Oleks
我有一个疑问... 你是从整个字符串中删除空格(即包含逗号分隔的电子邮件),还是逐个从每个单独的地址中删除空格? - digEmAll
https://dev59.com/f3A75IYBdhLWcg3wDUco - manoj
你可以在这里找到最佳答案。请访问以查看解决方案。 - manoj
13个回答

48
我会使用 StringBuilder 构建一个自定义扩展方法,例如:
public static string ExceptChars(this string str, IEnumerable<char> toExclude)
{
    StringBuilder sb = new StringBuilder(str.Length);
    for (int i = 0; i < str.Length; i++)
    {
        char c = str[i];
        if (!toExclude.Contains(c))
            sb.Append(c);
    }
    return sb.ToString();
}

使用方法:

var str = s.ExceptChars(new[] { ' ', '\t', '\n', '\r' });

或者更快的方式:

var str = s.ExceptChars(new HashSet<char>(new[] { ' ', '\t', '\n', '\r' }));

使用哈希集版本,一个包含1100万个字符的字符串只需要不到700毫秒的时间(而且我是在调试模式下)。
编辑:
之前的代码是通用的,可以排除任何字符,但如果你想以最快的方式仅删除空格,可以使用:
public static string ExceptBlanks(this string str)
{
    StringBuilder sb = new StringBuilder(str.Length);
    for (int i = 0; i < str.Length; i++)
    {
        char c = str[i];
        switch (c)
        {
            case '\r':
            case '\n':
            case '\t':
            case ' ':
                continue;
            default:
                sb.Append(c);
                break;
        }
    }
    return sb.ToString();
}

编辑 2:

正如评论中正确指出的那样,删除所有空格的正确方法是使用char.IsWhiteSpace方法:

public static string ExceptBlanks(this string str)
{
    StringBuilder sb = new StringBuilder(str.Length);
    for (int i = 0; i < str.Length; i++)
    {
        char c = str[i];
        if(!char.IsWhiteSpace(c))
            sb.Append(c);
    }
    return sb.ToString();
}

2
你可以为这个解决方案创建一个高速哈希:byte[] hash = new byte[255]; 如果你想排除 \t,你可以这样做 b[(int)'\t'] = 1,然后以同样的方式检查。但它只适用于 ASCII :) - Andrey
3
更好的做法是使用StringBuilder sb = new StringBuilder(str.Length); - Chris Ward
1
另一个需要考虑的问题是,如果给定的字符串不包含任何这些空格字符,则我们不需要将每个字符附加到 StringBuilder!因此,我们可以使用一个 标志 来测试是否找到了 第一个空白字符,然后开始添加到 StringBuilder。否则,我们只需返回输入字符串本身。当给定字符串通常不包含搜索字符串时,这可以提高性能。 - S.Serpooshan
@saeedserpooshan:没错,但我认为只有当至少80%的字符串包含空格需要删除时,才会使用这种方法;因此,使用特定代码处理剩余20%情况所获得的速度提升,并不能显著减少总执行时间... - digEmAll
你可以在结尾添加一个小测试,以便在不需要新字符串时保持相同的字符串:if (sb.Length == str.Length) return str; 我想我曾经在某个地方读到过,保留初始字符串可能对GC更好。 - Simon Mourier
显示剩余6条评论

15

考虑到 string.Replace 的实现是用 C++ 编写的,同时也是 CLR 运行时 的一部分,我敢打赌

email.Replace(" ","").Replace("\t","").Replace("\n","").Replace("\r","");

将会是最快的实现方式。如果你需要 包括所有类型的空格符号, 你可以提供相应的 Unicode 十六进制值。


2
是的,这确实很快,但这会创建4个字符串而不是1个。在处理长字符串时会稍微变慢,使用 StringBuilder 的自定义实现比这个更快。 - digEmAll
@digEmAll 这是一个电子邮件地址,所以不会占用太多内存。如果是一个大的1k文本文件,我会同意。 - Chris S
2
据我理解,它是一个由许多逗号分隔的电子邮件组成的单个字符串...但说实话,我不确定... - digEmAll

5

使用 LINQ,您可以轻松完成此操作:

emailaddress = new String(emailaddress
                                     .Where(x=>x!=' ' && x!='\r' && x!='\n')
                                     .ToArray());

我没有和stringbuilder方法进行比较,但是它比基于字符串的方法更快。 因为它不会创建多个字符串副本(字符串是不可变的,直接使用会导致内存和速度问题急剧增加),所以它不会占用很大的内存,也不会减慢速度(除了第一次需要额外遍历字符串)。


2
我真的怀疑它是否快速 - Andrey
@Andrey:它应该具有线性运行时间,并且仅构建一次数组。常见的正则表达式问题涉及非线性运行时间,常见的字符串替换问题涉及重复复制字符串。与使用正则表达式和字符串替换的解决方案相比,为什么这个解决方案不会快呢?我能想到的唯一可能是函数调用开销。没有对两者进行分析,这只是猜测。 - Merlyn Morgan-Graham
1
@digEmAll,是的,我会修复它:) 很有趣的错误。 - Saeed Amiri
1
@Andrey:是的,它确实很快。唯一的小问题是需要通过一个可丢弃的数组。 - digEmAll
1
如其他地方所提到的那样,x => !Char.IsWhiteSpace(x) 是首选。这个linq命令是我选择类似问题的解决方案。谢谢! - B2K
显示剩余2条评论

4
你可以尝试使用String.Trim()。它将从字符串的开头到结尾去掉所有空格。
或者你可以尝试这个链接中提到的方法:[链接]
    public static unsafe string StripTabsAndNewlines(string s)
    {
        int len = s.Length;
        char* newChars = stackalloc char[len];
        char* currentChar = newChars;

        for (int i = 0; i < len; ++i)
        {
            char c = s[i];
            switch (c)
            {
                case '\r':
                case '\n':
                case '\t':
                    continue;
                default:
                    *currentChar++ = c;
                    break;
            }
        }
        return new string(newChars, 0, (int)(currentChar - newChars));
    }

10
在安全代码中加入不安全代码,必须要有非常重要的原因。清理字符串显然不是这样一个原因。 - Andrey
我认为执行一个简单操作需要4-5分钟是不可接受的。它可以更快。 - Evgeny Gavrin
不需要使用指针,而且使用Char.IsWhiteSpace(c)代替switch语句是更好的解决方案。 - katbyte

4
emailaddress.Replace("  ", string.Empty);

2

有许多不同的方法,其中一些比其他方法更快:

public static string StripTabsAndNewlines(this string str) {

    //string builder (fast)
    StringBuilder sb = new StringBuilder(str.Length);
    for (int i = 0; i < str.Length; i++) {
        if ( !  Char.IsWhiteSpace(s[i])) {
            sb.Append();
        }
    }
    return sb.tostring();

    //linq (faster ?)
    return new string(str.ToCharArray().Where(c => !Char.IsWhiteSpace(c)).ToArray());

    //regex (slow)
    return Regex.Replace(str, @"\s+", "")

}

2
请使用 String 类的 TrimEnd() 方法。您可以在 这里 找到一个很好的例子。

1
String类的Trim*方法只能修剪字符串的开头或结尾。 - JohnZaj

2

如果可能的话,您应该考虑使用REPLACE()函数替换存储过程或查询中记录集中的空格,甚至更好的是修复您的数据库记录,因为电子邮件地址中的空格无效。

正如其他人所提到的,您需要对不同的方法进行分析。如果您正在使用Regex,则应将其最小化为类级静态变量:

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

emailAddress.Where(x=>{ return x != ' ';}).ToString( ) 可能会有函数开销,尽管Microsoft可以对其进行内联优化 - 再次使用性能分析工具可得到答案。

最有效的方法是分配一个缓冲区,在复制字符到新缓冲区时跳过空格。C#支持指针,您可以使用不安全代码,分配原始缓冲区,并使用指针算术来像在C中一样复制,这是可能实现的最快的方法。SQL REPLACE() 将会为您处理这个问题。


1
string str = "Hi!! this is a bunch of text with spaces";

MessageBox.Show(new String(str.Where(c => c != ' ').ToArray()));

1

我没有对此进行性能测试,但它比大多数其他答案更简单。

var s1 = "\tstring \r with \t\t  \nwhitespace\r\n";
var s2 = string.Join("", s1.Split());

结果是

stringwithwhitespace

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接