当前位置: 首页 > 面试题库 >

替代连续String.replace

杨轶
2023-03-14
问题内容

我想替换String输入中的一些字符串:

string=string.replace("<h1>","<big><big><big><b>");
string=string.replace("</h1>","</b></big></big></big>");
string=string.replace("<h2>","<big><big>");
string=string.replace("</h2>","</big></big>");
string=string.replace("<h3>","<big>");
string=string.replace("</h3>","</big>");
string=string.replace("<h4>","<b>");
string=string.replace("</h4>","</b>");
string=string.replace("<h5>","<small><b>");
string=string.replace("</h5>","</b><small>");
string=string.replace("<h6>","<small>");
string=string.replace("</h6>","</small>");

如您所见,这种方法不是最佳方法,因为每次我必须搜索要替换的部分时,等等,并且字符串是不可变的…而且输入很大,这意味着要考虑一些性能问题。

有没有更好的方法来减少此代码的复杂性?


问题答案:

尽管StringBuilder.replace()与相比是一个巨大的改进String.replace(),但它 优化还差 很远

问题StringBuilder.replace()在于,如果替换的长度与可替换部分的长度不同(适用于我们的情况),则char可能必须分配更大的内部数组,并且必须复制内容,然后替换才会发生(这也涉及复制)。

想象一下:您有一个包含10.000个字符的文本。如果要将"XY"位置1(第2个字符)处的子字符串替换为"ABC",则实现必须重新分配char至少大于1
的缓冲区,必须将旧内容复制到新数组,并且必须复制9.997个字符(从位置3)右移1以适应"ABC"位置"XY",最后将的字符"ABC"复制到启动位置1。每次更换都必须这样做!太慢了

更快的解决方案:即时构建输出

我们可以 即时 构建输出:不包含可替换文本的部分可以简单地附加到输出中,并且如果我们找到可替换的片段,则可以替换而不是替换。从理论上讲,仅循环输入
一次 即可生成输出。听起来很简单,实现起来并不难。

实现方式:

我们将使用Map预加载的replaceable-replacement字符串的映射:

Map<String, String> map = new HashMap<>();
map.put("<h1>", "<big><big><big><b>");
map.put("</h1>", "</b></big></big></big>");
map.put("<h2>", "<big><big>");
map.put("</h2>", "</big></big>");
map.put("<h3>", "<big>");
map.put("</h3>", "</big>");
map.put("<h4>", "<b>");
map.put("</h4>", "</b>");
map.put("<h5>", "<small><b>");
map.put("</h5>", "</b></small>");
map.put("<h6>", "<small>");
map.put("</h6>", "</small>");

并使用它,这里是替换代码:( 代码后有更多解释)

public static String replaceTags(String src, Map<String, String> map) {
    StringBuilder sb = new StringBuilder(src.length() + src.length() / 2);

    for (int pos = 0;;) {
        int ltIdx = src.indexOf('<', pos);
        if (ltIdx < 0) {
            // No more '<', we're done:
            sb.append(src, pos, src.length());
            return sb.toString();
        }

        sb.append(src, pos, ltIdx); // Copy chars before '<'
        // Check if our hit is replaceable:
        boolean mismatch = true;
        for (Entry<String, String> e : map.entrySet()) {
            String key = e.getKey();
            if (src.regionMatches(ltIdx, key, 0, key.length())) {
                // Match, append the replacement:
                sb.append(e.getValue());
                pos = ltIdx + key.length();
                mismatch = false;
                break;
            }
        }
        if (mismatch) {
            sb.append('<');
            pos = ltIdx + 1;
        }
    }
}

测试它:

String in = "Yo<h1>TITLE</h1><h3>Hi!</h3>Nice day.<h6>Hi back!</h6>End";
System.out.println(in);
System.out.println(replaceTags(in, map));

输出:( 包装以避免滚动条)

Yo<h1>TITLE</h1><h3>Hi!</h3>Nice day.<h6>Hi back!</h6>End

Yo<big><big><big><b>TITLE</b></big></big></big><big>Hi!</big>Nice day.
<small>Hi back!</small>End

该解决方案比使用正则表达式更快,因为它涉及很多开销,例如编译a
Pattern,创建Matcheretc等,并且regexp也更加通用。它还会在引擎盖下创建许多临时对象,这些对象在替换后会被丢弃。在这里,我只使用了一个StringBuilder(加号的char数组),并且代码String仅对输入进行一次迭代。同样,该解决方案比使用StringBuilder.replace()该答案顶部详细介绍的解决方案要快得多。

注释和解释

StringBuilder在这样的replaceTags()方法中初始化:

StringBuilder sb = new StringBuilder(src.length() + src.length() / 2);

