Создание лучшего ArrayList
Когда вы вставляете в ArrayList
в начале или середине списка, который вы всегда будете иметь O(n)
производительность, но вставка в конце может поддерживать O(1)
потому что вы просто привязываете его к концу списка.
У меня есть идея высокого уровня о том, как сделать вставку в начале O(1)
и сделать O(n)
вставки в среднем O(n/2)
но я не уверен, что это возможно с базовой структурой массива.
Моя идея
Так как ArrayList
на самом деле это просто больший массив, который "изменяет размеры" себя, когда достигает порогового значения, почему он не может изменить размеры как в начале, так и в конце списка, а не только в конце? Это позволило бы вставки в начале O(1)
в то же время позволяя вам переписать вставку в середине, чтобы выбрать самый короткий путь для вставки. (Например, если вы вставляете в индекс 1
было бы легче двигаться 0
над чем двигаться 2
в n
).
Я думаю, что ответ Sun почему-то не сделал это из-за того, как Arrays.copyOf
и, соответственно, System.arrayCopy
написаны. Оба начинаются с начала списка и копируются в больший массив. Я взглянул на исходный код Sun для этих двух методов, но он немного продвинулся до уровня моих навыков Java.
Вопросы
На мой взгляд, есть два основных вопроса относительно того, будет ли это работать или нет:
1) Во-первых, может Arrays.copyOf
или же System.arrayCopy
быть переписан / перегружен, чтобы вставить подмассив в середину большего массива?
2) Каковы будут большие последствия этого?
Так что это хорошая идея или я упускаю что-то очевидное, что сделало бы это неосуществимым?
Заранее спасибо!
3 ответа
Ваша идея полностью работоспособна, и она будет иметь большой смысл для реализации deque, где распространенным случаем является добавление и удаление элементов на обоих концах. Это не имеет большого смысла для ArrayList
потому что 99% его случаев использования только когда-либо добавляют к концу.
Что касается ваших вопросов о природе System.arraycopy
Я не уверен, что полностью вас понимаю
а) да, вы можете использовать arraycopy
перезаписать середину одного массива содержимым другого, и
б) arraycopy
не вставляется в массив, потому что эта операция не имеет никакого значения для массивов фиксированного размера Java.
Наиболее близким к идее "вставки в массив" является создание совершенно нового, большего массива и arraycopy
соответствующие диапазоны из двух входных массивов. Результат - лучшее, что вы можете получить.
То, что вы описываете, не соответствует идее ArrayList
,
Порядок элементов ArrayList
определяются порядком, в котором они вставляются (в этом случае вы всегда будете добавлять их в конец, если вы, конечно, не указали индекс).
Если порядок определяется значением членов объектов, а не порядком вставки, то вы хотите LinkedList
,
Да, вы можете сделать вставку в начале такой же эффективной, как вставку в конце. Вам просто нужно начинать заполнять вспомогательный массив посередине, а не с индекса 0. Для этого вам не нужна другая System.arraycopy, см. Пример кода ниже.
Вместо того, чтобы просто хранить резервный массив (data
) и текущий size
Вы также сохраняете текущий start
смещение, и принять это во внимание соответственно.
BetterArrayList<T> extends AbstractList<T> {
private int size; // Used elements in data.
private int start; // Position of element 0 in data.
private T[] data; // data.length is the current total capacity.
public BetterArrayList<T>(int initialCapacity) {
data = new Object[initialCapacity];
start = initialCapacity / 2;
size = 0;
}
public void add(int index, T value) {
if (index < size / 2) { // Insert at the front or end?
if (start == 0) {
realloc(data.length * 2);
}
System.arraycopy(data, start, data, start - 1, index);
start--;
} else {
if (size + start == data.length) {
realloc(data.length * 2);
}
System.arraycopy(data, start + index,
data, start + index + 1, size - index);
}
data[start + index] = value;
size++;
}
public T get(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException();
}
return data[start + index];
}
private void realloc(int newCapacity) {
T[] newData = new Object[newCapacity];
int newStart = newCapacity - size / 2;
System.arraycopy(data, start, newData, newStart, size);
start = newStart;
data = newData;
}
public T set(int index, T value) {
Value old = get(index);
data[size + index] = value;
return old;
}
public int size() {
return size;
}
}
Я бы предположил, что Sun не пошла по этому пути, потому что она тратит больше памяти в типичном случае использования для списков массивов (который добавляется в конце). И вы получаете только более эффективные вставки спереди, вставка в любом другом месте все равно будет O(n), потому что содержимое все еще необходимо сместить.
Кстати: в зависимости от вашего варианта использования, но вас может заинтересовать структура данных Rope: http://en.wikipedia.org/wiki/Rope_%28data_structure%29