我有一个StringBuilder对象,需要进行修剪(即从任一端删除所有空格字符/u0020及以下的字符)。
我似乎找不到StringBuilder中可以执行此操作的方法。
这是我现在正在做的事情:
String trimmedStr = strBuilder.toString().trim();
这种方式可以得到预期的输出结果,但需要分配两个字符串而不是一个。是否有更高效的方法,在StringBuilder中修剪字符串时使用?
我有一个StringBuilder对象,需要进行修剪(即从任一端删除所有空格字符/u0020及以下的字符)。
我似乎找不到StringBuilder中可以执行此操作的方法。
这是我现在正在做的事情:
String trimmedStr = strBuilder.toString().trim();
这种方式可以得到预期的输出结果,但需要分配两个字符串而不是一个。是否有更高效的方法,在StringBuilder中修剪字符串时使用?
不应使用 deleteCharAt 方法。
正如Boris所指出的那样,deleteCharAt方法每次都会复制数组。在Java 5中执行此操作的代码如下:
public AbstractStringBuilder deleteCharAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
System.arraycopy(value, index+1, value, index, count-index-1);
count--;
return this;
}
当然,仅凭猜测是不足以选择一种优化方法而非另一种的,因此我决定对这个线程中的3种方法进行计时比较:原始方法、删除方法和子字符串方法。
以下是我测试原始方法的代码:
public static String trimOriginal(StringBuilder sb) {
return sb.toString().trim();
}
删除方法:
public static String trimDelete(StringBuilder sb) {
while (sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) {
sb.deleteCharAt(0);
}
while (sb.length() > 0 && Character.isWhitespace(sb.charAt(sb.length() - 1))) {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
然后是使用子字符串的方法:
public static String trimSubstring(StringBuilder sb) {
int first, last;
for (first=0; first<sb.length(); first++)
if (!Character.isWhitespace(sb.charAt(first)))
break;
for (last=sb.length(); last>first; last--)
if (!Character.isWhitespace(sb.charAt(last-1)))
break;
return sb.substring(first, last);
}
我进行了100次测试,每次生成一个带有一万个前导和尾随空格的百万字符StringBuffer。测试本身非常基础,但它可以很好地说明这些方法需要多长时间。
这里是计时三种方法的代码:
public static void main(String[] args) {
long originalTime = 0;
long deleteTime = 0;
long substringTime = 0;
for (int i=0; i<100; i++) {
StringBuilder sb1 = new StringBuilder();
StringBuilder sb2 = new StringBuilder();
StringBuilder sb3 = new StringBuilder();
for (int j=0; j<10000; j++) {
sb1.append(" ");
sb2.append(" ");
sb3.append(" ");
}
for (int j=0; j<980000; j++) {
sb1.append("a");
sb2.append("a");
sb3.append("a");
}
for (int j=0; j<10000; j++) {
sb1.append(" ");
sb2.append(" ");
sb3.append(" ");
}
long timer1 = System.currentTimeMillis();
trimOriginal(sb1);
originalTime += System.currentTimeMillis() - timer1;
long timer2 = System.currentTimeMillis();
trimDelete(sb2);
deleteTime += System.currentTimeMillis() - timer2;
long timer3 = System.currentTimeMillis();
trimSubstring(sb3);
substringTime += System.currentTimeMillis() - timer3;
}
System.out.println("original: " + originalTime + " ms");
System.out.println("delete: " + deleteTime + " ms");
System.out.println("substring: " + substringTime + " ms");
}
我得到了以下输出:
original: 176 ms
delete: 179242 ms
substring: 154 ms
从我们可以看到,子字符串方法相对于原始的“两个字符串”方法提供了非常轻微的优化。然而,删除方法非常缓慢,应该避免使用。
因此,回答你的问题:按照你在问题中提出的方式修剪StringBuilder是没有问题的。子字符串方法提供的非常轻微的优化可能不足以证明额外的代码。
我使用了Zaven的分析方法和StringBuilder的delete(start, end)方法,它比deleteCharAt(index)方法表现更好,但略逊于substring()方法。该方法还使用了数组复制,但在最坏的情况下只调用了两次数组复制。此外,这种方法避免了在同一StringBuilder对象上多次重复调用trim()时创建多个中间字符串实例。
public class Main {
public static String trimOriginal(StringBuilder sb) {
return sb.toString().trim();
}
public static String trimDeleteRange(StringBuilder sb) {
int first, last;
for (first = 0; first < sb.length(); first++)
if (!Character.isWhitespace(sb.charAt(first)))
break;
for (last = sb.length(); last > first; last--)
if (!Character.isWhitespace(sb.charAt(last - 1)))
break;
if (first == last) {
sb.delete(0, sb.length());
} else {
if (last < sb.length()) {
sb.delete(last, sb.length());
}
if (first > 0) {
sb.delete(0, first);
}
}
return sb.toString();
}
public static String trimSubstring(StringBuilder sb) {
int first, last;
for (first = 0; first < sb.length(); first++)
if (!Character.isWhitespace(sb.charAt(first)))
break;
for (last = sb.length(); last > first; last--)
if (!Character.isWhitespace(sb.charAt(last - 1)))
break;
return sb.substring(first, last);
}
public static void main(String[] args) {
runAnalysis(1000);
runAnalysis(10000);
runAnalysis(100000);
runAnalysis(200000);
runAnalysis(500000);
runAnalysis(1000000);
}
private static void runAnalysis(int stringLength) {
System.out.println("Main:runAnalysis(string-length=" + stringLength + ")");
long originalTime = 0;
long deleteTime = 0;
long substringTime = 0;
for (int i = 0; i < 200; i++) {
StringBuilder temp = new StringBuilder();
char[] options = {' ', ' ', ' ', ' ', 'a', 'b', 'c', 'd'};
for (int j = 0; j < stringLength; j++) {
temp.append(options[(int) ((Math.random() * 1000)) % options.length]);
}
String testStr = temp.toString();
StringBuilder sb1 = new StringBuilder(testStr);
StringBuilder sb2 = new StringBuilder(testStr);
StringBuilder sb3 = new StringBuilder(testStr);
long timer1 = System.currentTimeMillis();
trimOriginal(sb1);
originalTime += System.currentTimeMillis() - timer1;
long timer2 = System.currentTimeMillis();
trimDeleteRange(sb2);
deleteTime += System.currentTimeMillis() - timer2;
long timer3 = System.currentTimeMillis();
trimSubstring(sb3);
substringTime += System.currentTimeMillis() - timer3;
}
System.out.println(" original: " + originalTime + " ms");
System.out.println(" delete-range: " + deleteTime + " ms");
System.out.println(" substring: " + substringTime + " ms");
}
}
输出:
Main:runAnalysis(string-length=1000)
original: 0 ms
delete-range: 4 ms
substring: 0 ms
Main:runAnalysis(string-length=10000)
original: 4 ms
delete-range: 9 ms
substring: 4 ms
Main:runAnalysis(string-length=100000)
original: 22 ms
delete-range: 33 ms
substring: 43 ms
Main:runAnalysis(string-length=200000)
original: 57 ms
delete-range: 93 ms
substring: 110 ms
Main:runAnalysis(string-length=500000)
original: 266 ms
delete-range: 220 ms
substring: 191 ms
Main:runAnalysis(string-length=1000000)
original: 479 ms
delete-range: 467 ms
substring: 426 ms
Character.isWhitespace(c)
。一开始我也有和你一样的问题,但是经过5分钟的思考后,我意识到实际上你不需要修剪StringBuffer!你只需要修剪你追加到StringBuffer中的字符串。
如果你想修剪一个初始的StringBuffer,你可以这样做:
StringBuffer sb = new StringBuffer(initialStr.trim());
如果您想即时修剪StringBuffer,您可以在附加期间执行此操作:
Sb.append(addOnStr.trim());
只有你考虑到了将字符串构建器转换为“字符串”,然后进行“修剪”会创建两个不可变对象,需要进行垃圾回收,因此总分配量为:
因此,尽管修剪可能看起来更快,但在实际世界和具有加载内存方案的情况下,它实际上会更糟。
使用调试器逐步执行trim()方法即可。
我写了一些代码。它能正常运行,测试用例也在那里供你查看。如果没问题的话,请告诉我。
主要代码 -
public static StringBuilder trimStringBuilderSpaces(StringBuilder sb) {
int len = sb.length();
if (len > 0) {
int start = 0;
int end = 1;
char space = ' ';
int i = 0;
// Remove spaces at start
for (i = 0; i < len; i++) {
if (sb.charAt(i) != space) {
break;
}
}
end = i;
//System.out.println("s = " + start + ", e = " + end);
sb.delete(start, end);
// Remove the ending spaces
len = sb.length();
if (len > 1) {
for (i = len - 1; i > 0; i--) {
if (sb.charAt(i) != space) {
i = i + 1;
break;
}
}
start = i;
end = len;// or len + any positive number !
//System.out.println("s = " + start + ", e = " + end);
sb.delete(start, end);
}
}
return sb;
}
完整的代码和测试 -
package source;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
public class StringBuilderTrim {
public static void main(String[] args) {
testCode();
}
public static void testCode() {
StringBuilder s1 = new StringBuilder("");
StringBuilder s2 = new StringBuilder(" ");
StringBuilder s3 = new StringBuilder(" ");
StringBuilder s4 = new StringBuilder(" 123");
StringBuilder s5 = new StringBuilder(" 123");
StringBuilder s6 = new StringBuilder("1");
StringBuilder s7 = new StringBuilder("123 ");
StringBuilder s8 = new StringBuilder("123 ");
StringBuilder s9 = new StringBuilder(" 123 ");
StringBuilder s10 = new StringBuilder(" 123 ");
/*
* Using a rough form of TDD here. Initially, one one test input
* "test case" was added and rest were commented. Write no code for the
* method being tested. So, the test will fail. Write just enough code
* to make it pass. Then, enable the next test. Repeat !!!
*/
ArrayList<StringBuilder> ins = new ArrayList<StringBuilder>();
ins.add(s1);
ins.add(s2);
ins.add(s3);
ins.add(s4);
ins.add(s5);
ins.add(s6);
ins.add(s7);
ins.add(s8);
ins.add(s9);
ins.add(s10);
// Run test
for (StringBuilder sb : ins) {
System.out
.println("\n\n---------------------------------------------");
String expected = sb.toString().trim();
String result = trimStringBuilderSpaces(sb).toString();
System.out.println("In [" + sb + "]" + ", Expected [" + expected
+ "]" + ", Out [" + result + "]");
if (result.equals(expected)) {
System.out.println("Success!");
} else {
System.out.println("FAILED!");
}
System.out.println("---------------------------------------------");
}
}
public static StringBuilder trimStringBuilderSpaces(StringBuilder inputSb) {
StringBuilder sb = new StringBuilder(inputSb);
int len = sb.length();
if (len > 0) {
try {
int start = 0;
int end = 1;
char space = ' ';
int i = 0;
// Remove spaces at start
for (i = 0; i < len; i++) {
if (sb.charAt(i) != space) {
break;
}
}
end = i;
//System.out.println("s = " + start + ", e = " + end);
sb.delete(start, end);
// Remove the ending spaces
len = sb.length();
if (len > 1) {
for (i = len - 1; i > 0; i--) {
if (sb.charAt(i) != space) {
i = i + 1;
break;
}
}
start = i;
end = len;// or len + any positive number !
//System.out.println("s = " + start + ", e = " + end);
sb.delete(start, end);
}
} catch (Exception ex) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ex.printStackTrace(pw);
sw.toString(); // stack trace as a string
sb = new StringBuilder("\nNo Out due to error:\n" + "\n" + sw);
return sb;
}
}
return sb;
}
}
strBuilder.replace(0,strBuilder.length(),strBuilder.toString().trim());
public static void trimStringBuilder(StringBuilder builder) {
int len = builder.length();
int start = 0;
// Remove whitespace from start
while (start < len && builder.charAt(start) == ' ') {
start++;
}
if (start > 0) {
builder.delete(0, start);
}
len = builder.length();
int end = len;
// Remove whitespace from end
while (end > 0 && builder.charAt(end - 1) == ' ') {
end--;
}
if (end < len) {
builder.delete(end, len);
}
}
delete(int, int)
方法来删除前导空格怎么样?只需计算起始位置,然后使用setLength
方法即可。 - gvaish