![](/img/trans.png)
[英]most efficient way to remove characters from a string of integers and decimals
[英]Most efficient way to remove special characters from string
我想從字符串中刪除所有特殊字符。 允許的字符為 AZ(大寫或小寫)、數字 (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 個字符之間。
為什么你認為你的方法效率不高? 這實際上是您可以做到的最有效的方法之一。
您當然應該將字符讀入局部變量或使用枚舉器來減少數組訪問次數:
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 ms。
正則表達式:294.4 毫秒。
編輯 2:我在上面的代碼中添加了 AZ 和 az 之間的區別。 (我重新進行了性能測試,沒有明顯差異。)
編輯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);
}
好吧,除非你真的需要從你的函數中擠出性能,否則就選擇最容易維護和理解的東西。 正則表達式如下所示:
為了獲得額外的性能,您可以預編譯它,或者只是告訴它在第一次調用時編譯(后續調用會更快。)
public static string RemoveSpecialCharacters(string str)
{
return Regex.Replace(str, "[^a-zA-Z0-9_.]+", "", RegexOptions.Compiled);
}
我建議創建一個簡單的查找表,您可以在靜態構造函數中對其進行初始化,以將任意字符組合設置為有效。 這使您可以進行快速、單一的檢查。
編輯
此外,為了速度,您需要將 StringBuilder 的容量初始化為輸入字符串的長度。 這將避免重新分配。 這兩種方法一起將為您提供速度和靈活性。
另一個編輯
我認為編譯器可能會對其進行優化,但出於風格和效率的考慮,我建議使用 foreach 而不是 for。
正則表達式將如下所示:
public string RemoveSpecialChars(string input)
{
return Regex.Replace(input, @"[^0-9a-zA-Z\._]", string.Empty);
}
但如果性能非常重要,我建議您在選擇“正則表達式路徑”之前做一些基准測試......
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);
}
如果您使用的是動態字符列表,LINQ 可能會提供更快、更優雅的解決方案:
public static string RemoveSpecialCharacters(string value, char[] specialCharacters)
{
return new String(value.Except(specialCharacters).ToArray());
}
我將這種方法與之前的兩種“快速”方法(發布編譯)進行了比較:
請注意,該算法略有修改 - 字符作為數組而不是硬編碼傳入,這可能會稍微影響一些事情(即/其他解決方案將有一個內部 foo 循環來檢查字符數組)。
如果我使用 LINQ where 子句切換到硬編碼解決方案,結果是:
如果您打算編寫更通用的解決方案,而不是對字符列表進行硬編碼,那么可能值得研究 LINQ 或修改后的方法。 LINQ 絕對可以為您提供簡潔、易讀的代碼——甚至比 Regex 還要多。
我不相信你的算法是有效的。 這是 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;
}
您可以使用正則表達式,如下所示:
return Regex.Replace(strIn, @"[^\w\.@-]", "", RegexOptions.None, TimeSpan.FromSeconds(1.0));
StringBuilder sb = new StringBuilder();
for (int i = 0; i < fName.Length; i++)
{
if (char.IsLetterOrDigit(fName[i]))
{
sb.Append(fName[i]);
}
}
這對我來說似乎很好。 我要做的唯一改進是用字符串的長度初始化StringBuilder
。
StringBuilder sb = new StringBuilder(str.Length);
我同意這個代碼示例。 唯一不同的是我把它變成了字符串類型的擴展方法。 這樣您就可以在非常簡單的行或代碼中使用它:
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();
}
}
我會使用字符串替換和正則表達式搜索“特殊字符”,用空字符串替換找到的所有字符。
我必須為工作做類似的事情,但在我的情況下,我必須過濾所有不是字母、數字或空格的東西(但你可以根據需要輕松修改它)。 過濾是在 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();
}
這里有很多建議的解決方案,有些比其他的更有效,但可能不是很可讀。 這可能不是最有效的,但肯定適用於大多數情況,並且非常簡潔易讀,利用了 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());
下面的代碼有以下輸出(結論是我們還可以節省一些內存資源分配數組更小的大小):
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);
}
我想知道基於正則表達式的替換(可能已編譯)是否更快。
必須測試
有人發現這要慢 5 倍。
除此之外,您應該使用預期長度初始化 StringBuilder,這樣中間字符串就不必在它增長時被復制。
一個好的數字是原始字符串的長度,或者稍低一些(取決於函數輸入的性質)。
最后,您可以使用查找表(范圍為 0..127)來確定是否要接受字符。
對於 S&G 的 Linq 化方式:
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());
但是,我認為這不會是最有效的方法。
利用:
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()
函數進行高度自定義。
哈希集是 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();
}
我進行了測試,這並不比接受的答案快。
我會保留它,就好像您需要一組可配置的字符一樣,這將是一個很好的解決方案。
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();
}
我不確定這是最有效的方法,但它對我有用
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
另一種嘗試通過減少分配來提高性能的方法,尤其是在多次調用此函數時。
它之所以有效,是因為您可以保證結果不會比輸入長,因此可以傳遞輸入和輸出,而無需在內存中創建額外的副本。 出於這個原因,您不能使用stackalloc
創建緩沖區數組,因為這需要從緩沖區中復制。
public static string RemoveSpecialCharacters(this string str)
{
return RemoveSpecialCharacters(str.AsSpan()).ToString();
}
public static ReadOnlySpan<char> RemoveSpecialCharacters(this ReadOnlySpan<char> str)
{
Span<char> buffer = new char[str.Length];
int idx = 0;
foreach (char c in str)
{
if (char.IsLetterOrDigit(c))
{
buffer[idx] = c;
idx++;
}
}
return buffer.Slice(0, idx);
}
最短的路只有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();
}
使用LINQ
的簡單方法
string text = "123a22 ";
var newText = String.Join(string.Empty, text.Where(x => x != 'a'));
如果您需要在出現注入或拼寫錯誤(罕見事件)的情況下清理輸入字符串,最快的方法是使用switch()
檢查所有字符(編譯器在優化switch()
) 加上附加代碼以刪除不需要的字符(如果發現)。 這是解決方案:
public static string RemoveExtraCharacters(string input)
{
if (string.IsNullOrEmpty(input))
return "";
input = input.Trim();
StringBuilder sb = null;
reStart:
if (!string.IsNullOrEmpty(input))
{
var len = input.Length; ;
for (int i = 0; i < len; i++)
{
switch (input[i])
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'H':
case 'I':
case 'J':
case 'K':
case 'L':
case 'M':
case 'N':
case 'O':
case 'Q':
case 'P':
case 'R':
case 'S':
case 'T':
case 'U':
case 'V':
case 'W':
case 'X':
case 'Y':
case 'Z':
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
case 'n':
case 'o':
case 'q':
case 'p':
case 'r':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
case 'x':
case 'y':
case 'z':
case '/':
case '_':
case '-':
case '+':
case '.':
case ',':
case '*':
case ':':
case '=':
case ' ':
case '^':
case '$':
break;
default:
if (sb == null)
sb = new StringBuilder();
sb.Append(input.Substring(0, i));
if (i + 1 < len)
{
input = input.Substring(i + 1);
goto reStart;
}
else
input = null;
break;
}
}
}
if (sb != null)
{
if (input != null)
sb.Append(input);
return sb.ToString();
}
return input;
}
public static string RemoveAllSpecialCharacters(this string text) {
if (string.IsNullOrEmpty(text))
return text;
string result = Regex.Replace(text, "[:!@#$%^&*()}{|\":?><\\[\\]\\;'/.,~]", " ");
return result;
}
如果您擔心速度,請使用指針來編輯現有字符串。 您可以固定字符串並獲取指向它的指針,然后對每個字符運行 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
}
public static string RemoveSpecialCharacters(string str){
return str.replaceAll("[^A-Za-z0-9_\\\\.]", "");
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.