众所周知,索引对数组访问的时间复杂度为O(1)。
由数组支持的Java ArrayList
的文档说明了get
操作的相同内容:
size,isEmpty,get,set,iterator和listIterator操作以恒定时间运行。
查找是通过在给定索引处获取元素的内存地址来完成的,与数组的大小无关(类似于start_address + element_size * index
)。我的理解是数组的元素必须在内存中彼此相邻存储,才能使这种查找机制成为可能。
但是,从this question,我知道Java中的数组不能保证它们的元素在内存中连续存储。如果是这种情况,它怎么可能总是O(1)?
编辑:我非常了解ArrayList
的工作原理。我的观点是,如果JVM规范无法保证数组的连续存储,则其元素可能位于内存中的不同区域。尽管这种情况极不可能,但它会使上面提到的查找机制变得不可能,而JVM将有另一种方法来进行查找,这不应该是O(1)。那时,它将违反本问题顶部所述的常识和ArrayList
关于其get
操作的文档。
感谢大家的回答。
编辑:最后,我认为这是一个特定于JVM的东西,但大多数(如果不是全部的话)JVM坚持连续存储一个数组的元素,即使在没有保证的情况下,也可以使用上面的查找机制。它简单,高效且具有成本效益。
据我所知,将元素存储在整个地方然后必须采用不同的方法进行查找会很愚蠢。
答案 0 :(得分:4)
据我所知,规范并不能保证数组会连续存储。我推测大多数JVM实现将。在基本情况下,它很简单,可以强制执行:如果你不能扩展数组,因为其他内存占用你需要的空间,那就把整个东西移到其他地方。
你的困惑源于对O(1)意义的误解。 O(1)并不意味着在单一操作中执行功能(例如start_address + element_size * index
)。这意味着无论输入数据的大小如何 - 在这种情况下,阵列,都在恒定的时间内执行操作。对于未连续存储的数据,这是完全可以实现的。例如,您可以将索引映射到内存位置。
答案 1 :(得分:1)
从链接的问题可以看出,即使JVM规则没有强制要求,1D阵列很可能在内存中连续。
给定一个连续的数组,ArrayList
的时间复杂度如下所示。然而,在特殊情况下或特殊JVM中,复杂性可能略有不同并非不可能。如果您必须考虑规范允许的各种虚拟机,则无法提供时间复杂性。
答案 2 :(得分:1)
每次添加一个元素,其容量都会被检查: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b27/java/util/ArrayList.java#ArrayList.add%28java.lang.Object%29
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
在这里,ensureCapacity()
可以保持数组顺序。怎么样?
http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b27/java/util/ArrayList.java#ArrayList.ensureCapacity%28int%29
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
因此,在每个阶段,它都会尝试确保数组具有足够的容量并且是线性的,即范围内的任何索引都可以在O(1)中检索。
答案 3 :(得分:-1)
ArrayList包装一个真正的数组。 (在add
上可能需要增长。)因此,get
和set
,O(1)具有相同的复杂性。
然而,ArrayList可以(直到Java的某个未来版本)只包含Objects。对于像int
或char
这样的原始类型,需要低效的包装类,并且在整个分配的内存中对对象进行混乱划分。仍为O(1)但具有较大的常数因子。
因此,对于原始类型,您可以使用数组并自己实现增长:
elementData = Arrays.copyOf(elementData, newCapacity);
或者如果适合,请使用 Bitset ,其中带有true的索引是值。