Как обрезать Java stringbuilder?
У меня есть объект StringBuilder, который нужно обрезать (т. е. все пробелы /u0020 и ниже удалены с обоих концов).
Я не могу найти метод в string builder, который бы это сделал.
вот что я сейчас делаю:
String trimmedStr = strBuilder.toString().trim();
Это дает точно желаемый результат, но для этого требуется выделить две строки вместо одной. Есть ли более эффективный способ обрезать строку, пока она все еще находится в StringBuilder?
7 ответов
вы не должны использовать подход deleteCharAt.
как отметил Борис, метод 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 подхода в этом потоке: исходный, подход удаления и подход подстроки.
вот код I проверено на оригинальность:
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 с десятью тысячами конечных и ведущих пробелов. Само тестирование очень простое, но оно дает хорошее представление о том, сколько времени занимают методы.
вот код для времени 3 подхода:
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 так, как вы предложили в вопросе. Очень небольшая оптимизация, которую предлагает метод подстроки, вероятно, не оправдывает избыточный код.
Не беспокойтесь о наличии двух строк. Это microoptimization.
Если вы действительно обнаружили узкое место, у вас может быть почти постоянная обрезка-просто повторите первые N символов, пока они не будут Character.isWhitespace(c)
я использовал подход анализа Завена и StringBuilder удалить(начало, конец) метод, который работает намного лучше, чем deleteCharAt(индекс) подход, но немного хуже, чем substring () подход. Этот метод также использует копию массива, но копия массива вызывается гораздо реже (только дважды в худшем случае). Кроме того, это это позволит избежать создания нескольких экземпляров промежуточных строк в случае, если trim () вызывается повторно на тот же объект StringBuilder.
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
только один из вас принял во внимание, что при преобразовании построителя строк в "строку", а затем "обрезать", что вы создаете неизменяемый объект дважды, который должен быть собран мусор, таким образом, общее распределение:
- объект Stringbuilder
- неизменяемая строка объекта SB 1 неизменяемый объект строки, которая была обрезана.
таким образом, пока он может "показаться", что обрезка быстрее, в реальном мире и с загруженной памятью схема будет на самом деле хуже.
у меня был именно ваш вопрос сначала, однако, после 5-минутной второй мысли, я понял, что на самом деле вам никогда не нужно обрезать StringBuffer! Вы нужно только обрезать строку, которую вы добавляете в StringBuffer.
Если вы хотите обрезать начальный StringBuffer, вы можете сделать следующее:
StringBuffer sb = new StringBuffer(initialStr.trim());
Если вы хотите обрезать StringBuffer на лету, вы можете сделать это во время добавления:
Sb.append(addOnStr.trim());
вы получаете две строки, но я ожидаю, что данные будут выделены только один раз. Поскольку строки в Java неизменяемы, я ожидаю, что реализация trim даст вам объект, который разделяет те же символьные данные, но с разными начальными и конечными индексами. По крайней мере, это то, что делает метод substr. Таким образом, все, что вы пытаетесь оптимизировать, наверняка будет иметь противоположный эффект, так как вы добавляете накладные расходы, которые не нужны.
просто пройдите через метод 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;
}
}