2009-07-16 87 views
9

给定一个字符串,像这样:如何替换字符串中的令牌不StringTokenizer的

Hello {FIRST_NAME}, this is a personalized message for you. 

凡FIRST_NAME是任意令牌(在传递到方法的映射中的键),写一个程序,其会变成将该字符串转换为:

Hello Jim, this is a personalized message for you. 

给出了一个包含条目FIRST_NAME - > Jim的地图。

似乎StringTokenizer是最直接的方法,但Javadocs真的说你应该更喜欢使用正则表达式的方法。你如何在基于正则表达式的解决方案中做到这一点?

+0

尝试http://github.com/niesfisch/tokenreplacer/ – Marcel 2011-04-20 20:07:56

回答

4

试试这个:

注:author's final solution建立在这个样本是简洁得多。

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

} 
+0

这将重新编译每行的模式。我更喜欢我的模式尽可能预先编译! :-)另外,你最好检查令牌的存在。 – 2009-07-16 17:08:47

0

该文档意味着您应该更喜欢编写基于正则表达式的标记器IIRC。什么可以更好地为你工作是一个标准的正则表达式搜索替换。

6
String.replaceAll("{FIRST_NAME}", actualName); 

查看它的javadocs here

+0

这将是o(n * k),其中n是输入字符串的大小,k是键的数量。 – 2009-07-16 17:11:51

+0

@Daniel您是否阅读了源代码来得出这个结论? Java用字符串做了一些非常聪明的事情。我希望有一个很好的机会会超越你能想出的其他任何解决方案。 – 2009-07-16 21:32:46

+0

@BillK我想他可能意味着如果你有多个关键字替换字符串,那么你必须重复调用`replaceAll`,因此`* k`。 – Svish 2013-12-10 10:57:35

2

最直接的似乎是沿着此线的东西:

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

遍历所有您的令牌,并替换你所需要的每一个令牌,并使用标准的字符串方法用于替换,从而跳过整个RegEx的挫折。

3

随着进口的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); 
    } 
} 

所以,建议正则表达式,因为它可以很容易地确定需要替换字符串中的位置,以及提取键的名称替代。它比打破整个弦乐更有效率。

您可能想要在内部的Matcher行和Pattern行外面循环,以便可以替换所有行。该模式永远不需要重新编译,避免不必要的更有效。

2

根据你的字符串是多么可笑复杂,你可以尝试使用更严重的字符串模板语言,如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()); 

但是,这可能是矫枉过正,如果你只是想更换一个或两个值。

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

} 
11

谢谢大家的回答!

Gizmo的答案绝对是开箱即用的,也是一个很好的解决方案,但不幸的是不适合,因为格式不能限制在这种情况下Formatter类所做的。

亚当Paynter真的把问题的核心,以正确的模式。

彼得尼克斯和肖恩布莱特有一个很好的解决方法,以避免所有的正则表达式的复杂性,但我需要提出一些错误,如果有不好的令牌,这是不行的。但是就做一个正则表达式和一个合理的替换循环而言,这是我想出的答案(来自谷歌和现有答案的一些帮助,包括肖恩布莱特关于如何使用组(1)的评论)对组()):

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

凡doParameter得到值出来的图,并将其转换为字符串,并抛出一个异常,如果它不存在。

另请注意,我更改了模式以查找空括号(即{}),因为这是显式检查的错误条件。

编辑: 请注意,appendReplacement是不可知的字符串的内容。根据javadocs,它将$和反斜杠识别为特殊字符,所以我添加了一些转义来处理上面的示例。没有以最具表现意识的方式完成,但在我的情况下,尝试对字符串创作进行微观优化是不值得的。

感谢来自Alan M的评论,这可以更简单地避免appendReplacement的特殊字符问题。

0

通常我们会在这种情况下使用MessageFormat,并加载ResourceBundle中的实际消息文本。这给你G10N友好的附加好处。