2009-07-16 23 views
9

Cho một chuỗi như vậy:Làm thế nào để thay thế thẻ trong một chuỗi mà không StringTokenizer

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

đâu FIRST_NAME là một thẻ tùy ý (một chìa khóa trong một bản đồ thông qua với phương pháp này), để viết một thói quen đó sẽ biến chuỗi đó vào:

Hello Jim, this is a personalized message for you. 

cho bản đồ có mục nhập FIRST_NAME -> Jim.

Dường như StringTokenizer là phương pháp tiếp cận thẳng tiến nhất, nhưng Javadocs thực sự nói bạn nên sử dụng regex aproach. Làm thế nào bạn sẽ làm điều đó trong một giải pháp dựa trên regex?

+0

thử http://github.com/niesfisch/tokenreplacer/ – Marcel

Trả lời

4

Hãy thử điều này:

Lưu ý: Các author's final solution xây dựng dựa trên mẫu này và nhiều hơn nữa súc tích.

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

Điều đó sẽ biên dịch lại mẫu cho mỗi dòng. Tôi thích các mẫu của tôi càng được biên dịch trước càng tốt! :-) Ngoài ra, bạn nên kiểm tra sự tồn tại của mã thông báo. –

+0

Ý tôi là, hãy kiểm tra xem các nhà khai báo có trong bản đồ hay không. –

+0

Bạn chỉ có thể làm cho 'tokenPattern' một biến cá thể của bất kỳ lớp nào sẽ chứa phương thức này để tránh biên dịch nó mỗi lần. Mã sẽ tự động điều chỉnh tình huống mà không phát hiện ra mã thông báo nào ('output.append (text.substring (cursor))'). –

0

Tài liệu có nghĩa là bạn nên viết mã thông báo dựa trên regex, IIRC. Điều gì có thể hoạt động tốt hơn cho bạn là tìm kiếm thay thế regex chuẩn.

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

Kiểm tra javadocs cho nó here.

+0

Hiệu suất sẽ là o (n * k), trong đó n là kích thước của chuỗi đầu vào và k số lượng khóa. –

+0

@Daniel Bạn đã đọc mã nguồn để đi đến kết luận đó chưa? Java thực hiện một số điều khá thông minh với các chuỗi. Tôi hy vọng rằng có một cơ hội rất tốt nó sẽ tốt hơn bất kỳ giải pháp khác mà bạn có thể đưa ra. –

+0

@BillK Tôi nghĩ rằng anh ta có thể có nghĩa là bạn phải gọi 'replaceAll' nhiều lần nếu bạn có nhiều hơn một khóa để thay thế trong chuỗi, do đó' * k'. – Svish

2

Tiền đạo thẳng nhất có vẻ như là một cái gì đó dọc theo dòng này:

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

Nó vòng qua tất cả thẻ của bạn và thay thế tất cả các mã thông báo với những gì bạn cần, và sử dụng phương pháp chuỗi tiêu chuẩn để thay thế, do đó bỏ qua toàn bộ sự thất vọng của RegEx.

+2

Điều đó có nghĩa là đọc toàn bộ chuỗi cho mỗi mã thông báo. Nếu bạn có k thẻ và n byte để xử lý, thì thuật toán sẽ có thứ tự o (n * k). Rất kém hiệu quả. –

+1

Về mặt lý thuyết, nó là o (n * k) như đã nêu, nhưng tuyên bố của bạn cảm thấy như một sự tối ưu hóa sớm với tôi. Nếu không biết nhiều hơn về thuật toán này được gọi bao nhiêu lần, có bao nhiêu mã thông báo trong chuỗi, chuỗi dài bao lâu và thời gian tiết kiệm tới hạn như thế nào thì không thể nói tác động lớn đến mức không hiệu quả là bao nhiêu. Nếu điều này chỉ được gọi một lần với tổng thời gian là 10 ms mặc dù nó có thể hiệu quả ở 1 mili giây (ví dụ) chắc chắn là một đơn hàng có cường độ chậm hơn nó có thể. trong kế hoạch lớn của sự vật? – Peter

3

