【问题标题】:How to optimize replacing a large amount of entries in strings?如何优化替换字符串中的大量条目?
【发布时间】:2021-10-19 14:28:11
【问题描述】:

我正在寻找一种用大量条目替换字符串的方法。

条目的大小可以是500~5000,其中大部分有不同的字符长度。条目中没有正则表达式。不会改变。
字符串的长度可以是 10~250。包含 Unicode,每一种都不同。
该方法每秒将被调用大约 100 次。每个都是异步的。
更换必须按顺序进行,并以最后更换为基础。

所以,我尝试过的方法是

public String method(String text) {
    String result = text; // "This is a text. aabbuisaufdsafdsaa."
    HashMap<String, String> map = getMap();
    for (Map.Entry<String, String> entry : map.entrySet())
        result = result.replace(entry.getKey(), entry.getValue());
    return result;
}

非常慢。平均 31000 ns,包含 1000 个大小条目。

以及我目前使用的StringUtils的方法(commons-lang3 version 3.12.0):

private String[] searchList = null;
private String[] replacementList = null;

public String method(String text) {
    String result = text; // "This is a text. aabbuisaufdsafdsaa."
    result = StringUtils.replaceEachRepeatedly(result, getSearchList(), getReplacementList());
    return result;
}

// Same with #getReplacementList.
private String[] getSearchList() {
    // This will be init when this class loaded. I just paste it here.
    if (this.searchList == null) {
        HashMap<String, String> map = getMap();
        this.searchList = new String[map.size()];
        int index = 0;
        for (Map.Entry<String, String> entry : map.entrySet()) {
            this.searchList[index] = entry.getKey();
            index++;
        }
    }
    return this.searchList;
}

比上一个快 50%~70%。平均 11000 ns,包含 1000 个大小条目。

所以我正在寻找一种更好的方法,它可以更复杂。我唯一需要的是更快。
如果有任何想法,我将不胜感激!

【问题讨论】:

  • 由于数据大且速度关键,没有通用的方法来解决它,您必须尽可能利用所有不变量(例如,频率getMap()键/值更改?)考虑到一些不变量的差异可能是一个决定因素。此外,提供客观基准测试的实例。
  • 感谢您的提问。运行过程中键/值不会改变。
  • 你说没有正则表达式,但为什么不呢?它可能会加快速度。
  • 那么 en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithmen.wikipedia.org/wiki/Commentz-Walter_algorithm 算法必须考虑在内,但输入数据的任何不变量也是有用的。
  • 你是如何进行基准测试的? IDE 调试模式,HotSpot JVM 重新启动,还是代码运行多次后可以被 JIT 编译器优化?不妨看看原生模式下的 GraalVM。

标签: java string optimization


【解决方案1】:

假设getMap() 不变,每秒替换 >1300 次的完整实现(在我的机器上使用我的随机实例):

public static void main(String... args) {

    // 10k keys to search in
    final Map<String, String> map = IntStream
            .range(0, 10_000)
            .mapToObj(x -> randomWord())
            .distinct()
            .collect(toMap(x -> x, x -> randomWord()));

    final StringSearcher<String> s = StringSearcher
            .builder()
            .ignoreOverlaps()
            .addSearchStrings(map.keySet())
            .build();

    // bench 1000 times
    final int TIMES = 1000;
    long total_time = 0L;
    for(int j = 0; j < TIMES; j++) {

        // random big string 7k chars
        final String big = IntStream
                .range(0, 1_000)
                .mapToObj(x -> randomWord())
                .collect(joining());

        final long startTime = System.currentTimeMillis();

        // current index on the input string
        int i = 0;

        // output buffer, we will append text chunks
        StringBuilder output = new StringBuilder();

        // for every key in the input string...
        for (Emit e : s.parseText(big)) {
            // if there are text between previous key and current
            if (e.getStart() > i)
                // copy to buffer
                output.append(big.subSequence(i, e.getStart() - 1));
            // instead copy the key we copy the value (replace)
            output.append(map.get(e.getSearchString()));

            // the new pending position is at the end of the key
            i = e.getEnd() + 1;
        }
        // if no more keys found may be text to copy to buffer
        if (i < big.length())
            output.append(big.subSequence(i, big.length() - 1));

        // here output contains the replaced string.

        total_time += System.currentTimeMillis() - startTime;
    }

    System.out.printf("Avg time: %f secs%n", total_time / (1000.0 * TIMES));

}

private static String randomWord() {
    return Integer.toString(ThreadLocalRandom.current().nextInt(1_000_000, 10_000_000));
}

有输出

Avg time: 0,000768 secs

使用的 Aho-Corasick-fast 实现是 neo-search:

<dependency>
    <groupId>org.neosearch.stringsearcher</groupId>
    <artifactId>multiple-string-searcher</artifactId>
    <version>0.1.1</version>
</dependency>

键长度为 6/7 个字符,映射包含 10k 个要搜索的键,用于搜索和替换的文本长度约为 7k。

使用您的参数(地图中的 5k 键和 250 字节长度):

Avg time: 0,000052 secs

也就是说,>每秒 19k 次替换

【讨论】:

  • 非常感谢!我现在正在检查 Aho-Corasick 算法。必须参加计划。
  • (我的实现是直接可用的,只需添加从// current indextotal_time += ...的依赖和使用行)
  • 好的,非常感谢。我只是想了解它是如何工作的。
  • 好的,我已经添加了cmets,希望更清楚。
  • 我真的很感激。它真的快得多。在长弦上非常明显。并感谢 cmets。但这会在最后分割一些字符,不要 subSequence length - 1
猜你喜欢
  • 2017-03-16
  • 1970-01-01
  • 1970-01-01
  • 2013-04-13
  • 2022-12-10
  • 1970-01-01
  • 2010-12-27
  • 1970-01-01
  • 2017-05-17
相关资源
最近更新 更多