【发布时间】:2018-08-27 21:11:10
【问题描述】:
一个面试问题,我无法回答,也无法在网上找到任何相关答案。
假设在arraylist中有10000条数据,我想找到当前在第5000个索引上的数字,arraylist如何知道索引并在恒定时间内给出结果?
因为如果我们遍历arraylist来查找数据,这将需要线性时间而不是恒定时间。
提前致谢。
【问题讨论】:
一个面试问题,我无法回答,也无法在网上找到任何相关答案。
假设在arraylist中有10000条数据,我想找到当前在第5000个索引上的数字,arraylist如何知道索引并在恒定时间内给出结果?
因为如果我们遍历arraylist来查找数据,这将需要线性时间而不是恒定时间。
提前致谢。
【问题讨论】:
支持ArrayList 的存储是一个数组。无论是存储原始值还是对象引用,数组中的所有对象在内存中都是连续的。
对于数组访问,编译器所要做的就是根据初始地址和所需的索引计算正确的内存地址,即 O(1)。然后它可以直接去那个计算的地址。没有遍历,所以不是O(n)。
【讨论】:
@我们。甚至更好:发布一个新问题,可能会将其作为参考链接。
ArrayLists 可以被认为是一个对象数组(这恰好是它们的实现方式)。您可以在 O(1) 处将其作为任何其他数组进行索引。与真正的“数组”相比,它的优势在于它跟踪独立于数组长度的“长度”,并在“溢出”时自动扩展数组——加上一些额外的操作。
LinkedLists(可能是您正在考虑的结构)要求您从一个项目走到下一个项目,因此实现是 O(n) 以在索引处找到一个项目。
【讨论】:
ArrayList 在底层使用了一个 array,因此得名。数组是具有直接、快速、基于索引访问的数据结构。
因此,如果您要求索引 5 000 处的元素,它只会询问其内部数组:
// More or less
return array[5000];
这是来自OpenJDK 8的完整方法:
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
特别是,它确实不会遍历直到该点的所有元素。这就是其他没有基于索引访问的数据结构需要做的事情。如LinkedList。请注意,有一个指标接口,称为RandomAccess (documentation)。实现该接口的类具有直接的基于索引的访问。当前的实现是:
ArrayList, AttributeList, CopyOnWriteArrayList,
RoleList, RoleUnresolvedList, Stack, Vector
那么,数组如何直接访问该元素?好吧,数组的大小是固定的。创建它时,您需要告诉它大小。例如10 000:
Foo[] array = new Foo[10000];
您的计算机随后将为Foo 的10 000 对象分配连续内存。关键是内存区域是连续的,而不是四处散落。所以在你的记忆中,第三个元素直接出现在第二个元素之后和第四个元素之前。
当您现在要检索位置 5 000 的元素时,您的计算机会检索以下内存位置的 Foo 对象:
startAddressOfArray + 5000 * sizeOfFoo
自从声明数组以来,一切都是已知的,计算速度很快,显然是在恒定时间内O(1)。因此,数组可以直接基于索引访问其元素。因为这些东西是一起存储的,在内存中是连续的。
您可以在Wikipedia 上阅读有关数组的更多信息。
这是来自techcrashcourse.com 的图片,显示了一个包含每个元素地址的数组:
数组大小为7,并存储使用2 字节(16 位)的整数。通常称为short,所以是new short[7] 数组。您可以看到每个元素与其前一个元素偏移了2 字节(short 的大小)。这样就可以通过简单的计算直接访问给定位置的元素,如图所示。
【讨论】:
ArrayList(或更一般的Lists)的设计目的。通常,您会使用基于哈希的结构,例如 HashSet,用于在 O(1) 中进行基于值的访问。
startAddressOfArray + 5000 * sizeOfFoo 或 startAddressOfArray + 5000 * sizeOfReference ?由于Foo 的大小对于Foo 的不同实例可能不同,我认为这是不可能的,而是参考大小是固定的,4 字节或 8 字节取决于机器是 32 位还是 64 位
顾名思义,ArrayList 将元素存储在一个数组中。这是oracle JDK中的相关代码:
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
因此,毫不奇怪,list.get(index) 只获取内部数组中的第 n 个元素:
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
【讨论】: