【问题标题】:More efficient way to make a string in a string of just words更有效的方法来在一串单词中创建一个字符串
【发布时间】:2014-04-16 16:31:18
【问题描述】:

我正在制作一个应用程序,我将在其中获取推文并将它们存储在数据库中。我将有一列用于完整的推文文本,另一列只保留推文的单词(我需要单词来计算以后最常用的单词)。

我目前的做法是使用 6 个不同的 .replaceAll() 函数,其中一些函数可能会被触发两次。例如,我将有一个 for 循环来使用 replaceAll() 删除每个“主题标签”。

问题是我每隔几分钟就会编辑多达数千条推文,我认为我这样做的方式不会太高效。

我的要求是按这个顺序(下面也用 cmets 写的):

  1. 删除所有提及的用户名
  2. 删除所有 RT(转发标志)
  3. 删除所有提及的主题标签
  4. 用空格替换所有换行符
  5. 用单空格替换所有双空格
  6. 删除除空格以外的所有特殊字符

这是一个简短且可编译的示例:

public class StringTest {

    public static void main(String args[]) {

        String text = "RT @AshStewart09: Vote for Lady Gaga for \"Best Fans\""
                + " at iHeart Awards\n"
                + "\n"
                + "RT!!\n"
                + "\n"
                + "My vote for #FanArmy goes to #LittleMonsters #iHeartAwards"
                + " htt…";

        String[] hashtags = {"#FanArmy", "#LittleMonsters", "#iHeartAwards"};
        System.out.println("Before: " + text + "\n");

        // Delete all usernames mentioned (may run multiple times)
        text = text.replaceAll("@AshStewart09", "");
        System.out.println("First Phase: " + text + "\n");

        // Delete all RT (retweets flags)
        text = text.replaceAll("RT", "");
        System.out.println("Second Phase: " + text + "\n");

        // Delete all hashtags mentioned
        for (String hashtag : hashtags) {
            text = text.replaceAll(hashtag, "");
        }
        System.out.println("Third Phase: " + text + "\n");

        // Replace all break lines with spaces
        text = text.replaceAll("\n", " ");
        System.out.println("Fourth Phase: " + text + "\n");

        // Replace all double spaces with single spaces
        text = text.replaceAll(" +", " ");
        System.out.println("Fifth Phase: " + text + "\n");

        // Delete all special characters except spaces 
        text = text.replaceAll("[^a-zA-Z0-9 ]+", "").trim();
        System.out.println("Finaly: " + text);
    }
}

【问题讨论】:

  • 在执行替换代码之前将字符串按空格拆分成单词会更有效吗?然后分别对每个单词运行匹配过滤器。
  • 您的一些模式可以合并为一个,例如"#FanArmy|#LittleMonsters|#iHeartAwards"
  • @Salauyou 我编辑了我的代码以更好地描述主题标签在我的主应用程序中的处理方式。
  • 我明白了...让我解释一个性能问题。如您所知,在 Java 中,String 对象是不可变的,因此每个与旧字符串至少相差 1 个字符的新字符串都必须是另一个对象——因此会执行分配内存和创建对象的操作。
  • 但是! replaceAll 方法内部不使用字符串,它对 StringBuilder 对象执行操作,该对象是可变的,因此每次更改操作要便宜得多。正如我所提到的,您可以将要替换的子字符串组合成一个正则表达式模式——您可以在 for 循环中执行此操作,并将这种组合模式应用于您的字符串一次。

标签: java regex string performance replace


【解决方案1】:

依赖replaceAll 可能是最大的性能杀手,因为它一次又一次地编译正则表达式。对所有事物都使用正则表达式可能是第二个最重要的问题。

假设所有用户名都以@ 开头,我会替换

// Delete all usernames mentioned (may run multiple times)
text = text.replaceAll("@AshStewart09", "");

通过循环复制所有内容直到找到@,然后检查以下字符是否与列出的任何用户名匹配并可能跳过它们。对于此查找,您可以使用 trie。更简单的方法是为正则表达式 #\w+ 使用类似于 replaceAll 的循环以及 HashMap 查找。

// Delete all RT (retweets flags)
text = text.replaceAll("RT", "");

这里,

private static final Pattern RT_PATTERN = Pattern.compile("RT");

肯定会赢。以下所有部分都可以类似处理。而不是

// Delete all special characters except spaces 
text = text.replaceAll("[^a-zA-Z0-9 ]+", "").trim();

您可以使用 Guava 的 CharMatcherremoveFrom 方法与您所做的完全相同,但 collapseFromtrimAndCollapseFrom 可能会更好。

【讨论】:

    【解决方案2】:

    根据现在关闭的question,这一切都归结为

    tweet = tweet.replaceAll("@\\w+|#\\w+|\\bRT\\b", "")
                    .replaceAll("\n", " ")
                    .replaceAll("[^\\p{L}\\p{N} ]+", " ")
                    .replaceAll(" +", " ")
                    .trim();
    

    第二行似乎是多余的,因为第三行也删除了\n。将第一行的替换更改为 " " 不会更改结果并允许聚合替换。

    tweet = tweet.replaceAll("@\\w*|#\\w*|\\bRT\\b|[^@#\\p{L}\\p{N} ]+", " ")
                    .replaceAll(" +", " ")
                    .trim();
    

    我已将用户名和主题标签部分更改为单独吃 #@,这样特殊字符部分就不需要消耗它了。这是正确处理!@AshStewart09等字符串所必需的。

    为了获得最佳性能,您肯定需要预编译模式。我还建议在第二部分使用 Guava 的 CharMatcher。 Guava 很大(我猜是 2 MB),但你肯定会在那里找到更多有用的东西。所以最后你可以得到

    private static final Pattern PATTERN =
        Pattern.compile("@\\w*|#\\w*|\\bRT\\b|[^@#\\p{L}\\p{N} ]+");
    private static final CharMatcher CHAR_MATCHER = CharMacher.is(" ");
    
    tweet = PATTERN.matcher(tweet).replaceAll(" ");
    tweet = CHAR_MATCHER.trimAndCollapseFrom(tweet, " ");
    

    【讨论】:

    • 我已经做了你在第二个代码 sn-p 中所做的事情,我几乎把所有东西都放在了一个 replaceAll 中,然后删除了所有多余的空格。关于孤独的#@ 让他们被[\\p{L}\\p{N} ]+ 吃掉有什么问题?我的意思是用户肯定可以拥有类似!@AshStewart09 的东西,它不会是用户名,因为我很确定它也不会算作高音扬声器本身的用户名。还有一个简单的问题,static 是否像在 C++ 中一样工作,它只定义一次对象?
    • 我想做你所做的,你先用!@AshStewart09替换!,然后用一个空白。为了保留这一点,我不能让正则表达式的第二部分吃掉@。不知道推特是如何运作的。
    • 是的,Java 中的 static 字段在每个类中仅存在一次,并在类加载时被初始化(即,实际上只有一次,或者如果您不使用该类,则永远不会:D) .
    • @SilliconTouch static 在 Java 中不如在 C++ 中灵活,您不能将其应用于局部变量,但在字段上它们的工作方式与 @maaartinus 提到的相同。 (可能静态嵌套类在 C++ 中不可用,而在 Java 中。)
    【解决方案3】:

    您可以将所有被替换为空的内容内联到一个调用中,以将所有替换为空格的所有内容替换为一个调用,就像这样(也使用正则表达式来查找主题标签和用户名,因为这似乎更容易):

    text = text.replaceAll("@\w+|#\w+|RT", "");
    text = text.replaceAll("\n| +", " ");
    text = text.replaceAll("[^a-zA-Z0-9 ]+", "").trim();
    

    【讨论】:

      猜你喜欢
      • 2021-01-22
      • 1970-01-01
      • 2011-03-22
      • 2016-12-21
      • 1970-01-01
      • 1970-01-01
      • 2017-05-07
      • 2011-03-21
      • 1970-01-01
      相关资源
      最近更新 更多