从字符串中删除特殊字符的最有效方法

Posted

技术标签:

【中文标题】从字符串中删除特殊字符的最有效方法【英文标题】:Most efficient way to remove special characters from string 【发布时间】:2010-11-10 08:23:38 【问题描述】:

我想从字符串中删除所有特殊字符。允许的字符是 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 个字符之间。

【问题讨论】:

我不会把它放在答案中,因为它不会更有效,但是有许多静态 char 方法,如 char.IsLetterOrDigit() 可以在 if 语句中使用至少让它更清晰。 我不确定检查 A 到 z 是否安全,因为它会输入 6 个非字母字符,只需要其中一个字符(下划线)。 专注于使您的代码更具可读性。除非您以每秒 500 次的循环执行此操作,否则效率并不是什么大问题。使用正则表达式,它会更容易阅读。l 拜伦,您需要强调可读性可能是对的。但是,我对正则表达式的可读性持怀疑态度。 :-) 正则表达式是否可读有点像德语是否可读;这取决于您是否知道(尽管在这两种情况下,您都会不时遇到毫无意义的语法规则;) 【参考方案1】:

为什么你认为你的方法效率不高?这实际上是您可以做到的最有效的方法之一。

您当然应该将字符读入局部变量或使用枚举器来减少数组访问次数:

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 毫秒。

编辑 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);

【讨论】:

我同意。我要做的唯一其他更改是将初始容量参数添加到 StringBuilder 构造函数“= new StringBuilder(str.Length)”。 根据我的测试,我的答案是使用char[] 缓冲区而不是StringBuilder,在这个问题上略有优势。 (虽然我的可读性较差,所以性能上的小小好处可能不值得。) @Steven:很可能是这样,但基准不言自明!在我的测试中,使用char[] 缓冲区的性能(略)优于StringBuilder,即使在扩展到数万个字符长度的字符串时也是如此。 @downvoter:为什么要投反对票?如果你不解释你认为错误的地方,它就无法改进答案。 @SILENT:不,它没有,但你应该只这样做一次。如果每次调用该方法时分配一个那么大的数组(并且如果您频繁调用该方法),那么该方法将成为迄今为止最慢的方法,并为垃圾收集器带来大量工作。【参考方案2】:

好吧,除非你真的需要从你的函数中挤出性能,否则就选择最容易维护和理解的东西。正则表达式如下所示:

为了提高性能,您可以预编译它,也可以告诉它在第一次调用时编译(后续调用会更快。)

public static string RemoveSpecialCharacters(string str)

    return Regex.Replace(str, "[^a-zA-Z0-9_.]+", "", RegexOptions.Compiled);

【讨论】:

我猜这可能是一个足够复杂的查询,它会比 OP 的方法更快,尤其是在预编译的情况下。然而,我没有证据支持这一点。应该对其进行测试。除非它非常慢,否则无论如何我都会选择这种方法,因为它更容易阅读和维护。 +1 它是一个非常简单的正则表达式(没有回溯或任何复杂的东西),所以它应该非常快。 @rmeador:如果不编译,它的速度大约慢 5 倍,编译速度比他的方法慢 3 倍。虽然仍然简单 10 倍:-D 正则表达式不是神奇的锤子,也永远不会比手工优化的代码快。 对于那些记得 Knuth 关于优化的名言的人,这就是开始的地方。然后,如果您发现需要额外的千分之一毫秒的性能,请使用其他技术之一。【参考方案3】:

我建议创建一个简单的查找表,您可以在静态构造函数中对其进行初始化,以将任意字符组合设置为有效。这让您可以进行快速、单一的检查。

编辑

另外,为了速度,您需要将 StringBuilder 的容量初始化为输入字符串的长度。这将避免重新分配。将这两种方法结合使用将为您提供速度和灵活性。

另一个编辑

我认为编译器可能会对其进行优化,但考虑到样式和效率,我建议使用 foreach 而不是 for。

【讨论】:

对于数组,forforeach 产生类似的代码。我不知道字符串虽然。我怀疑 JIT 是否知道 String 的类数组性质。 我敢打赌,JIT 比您的 [笑话删除] 更了解字符串的类似数组的性质。 Anders etal 做了很多工作来优化 .net 中关于字符串的所有内容 我已经使用 HashSet 完成了这个,它比他的方法慢了大约 2 倍。使用 bool[] 比他在 OP 中的版本快一点(0.0469ms/iter v. 0.0559ms/iter)......但可读性较差。 我看不出使用 bool 数组和 int 数组之间的任何性能差异。我会使用 bool 数组,因为它会将查找表从 256 kb 降低到 64 kb,但对于这样一个微不足道的函数来说,它仍然需要大量数据......而且它只快 30% 左右。 @Guffa 2) 鉴于我们只保留字母数字和一些基本拉丁字符,我们只需要一个用于低字节的表,因此大小并不是真正的问题。如果我们想要通用,那么标准的 Unicode 技术就是双重间接。换句话说,一个包含 256 个表引用的表,其中许多都指向同一个空表。【参考方案4】:
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);

