我想从字符串中删除所有特殊字符。允许的字符是A-Z(大写或小写),数字(0-9),下划线(_)或点号(。)。
我有以下内容,它有效,但我怀疑(我知道!)它效率不高:
public static string RemoveSpecialCharacters(string str)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.Length; i++)
{
if ((str[i] >= '0' && str[i] <= '9')
|| (str[i] >= 'A' && str[i] <= 'z'
|| (str[i] == '.' || str[i] == '_')))
{
sb.Append(str[i]);
}
}
return sb.ToString();
}
最有效的方法是什么?正则表达式会是什么样的,它与正常的字符串操作相比如何?
将要清理的字符串相当短,通常长度在10到30个字符之间。
答案 0 :(得分:306)
为什么你认为你的方法效率不高?它实际上是你可以做到的最有效的方法之一。
您当然应该将字符读入局部变量或使用枚举器来减少数组访问次数:
public static string RemoveSpecialCharacters(this string str) {
StringBuilder sb = new StringBuilder();
foreach (char c in str) {
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '.' || c == '_') {
sb.Append(c);
}
}
return sb.ToString();
}
使这种方法有效的一点是它可以很好地扩展。执行时间将相对于字符串的长度。如果你在一个大字符串上使用它,就没有令人讨厌的惊喜。
编辑:
我做了一个快速的性能测试,用24个字符串运行每个函数一百万次。结果如下:
原始功能:54.5毫秒 我建议的改变:47.1毫秒 设置StringBuilder容量为43.3毫秒 正则表达式:294.4 ms。
编辑2: 我在上面的代码中添加了A-Z和a-z之间的区别。 (我重新进行了性能测试,并没有明显的区别。)
编辑3:
我测试了lookup + char []解决方案,它运行大约13毫秒。
支付的价格当然是巨大的查找表的初始化并将其保留在内存中。嗯,这不是那么多的数据,但它对于这样一个微不足道的功能......
private static bool[] _lookup;
static Program() {
_lookup = new bool[65536];
for (char c = '0'; c <= '9'; c++) _lookup[c] = true;
for (char c = 'A'; c <= 'Z'; c++) _lookup[c] = true;
for (char c = 'a'; c <= 'z'; c++) _lookup[c] = true;
_lookup['.'] = true;
_lookup['_'] = true;
}
public static string RemoveSpecialCharacters(string str) {
char[] buffer = new char[str.Length];
int index = 0;
foreach (char c in str) {
if (_lookup[c]) {
buffer[index] = c;
index++;
}
}
return new string(buffer, 0, index);
}
答案 1 :(得分:172)
好吧,除非你真的需要从你的功能中挤出性能,否则只需要最简单的维护和理解。正则表达式如下所示:
为了获得额外的性能,您可以预编译它,也可以告诉它在第一次调用时编译(后续调用会更快。)
public static string RemoveSpecialCharacters(string str)
{
return Regex.Replace(str, "[^a-zA-Z0-9_.]+", "", RegexOptions.Compiled);
}
答案 2 :(得分:15)
我建议创建一个简单的查找表,您可以在静态构造函数中初始化该表以将任何字符组合设置为有效。这使您可以快速进行单一检查。
修改的
另外,对于速度,您需要将StringBuilder的容量初始化为输入字符串的长度。这将避免重新分配。这两种方法将为您提供速度和灵活性。
另一个编辑
我认为编译器可能会优化它,但是作为风格和效率的问题,我建议使用foreach而不是for。
答案 3 :(得分:12)
public static string RemoveSpecialCharacters(string str)
{
char[] buffer = new char[str.Length];
int idx = 0;
foreach (char c in str)
{
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')
|| (c >= 'a' && c <= 'z') || (c == '.') || (c == '_'))
{
buffer[idx] = c;
idx++;
}
}
return new string(buffer, 0, idx);
}
答案 4 :(得分:11)
正则表达式如下所示:
public string RemoveSpecialChars(string input)
{
return Regex.Replace(input, @"[^0-9a-zA-Z\._]", string.Empty);
}
但如果性能非常重要,我建议您在选择“正则表达式路径”之前先做一些基准测试......
答案 5 :(得分:9)
如果您使用的是动态字符列表,LINQ可能会提供更快更优雅的解决方案:
public static string RemoveSpecialCharacters(string value, char[] specialCharacters)
{
return new String(value.Except(specialCharacters).ToArray());
}
我将这种方法与之前的两种“快速”方法(发布编译)进行了比较:
请注意,该算法稍作修改 - 字符作为数组而不是硬编码传入,这可能会略微影响事物(即/其他解决方案将有一个内部循环来检查字符数组)。
如果我使用LINQ where子句切换到硬编码解决方案,结果是:
如果您计划编写更通用的解决方案,而不是对字符列表进行硬编码,那么可能值得查看LINQ或修改后的方法。 LINQ绝对能为您提供简洁,高度可读的代码 - 甚至比Regex还要多。
答案 6 :(得分:5)
我不相信你的算法不算高效。它是O(n)并且只查看每个角色一次。 除非你在检查之前神奇地知道价值,否则你不会比这更好。
然而,我会将StringBuilder
的容量初始化为字符串的初始大小。我猜你的感知性能问题来自内存重新分配。
旁注:检查A
- z
并不安全。您包括[
,\
,]
,^
,_
和`...
附注2:为了获得额外的效率,请按顺序进行比较,以最大限度地减少比较次数。 (最糟糕的是,你正在谈论8次比较,所以不要过于考虑。)这会随着你的预期输入而改变,但一个例子可能是:
if (str[i] >= '0' && str[i] <= 'z' &&
(str[i] >= 'a' || str[i] <= '9' || (str[i] >= 'A' && str[i] <= 'Z') ||
str[i] == '_') || str[i] == '.')
旁注3:如果出于某种原因你真的需要快速,那么switch语句可能会更快。编译器应该为您创建一个跳转表,结果只有一个比较:
switch (str[i])
{
case '0':
case '1':
.
.
.
case '.':
sb.Append(str[i]);
break;
}
答案 7 :(得分:3)
您可以按如下方式使用常规表达式:
return Regex.Replace(strIn, @"[^\w\.@-]", "", RegexOptions.None, TimeSpan.FromSeconds(1.0));
答案 8 :(得分:3)
我同意此代码示例。唯一不同的是我把它变成了字符串类型的扩展方法。这样您就可以在一个非常简单的行或代码中使用它:
string test = "abc@#$123";
test.RemoveSpecialCharacters();
感谢Guffa的实验。
public static class MethodExtensionHelper
{
public static string RemoveSpecialCharacters(this string str)
{
StringBuilder sb = new StringBuilder();
foreach (char c in str)
{
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
{
sb.Append(c);
}
}
return sb.ToString();
}
}
答案 9 :(得分:3)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < fName.Length; i++)
{
if (char.IsLetterOrDigit(fName[i]))
{
sb.Append(fName[i]);
}
}
答案 10 :(得分:3)
对我来说似乎很好。我要做的唯一改进是使用字符串的长度初始化StringBuilder
。
StringBuilder sb = new StringBuilder(str.Length);
答案 11 :(得分:2)
我会使用正则表达式的字符串替换搜索“特殊字符”,替换用空字符串找到的所有字符。
答案 12 :(得分:2)
我必须为工作做类似的事情,但在我的情况下,我必须过滤所有不是字母,数字或空格(但你可以根据自己的需要轻松修改它)。 过滤在JavaScript中以客户端方式完成,但出于安全考虑,我也在进行过滤服务器端。因为我可以期望大多数字符串都是干净的,所以我想避免复制字符串,除非我真的需要。这让我接下来的实现,这对于干净和脏的字符串应该更好。
public static string EnsureOnlyLetterDigitOrWhiteSpace(string input)
{
StringBuilder cleanedInput = null;
for (var i = 0; i < input.Length; ++i)
{
var currentChar = input[i];
var charIsValid = char.IsLetterOrDigit(currentChar) || char.IsWhiteSpace(currentChar);
if (charIsValid)
{
if(cleanedInput != null)
cleanedInput.Append(currentChar);
}
else
{
if (cleanedInput != null) continue;
cleanedInput = new StringBuilder();
if (i > 0)
cleanedInput.Append(input.Substring(0, i));
}
}
return cleanedInput == null ? input : cleanedInput.ToString();
}
答案 13 :(得分:1)
我不确定这是最有效的方式,但它对我有用
Public Function RemoverTildes(stIn As String) As String
Dim stFormD As String = stIn.Normalize(NormalizationForm.FormD)
Dim sb As New StringBuilder()
For ich As Integer = 0 To stFormD.Length - 1
Dim uc As UnicodeCategory = CharUnicodeInfo.GetUnicodeCategory(stFormD(ich))
If uc <> UnicodeCategory.NonSpacingMark Then
sb.Append(stFormD(ich))
End If
Next
Return (sb.ToString().Normalize(NormalizationForm.FormC))
End Function
答案 14 :(得分:1)
public string RemoveSpecial(string evalstr)
{
StringBuilder finalstr = new StringBuilder();
foreach(char c in evalstr){
int charassci = Convert.ToInt16(c);
if (!(charassci >= 33 && charassci <= 47))// special char ???
finalstr.append(c);
}
return finalstr.ToString();
}
答案 15 :(得分:1)
这里提出了许多解决方案,其中一些比其他解决方案更有效,但可能不太可读。这是一个可能不是最有效的,但在大多数情况下肯定可用的,并且非常简洁和可读,利用Linq:
string stringToclean = "This is a test. Do not try this at home; you might get hurt. Don't believe it?";
var validPunctuation = new HashSet<char>(". -");
var cleanedVersion = new String(stringToclean.Where(x => (x >= 'A' && x <= 'Z') || (x >= 'a' && x <= 'z') || validPunctuation.Contains(x)).ToArray());
var cleanedLowercaseVersion = new String(stringToclean.ToLower().Where(x => (x >= 'a' && x <= 'z') || validPunctuation.Contains(x)).ToArray());
答案 16 :(得分:1)
HashSet是O(1)
不确定它是否比现有比较更快
private static HashSet<char> ValidChars = new HashSet<char>() { 'a', 'b', 'c', 'A', 'B', 'C', '1', '2', '3', '_' };
public static string RemoveSpecialCharacters(string str)
{
StringBuilder sb = new StringBuilder(str.Length / 2);
foreach (char c in str)
{
if (ValidChars.Contains(c)) sb.Append(c);
}
return sb.ToString();
}
我测试了这个并不比接受的答案快。
我会把它留下来,好像你需要一组可配置的字符,这将是一个很好的解决方案。
答案 17 :(得分:1)
使用:
s.erase(std::remove_if(s.begin(), s.end(), my_predicate), s.end());
bool my_predicate(char c)
{
return !(isalpha(c) || c=='_' || c==' '); // depending on you definition of special characters
}
你会得到一个干净的字符串s
。
erase()
会删除所有特殊字符,并且可以使用my_predicate()
函数进行高度自定义。
答案 18 :(得分:1)
以下代码具有以下输出(结论是我们还可以节省一些分配数组较小的内存资源):
lookup = new bool[123];
for (var c = '0'; c <= '9'; c++)
{
lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}
for (var c = 'A'; c <= 'Z'; c++)
{
lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}
for (var c = 'a'; c <= 'z'; c++)
{
lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}
48: 0
49: 1
50: 2
51: 3
52: 4
53: 5
54: 6
55: 7
56: 8
57: 9
65: A
66: B
67: C
68: D
69: E
70: F
71: G
72: H
73: I
74: J
75: K
76: L
77: M
78: N
79: O
80: P
81: Q
82: R
83: S
84: T
85: U
86: V
87: W
88: X
89: Y
90: Z
97: a
98: b
99: c
100: d
101: e
102: f
103: g
104: h
105: i
106: j
107: k
108: l
109: m
110: n
111: o
112: p
113: q
114: r
115: s
116: t
117: u
118: v
119: w
120: x
121: y
122: z
您还可以添加以下代码行以支持俄语语言环境(数组大小为1104):
for (var c = 'А'; c <= 'Я'; c++)
{
lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}
for (var c = 'а'; c <= 'я'; c++)
{
lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}
答案 19 :(得分:1)
对于S&amp; G's,Linq-ified方式:
var original = "(*^%foo)(@)&^@#><>?:\":';=-+_";
var valid = new char[] {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D',
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8',
'9', '0', '.', '_' };
var result = string.Join("",
(from x in original.ToCharArray()
where valid.Contains(x) select x.ToString())
.ToArray());
然而,我不认为这将是最有效的方式。
答案 20 :(得分:1)
我想知道基于正则表达式的替换(可能已编译)是否更快。 必须测试有人发现这个速度要慢5倍。
除此之外,您应该使用预期长度初始化StringBuilder,以便在中间字符串增长时不必复制它。
一个好的数字是原始字符串的长度,或稍微低一些(取决于函数输入的性质)。
最后,您可以使用查找表(在0..127范围内)来确定是否接受某个字符。
答案 21 :(得分:0)
最短的方式只是 3 行...
public static string RemoveSpecialCharacters(string str)
{
var sb = new StringBuilder();
foreach (var c in str.Where(c => c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '.' || c == '_')) sb.Append(c);
return sb.ToString();
}
答案 22 :(得分:-1)
public static string RemoveSpecialCharacters(string str){
return str.replaceAll("[^A-Za-z0-9_\\\\.]", "");
}
答案 23 :(得分:-2)
如果您担心速度,请使用指针编辑现有字符串。您可以固定字符串并获取指向它的指针,然后在每个字符上运行for循环,用替换字符覆盖每个无效字符。它将非常高效,不需要分配任何新的字符串内存。您还需要使用unsafe选项编译模块,并将“unsafe”修饰符添加到方法头中以使用指针。
static void Main(string[] args)
{
string str = "string!$%with^&*invalid!!characters";
Console.WriteLine( str ); //print original string
FixMyString( str, ' ' );
Console.WriteLine( str ); //print string again to verify that it has been modified
Console.ReadLine(); //pause to leave command prompt open
}
public static unsafe void FixMyString( string str, char replacement_char )
{
fixed (char* p_str = str)
{
char* c = p_str; //temp pointer, since p_str is read-only
for (int i = 0; i < str.Length; i++, c++) //loop through each character in string, advancing the character pointer as well
if (!IsValidChar(*c)) //check whether the current character is invalid
(*c) = replacement_char; //overwrite character in existing string with replacement character
}
}
public static bool IsValidChar( char c )
{
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '.' || c == '_');
//return char.IsLetterOrDigit( c ) || c == '.' || c == '_'; //this may work as well
}
答案 24 :(得分:-2)
public static string RemoveAllSpecialCharacters(this string text) {
if (string.IsNullOrEmpty(text))
return text;
string result = Regex.Replace(text, "[:!@#$%^&*()}{|\":?><\\[\\]\\;'/.,~]", " ");
return result;
}