【问题标题】:Efficiently removing specific characters (some punctuation) from Strings in Java?有效地从 Java 中的字符串中删除特定字符(一些标点符号)?
【发布时间】:2013-07-08 16:17:19
【问题描述】:

在 Java 中,从字符串中删除给定字符的最有效方法是什么?目前,我有这个代码:

private static String processWord(String x) {
    String tmp;

    tmp = x.toLowerCase();
    tmp = tmp.replace(",", "");
    tmp = tmp.replace(".", "");
    tmp = tmp.replace(";", "");
    tmp = tmp.replace("!", "");
    tmp = tmp.replace("?", "");
    tmp = tmp.replace("(", "");
    tmp = tmp.replace(")", "");
    tmp = tmp.replace("{", "");
    tmp = tmp.replace("}", "");
    tmp = tmp.replace("[", "");
    tmp = tmp.replace("]", "");
    tmp = tmp.replace("<", "");
    tmp = tmp.replace(">", "");
    tmp = tmp.replace("%", "");

    return tmp;
}

如果我使用某种 StringBuilder、正则表达式或其他东西会更快吗?是的,我知道:分析它并查看,但我希望有人可以提供他们头顶的答案,因为这是一项常见任务。

【问题讨论】:

  • 一般来说,Java Regex 的表现相当不错,除非我在做一些对性能非常敏感的事情,否则我通常只使用'm 并继续前进。 tmp.replaceAll("\\W", "") 也会去掉字符串中的标点符号和空格。
  • 澄清请求:请定义标点符号是什么意思。 只是您在上面列出的那些字符吗?破折号呢?弯引号呢?非英语语言的引号符号呢?
  • 关注this,下次您可能会自己找到有关正则表达式的解决方案。
  • @Pshemo 那里没有直接答案。但是除了复制之外,这将有助于理解..下次他可能会找到解决这种事情的方法。我从不说那将是答案。我说这可能对你有帮助

标签: java regex string


【解决方案1】:

虽然\\p{Punct} 将指定比问题中更广泛的字符范围,但它确实允许更短的替换表达式:

tmp = tmp.replaceAll("\\p{Punct}+", "");

【讨论】:

  • 您是否尝试过\p{P} 来捕获非ASCII 标点符号? P 是标点符号的 Unicode 类别。
  • 嗯,试过了,但它留下了一些原始字符(例如&gt;)所以会坚持这个:)
  • 你认为'\\p{Punct}' 会比仅仅指定 [.,!{}](和其他)作为字符类更有效吗?
  • @RayToal 实际上,我是 OP。我澄清了这个问题,我希望你现在发现它更清楚了。
  • @VPeric 我明白了,谢谢! 最清晰 的答案是x.replaceAll("[][(){},.;!?&lt;&gt;%]", ""),但如果多次这样做会很慢。通过使用模式[][(){},.;!?&lt;&gt;%] 编译正则表达式来加速它,然后执行p.matcher(x).replaceAll("")。如果您试图从代码中挤出每个最后一个机器周期,那么您可以创建一个 65536 元素布尔数组,其中包含要保留的每个代码点 true 和要丢弃的代码点 false,然后迭代您的字符串,制作一个新的一个。在这里学究气,这可能会在代理和 Unicode 组合字符上失败,但那又怎样。 :)
【解决方案2】:

这是一个迟到的答案,只是为了好玩。

在这种情况下,我建议以可读性而非速度为目标。当然你可以超级可读但太慢,就像这个超级简洁的版本一样:

private static String processWord(String x) {
    return x.replaceAll("[][(){},.;!?<>%]", "");
}

这很慢,因为每次调用此方法时,都会编译正则表达式。所以你可以预编译正则表达式。

private static final Pattern UNDESIRABLES = Pattern.compile("[][(){},.;!?<>%]");

private static String processWord(String x) {
    return UNDESIRABLES.matcher(x).replaceAll("");
}

假设 JVM 的正则表达式引擎优化了字符类查找,这对于大多数用途来说应该足够快。这是我个人会使用的解决方案。

现在如果没有分析,我不知道您是否可以通过制作自己的角色(实际上是代码点)查找表来做得更好:

private static final boolean[] CHARS_TO_KEEP = new boolean[];

填写一次,然后迭代,生成结果字符串。我会把代码留给你。 :)

同样,我不会深入研究这种优化。代码变得太难阅读了。性能有那么重要吗?还要记住,现代语言是 JITted 的,在预热之后它们会表现得更好,所以使用一个好的分析器。

应该提到的一件事是原始问题中的示例性能非常差,因为您正在创建一大堆临时字符串!除非编译器优化所有这些,否则该特定解决方案的性能将最差。

【讨论】:

  • 这不是相当正确的:给出的正则表达式“[][(){},.;!?%]”是无效的,因为它包含特殊字符。但是,使用 \Q 和 \E 引用效果很好: ""[\\Q][(){},.;!?%\\E]" (反斜杠加倍,因为 Java)。我会接受这个答案,因为它是我最终使用的答案,并且所有替代方案都得到了清楚的解释;更不用说只有提到的确切字符被删除了。
  • Negative lookaheads 是我在这个答案中发现的一个很好的补充:stackoverflow.com/questions/9880941/…。这种模式: "(?![._])\\p{Punct}" 删除 Punct 类中的所有标点符号,除了 ._ 如果您想删除所有标点符号但保留一些字符,这很好。