【讨论】:

+1,经过测试,比 StringBuilder 快 40%。 0.0294 毫秒/字符串与 0.0399 毫秒/字符串 只是为了确定,你的意思是有或没有预分配的StringBuilder? 使用预分配,仍然比 char[] 分配和新字符串慢 40%。 我喜欢这个。我调整了这个方法foreach (char c in input.Where(c =&gt; char.IsLetterOrDigit(c) || allowedSpecialCharacters.Any(x =&gt; x == c))) buffer[idx++] = c;【参考方案5】:

正则表达式如下所示:

public string RemoveSpecialChars(string input)

    return Regex.Replace(input, @"[^0-9a-zA-Z\._]", string.Empty);

但如果性能非常重要,我建议您在选择“正则表达式路径”之前做一些基准测试...

【讨论】:

【参考方案6】:

如果您使用的是动态字符列表,LINQ 可能会提供更快、更优雅的解决方案:

public static string RemoveSpecialCharacters(string value, char[] specialCharacters)

    return new String(value.Except(specialCharacters).ToArray());

我将这种方法与之前的两种“快速”方法(发布编译)进行了比较:

LukeH 的字符数组解决方案 - 427 毫秒 StringBuilder 解决方案 - 429 毫秒 LINQ(此答案)- 98 毫秒

请注意,该算法稍作修改 - 字符作为数组而不是硬编码传入,这可能会稍微影响一些事情(即/其他解决方案将有一个内部 foror 循环来检查字符数组)。

如果我使用 LINQ where 子句切换到硬编码解决方案,结果是:

字符数组解决方案 - 7 毫秒 StringBuilder 解决方案 - 22 毫秒 LINQ - 60 毫秒

如果您打算编写更通用的解决方案,而不是对字符列表进行硬编码,那么可能值得研究 LINQ 或修改后的方法。 LINQ 绝对可以为您提供简洁、易读的代码——甚至比正则表达式还要多。

【讨论】:

这种方法看起来不错,但它不起作用 - except() 是一个集合操作,因此您最终只会得到字符串中每个唯一字符的第一次出现。【参考方案7】:

我不相信你的算法是高效的。这是 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;

【讨论】:

我同意你不能在这个上击败 O(n)。但是,每次比较的成本可以降低。表查找具有较低的固定成本,而随着您添加更多异常,一系列比较的成本将会增加。 关于旁注3,你真的认为跳转表会比查表快吗? 我对交换机方案进行了快速性能测试,结果与对比结果相同。 @Steven Sudit - 我敢说他们实际上差不多。想进行测试吗? O(n) 符号有时让我很生气。人们会基于算法已经是 O(n) 的事实做出愚蠢的假设。如果我们更改此例程以将 str[i] 调用替换为一个函数,该函数通过与世界另一端的服务器构建一次性 SSL 连接来检索比较值......你肯定会看到巨大的性能差异,算法仍然是 O(n)。每种算法的 O(1) 成本都很重要,而且不等价!【参考方案8】:

你可以使用正则表达式如下:

return Regex.Replace(strIn, @"[^\w\.@-]", "", RegexOptions.None, TimeSpan.FromSeconds(1.0));

【讨论】:

【参考方案9】:
StringBuilder sb = new StringBuilder();

for (int i = 0; i < fName.Length; i++)

   if (char.IsLetterOrDigit(fName[i]))
    
       sb.Append(fName[i]);
    

【讨论】:

【参考方案10】:

这对我来说似乎很好。我要做的唯一改进是用字符串的长度初始化StringBuilder

StringBuilder sb = new StringBuilder(str.Length);

【讨论】:

【参考方案11】:

我同意此代码示例。唯一不同的是我把它变成了字符串类型的扩展方法。这样您就可以在非常简单的行或代码中使用它:

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();
        

【讨论】:

【参考方案12】:

我会使用字符串替换和正则表达式搜索“特殊字符”,将找到的所有字符替换为空字符串。

【讨论】:

+1 代码肯定更少,并且可以说更易读,忽略一次写入正则表达式。 @kenny - 我同意。最初的问题甚至指出字符串很短 - 10-30 个字符。但显然很多人仍然认为我们正在以秒为单位出售 CPU 时间...... Reguler expressin 太懒惰了,所以不要一直用。【参考方案13】:

我不得不为工作做一些类似的事情,但在我的情况下,我必须过滤所有不是字母、数字或空格的东西(但您可以根据需要轻松修改它)。 过滤是在 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();

【讨论】:

【参考方案14】:

这里有很多建议的解决方案,有些比其他的更有效,但可能不是很可读。这可能不是最有效的,但肯定适用于大多数情况,并且非常简洁易读,利用了 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());

【讨论】:

【参考方案15】:

对于 S&G 的,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());

不过,我认为这不会是最有效的方法。

【讨论】:

不是,因为它是线性搜索。【参考方案16】:
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();

【讨论】:

【参考方案17】:

用途:

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】:

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();

我进行了测试,这并不比接受的答案快。 我会保留它,就好像您需要一组可配置的字符一样,这​​将是一个很好的解决方案。

【讨论】:

为什么你认为比较不是O(1)? @Guffa 我不确定不是,我删除了我的评论。和+1。在发表评论之前我应该​​做更多的测试。【参考方案19】:

我想知道基于正则表达式的替换(可能已编译)是否更快。 必须测试一下有人发现这要慢约 5 倍。

除此之外,您应该使用预期长度初始化 StringBuilder,这样中间字符串在增长时不必被复制。

一个好的数字是原始字符串的长度,或者稍短一些(取决于函数输入的性质)。

最后,您可以使用查找表(范围为 0..127)来确定是否要接受一个字符。

【讨论】:

一个正则表达式已经测试过了,它慢了大约五倍。对于 0..127 范围内的查找表,您仍然需要在使用查找表之前对字符代码进行范围检查,因为字符是 16 位值,而不是 7 位值。 @Guffa Err... 是吗? ;)【参考方案20】:

以下代码有如下输出(结论是我们还可以通过分配更小的数组来节省一些内存资源):

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);

【讨论】:

【参考方案21】:

我不确定这是最有效的方法,但它对我有用

 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

【讨论】:

答案确实有效,但问题是针对C#。(PS:我知道这几乎是五年前的事了,但仍然...... ) 我使用 Telerik VB 到 C# 转换器,(反之亦然)并且代码工作得很好 - 不过不确定其他人。 (另一件事,converter.telerik.com)【参考方案22】:

最短的路只有 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();

【讨论】:

【参考方案23】:

LINQ 的简单方法

string text = "123a22 ";
var newText = String.Join(string.Empty, text.Where(x => x != 'a'));

【讨论】:

【参考方案24】:

另一种尝试通过减少分配来提高性能的方法,尤其是在多次调用此函数时。

它之所以有效,是因为您可以保证结果不会比输入长,因此可以传递输入和输出,而无需在内存中创建额外的副本。由于这个原因,您不能使用 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);

【讨论】:

【参考方案25】:
public static string RemoveAllSpecialCharacters(this string text) 
  if (string.IsNullOrEmpty(text))
    return text;

  string result = Regex.Replace(text, "[:!@#$%^&*()|\":?><\\[\\]\\;'/.,~]", " ");
  return result;

【讨论】:

答案错误。如果你要使用正则表达式,它应该是包容性的,而不是排他性的,因为你现在错过了一些字符。实际上,正则表达式已经有了答案。并且要完整 - 正则表达式比较慢,然后直接比较字符函数。【参考方案26】:

如果您担心速度,请使用指针来编辑现有字符串。您可以固定字符串并获取指向它的指针,然后对每个字符运行 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

【讨论】:

Noooooooooo!在 .NET 中更改字符串是 BAAAAAAAAAAAAD!框架中的一切都依赖于字符串是不可变的规则,如果你打破它,你会得到非常令人惊讶的副作用......【参考方案27】:
public static string RemoveSpecialCharacters(string str)
    return str.replaceAll("[^A-Za-z0-9_\\\\.]", "");

【讨论】:

恐怕replaceAll不是C#字符串函数,而是Java或JavaScript

以上是关于从字符串中删除特殊字符的最有效方法的主要内容,如果未能解决你的问题,请参考以下文章

从Unicode字符串中删除文件名中禁用字符的最有效方法[复制]

删除 PHP 中可以从其他 textEditor 输入的特殊字符

从文件名中删除特殊字符

Javascript - 正则表达式从标题中删除特殊字符

从R中的字符串中删除所有特殊字符?

从JS中的字符串中删除所有特殊字符