I\'ve been trying to devise a method of replacing multiple String#replaceAll calls with a Pattern/Matcher instance in the hopes that it would be faster than my current metho
This is a more dynamic version of previous answer to another similar question.
Here is a helper method for searching for any @keyword@
you want. They don't have to be 3 characters long.
private static String replace(String input, Map<String, String> replacement) {
StringJoiner regex = new StringJoiner("|", "@(", ")@");
for (String keyword : replacement.keySet())
regex.add(Pattern.quote(keyword));
StringBuffer output = new StringBuffer();
Matcher m = Pattern.compile(regex.toString()).matcher(input);
while (m.find())
m.appendReplacement(output, Matcher.quoteReplacement(replacement.get(m.group(1))));
return m.appendTail(output).toString();
}
The above runs on Java 8+. In Java 9+, this can be done with lambda expression. The following also fixes the potential issue of a short keyword being a substring of a longer one, by sorting the keywords descending by length.
private static String replace(String input, Map<String, String> replacement) {
String regex = replacement.keySet().stream()
.sorted(Comparator.comparingInt(String::length).reversed())
.map(Pattern::quote).collect(Collectors.joining("|", "@(", ")@"));
return Pattern.compile(regex).matcher(input)
.replaceAll(m -> Matcher.quoteReplacement(replacement.get(m.group(1))));
}
Test
Map<String,String> replacement = new HashMap<>();
replacement.put("bla", "hello,");
replacement.put("red", "world!");
replacement.put("Hold", "wait");
replacement.put("Better", "more");
replacement.put("a?b*c", "special regex characters");
replacement.put("foo @ bar", "with spaces and the @ boundary character work");
System.out.println(replace("@bla@This is a @red@line @bla@of text", replacement));
System.out.println(replace("But @Hold@, this can do @Better@!", replacement));
System.out.println(replace("It can even handle @a?b*c@ without dying", replacement));
System.out.println(replace("Keyword @foo @ bar@ too", replacement));
Output
hello,This is a world!line hello,of text
But wait, this can do more!
It can even handle special regex characters without dying
Keyword with spaces and the @ boundary character work too
This is a relatively straightforward case for appendReplacement:
// Prepare map of replacements
Map<String,String> replacement = new HashMap<>();
replacement.put("bla", "hello,");
replacement.put("red", "world!");
// Use a pattern that matches three non-@s between two @s
Pattern p = Pattern.compile("@([^@]{3})@");
Matcher m = p.matcher("@bla@This is a @red@line @bla@of text");
StringBuffer sb = new StringBuffer();
while (m.find()) {
// Group 1 captures what's between the @s
String tag = m.group(1);
String repString = replacement.get(tag);
if (repString == null) {
System.err.println("Tag @"+tag+"@ is unexpected.");
continue;
}
// Replacement could have special characters, e.g. '\'
// Matcher.quoteReplacement() will deal with them correctly:
m.appendReplacement(sb, Matcher.quoteReplacement(repString));
}
m.appendTail(sb);
String result = sb.toString();
Demo.