有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java替代连续字符串。代替

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

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

正如您所看到的,这种方法不是最好的,因为每次我都要搜索要替换的部分等,而字符串是不可变的。。。此外,输入量很大,这意味着需要考虑一些性能问题

有没有更好的方法来降低代码的复杂性


共 (6) 个答案

  1. # 1 楼答案

    <^ >不幸的是^ {< CD1>}不提供^ {< CD2>}方法,所以您可能需要考虑使用^ {CD3>}和^ {CD4>},结合^ {< CD5>}:

    String input = ...;
    StringBuffer sb = new StringBuffer();
    
    Pattern p = Pattern.compile("</?(h1|h2|...)>");
    Matcher m = p.matcher( input );
    while( m.find() )
    {
      String match = m.group();
      String replacement = ...; //get replacement for match, e.g. by lookup in a map
    
      m.appendReplacement( sb, replacement );
    }
    m.appendTail( sb );
    

    你可以用StringBuilder做类似的事情,但在那种情况下,你必须自己实现appendReplacement等等

    至于表达式,您也可以尝试匹配任何html标记(尽管这可能会导致问题,因为正则表达式和任意html不太适合),当查找没有任何结果时,您只需将匹配替换为自身

  2. # 2 楼答案

    我会这样做

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.length(); i++) {
            if (tagEquals(str, i, "h1")) {
                sb.append("<big><big><big><b>");
                i += 2;
            } else (tagEquals(s, i, "/h1")) { 
                ...
            } else {
                sb.append(str.charAt(i));
            }
        }
    

    tagEquals是一个检查标记名的函数

  3. # 3 楼答案

    尽管与^{}相比^{}是一个巨大的改进,但它仍然远远不是最佳的

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

    想象一下:你有一个包含10000个字符的文本。如果要将位于位置1(第二个字符)的"XY"子字符串替换为"ABC",则实现必须重新分配一个至少大于1的char缓冲区,必须将旧内容复制到新数组,它必须将9.997个字符(从3位置开始)向右复制1,以将"ABC"放在"XY"位置,最后将"ABC"的字符复制到起始位置1。每次更换都必须这样做!这太慢了

    更快的解决方案:动态生成输出

    我们可以动态地构建输出:不包含可替换文本的部分可以简单地附加到输出中,如果我们找到一个可替换的片段,我们会附加替换,而不是它。理论上,只需在输入上循环一次即可生成输出。听起来很简单,实现起来也不难

    实施:

    我们将使用预加载了可替换替换字符串映射的Map

    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
    

    这个解决方案比使用正则表达式要快,因为这需要很多开销,比如编译Pattern,创建Matcher等等,而且regexp也更通用。它还会在引擎盖下创建许多临时对象,这些对象在更换后会被扔掉。在这里,我只使用了一个StringBuilder(加上它下面的char数组),代码只在输入String上迭代一次。此外,这个解决方案比使用StringBuilder.replace()要快得多,如本答案顶部所述

    注释和解释

    我在replaceTags()方法中初始化了StringBuilder,如下所示:

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

    所以基本上我创建它的初始容量是原始String长度的150%。这是因为我们替换的文本比替换的文本长,所以如果替换发生,输出显然会比输入长。为StringBuilder提供更大的初始容量将不会导致任何内部char[]重新分配(当然,所需的初始容量取决于可替换替换对及其在输入中的频率/出现率,但这+50%是一个很好的上限估计)

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

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

    它只是一个简单的循环和char内的String比较,而且由于它总是从pos开始搜索(而不是从输入的开始),所以总体而言,代码只在输入String上迭代一次

    最后,为了判断一个可替换的String是否在潜在位置发生,我们使用^{}方法来检查可替换的stings,这也很快,因为它只需比较循环中的char值,并在第一个不匹配的字符处返回

    还有一个加号:

    问题没有提到它,但我们的输入是一个HTML文档。HTML标记不区分大小写,这意味着输入可能包含<H1>,而不是<h1>
    对于这个算法来说,这不是问题。String类中的regionMatches()有一个重载supports case-insensitive comparison

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

    所以如果我们想修改我们的alg算法为了找到和替换相同但使用不同字母大小写的输入标记,我们只需修改这一行:

    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
    
  4. # 4 楼答案

    StringBuilder由字符数组支持。因此,与字符串实例不同,它是可变的。因此,您可以在StringBuilder上调用indexOf()replace()

  5. # 5 楼答案

    您提供的特定示例似乎是HTML或XHTML。尝试使用正则表达式编辑HTML或XML会遇到很多问题。对于您似乎感兴趣的编辑类型,您应该考虑使用XSLT。另一种可能性是使用流式XML解析器SAX,让后端动态编写编辑后的输出。如果文本实际上是HTML,那么最好使用一个宽容的HTML解析器,比如JSoup,来构建文档的解析表示(比如DOM),并在输出之前对其进行处理

  6. # 6 楼答案

    对于性能-使用StringBuilder。 为了方便起见,您可以使用Map来存储值和替换

    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>");
    ...
    StringBuilder builder = new StringBuilder(yourString);
    for (String key : map.keySet()) {
        replaceAll(builder, key, map.get(key));
    }
    

    。。。要替换StringBuilder中的所有事件,可以在此处进行检查: Replace all occurrences of a String using StringBuilder?

    public static void replaceAll(StringBuilder builder, String from, String to)
    {
        int index = builder.indexOf(from);
        while (index != -1)
        {
            builder.replace(index, index + from.length(), to);
            index += to.length(); // Move to the end of the replacement
            index = builder.indexOf(from, index);
        }
    }