arraylist为什么查询快_ArrayList个LinkedList的区别

c6cf0e5f83cbbff254e713689c67a61f.png

ArrayList个LinkedList的区别

  1. ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
  2. 随机获取参数,ArrayList速度高于LinkedList。
  3. 对于增删,ArrayList需要选择内存片区重新建立新数组,相比LinkedArray优势比较明显。

数组指定长度和不指定长度影响

public class ListTest {

    public static int length = 2000000;
    public static List<String> listNoLength = new ArrayList<>();
    public static List<String> listLength = new ArrayList<>(length);

    public static void addList(int sign){
        long start = System.currentTimeMillis();
        for (int i = 0; i < length; i++) {
            if(sign==0){
                listNoLength.add("YoniYuan");
            }else{
                listLength.add("YoniYuan");
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("sign: "+ sign+" "+(end - start));
    }
    public static void main(String[] args) {
        addList(0);
        addList(1);
    }
}

细节优化:长度尽量使用2的幂,计算机分配空间大都使用次幂去分配,减少水平空间。

存储、扩容

数组

a8ca05b096dea6fe2707baf9064b356b.png
数组扩容示意图

数组从5扩容到8流程

  1. 新增数组空间判断。判断有没有空闲空间存储新数组。
  2. 申请连续空间。
  3. 复制老数组。
  4. 增加内容。
  5. 删除老数组。

链表

f02a95e2c9321f9b24054eedc6409446.png
链表扩容示意图

链表扩容流程

  1. 不需要连续空间。
  2. 大小不定。

时间复杂度

df6bab44f39abdaeb9e9178dccd1435e.png
时间复杂度
  1. 同样查找O(n)数组遍历比链表快。
    1. 数组是连续内存,会有一部分或全部数据一起进入到CPU缓存,而链表还需要再去内存中根据上下标查找。CPU缓存比内存块。
  2. 数组大小固定,不适合动态存储,动态添加,内存为一连续的地址,可随机访问,查询快。而链表大小可变,扩展性强,只能顺着指针的方向查询,速度较慢。

源码分析

尾部添加

ArrayList

    transient Object[] elementData;
    private int size;
    protected transient int modCount = 0;

    public boolean add(E e) {
        ensureCapacityInternal(size + 1); // 确保有足够的空间
        elementData[size++] = e; // 完成添加
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }   
        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;  
        if (minCapacity - elementData.length > 0)
        grow(minCapacity);
    }

    private void grow(int minCapacity) {
        // overflow-conscious code    
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;   
        if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:    
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

结论:如果容量足够大,add()操作效率还是很高的,当需要扩容时会进行大量数组复制操作,从而影响效率。modCount变量用于在遍历集合时,检测是否发生了add,remove。

LinkedList

    public boolean add(E e) {
        linkLast(e);
        return true;
    }

    void linkLast(E e) {
        final Node<E> l = last; // 指向链表尾部  
        final Node<E> newNode = new Node<>(l, e, null); // 以尾部为前驱结点创建一个新节点    
        last = newNode; // 将链表尾部指向新节点    
        if (l == null) // 如果链表为空,那么该节点即是头结点也是为节点        
            first = newNode;
        else // 链表不为空,那么将该节点作为原链表尾部的后继节点        
            l.next = newNode;
        size++;
        modCount++;
    }

结论:LinkedList不需要扩容,对比ArrayList来说是有优势的。

随机插入

ArrayList

    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++;
    }

结论:每次操作都会有大量的数组复制,从而导致性能下降。

LinkedList

    public void add(int index, E element) {
        checkPositionIndex(index);
        if (index == size) // 链表尾部添加
            linkLast(element);
    else // 链表中间添加
        linkBefore(element, node(index));
    }

结论:与add(E e)是一样的,不会导致性能下降。

删除元素

ArrayList

    public boolean remove(Object o) {
        if (o == null) { // 移除对象数组中的第一个null
            for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
        } else { // 移除对象数组中的第一个o    
            for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
        }
        return false;
    }

    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index + 1, elementData, index, numMoved); // 删除元素之后的元素整块前移
        elementData[--size] = null; // clear to let GC do its work
    }

    // 删除指定索引index下的元素,返回被删除的元素
    public E remove(int index) {
        rangeCheck(index);
        modCount++;
        E oldValue = elementData(index);
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index + 1, elementData, index, numMoved);
        elementData[--size] = null; // clear to let GC do its work   
        return oldValue;
    }

结论:删除任意位置元素都需要数组重建。而且remove(Object o)需要重建数组,而remove(int index)不需要。

LinkedList

    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

    E unlink(Node<E> x) {
        // assert x != null;    
        final E element = x.item;
        final Node<E> next = x.next; // 得到后继节点    
        final Node<E> prev = x.prev; // 得到前驱节点
        // 删除前驱指针
        if (prev == null) {
            first = next; // 删除头节点,则令头结点指向该节点的后继节点   
        } else {
            prev.next = next; // 将前驱节点的后继节点指向后继节点        
            x.prev = null;
        }
        if (next == null) {
            last = prev; // 删除尾节点,令尾节点指向该节点的前驱节点    
        } else {
            next.prev = prev;
            x.next = null;
        }
        x.item = null;
        size--;
        modCount++;
        return element;
    }

结论:首先通过循环找到元素。如果处于前半段则从前循环,如果处于后半段,则从后循环。如果处于中间位置,则效率较低。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值