Производительность Java CharAt () и deleteCharAt () - PullRequest
18 голосов
/ 24 июня 2011

Мне было интересно узнать о реализации функции charAt для String / StringBuilder / StringBuffer в Java, что это за сложность?а как насчет deleteCharAt () в StringBuffer / StringBuilder?

Ответы [ 4 ]

28 голосов
/ 24 июня 2011

Для String, StringBuffer и StringBuilder, charAt() - операция с постоянным временем.

Для StringBuffer и StringBuilder, deleteCharAt() - операция с линейным временем.

StringBuffer и StringBuilder имеют очень похожие характеристики производительности. Основное отличие состоит в том, что первый является synchronized (так что потокобезопасен), а второй - нет.

16 голосов
/ 25 сентября 2014

Давайте просто посмотрим на соответствующую фактическую реализацию Java (только соответствующий код) для каждого из этих методов по очереди. Это само по себе ответит об их эффективности.

String.charAt:

public char charAt(int index) {
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return value[index];
}

Как мы видим, это просто доступ к одному массиву, который является операцией с постоянным временем .

StringBuffer.charAt:

public synchronized char charAt(int index) {
  if ((index < 0) || (index >= count))
    throw new StringIndexOutOfBoundsException(index);
  return value[index];
}

Опять же, доступ к одному массиву, поэтому операция с постоянным временем .

StringBuilder.charAt:

public char charAt(int index) {
    if ((index < 0) || (index >= count))
        throw new StringIndexOutOfBoundsException(index);
    return value[index];
}

Опять же, доступ к одному массиву, поэтому операция с постоянным временем . Хотя все эти три метода выглядят одинаково, есть некоторые незначительные различия. Например, синхронизируется только метод StringBuffer.charAt, но не другие методы. Точно так же , если проверка немного отличается для String.charAt (угадайте почему?). Более внимательное рассмотрение этих реализаций методов дает нам другие незначительные различия между ними.

Теперь давайте посмотрим на реализации deleteCharAt. * ​​1026 *

Строка не имеет метода deleteCharAt. Причина может быть в том, что это неизменный объект. Поэтому показ API, который явно указывает, что этот метод изменяет объект, не является хорошей идеей.

И StringBuffer, и StringBuilder являются подклассами AbstractStringBuilder. Метод deleteCharAt этих двух классов делегирует реализацию самому родительскому классу.

StringBuffer.deleteCharAt:

  public synchronized StringBuffer deleteCharAt(int index) {
        super.deleteCharAt(index);
        return this;
    }

StringBuilder.deleteCharAt:

 public StringBuilder deleteCharAt(int index) {
        super.deleteCharAt(index);
        return this;
    }

AbstractStringBuilder.deleteCharAt:

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

Более внимательный взгляд на метод AbstractStringBuilder.deleteCharAt показывает, что он на самом деле вызывает System.arraycopy. Это может быть O (N) в худшем случае. Так что метод deleteChatAt - это O (N) сложность по времени.

7 голосов
/ 24 июня 2011

charAt метод O(1).

Метод deleteCharAt для StringBuilder и StringBuffer в среднем равен O(N), при условии, что вы удаляете случайный символ из N символа StringBuffer / StringBuilder. (В среднем необходимо переместить половину оставшихся символов, чтобы заполнить «дыру», оставленную удаленным символом. нет амортизации по нескольким операциям ; см. Ниже.) Однако, если вы удалите последний символ, стоимость будет O(1).

Не существует метода deleteCharAt для String.


Теоретически StringBuilder и StringBuffer могут быть оптимизированы для случая, когда вы вставляете или удаляете несколько символов в «проходе» через буфер. Они могли бы сделать это, поддерживая необязательный «пробел» в буфере и перемещая по нему символы. (IIRC, emacs реализует свои текстовые буферы таким образом.) Проблемы с этим подходом:

  • Требуется больше места для атрибутов, указывающих, где находится пробел, и для самого пробела.
  • Это делает код намного сложнее и замедляет другие операции. Например, charAt придется сравнить offset с начальной и конечной точками зазора и внести соответствующие корректировки в фактическое значение индекса, прежде чем извлекать элемент массива символов.
  • Это поможет, только если приложение выполнит несколько вставок / удалений в одном буфере.

Не удивительно, что эта «оптимизация» не была реализована в стандартных StringBuilder / StringBuffer классах. Однако пользовательский класс CharSequence может использовать этот подход.

5 голосов
/ 24 июня 2011

charAt очень быстрый (и может использовать встроенные функции для String), это простой индекс в массиве. deleteCharAt потребует копирования массива, поэтому удаление символа не будет быстрым.

...