2009-11-04 4 views
11

Я хочу пройти через каждый символ в String и передать каждому символу String в виде строки в другую функцию.charAt() или подстрока? Что быстрее?

String s = "abcdefg"; 
for(int i = 0; i < s.length(); i++){ 
    newFunction(s.substring(i, i+1));} 

или

String s = "abcdefg"; 
for(int i = 0; i < s.length(); i++){ 
    newFunction(Character.toString(s.charAt(i)));} 

Конечный результат должен быть строкой. Итак, любая идея, которая будет быстрее или эффективнее?

ответ

15

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

import org.junit.Assert; 
import org.junit.Test; 

public class StringCharTest { 

    // Times: 
    // 1. Initialization of "s" outside the loop 
    // 2. Init of "s" inside the loop 
    // 3. newFunction() actually checks the string length, 
    // so the function will not be optimized away by the hotstop compiler 

    @Test 
    // Fastest: 237ms/562ms/2434ms 
    public void testCacheStrings() throws Exception { 
     // Cache all possible Char strings 
     String[] char2string = new String[Character.MAX_VALUE]; 
     for (char i = Character.MIN_VALUE; i < Character.MAX_VALUE; i++) { 
      char2string[i] = Character.toString(i); 
     } 

     for (int x = 0; x < 10000000; x++) { 
      char[] s = "abcdefg".toCharArray(); 
      for (int i = 0; i < s.length; i++) { 
       newFunction(char2string[s[i]]); 
      } 
     } 
    } 

    @Test 
    // Fast: 1687ms/1725ms/3382ms 
    public void testCharToString() throws Exception { 
     for (int x = 0; x < 10000000; x++) { 
      String s = "abcdefg"; 
      for (int i = 0; i < s.length(); i++) { 
       // Fast: Creates new String objects, but does not copy an array 
       newFunction(Character.toString(s.charAt(i))); 
      } 
     } 
    } 

    @Test 
    // Very fast: 1331 ms/ 1414ms/3190ms 
    public void testSubstring() throws Exception { 
     for (int x = 0; x < 10000000; x++) { 
      String s = "abcdefg"; 
      for (int i = 0; i < s.length(); i++) { 
       // The fastest! Reuses the internal char array 
       newFunction(s.substring(i, i + 1)); 
      } 
     } 
    } 

    @Test 
    // Slowest: 2525ms/2961ms/4703ms 
    public void testNewString() throws Exception { 
     char[] value = new char[1]; 
     for (int x = 0; x < 10000000; x++) { 
      char[] s = "abcdefg".toCharArray(); 
      for (int i = 0; i < s.length; i++) { 
       value[0] = s[i]; 
       // Slow! Copies the array 
       newFunction(new String(value)); 
      } 
     } 
    } 

    private void newFunction(String string) { 
     // Do something with the one-character string 
     Assert.assertEquals(1, string.length()); 
    } 

} 
+0

Как только вам передадут строку, вам нужно немного изменить свое тестирование в первом тесте. {char [] s = "abcdefg" .toCharArray();} должен быть внутри цикла или даже лучше (чтобы избежать умной оптимизации JVM, поместите весь цикл и .toCharArray() внутри отдельной функции). Важно измерить все начальные накладные расходы, а также затраты на цикл. Тем более, что производительность может реалистично переходить от одного к другому на основе длины строки. Таким образом, тестирование различных длин укусов также важно. – MatBailie

+5

+1 для ответа на вопрос. – gustafc

+0

Переместил «s» внутри цикла и добавил assert(), чтобы предотвратить оптимизацию JVM newFunction(). Конечно, сейчас он медленнее, но относительные измерения все те же. Моя точка зрения состоит лишь в том, что есть возможности для оптимизации, если проблема точно известна. Дело не в том, чтобы изменить какую функцию использовать для определенной операции, а чтобы увидеть операцию на более высоком уровне для улучшения, например. путем кэширования – mhaller

4

Действительно ли newFunction действительно необходимо принять String? Было бы лучше, если бы вы могли сделать newFunction взять char и назвать его так:

newFunction(s.charAt(i)); 

Таким образом, вы избежите создание временного объекта String.

Чтобы ответить на ваш вопрос: трудно сказать, какой из них более эффективен. В обоих примерах должен быть создан объект String, который содержит только один символ. Что более эффективно, зависит от того, как именно String.substring(...) и Character.toString(...) реализованы в вашей конкретной реализации Java. Единственный способ узнать это - запустить вашу программу через профилировщик и посмотреть, какая версия использует больше ЦП и/или больше памяти. Обычно вам не стоит беспокоиться о таких микро-оптимизации, как это, - тратьте время на это, только когда обнаружите, что это является причиной проблемы производительности и/или памяти.

