ArrayList、LinkedList以及SkipList的特性与比较

ArrayList

当我们在代码中创建一个Array时,计算机实际上会在内存中开辟一块连续的地址,每个地址都可以直接访问,访问任意一个元素的时间复杂度都是相同的,也就是O(1)。但是如果我们要增加或者删除元素时,因为地址连续的缘故,就需要做比较多的事情了。

假设有个数组长度为10,现在要插入一个元素下标为4,那么则需要将原来index>=4的所有元素都向后移一位,将index=4的地址空间让出来给新增的元素,这样的操作导致时间复杂度上升为O(n),删除操作也同理。

源码分析

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

这个add方法的作用是在数组的末端插入一个元素,ensureCapacityInternal(size + 1)保证数组的size有size+1这么大,然后在数组末尾添加上e这个元素,最后size++。

    /**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

这个重载的add方法的作用就是在数组中间插入元素,首先会检查上下界,同时保证数组的大小,然后调用arrayCopy复制数组,这个方法的意思是,从elementData这个数组的下标index的元素开始,复制到目标数组elementData(第二个)的index+1的地方,总共复制size-index个元素。简单的来说就是将数组中下标从index开始的元素全部向后移动一个单位,将index的地址让出来。然后elementData[index] = element,将新元素插入到数组里,最后size++。

LinkedList

在插入和删除操作比较频繁的情况下,数组并不好用。而链表这个数组结构就是为了弥补数组的缺点。

LinkedList每个节点都有value和next,next指向下一个节点所在的地址,最后一个节点指向null,如果指向第一个节点,则这个链表就是循环链表。

在Java中的链表是双向链表,在源码中提供了一个prev来指向前一个节点。

假设我们需要在链表的第二个和第三个节点之间,插入一个新节点,只需要将第二个节点的next指向新插入的节点,然后将新插入的节点的next指向原来的第三个节点,即可完成插入操作,不需要操作其他节点,所以链表的插入效率非常高,时间复杂度为O(1),删除节点同理。

而正是因为链表的地址空间不连续,导致访问某个节点,只能一个个迭代过去,效率比较差,时间复杂度为O(n)。

SkipList

链表的优点是插入快而定位慢,但当链表内的元素都是按照一定的顺序排列的情况下,就可以使用一些方法来加速定位,再使用迭代器定位元素就显得很蠢,所以就出现了SkipList跳表,在一些热门项目里替代了平衡树,如Redis。

跳表的插入、删除、搜索的时间复杂度都是O(logn)。

优势:原理简单,方便实现,容易拓展,效率更高。

跳表的使用只能在链表的元素都是有序的情况下!!

跳表加速的方式

加速一维的数组结构经常采用的方式就是升维,如图

索引的元素并没有指向nxet元素,而是指向了距离更远的元素,这样划分出一个个区间来,定位元素的时候先通过高级索引来定位元素在哪个区间,然后逐步缩小区间,最后在原始链表中迭代定位元素,如果在原始链表中没找到则代表该元素不存在。

比如说要定位元素5,第一步在二级索引中,确定要定位的元素在1到7这两个元素之间,然后再通过一级索引,发现元素5在4和7之间,然后从元素4开始一个个查找,很快就能定位出元素。

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值