因此,基本上,我以初始文件长度的150%的初始容量创建了它String。这是因为我们的替换项比可替换的文本长,因此,如果发生替换,则输出将明显长于输入项。赋予较大的初始容量StringBuilder将根本不会进行内部char[]重新分配(当然,所需的初始容量取决于可更换的替换对及其在输入中的出现频率/发生率,但这+
50%是一个不错的上限估计值)。

我还利用了所有可替换字符串都以'<'字符开头的事实,因此找到下一个潜在的可替换位置变得非常快:

int ltIdx = src.indexOf('<', pos);

这只是一个简单的循环和char内部的比较String,并且由于它总是从pos(而不是从输入的开头)开始搜索,因此,整个代码String仅对输入进行一次迭代。

最后要判断可替换String位置是否确实出现在可替换位置,我们使用该String.regionMatches()方法来检查可替换字符串,这也非常快,因为它所做的只是char在循环中比较值并在第一个不匹配字符处返回。

还有一个加号:

这个问题没有提及,但是我们的输入是一个HTML文档。HTML标记不区分大小写,这意味着输入内容可能包含<H1>而不是<h1>
对于此算法,这不是问题。类中的regionMatches()in
String有一个重载,它支持不区分大小写的比较:

boolean regionMatches(boolean ignoreCase, int toffset, String other,
                          int ooffset, int len);

因此,如果我们想修改算法以查找和替换相同但使用不同字母大小写的输入标签,则只需修改以下一行:

if (src.regionMatches(true, ltIdx, key, 0, key.length())) {

使用此修改后的代码,可替换标签变得不区分大小写:

Yo<H1>TITLE</H1><h3>Hi!</h3>Nice day.<H6>Hi back!</H6>End
Yo<big><big><big><b>TITLE</b></big></big></big><big>Hi!</big>Nice day.
<small>Hi back!</small>End


 类似资料:
  • 问题内容: 在我们公司,我们正在认真研究Java开发/质量保证部门中的产品。我们正在寻找的产品之一是Apache Continuum。 有Continuum经验(最好还有Jenkins经验)的人可以解释一下Apache Continuum的优点和缺点(尤其是关于Jenkins的优点)吗? 我的第一印象是: 优点: 更适合对构建和项目进行分组(从UI角度来看)。 缺点: 没有清晰的仪表板。 一个相对

  • 问题内容: 我正在致力于Twitter数据标准化。Twitter用户经常使用诸如此类的术语来强调“爱”一词。我希望通过替换重复的字符,直到获得合适的有意义的词,将这样的重复字符变成一个适当的英语单词(我知道通过这种机制我无法区分善与恶)。 我的策略是 识别此类重复字符串的存在。我会寻找两个以上相同的字符,因为可能没有一个英文单词包含两个以上的重复字符。 在诸如Wordnet之类的词典中搜索此类单词

  • 我正在建立一个CD管道。我正在计划它的自动化测试部分。我计划做UI、Web服务、安全性和性能测试。我有一个关于代码结构的问题。因此,我计划将测试与代码放在同一个repo中,然后对核心测试框架进行单独的repo。 回购产品 产品代码(项目) 集成测试(项目) 功能/e2e测试(项目) 用户界面测试(软件包) WebVC测试(软件包) 性能测试(套装) 秒。测试(包) 回购测试核心 UI测试框架代码(

  • 本文向大家介绍SAP .NET连接器的替代,包括了SAP .NET连接器的替代的使用技巧和注意事项,需要的朋友参考一下 有多种方法(对于JAVA和其他方法并不多)可以完成任务。一种方法是使用现有的远程函数调用库来建立连接。可以免费使用围绕RFC编写的包装器来完成任务。 另一个可用且最常用的库是ERPConnect。它易于使用,并提供了一种简单的机制来调用BAPI,函数和表调用。 但是最好的使用方式

  • 我有两个双数组: 我的循环结构如下所示: 我要做的是连续获取项目LatArr[0]和LonArr[0]<这样我就可以在lat1中使用LatArr[0],在lon1中使用LonArr[0]。此外,LatArr[1]应位于lat2,LonArr[1]应位于lon2 但此代码获取LatArr[0]。。[1] 。。和LonArr[0]。。[1] 。。两次每个元素打印两次 任何人都请纠正我的错误,我完全不希

  • 问题内容: 这是一个从来没有一个正确答案的问题,我已经在网上搜索了很多次,却找不到解决方案。 它适用于Firefox,Chrome。我的responseText将像Réunion这样返回char,它将显示为奇怪的符号。 我尝试了许多方法,例如编码和解码,在响应文件中设置标头都无效。我没主意了。请帮助某人。 在主文件中,确保设置了内容类型和字符集。 在您的AJAX加载页面中,确保您位于顶部。 问题解