Подтвердить что ты не робот

Как обрезать java stringbuilder?

У меня есть объект StringBuilder, который нужно обрезать (т.е. все пробельные символы /u 0020 и ниже удалены с любого конца).

Я не могу найти метод в построителе строк, который бы это сделал.

Вот что я делаю сейчас:

String trimmedStr = strBuilder.toString().trim();

Это дает точно желаемый результат, но для него требуется выделить две строки вместо одной. Есть ли более эффективная подравнивание строки, пока она еще находится в StringBuilder?

4b9b3361

Ответ 1

Нельзя использовать метод 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;
}

Конечно, одной только спекуляции недостаточно, чтобы выбрать один метод оптимизации по сравнению с другим, поэтому я решил использовать три подхода в этом потоке: исходный, метод удаления и подстрочный подход.

Вот код, который я тестировал для orignal:

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 так, как вы предлагали в вопросе. Очень небольшая оптимизация, которую предлагает метод подстроки, не оправдывает избыточный код.

Ответ 2

Не беспокойтесь о двух строках. Это микрооптимизация.

Если вы действительно обнаружили узкое место, вы можете провести обрезку почти постоянной продолжительности - просто повторите первые N символов, пока они не станут Character.isWhitespace(c)

Ответ 3

Я использовал подход анализа Zaven и метод удаления StringBuilder (начало, конец), который работает намного лучше, чем метод deleteCharAt (index), но немного хуже, чем метод substring(). Этот метод также использует копию массива, но копия массива называется гораздо реже (только в худшем случае). Кроме того, этот избегает создания нескольких экземпляров промежуточных строк в case 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

Ответ 4

только один из вас принял во внимание, что при преобразовании построителя строк в строку и затем "обрезку", когда вы создаете неизменяемый объект дважды, который должен быть собран в мусор, поэтому общее распределение:

  • Объект Stringbuilder
  • неизменяемая строка объекта SB 1 неизменяемый объект строки, которая была обрезана.

Итак, пока он может "появиться", что обрезка быстрее, в реальном мире и с загруженной схемой памяти, это будет на самом деле хуже.

Ответ 5

Вы получаете две строки, но я ожидаю, что данные будут выделены только один раз. Поскольку строки в Java неизменяемы, я бы ожидал, что реализация trim даст вам объект, который использует одни и те же данные символа, но с разными начальными и конечными индексами. По крайней мере, то, что делает метод substr. Итак, все, что вы пытаетесь оптимизировать, это, безусловно, будет иметь обратный эффект, поскольку вы добавляете лишние накладные расходы, которые не нужны.

Просто пройдите через метод trim() с вашим отладчиком.

Ответ 6

Я сделал код. Он работает, и тестовые примеры доступны для вас. Дайте мне знать, если все в порядке.

Основной код -

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

Ответ 7

Сначала у меня был именно ваш вопрос, однако после пятиминутного размышления я понял, что вам никогда не нужно обрезать StringBuffer! Вам нужно только обрезать строку, добавляемую в StringBuffer.

Если вы хотите обрезать начальный StringBuffer, вы можете сделать это:

StringBuffer sb = new StringBuffer(initialStr.trim());

Если вы хотите обрезать StringBuffer "на лету", вы можете сделать это во время добавления:

Sb.append(addOnStr.trim());