使用 StringBuilder 替换 String 的所有匹配项?

是我遗漏了什么,还是 StringBuilder 缺少与普通 String 类相同的“用字符串 B 替换字符串 A 的所有匹配项”函数?StringBuilder 替换函数并不完全相同。有没有什么方法可以更有效地避免使用普通的 String 类生成多个 String?

135913 次浏览

Well, you can write a loop:

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

Note that in some cases it may be faster to use lastIndexOf, working from the back. I suspect that's the case if you're replacing a long string with a short one - so when you get to the start, any replacements have less to copy. Anyway, this should give you a starting point.

java.util.regex.Pattern.matcher(CharSequence s) can use a StringBuilder as an argument so you can find and replace each occurence of your pattern using start() and end() without calling builder.toString()

You could use Pattern/Matcher. From the Matcher javadocs:

 Pattern p = Pattern.compile("cat");
Matcher m = p.matcher("one cat two cats in the yard");
StringBuffer sb = new StringBuffer();
while (m.find()) {
m.appendReplacement(sb, "dog");
}
m.appendTail(sb);
System.out.println(sb.toString());

Look at JavaDoc of replaceAll method of String class:

Replaces each substring of this string that matches the given regular expression with the given replacement. An invocation of this method of the form str.replaceAll(regex, repl) yields exactly the same result as the expression

java.util.regex.Pattern.compile(regex).matcher(str).replaceAll(repl)

As you can see you can use Pattern and Matcher to do that.

Use the following:

/**
* Utility method to replace the string from StringBuilder.
* @param sb          the StringBuilder object.
* @param toReplace   the String that should be replaced.
* @param replacement the String that has to be replaced by.
*
*/
public static void replaceString(StringBuilder sb,
String toReplace,
String replacement) {
int index = -1;
while ((index = sb.lastIndexOf(toReplace)) != -1) {
sb.replace(index, index + toReplace.length(), replacement);
}
}

Even simple one is using the String ReplaceAll function itself. You can write it as

StringBuilder sb = new StringBuilder("Hi there, are you there?")
System.out.println(Pattern.compile("there").matcher(sb).replaceAll("niru"));

Here is an in place replaceAll that will modify the passed in StringBuilder. I thought that I would post this as I was looking to do replaceAll with out creating a new String.

public static void replaceAll(StringBuilder sb, Pattern pattern, String replacement) {
Matcher m = pattern.matcher(sb);
while(m.find()) {
sb.replace(m.start(), m.end(), replacement);
}
}

I was shocked how simple the code to do this was (for some reason I thought changing the StringBuilder while using the matcher would throw of the group start/end but it does not).

This is probably faster than the other regex answers because the pattern is already compiled and your not creating a new String but I didn't do any benchmarking.

The class org.apache.commons.lang3.text.StrBuilder in Apache Commons Lang allows replacements:

public StrBuilder replaceAll(String searchStr, String replaceStr)

* This does not receive a regular expression but a simple string.

@Adam: I think in your code snippet you should track the start position for m.find() because string replacement may change the offset after the last character matched.

public static void replaceAll(StringBuilder sb, Pattern pattern, String replacement) {
Matcher m = pattern.matcher(sb);
int start = 0;
while (m.find(start)) {
sb.replace(m.start(), m.end(), replacement);
start = m.start() + replacement.length();
}
}
public static String replaceCharsNew(String replaceStr,Map<String,String> replaceStrMap){
StringBuilder replaceStrBuilder = new StringBuilder(replaceStr);
Set<String> keys=replaceStrMap.keySet();
for(String invalidChar:keys){
int index = -1;
while((index=replaceStrBuilder.indexOf(invalidChar,index)) !=-1){
replaceStrBuilder.replace(index,index+invalidChar.length(),replaceStrMap.get(invalidChar));
}
}
return replaceStrBuilder.toString();
}

How about create a method and let String.replaceAll do it for you:

public static void replaceAll(StringBuilder sb, String regex, String replacement)
{
String aux = sb.toString();
aux = aux.replaceAll(regex, replacement);
sb.setLength(0);
sb.append(aux);
}

I found this method: Matcher.replaceAll(String replacement); In java.util.regex.Matcher.java you can see more:

 /**
* Replaces every subsequence of the input sequence that matches the
* pattern with the given replacement string.
*
* <p> This method first resets this matcher.  It then scans the input
* sequence looking for matches of the pattern.  Characters that are not
* part of any match are appended directly to the result string; each match
* is replaced in the result by the replacement string.  The replacement
* string may contain references to captured subsequences as in the {@link
* #appendReplacement appendReplacement} method.
*
* <p> Note that backslashes (<tt>\</tt>) and dollar signs (<tt>$</tt>) in
* the replacement string may cause the results to be different than if it
* were being treated as a literal replacement string. Dollar signs may be
* treated as references to captured subsequences as described above, and
* backslashes are used to escape literal characters in the replacement
* string.
*
* <p> Given the regular expression <tt>a*b</tt>, the input
* <tt>"aabfooaabfooabfoob"</tt>, and the replacement string
* <tt>"-"</tt>, an invocation of this method on a matcher for that
* expression would yield the string <tt>"-foo-foo-foo-"</tt>.
*
* <p> Invoking this method changes this matcher's state.  If the matcher
* is to be used in further matching operations then it should first be
* reset.  </p>
*
* @param  replacement
*         The replacement string
*
* @return  The string constructed by replacing each matching subsequence
*          by the replacement string, substituting captured subsequences
*          as needed
*/
public String replaceAll(String replacement) {
reset();
StringBuffer buffer = new StringBuffer(input.length());
while (find()) {
appendReplacement(buffer, replacement);
}
return appendTail(buffer).toString();
}

Yes. It is very simple to use String.replaceAll() method:

package com.test;


public class Replace {


public static void main(String[] args) {
String input = "Hello World";
input = input.replaceAll("o", "0");
System.out.println(input);
}
}

Output:

Hell0 W0rld

If you really want to use StringBuilder.replace(int start, int end, String str) instead then here you go:

public static void main(String args[]) {
StringBuilder sb = new StringBuilder("This is a new StringBuilder");


System.out.println("Before: " + sb);


String from = "new";
String to = "replaced";
sb = sb.replace(sb.indexOf(from), sb.indexOf(from) + from.length(), to);


System.out.println("After: " + sb);
}

Output:

Before: This is a new StringBuilder
After: This is a replaced StringBuilder

Kotlin Method

  fun replaceAllStringBuilder(
builder: java.lang.StringBuilder,
from: String,
to: String
) {
if (!builder.contains(from)||from.isNullOrEmpty()) return
        

var index = builder.indexOf(from)
while (index != -1) {
builder.replace(index, index + from.length, to)
index += to.length
index = builder.indexOf(from, index)
}
        



}