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

CharAt() или подстрока? Что быстрее?

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

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

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

4b9b3361

Ответ 1

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

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

}

Ответ 3

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

newFunction(s.charAt(i));

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

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

Ответ 4

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

Тем не менее, вероятно, что второй фрагмент будет лучше, если сначала преобразовать String в массив char, а затем выполнить ваши итерации по массиву. Выполнение этого способа будет выполнять служебные данные 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 довольно хорошо оптимизирована в этой точке.

Ответ 5

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

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

Ответ 6

Кажется, Leetcode предпочитает параметр подстроки здесь.

Вот как я решил эту проблему:

class Solution {
public int strStr(String haystack, String needle) {
    if(needle.length() == 0) {
        return 0;
    }

    if(haystack.length() == 0) {
        return -1;
    }

    for(int i=0; i<=haystack.length()-needle.length(); i++) {
        int count = 0;
        for(int j=0; j<needle.length(); j++) {
            if(haystack.charAt(i+j) == needle.charAt(j)) {
                count++;
            }
        }
        if(count == needle.length()) {
            return i;
        }
    }
    return -1;
}

}

И это оптимальное решение, которое они дают:

class Solution {
public int strStr(String haystack, String needle) {
    int length;
    int n=needle.length();
    int h=haystack.length();
    if(n==0)
        return 0;
    // if(n==h)
    //     length = h;
    // else
        length = h-n;
    if(h==n && haystack.charAt(0)!=needle.charAt(0))
            return -1;
    for(int i=0; i<=length; i++){
        if(haystack.substring(i, i+needle.length()).equals(needle))
            return i;
    }
    return -1;
}

}

Честно говоря, я не могу понять, почему это важно.