Câu trả lời:
Nếu chuỗi bạn đang thao tác rất dài hoặc bạn đang thao tác trên nhiều chuỗi, thì việc sử dụng java.util.regex.Matcher có thể đáng giá (điều này đòi hỏi thời gian báo trước để biên dịch, vì vậy nó sẽ không hiệu quả nếu đầu vào của bạn rất nhỏ hoặc mẫu tìm kiếm của bạn thay đổi thường xuyên).
Dưới đây là một ví dụ đầy đủ, dựa trên danh sách các mã thông báo được lấy từ bản đồ. (Sử dụng StringUtils từ Apache Commons Lang).
Map<String,String> tokens = new HashMap<String,String>();
tokens.put("cat", "Garfield");
tokens.put("beverage", "coffee");
String template = "%cat% really needs some %beverage%.";
// Create pattern of the format "%(cat|beverage)%"
String patternString = "%(" + StringUtils.join(tokens.keySet(), "|") + ")%";
Pattern pattern = Pattern.compile(patternString);
Matcher matcher = pattern.matcher(template);
StringBuffer sb = new StringBuffer();
while(matcher.find()) {
matcher.appendReplacement(sb, tokens.get(matcher.group(1)));
}
matcher.appendTail(sb);
System.out.println(sb.toString());
Khi biểu thức chính quy được biên dịch, việc quét chuỗi đầu vào thường rất nhanh chóng (mặc dù nếu biểu thức chính quy của bạn phức tạp hoặc liên quan đến việc bẻ khóa ngược thì bạn vẫn cần phải chuẩn để xác nhận điều này!)
"%(" + StringUtils.join(tokens.keySet(), "|") + ")%";
Một trong những cách hiệu quả nhất để thay thế các chuỗi phù hợp (không có biểu thức chính quy) là sử dụng thuật toán Aho-Corasick với một Trie hiệu quả (phát âm là "try"), thuật toán băm nhanh và triển khai tập hợp hiệu quả .
Một giải pháp đơn giản thúc đẩy Apache StringUtils.replaceEach
như sau:
private String testStringUtils(
final String text, final Map<String, String> definitions ) {
final String[] keys = keys( definitions );
final String[] values = values( definitions );
return StringUtils.replaceEach( text, keys, values );
}
Điều này làm chậm các văn bản lớn.
Việc thực hiện thuật toán Aho-Corasick của Bor giới thiệu độ phức tạp hơn một chút, trở thành chi tiết triển khai bằng cách sử dụng mặt tiền có cùng chữ ký phương thức:
private String testBorAhoCorasick(
final String text, final Map<String, String> definitions ) {
// Create a buffer sufficiently large that re-allocations are minimized.
final StringBuilder sb = new StringBuilder( text.length() << 1 );
final TrieBuilder builder = Trie.builder();
builder.onlyWholeWords();
builder.removeOverlaps();
final String[] keys = keys( definitions );
for( final String key : keys ) {
builder.addKeyword( key );
}
final Trie trie = builder.build();
final Collection<Emit> emits = trie.parseText( text );
int prevIndex = 0;
for( final Emit emit : emits ) {
final int matchIndex = emit.getStart();
sb.append( text.substring( prevIndex, matchIndex ) );
sb.append( definitions.get( emit.getKeyword() ) );
prevIndex = emit.getEnd() + 1;
}
// Add the remainder of the string (contains no more matches).
sb.append( text.substring( prevIndex ) );
return sb.toString();
}
Đối với các điểm chuẩn, bộ đệm được tạo bằng randomNumeric như sau:
private final static int TEXT_SIZE = 1000;
private final static int MATCHES_DIVISOR = 10;
private final static StringBuilder SOURCE
= new StringBuilder( randomNumeric( TEXT_SIZE ) );
Nơi MATCHES_DIVISOR
quy định số lượng biến để đưa vào:
private void injectVariables( final Map<String, String> definitions ) {
for( int i = (SOURCE.length() / MATCHES_DIVISOR) + 1; i > 0; i-- ) {
final int r = current().nextInt( 1, SOURCE.length() );
SOURCE.insert( r, randomKey( definitions ) );
}
}
Bản thân mã điểm chuẩn ( JMH dường như quá mức cần thiết):
long duration = System.nanoTime();
final String result = testBorAhoCorasick( text, definitions );
duration = System.nanoTime() - duration;
System.out.println( elapsed( duration ) );
Một điểm chuẩn vi mô đơn giản với 1.000.000 ký tự và 1.000 chuỗi được đặt ngẫu nhiên để thay thế.
Không có cuộc thi.
Sử dụng 10.000 ký tự và 1.000 chuỗi phù hợp để thay thế:
Sự phân chia kết thúc.
Sử dụng 1.000 ký tự và 10 chuỗi phù hợp để thay thế:
Đối với các dây ngắn, chi phí thiết lập Aho-Corasick làm lu mờ cách tiếp cận brute-force StringUtils.replaceEach
.
Có thể sử dụng phương pháp kết hợp dựa trên độ dài văn bản để đạt được hiệu quả tốt nhất của cả hai cách triển khai.
Cân nhắc so sánh các cách triển khai khác cho văn bản dài hơn 1 MB, bao gồm:
Giấy tờ và thông tin liên quan đến thuật toán:
Điều này đã làm việc cho tôi:
String result = input.replaceAll("string1|string2|string3","replacementString");
Thí dụ:
String input = "applemangobananaarefruits";
String result = input.replaceAll("mango|are|ts","-");
System.out.println(result);
Đầu ra: táo-chuối-trái cây-
Nếu bạn định thay đổi một Chuỗi nhiều lần, thì việc sử dụng StringBuilder thường hiệu quả hơn (nhưng hãy đo lường hiệu suất của bạn để tìm hiểu) :
String str = "The rain in Spain falls mainly on the plain";
StringBuilder sb = new StringBuilder(str);
// do your replacing in sb - although you'll find this trickier than simply using String
String newStr = sb.toString();
Mỗi khi bạn thực hiện thay thế trên một Chuỗi, một đối tượng Chuỗi mới sẽ được tạo ra, vì Chuỗi là bất biến. StringBuilder có thể thay đổi, có nghĩa là, nó có thể được thay đổi nhiều như bạn muốn.
StringBuilder
sẽ thực hiện thay thế hiệu quả hơn, vì bộ đệm mảng ký tự của nó có thể được chỉ định độ dài cần thiết. StringBuilder
được thiết kế cho nhiều hơn là thêm vào!
Tất nhiên câu hỏi thực sự là liệu đây có phải là một sự tối ưu hóa quá xa? JVM rất tốt trong việc xử lý việc tạo nhiều đối tượng và thu thập rác sau đó, và giống như tất cả các câu hỏi tối ưu hóa, câu hỏi đầu tiên của tôi là liệu bạn đã đo lường điều này và xác định rằng đó là một vấn đề.
Làm thế nào về việc sử dụng phương thức ReplaceAll () ?
str.replaceAll(search1, replace1).replaceAll(search2, replace2).replaceAll(search3, replace3).replaceAll(search4, replace4)
Rythm một công cụ mẫu java hiện được phát hành với một tính năng mới được gọi là chế độ nội suy chuỗi cho phép bạn thực hiện một số việc như:
String result = Rythm.render("@name is inviting you", "Diana");
Trường hợp trên cho thấy bạn có thể chuyển đối số vào mẫu theo vị trí. Rythm cũng cho phép bạn chuyển các đối số theo tên:
Map<String, Object> args = new HashMap<String, Object>();
args.put("title", "Mr.");
args.put("name", "John");
String result = Rythm.render("Hello @title @name", args);
Lưu ý Rythm RẤT NHANH, nhanh hơn khoảng 2 đến 3 lần so với String.format và tốc độ, bởi vì nó biên dịch mẫu thành mã byte java, hiệu suất thời gian chạy rất gần với sự đồng nhất với StringBuilder.
Liên kết:
"%cat% really needs some %beverage%.";
không phải %
mã thông báo được phân tách đó là một định dạng được xác định trước? Điểm đầu tiên của bạn thậm chí còn buồn cười hơn, JDK cung cấp rất nhiều "khả năng cũ", một số trong số chúng bắt đầu từ những năm 90, tại sao mọi người lại bận tâm sử dụng chúng? Ý kiến và downvoting của bạn không thực hiện bất kỳ ý nghĩa thực tế
Dưới đây là câu trả lời của Todd Owen . Giải pháp đó có vấn đề là nếu các phần thay thế chứa các ký tự có ý nghĩa đặc biệt trong biểu thức chính quy, bạn có thể nhận được kết quả không mong muốn. Tôi cũng muốn có thể tùy ý thực hiện tìm kiếm không phân biệt chữ hoa chữ thường. Đây là những gì tôi nghĩ ra:
/**
* Performs simultaneous search/replace of multiple strings. Case Sensitive!
*/
public String replaceMultiple(String target, Map<String, String> replacements) {
return replaceMultiple(target, replacements, true);
}
/**
* Performs simultaneous search/replace of multiple strings.
*
* @param target string to perform replacements on.
* @param replacements map where key represents value to search for, and value represents replacem
* @param caseSensitive whether or not the search is case-sensitive.
* @return replaced string
*/
public String replaceMultiple(String target, Map<String, String> replacements, boolean caseSensitive) {
if(target == null || "".equals(target) || replacements == null || replacements.size() == 0)
return target;
//if we are doing case-insensitive replacements, we need to make the map case-insensitive--make a new map with all-lower-case keys
if(!caseSensitive) {
Map<String, String> altReplacements = new HashMap<String, String>(replacements.size());
for(String key : replacements.keySet())
altReplacements.put(key.toLowerCase(), replacements.get(key));
replacements = altReplacements;
}
StringBuilder patternString = new StringBuilder();
if(!caseSensitive)
patternString.append("(?i)");
patternString.append('(');
boolean first = true;
for(String key : replacements.keySet()) {
if(first)
first = false;
else
patternString.append('|');
patternString.append(Pattern.quote(key));
}
patternString.append(')');
Pattern pattern = Pattern.compile(patternString.toString());
Matcher matcher = pattern.matcher(target);
StringBuffer res = new StringBuffer();
while(matcher.find()) {
String match = matcher.group(1);
if(!caseSensitive)
match = match.toLowerCase();
matcher.appendReplacement(res, replacements.get(match));
}
matcher.appendTail(res);
return res.toString();
}
Đây là các trường hợp thử nghiệm đơn vị của tôi:
@Test
public void replaceMultipleTest() {
assertNull(ExtStringUtils.replaceMultiple(null, null));
assertNull(ExtStringUtils.replaceMultiple(null, Collections.<String, String>emptyMap()));
assertEquals("", ExtStringUtils.replaceMultiple("", null));
assertEquals("", ExtStringUtils.replaceMultiple("", Collections.<String, String>emptyMap()));
assertEquals("folks, we are not sane anymore. with me, i promise you, we will burn in flames", ExtStringUtils.replaceMultiple("folks, we are not winning anymore. with me, i promise you, we will win big league", makeMap("win big league", "burn in flames", "winning", "sane")));
assertEquals("bcaacbbcaacb", ExtStringUtils.replaceMultiple("abccbaabccba", makeMap("a", "b", "b", "c", "c", "a")));
assertEquals("bcaCBAbcCCBb", ExtStringUtils.replaceMultiple("abcCBAabCCBa", makeMap("a", "b", "b", "c", "c", "a")));
assertEquals("bcaacbbcaacb", ExtStringUtils.replaceMultiple("abcCBAabCCBa", makeMap("a", "b", "b", "c", "c", "a"), false));
assertEquals("c colon backslash temp backslash star dot star ", ExtStringUtils.replaceMultiple("c:\\temp\\*.*", makeMap(".", " dot ", ":", " colon ", "\\", " backslash ", "*", " star "), false));
}
private Map<String, String> makeMap(String ... vals) {
Map<String, String> map = new HashMap<String, String>(vals.length / 2);
for(int i = 1; i < vals.length; i+= 2)
map.put(vals[i-1], vals[i]);
return map;
}
public String replace(String input, Map<String, String> pairs) {
// Reverse lexic-order of keys is good enough for most cases,
// as it puts longer words before their prefixes ("tool" before "too").
// However, there are corner cases, which this algorithm doesn't handle
// no matter what order of keys you choose, eg. it fails to match "edit"
// before "bed" in "..bedit.." because "bed" appears first in the input,
// but "edit" may be the desired longer match. Depends which you prefer.
final Map<String, String> sorted =
new TreeMap<String, String>(Collections.reverseOrder());
sorted.putAll(pairs);
final String[] keys = sorted.keySet().toArray(new String[sorted.size()]);
final String[] vals = sorted.values().toArray(new String[sorted.size()]);
final int lo = 0, hi = input.length();
final StringBuilder result = new StringBuilder();
int s = lo;
for (int i = s; i < hi; i++) {
for (int p = 0; p < keys.length; p++) {
if (input.regionMatches(i, keys[p], 0, keys[p].length())) {
/* TODO: check for "edit", if this is "bed" in "..bedit.." case,
* i.e. look ahead for all prioritized/longer keys starting within
* the current match region; iff found, then ignore match ("bed")
* and continue search (find "edit" later), else handle match. */
// if (better-match-overlaps-right-ahead)
// continue;
result.append(input, s, i).append(vals[p]);
i += keys[p].length();
s = i--;
}
}
}
if (s == lo) // no matches? no changes!
return input;
return result.append(input, s, hi).toString();
}
Đây là cách triển khai toàn bộ, một lớp dựa trên câu trả lời xuất sắc ở trên của Dave Jarvis . Lớp tự động chọn giữa hai thuật toán được cung cấp khác nhau, để đạt hiệu quả tối đa. (Câu trả lời này dành cho những người chỉ muốn sao chép và dán nhanh chóng.)
package somepackage
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.ahocorasick.trie.Emit;
import org.ahocorasick.trie.Trie;
import org.ahocorasick.trie.Trie.TrieBuilder;
import org.apache.commons.lang3.StringUtils;
/**
* ReplaceStrings, This class is used to replace multiple strings in a section of text, with high
* time efficiency. The chosen algorithms were adapted from: https://stackoverflow.com/a/40836618
*/
public final class ReplaceStrings {
/**
* replace, This replaces multiple strings in a section of text, according to the supplied
* search and replace definitions. For maximum efficiency, this will automatically choose
* between two possible replacement algorithms.
*
* Performance note: If it is known in advance that the source text is long, then this method
* signature has a very small additional performance advantage over the other method signature.
* (Although either method signature will still choose the best algorithm.)
*/
public static String replace(
final String sourceText, final Map<String, String> searchReplaceDefinitions) {
final boolean useLongAlgorithm
= (sourceText.length() > 1000 || searchReplaceDefinitions.size() > 25);
if (useLongAlgorithm) {
// No parameter adaptations are needed for the long algorithm.
return replaceUsing_AhoCorasickAlgorithm(sourceText, searchReplaceDefinitions);
} else {
// Create search and replace arrays, which are needed by the short algorithm.
final ArrayList<String> searchList = new ArrayList<>();
final ArrayList<String> replaceList = new ArrayList<>();
final Set<Map.Entry<String, String>> allEntries = searchReplaceDefinitions.entrySet();
for (Map.Entry<String, String> entry : allEntries) {
searchList.add(entry.getKey());
replaceList.add(entry.getValue());
}
return replaceUsing_StringUtilsAlgorithm(sourceText, searchList, replaceList);
}
}
/**
* replace, This replaces multiple strings in a section of text, according to the supplied
* search strings and replacement strings. For maximum efficiency, this will automatically
* choose between two possible replacement algorithms.
*
* Performance note: If it is known in advance that the source text is short, then this method
* signature has a very small additional performance advantage over the other method signature.
* (Although either method signature will still choose the best algorithm.)
*/
public static String replace(final String sourceText,
final ArrayList<String> searchList, final ArrayList<String> replacementList) {
if (searchList.size() != replacementList.size()) {
throw new RuntimeException("ReplaceStrings.replace(), "
+ "The search list and the replacement list must be the same size.");
}
final boolean useLongAlgorithm = (sourceText.length() > 1000 || searchList.size() > 25);
if (useLongAlgorithm) {
// Create a definitions map, which is needed by the long algorithm.
HashMap<String, String> definitions = new HashMap<>();
final int searchListLength = searchList.size();
for (int index = 0; index < searchListLength; ++index) {
definitions.put(searchList.get(index), replacementList.get(index));
}
return replaceUsing_AhoCorasickAlgorithm(sourceText, definitions);
} else {
// No parameter adaptations are needed for the short algorithm.
return replaceUsing_StringUtilsAlgorithm(sourceText, searchList, replacementList);
}
}
/**
* replaceUsing_StringUtilsAlgorithm, This is a string replacement algorithm that is most
* efficient for sourceText under 1000 characters, and less than 25 search strings.
*/
private static String replaceUsing_StringUtilsAlgorithm(final String sourceText,
final ArrayList<String> searchList, final ArrayList<String> replacementList) {
final String[] searchArray = searchList.toArray(new String[]{});
final String[] replacementArray = replacementList.toArray(new String[]{});
return StringUtils.replaceEach(sourceText, searchArray, replacementArray);
}
/**
* replaceUsing_AhoCorasickAlgorithm, This is a string replacement algorithm that is most
* efficient for sourceText over 1000 characters, or large lists of search strings.
*/
private static String replaceUsing_AhoCorasickAlgorithm(final String sourceText,
final Map<String, String> searchReplaceDefinitions) {
// Create a buffer sufficiently large that re-allocations are minimized.
final StringBuilder sb = new StringBuilder(sourceText.length() << 1);
final TrieBuilder builder = Trie.builder();
builder.onlyWholeWords();
builder.ignoreOverlaps();
for (final String key : searchReplaceDefinitions.keySet()) {
builder.addKeyword(key);
}
final Trie trie = builder.build();
final Collection<Emit> emits = trie.parseText(sourceText);
int prevIndex = 0;
for (final Emit emit : emits) {
final int matchIndex = emit.getStart();
sb.append(sourceText.substring(prevIndex, matchIndex));
sb.append(searchReplaceDefinitions.get(emit.getKeyword()));
prevIndex = emit.getEnd() + 1;
}
// Add the remainder of the string (contains no more matches).
sb.append(sourceText.substring(prevIndex));
return sb.toString();
}
/**
* main, This contains some test and example code.
*/
public static void main(String[] args) {
String shortSource = "The quick brown fox jumped over something. ";
StringBuilder longSourceBuilder = new StringBuilder();
for (int i = 0; i < 50; ++i) {
longSourceBuilder.append(shortSource);
}
String longSource = longSourceBuilder.toString();
HashMap<String, String> searchReplaceMap = new HashMap<>();
ArrayList<String> searchList = new ArrayList<>();
ArrayList<String> replaceList = new ArrayList<>();
searchReplaceMap.put("fox", "grasshopper");
searchReplaceMap.put("something", "the mountain");
searchList.add("fox");
replaceList.add("grasshopper");
searchList.add("something");
replaceList.add("the mountain");
String shortResultUsingArrays = replace(shortSource, searchList, replaceList);
String shortResultUsingMap = replace(shortSource, searchReplaceMap);
String longResultUsingArrays = replace(longSource, searchList, replaceList);
String longResultUsingMap = replace(longSource, searchReplaceMap);
System.out.println(shortResultUsingArrays);
System.out.println("----------------------------------------------");
System.out.println(shortResultUsingMap);
System.out.println("----------------------------------------------");
System.out.println(longResultUsingArrays);
System.out.println("----------------------------------------------");
System.out.println(longResultUsingMap);
System.out.println("----------------------------------------------");
}
}
(Thêm những thứ này vào tệp pom của bạn nếu cần.)
<!-- Apache Commons utilities. Super commonly used utilities.
https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
<!-- ahocorasick, An algorithm used for efficient searching and
replacing of multiple strings.
https://mvnrepository.com/artifact/org.ahocorasick/ahocorasick -->
<dependency>
<groupId>org.ahocorasick</groupId>
<artifactId>ahocorasick</artifactId>
<version>0.4.0</version>
</dependency>