Với java.util.regex nhập khẩu *:.

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

Vì vậy, các regex được khuyến khích vì nó có thể dễ dàng nhận ra những nơi mà yêu cầu thay thế trong chuỗi, cũng như trích xuất tên của phím để thay thế. Nó hiệu quả hơn nhiều so với phá vỡ toàn bộ chuỗi.

Có thể bạn sẽ muốn lặp lại với đường Matcher bên trong và dòng Pattern bên ngoài, vì vậy bạn có thể thay thế tất cả các dòng. Các mô hình không bao giờ cần phải được biên dịch lại, và nó hiệu quả hơn để tránh làm như vậy không cần thiết.

+1

m.group (0) là toàn bộ trận đấu (ví dụ: {FIRST_NAME}). m.group (1) sẽ chỉ là chìa khóa (ví dụ: FIRST_NAME). –

+0

cảm ơn cho việc bắt giữ –

2

Tùy thuộc vào chuỗi phức tạp của bạn phức tạp như thế nào, bạn có thể thử sử dụng ngôn ngữ ghép chuỗi nghiêm trọng hơn, như Vận tốc. Trong trường hợp của Velocity, bạn sẽ làm một cái gì đó như thế này:

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

Nhưng điều đó có thể quá mức nếu bạn chỉ muốn thay thế một hoặc hai giá trị.

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

Cảm ơn mọi người đã trả lời!

Câu trả lời của Gizmo chắc chắn nằm ngoài hộp và giải pháp tuyệt vời, nhưng không may là không phù hợp vì định dạng không thể giới hạn đối với lớp Formatter trong trường hợp này.

Adam Paynter thực sự trở thành trung tâm của vấn đề, với mô hình phù hợp.

Peter Nix và Sean Bright đã có một giải pháp tuyệt vời để tránh tất cả sự phức tạp của regex, nhưng tôi cần phải nêu ra một số lỗi nếu có mã thông báo lỗi không được thực hiện. Tuy nhiên, cả về việc thực hiện regex và vòng lặp thay thế hợp lý, đây là câu trả lời tôi đưa ra (với một chút trợ giúp từ Google và câu trả lời hiện có, bao gồm cả nhận xét của Sean Bright về cách sử dụng nhóm (1) so với nhóm()):

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

Nơi doParameter lấy giá trị ra khỏi bản đồ và chuyển đổi nó thành chuỗi và ném ngoại lệ nếu không có.

Cũng lưu ý rằng tôi đã thay đổi mẫu để tìm dấu ngoặc rỗng (tức là {}), vì đó là điều kiện lỗi được kiểm tra rõ ràng.

EDIT: Lưu ý rằng appendReplacement không phải là bất khả tri về nội dung của chuỗi. Mỗi javadocs, nó nhận ra $ và dấu gạch chéo ngược là một ký tự đặc biệt, vì vậy tôi đã thêm một số thoát để xử lý mẫu đó ở trên. Không được thực hiện theo cách thức hiệu quả nhất, nhưng trong trường hợp của tôi nó không phải là một thỏa thuận đủ lớn để có giá trị cố gắng để vi-tối ưu hóa các sáng tạo chuỗi.

Nhờ nhận xét từ Alan M, điều này có thể được thực hiện đơn giản hơn để tránh các vấn đề ký tự đặc biệt của appendReplacement.

+0

Đó là một câu trả lời rất tốt. Đó là một sự xấu hổ tôi đã không đọc kỹ JavaDocs ... –

+1

Bạn không cần phải thoát khỏi sự thay thế, chỉ cần giữ nó ra khỏi appendReplacement(): 'myMatcher.appendReplacement (sb," "); sb.append (doParameter (trường, thông số)); ' –

+0

Cảm ơn bạn đã bao gồm câu hỏi và câu trả lời rất hữu ích này! –

0

Nói chung, chúng tôi sẽ sử dụng MessageFormat trong trường hợp như thế này, cùng với việc tải văn bản thư thực tế từ ResourceBundle. Điều này mang lại cho bạn lợi ích bổ sung khi được G10N thân thiện.

Các vấn đề liên quan