当您在列表的开头或中间插入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)这样做的重大影响是什么?
这是一个好主意,还是我遗漏了一些明显不合理的东西?
提前致谢!
答案 0 :(得分:2)
你的想法完全可行,对于 deque 实现很有意义,其中常见的情况是在两端添加和删除元素。它对ArrayList
没有多大意义,因为99%的用例只会添加到最后。
至于你关于System.arraycopy
性质的问题,我不确定我是否完全理解你
a)是的,您可以使用arraycopy
用另一个数据的内容覆盖一个数组的中间位置,并且
b)arraycopy
没有插入到数组中,因为该操作对Java的固定大小数组没有意义。
最接近“插入数组”的想法是创建一个全新的,更大的数组,并arraycopy
来自两个输入数组的适当范围。由此产生的性能是您可以获得的最佳性能。
答案 1 :(得分:1)
您描述的内容与ArrayList
的概念不符。
ArrayList
元素的顺序由它们的插入顺序定义(在这种情况下,除非你指定一个索引,否则你总是将它们添加到最后)。
如果订单是由对象成员的值而不是插入顺序定义的,那么您需要LinkedList
。
答案 2 :(得分:1)
是的,您可以在开头插入,与插入结束时一样高效。您只需要在中间而不是索引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)因为内容仍然需要移位。
BTW:根据您的使用案例,您可能对Rope数据结构感兴趣:http://en.wikipedia.org/wiki/Rope_%28data_structure%29