【解决方案3】:

你可以这样做:

static String RemovePunct(String input) 
{
    char[] output = new char[input.length()];
    int i = 0;

    for (char ch : input.toCharArray())
    {
        if (Character.isLetterOrDigit(ch) || Character.isWhitespace(ch)) 
        {
            output[i++] = ch;
        }        
    }

    return new String(output, 0, i);
}

// ...

String s = RemovePunct("This is (a) test string.");

如果您发现它们会因您的需要而变慢,那么这可能会比使用正则表达式执行得更好。

但是,如果您想要删除一长串不同的特殊字符,它可能会很快变得一团糟。在这种情况下,正则表达式更容易处理。

http://ideone.com/mS8Irl

【讨论】:

  • +1 表示追求“最有效”,并对非常模糊的术语“标点符号”可能的含义给出最佳解释。做得很好。
  • 我认为返回行应该是:return new String(output, 0, i);
  • @Thomas 不是绝对必要的。 char 数组将使用 \0 初始化,new String () 将在第一个 \0 处被切断。
  • @Tomalak 是的,尽管 NUL 字符仍然存在并构成生成的 String 对象的一部分。
【解决方案4】:

字符串是不可变的,因此尝试非常动态地使用它们并不好尝试使用 StringBuilder 而不是 String 并使用其所有出色的方法!它会让你做任何你想做的事。另外,是的,如果您有想做的事情,请找出它的正则表达式,它会为您工作得更好。

【讨论】:

  • 我看到有人对你投了反对票。这可能是因为这作为评论而不是作为答案会更好
  • @sunrize920 投票数为 +0/-0。没有投反对票。
  • @hexafraction 它被删除了
  • 我什至没有想到您没有足够的代表发表评论这一事实。我只是讨厌人们投反对票但不评论为什么。
  • StringBuilder 非常适合从片段构建字符串。通过正则表达式将字符串拆分并重新组装可能是一个不错的策略。我相信String.replaceAll 在内部做了类似的事情。
【解决方案5】:

String#replaceAll(String regex, String replacement) 用作

tmp = tmp.replaceAll("[,.;!?(){}\\[\\]<>%]", "");

System.out.println(
   "f,i.l;t!e?r(e)d {s}t[r]i<n>g%".replaceAll(
                   "[,.;!?(){}\\[\\]<>%]", "")); // prints "filtered string"

【讨论】:

  • @Pshemo, Jerry: 哦,我很尴尬。不过感谢您指出:)
  • @Pshemo 的字符类变体实际上应该比列出所有带有替代项的内容要快得多。此外,它更易于阅读(由于转义次数较少)。
【解决方案6】:

现在您的代码将遍历 tmp 的所有字符,并将它们与您要删除的所有可能的字符进行比较,因此它将使用
number of tmp characters x number or characters you want to remove比较。

要优化您的代码,您可以使用短路或|| 并执行类似

StringBuilder sb = new StringBuilder();
for (char c : tmp.toCharArray()) {
    if (!(c == ',' || c == '.' || c == ';' || c == '!' || c == '?'
            || c == '(' || c == ')' || c == '{' || c == '}' || c == '['
            || c == ']' || c == '<' || c == '>' || c == '%'))
        sb.append(c);
}
tmp = sb.toString();

或者像这样

StringBuilder sb = new StringBuilder();
char[] badChars = ",.;!?(){}[]<>%".toCharArray();

outer: 
for (char strChar : tmp.toCharArray()) {
    for (char badChar : badChars) {
        if (badChar == strChar)
            continue outer;// we skip `strChar` since it is bad character
    }
    sb.append(strChar);
}
tmp = sb.toString();

这样您将遍历每个tmp 字符,但如果不是%,则该字符的比较次数可能会减少(因为它将是最后一次比较,如果字符为.,程序将得到他的结果在一个比较中)。


如果我没记错的话,这种方法与character class ([...]) 一起使用,所以不妨试试这种方式

Pattern p = Pattern.compile("[,.;!?(){}\\[\\]<>%]"); //store it somewhere so 
                                         //you wont need to compile it again
tmp = p.matcher(tmp).replaceAll("");

【讨论】:

    【解决方案7】:

    你可以这样做:

    tmp.replaceAll("\\W", "");
    

    删除标点符号

    【讨论】:

    • 这也将删除空格...可能还有许多其他 OP 不想删除的字符,例如 "
    • \W 代表任何不是字母、数字或下划线的东西!!!如果您可以使正则表达式更具体会更好。
    • 不,这不好。 [\W] 表示[^\A-Za-z_0-9],这意味着此解决方案还将删除é 之类的字母。至少使用尊重 Unicode 而不是以 ASCII 为中心的 something
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-04-01
    • 1970-01-01
    • 2011-08-16
    • 2013-10-08
    • 2014-08-29
    • 1970-01-01
    相关资源
    最近更新 更多