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

Производительность Java CharAt() и deleteCharAt()

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

Давайте просто посмотрим на соответствующую фактическую реализацию 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.

Строка не имеет метода 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).

Ответ 3

Метод charAt O(1).

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

Нет метода deleteCharAt для String.


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

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

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

Ответ 4

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