java 如何在没有 StringTokenizer 的情况下替换字符串中的标记
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1138830/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to replace tokens in a string without StringTokenizer
提问by Yishai
Given a string like so:
给定一个像这样的字符串:
Hello {FIRST_NAME}, this is a personalized message for you.
Where FIRST_NAME is an arbitrary token (a key in a map passed to the method), to write a routine which would turn that string into:
其中 FIRST_NAME 是一个任意标记(传递给该方法的映射中的键),用于编写一个将该字符串转换为的例程:
Hello Jim, this is a personalized message for you.
given a map with an entry FIRST_NAME -> Jim.
给定一个带有 FIRST_NAME -> Jim 条目的地图。
It would seem that StringTokenizer is the most straight forward approach, but the Javadocs really say you should prefer to use the regex aproach. How would you do that in a regex based solution?
看起来 StringTokenizer 是最直接的方法,但 Javadocs 确实说您应该更喜欢使用正则表达式方法。您将如何在基于正则表达式的解决方案中做到这一点?
采纳答案by Adam Paynter
Try this:
试试这个:
Note:The author's final solutionbuilds upon this sample and is much more concise.
注:在作者的最终解决方案是建立在这个样品和更加简练。
public class TokenReplacer {
private Pattern tokenPattern;
public TokenReplacer() {
tokenPattern = Pattern.compile("\{([^}]+)\}");
}
public String replaceTokens(String text, Map<String, String> valuesByKey) {
StringBuilder output = new StringBuilder();
Matcher tokenMatcher = tokenPattern.matcher(text);
int cursor = 0;
while (tokenMatcher.find()) {
// A token is defined as a sequence of the format "{...}".
// A key is defined as the content between the brackets.
int tokenStart = tokenMatcher.start();
int tokenEnd = tokenMatcher.end();
int keyStart = tokenMatcher.start(1);
int keyEnd = tokenMatcher.end(1);
output.append(text.substring(cursor, tokenStart));
String token = text.substring(tokenStart, tokenEnd);
String key = text.substring(keyStart, keyEnd);
if (valuesByKey.containsKey(key)) {
String value = valuesByKey.get(key);
output.append(value);
} else {
output.append(token);
}
cursor = tokenEnd;
}
output.append(text.substring(cursor));
return output.toString();
}
}
回答by Yishai
Thanks everyone for the answers!
谢谢大家的回答!
Gizmo's answer was definitely out of the box, and a great solution, but unfortunately not appropriate as the format can't be limited to what the Formatter class does in this case.
Gizmo 的答案绝对是开箱即用的,是一个很好的解决方案,但不幸的是不合适,因为格式不能仅限于 Formatter 类在这种情况下所做的事情。
Adam Paynter really got to the heart of the matter, with the right pattern.
亚当·佩恩特 (Adam Paynter) 以正确的模式真正抓住了问题的核心。
Peter Nix and Sean Bright had a great workaround to avoid all of the complexities of the regex, but I needed to raise some errors if there were bad tokens, which that didn't do.
Peter Nix 和 Sean Bright 有一个很好的解决方法来避免正则表达式的所有复杂性,但是如果有错误的标记,我需要提出一些错误,但没有这样做。
But in terms of both doing a regex and a reasonable replace loop, this is the answer I came up with (with a little help from Google and the existing answer, including Sean Bright's comment about how to use group(1) vs group()):
但是就做正则表达式和合理的替换循环而言,这是我想出的答案(在谷歌的帮助下和现有答案,包括肖恩·布莱特关于如何使用 group(1) vs group() ):
private static Pattern tokenPattern = Pattern.compile("\{([^}]*)\}");
public static String process(String template, Map<String, Object> params) {
StringBuffer sb = new StringBuffer();
Matcher myMatcher = tokenPattern.matcher(template);
while (myMatcher.find()) {
String field = myMatcher.group(1);
myMatcher.appendReplacement(sb, "");
sb.append(doParameter(field, params));
}
myMatcher.appendTail(sb);
return sb.toString();
}
Where doParameter gets the value out of the map and converts it to a string and throws an exception if it isn't there.
doParameter 从映射中获取值并将其转换为字符串,如果不存在则抛出异常。
Note also I changed the pattern to find empty braces (i.e. {}), as that is an error condition explicitly checked for.
另请注意,我更改了模式以查找空大括号(即 {}),因为这是明确检查的错误条件。
EDIT: Note that appendReplacement is not agnostic about the content of the string. Per the javadocs, it recognizes $ and backslash as a special character, so I added some escaping to handle that to the sample above. Not done in the most performance conscious way, but in my case it isn't a big enough deal to be worth attempting to micro-optimize the string creations.
编辑:请注意, appendReplacement 与字符串的内容无关。根据 javadocs,它将 $ 和反斜杠识别为特殊字符,因此我添加了一些转义来处理上面的示例。不是以最注重性能的方式完成的,但在我的情况下,这还不够大,值得尝试对弦乐创作进行微优化。
Thanks to the comment from Alan M, this can be made even simpler to avoid the special character issues of appendReplacement.
感谢 Alan M 的评论,这可以变得更简单,以避免 appendReplacement 的特殊字符问题。
回答by gizmo
Well, I would rather use String.format(), or better MessageFormat.
好吧,我宁愿使用 String.format() 或更好的MessageFormat。
回答by jjnguy
回答by Daniel C. Sobral
With import java.util.regex.*:
使用导入 java.util.regex.*:
Pattern p = Pattern.compile("{([^{}]*)}");
Matcher m = p.matcher(line); // line being "Hello, {FIRST_NAME}..."
while (m.find) {
String key = m.group(1);
if (map.containsKey(key)) {
String value= map.get(key);
m.replaceFirst(value);
}
}
So, the regex is recommended because it can easily identify the places that require substitution in the string, as well as extracting the name of the key for substitution. It's much more efficient than breaking the whole string.
因此,推荐使用正则表达式,因为它可以轻松识别字符串中需要替换的位置,以及提取替换键的名称。它比打破整个字符串更有效。
You'll probably want to loop with the Matcher line inside and the Pattern line outside, so you can replace all lines. The pattern never needs to be recompiled, and it's more efficient to avoid doing so unnecessarily.
您可能希望使用内部的 Matcher 行和外部的 Pattern 行进行循环,这样您就可以替换所有行。模式永远不需要重新编译,避免不必要地重新编译会更有效率。
回答by Brandon Yarbrough
Depending on how ridiculously complex your string is, you could try using a more serious string templating language, like Velocity. In Velocity's case, you'd do something like this:
根据您的字符串的复杂程度,您可以尝试使用更严格的字符串模板语言,例如 Velocity。在 Velocity 的情况下,你会做这样的事情:
Velocity.init();
VelocityContext context = new VelocityContext();
context.put( "name", "Bob" );
StringWriter output = new StringWriter();
Velocity.evaluate( context, output, "",
"Hello, #name, this is a personalized message for you.");
System.out.println(output.toString());
But that is likely overkill if you only want to replace one or two values.
但是,如果您只想替换一两个值,这可能是矫枉过正。
回答by Peter
The most straight forward would seem to be something along the lines of this:
最直接的似乎是这样的:
public static void main(String[] args) {
String tokenString = "Hello {FIRST_NAME}, this is a personalized message for you.";
Map<String, String> tokenMap = new HashMap<String, String>();
tokenMap.put("{FIRST_NAME}", "Jim");
String transformedString = tokenString;
for (String token : tokenMap.keySet()) {
transformedString = transformedString.replace(token, tokenMap.get(token));
}
System.out.println("New String: " + transformedString);
}
It loops through all your tokens and replaces every token with what you need, and uses the standard String method for replacement, thus skipping the whole RegEx frustrations.
它循环遍历您的所有标记并用您需要的内容替换每个标记,并使用标准的 String 方法进行替换,从而跳过整个 RegEx 的麻烦。
回答by Sean Bright
import java.util.HashMap;
public class ReplaceTest {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("FIRST_NAME", "Jim");
map.put("LAST_NAME", "Johnson");
map.put("PHONE", "410-555-1212");
String s = "Hello {FIRST_NAME} {LAST_NAME}, this is a personalized message for you.";
for (String key : map.keySet()) {
s = s.replaceAll("\{" + key + "\}", map.get(key));
}
System.out.println(s);
}
}
回答by Sean Bright
Generally we'd use MessageFormat in a case like this, coupled with loading the actual message text from a ResourceBundle. This gives you the added benefit of being G10N friendly.
通常我们会在这种情况下使用 MessageFormat,同时从 ResourceBundle 加载实际的消息文本。这为您提供了 G10N 友好的额外好处。
回答by Draemon
The docs mean that you should prefer writing a regex-based tokenizer, IIRC. What might work better for you is a standard regex search-replace.
文档意味着您应该更喜欢编写基于正则表达式的标记器 IIRC。可能对您更好的是标准正则表达式搜索替换。