+0

newFunction действительно нужно возьмите строку. Помимо отдельных символов, newFunction также обрабатывает более длинные строки. И он обрабатывает их одинаково. Я не хочу перегружать newFunction, чтобы взять char, потому что он делает то же самое в обоих случаях. – estacado

+1

Я полностью согласен с тем, что в процессе разработки следует избегать микрооптимизации до тех пор, пока не окажется необходимым. Я также считаю, что, поскольку учебное упражнение, изучение распределения памяти и другого «скрытого поведения» очень важно. Я лично устал от программистов-программистов, которые выбивают короткий код, полагая, что короткие = результативные и невольно используют крайне неэффективные алгоритмы. Люди, которые этого не узнают = ленивы. Люди, которые это фиксируются = медленно. Есть баланс, который нужно поразить. На мой взгляд :) – MatBailie

+0

@estacado: Если производительность - это ваш водитель (как подразумевается вашим постом), оптимизируйте нужные места. Перегрузка новой функции, чтобы избежать накладных расходов String, может быть разумной опцией в зависимости от того, как будет выглядеть версия на основе [char]. Смещение вашего кода вокруг функции может быть более экономным, менее эффективным и менее ремонтопригодным. – MatBailie

15

Ответ: it doesn't matter.

Профиль вашего кода. Это ваше узкое место?

+0

Профиль в каком направлении? Для использования памяти? –

0

Сначала я получил основной символ [] из строки источника, используя String.toCharArray(), а затем перейдите к вызову newFunction.

Но я согласен с Jesper, что было бы лучше, если вы могли бы просто иметь дело с персонажами и избежать всех функций Строковых ...

+0

String.charAt (i) выполняет этот поиск, насколько мне известно. Копирование строки в новый массив (это то, что я понимаю, для выполнения String.toCharArray()) вводит новые и другие накладные расходы. Является ли повторная передача строковой ссылки на charAt() медленнее, чем преобразование в собственный массив? Я подозреваю, что это зависит от длины строки ... – MatBailie

+0

Всегда есть компромиссы :) Только ОП может действительно сказать, что более эффективно. – 2009-11-04 12:03:07

2

Из двух фрагментов вы вывешенные, я не хотел бы сказать. Я бы согласился с Виллом, что это почти не имеет никакого отношения к общей производительности вашего кода - и если это не так, вы можете просто внести изменения и определить для себя, что наиболее быстро для ваших данных с вашей JVM на вашем оборудовании.

Сказанное, вероятно, что второй фрагмент будет лучше, если сначала преобразовать строку в массив символов, а затем выполнить итерации по массиву. Выполнение этого способа будет выполнять служебные данные String только один раз (преобразование в массив) вместо каждого вызова. Кроме того, вы можете передать массив непосредственно в конструктор String с некоторыми индексами, который более эффективен, чем принимать char из массива для передачи его отдельно (который затем преобразуется в массив символов):

String s = "abcdefg"; 
char[] chars = s.toCharArray(); 
for(int i = 0; i < chars.length; i++) { 
    newFunction(String.valueOf(chars, i, 1)); 
} 

Но, чтобы укрепить мой первый момент, когда вы смотрите на то, чего фактически избегаете при каждом вызове String.charAt() - это две проверки границ, (ленивое) логическое ИЛИ и добавление. Это не приведет к заметным различиям. Также нет различий в конструкторах String.

По сути, обе идиомы в порядке с точки зрения производительности (ни одна из них не сразу явно неэффективна), поэтому вы не должны тратить больше времени на их работу, если профилировщик не показывает, что это занимает большую часть времени выполнения вашего приложения.И даже тогда вы почти наверняка получите больше выигрышей в производительности за счет реструктуризации вашего поддерживающего кода в этой области (например, newFunction возьмите всю строку); java.lang.String довольно хорошо оптимизирована к этому моменту.

+0

'substring' в текущем jvm фактически использует исходный массив символов в качестве хранилища резервных копий, в то время как вы запускаете копию. Таким образом, мое чувство кишки говорит, что подстрока будет на самом деле быстрее, поскольку memcpy, вероятно, будет дороже (в зависимости от того, насколько велика строка, больше - лучше). – wds

Смежные вопросы