【问题标题】:How does an ArrayList retrieve data in constant time? [duplicate]ArrayList 如何在恒定时间内检索数据? [复制]
【发布时间】:2018-08-27 21:11:10
【问题描述】:

一个面试问题,我无法回答,也无法在网上找到任何相关答案。

假设在arraylist中有10000条数据,我想找到当前在第5000个索引上的数字,arraylist如何知道索引并在恒定时间内给出结果?

因为如果我们遍历arraylist来查找数据,这将需要线性时间而不是恒定时间。

提前致谢。

【问题讨论】:

    标签: java arraylist


    【解决方案1】:

    支持ArrayList 的存储是一个数组。无论是存储原始值还是对象引用,数组中的所有对象在内存中都是连续的。

    对于数组访问,编译器所要做的就是根据初始地址和所需的索引计算正确的内存地址,即 O(1)。然后它可以直接去那个计算的地址。没有遍历,所以不是O(n)。

    【讨论】:

    • 假设我们不知道元素在第 5000 个位置,我们只需要搜索一个特定的值(例如,恰好在第 5000 个索引上的整数 3),搜索该值,我们必须遍历数组列表才能找到值,这需要线性时间,对吧??
    • @neil 无需在每个答案下都发布后续内容。您可以评论您的问题并@我们。甚至更好:发布一个新问题,可能会将其作为参考链接。
    • 非常感谢@Zabuza
    【解决方案2】:

    ArrayLists 可以被认为是一个对象数组(这恰好是它们的实现方式)。您可以在 O(1) 处将其作为任何其他数组进行索引。与真正的“数组”相比,它的优势在于它跟踪独立于数组长度的“长度”,并在“溢出”时自动扩展数组——加上一些额外的操作。

    LinkedLists(可能是您正在考虑的结构)要求您从一个项目走到下一个项目,因此实现是 O(n) 以在索引处找到一个项目。

    【讨论】:

    • 假设我们不知道元素在第 5000 个位置,我们只需要搜索一个特定的值(例如,恰好在第 5000 个索引上的整数 3),搜索该值,我们必须遍历数组列表才能找到值,这需要线性时间,对吧??
    • 这不是问题,但它是正确的。在这种情况下,您不会使用数组列表,而是使用哈希图,其中键是整数,值是索引。知道在什么情况下使用什么集合是极其重要的。问题的关键词是:“我想找到目前在第 5000 个索引上的数字”
    【解决方案3】:

    数组列表

    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];
    

    您的计算机随后将为Foo10 000 对象分配连续内存。关键是内存区域是连续的,而不是四处散落。所以在你的记忆中,第三个元素直接出现在第二个元素之后和第四个元素之前。

    当您现在要检索位置 5 000 的元素时,您的计算机会检索以下内存位置的 Foo 对象:

    startAddressOfArray + 5000 * sizeOfFoo
    

    自从声明数组以来,一切都是已知的,计算速度很快,显然是在恒定时间内O(1)。因此,数组可以直接基于索引访问其元素。因为这些东西是一起存储的,在内存中是连续的。

    您可以在Wikipedia 上阅读有关数组的更多信息。

    这是来自techcrashcourse.com 的图片,显示了一个包含每个元素地址的数组:

    数组大小为7,并存储使用2 字节(16 位)的整数。通常称为short,所以是new short[7] 数组。您可以看到每个元素与其前一个元素偏移了2 字节(short 的大小)。这样就可以通过简单的计算直接访问给定位置的元素,如图所示。

    【讨论】:

    • 假设我们不知道元素在第 5000 个位置,我们只需要搜索一个特定的值(例如,恰好在第 5000 个索引上的整数 3),搜索该值,我们必须遍历数组列表才能找到值,这需要线性时间,对吧??
    • @neil 没错。这不是ArrayList(或更一般的Lists)的设计目的。通常,您会使用基于哈希的结构,例如 HashSet,用于在 O(1) 中进行基于值的访问。
    • @Zabuzard startAddressOfArray + 5000 * sizeOfFoostartAddressOfArray + 5000 * sizeOfReference ?由于Foo 的大小对于Foo 的不同实例可能不同,我认为这是不可能的,而是参考大小是固定的,4 字节或 8 字节取决于机器是 32 位还是 64 位
    【解决方案4】:

    顾名思义,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];
    }
    

    【讨论】:

    • 假设我们不知道元素在第 5000 个位置,我们只需要搜索一个特定的值(例如,恰好在第 5000 个索引上的整数 3),搜索该值,我们必须遍历数组列表才能找到值,这需要线性时间,对吧??
    • @neil 是的。如果您经常需要搜索值并且不关心元素的顺序,您可以对 Vector 进行一次排序(这需要 O(n log(n)) 时间)。这将允许您以对数时间搜索元素。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-12-16
    • 1970-01-01
    • 2019-04-29
    • 2018-01-18
    • 1970-01-01
    • 1970-01-01
    • 2013-10-20
    相关资源
    最近更新 更多