ArrayList与LinkedList源码分析比较

ArrayList

构造方法

  • ArrayList():默认构造方法,初始化一个长度为0的Object[]
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
  • ArrayList(int initialCapacity):初始化一个指定长度的Object[]
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    
  • ArrayList(Collection<? extends E> c):将传入的集合转为数组,将该数组赋值到elementData数组引用中
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
    

考点1:ArrayList其底层数据结构是数组
考点2:ArrayList默认容量是10

相关API

  • add(E e):添加新的元素到集合中,需要对添加后的长度校验是否溢出,否则需要进行扩容处理;然后将对应的索引设值

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
  • add(int index,E e):将新元素添加到指定位置,同理添加前需要进行容量校验,溢出需要扩容;然后将index至–size的所有元素往后移动一位,最后将index位置元素设值

    public void add(int index, E element) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    
  • addAll(Collection<? extends E> c):添加一个集合,先将添加集合转数组,然后对添加后的容量进行校验是否溢出,否则需要扩容;最后通过数组拷贝,移植到elementData数组中

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
    
  • addAll(int index,Collection<? extends E> c):同理与add(int,Collection<? extends E>逻辑一致,区别是在数组拷贝时,需要空出要添加的元素数组大小

    public boolean addAll(int index, Collection<? extends E> c) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
    
        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
    
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }
    
  • remove(int index):移除指定索引位置上的元素,将index+1至–size的所有元素往前移动一位,然后将–size位置的元素设null

    public E remove(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    
        modCount++;
        E oldValue = (E) 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)的逻辑

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    

考点1:频繁的增删,会对性能造成影响,每次操作都需要对数组进行元素的移动,但是读取是它的优势
考点2:扩容规则:先是对原先的容量>>1(即1/2倍),然后+原来的容量(扩容后的容量则是原先的1.5倍);如果不够,则设置添加元素后的容量大小作为扩容后的数组大小,最大不可超过int最大值
考点3:集合Vector与ArrayList的区别?相同的是二者的底层数据结构都是数组,不同点在于Vector是线程安全的,有synchronized关键字修饰

LinkedList

构造方法

  • LinkedList():默认构造方法,什么也没做处理
  • LinkedList(Collection<? extends E> c):将添加的集合数组化处理,然后把每个元素包装到Node对象中,将first节点指向第一个Node元素节点,然后依次将当前的Node.next绑定到下一个元素对应的Node对象,最终将last节点指向最后一个元素节点
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
    

考点:LinkedList的底层数据结构是一个链表

核心API

  • add(E e):添加元素;如果当前集合为null,则将first和last指向添加的元素的Node对象并将其prev节点设置前一个节点对象;如果当前集合存在元素,则将最后一个节点的p.next指向添加的元素节点,last从新指向添加元素节点
    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++;
    }
    
  • add(int index,E e):添加元素到指定位置;根据index遍历集合,找到对应的Node,然后将Node.prev.next节点指向添加元素节点,将添加元素节点的next节点指向Node节点,如果是添加到头尾节点,则需要变更first和last节点的指向
    public void add(int index, E element) {
        checkPositionIndex(index);
    
        if (index == size)
            linkLast(element);
        else
        	// node(index),就是通过遍历查找角标对应的节点
            linkBefore(element, node(index));
    }
    
  • addAll(int index,Collection<? extends E> c):找到插入点的位置对应的元素节点Node,然后for循环依次将插入集合的元素节点与Node.prev.next关联,然后将插入集合的最后一个元素节点的next节点指向插入点Node的节点,这样就将集合插入到指定位置的链表中
    public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);
    
        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;
    
        Node<E> pred, succ;
        if (index == size) {
            succ = null;
            pred = last;
        } else {
        	// 找到目标index对应的节点
            succ = node(index);
            pred = succ.prev;
        }
    
    	// for循环将集合元素节点依次链到目标位置上
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            pred = newNode;
        }
    
        if (succ == null) {
            last = pred;
        } else {
        	// 将插入链表的最后一个节点的next节点指向index对应节点
            pred.next = succ;
            succ.prev = pred;
        }
    
        size += numNew;
        modCount++;
        return true;
    }
    
  • addAll(Collection<? extends E> c):同理addAll(index,c)原理一致,只不过index为size
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
    	```
    
  • remove(Object o):移除指定元素;遍历集合,找出目标元素对应的节点Node,将Node.prev.next指向Node.next节点,然后把Node相关引用置空
    public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                	// 找到对应节点,然后进行断链、重链、清空节点
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                	// 找到对应节点,然后进行断链、重链、清空节点
                    unlink(x);
    return true;
                }
            }
        }
        return false;
    }
    
  • remove(int index):移除指定位置上的元素;先通过index找到对应的元素节点,然后同理进行断链、重链、清空

考点1:在频繁的查找中,会影响效率,因为每次操作,都需要进行遍历链表,找到目标所在的节点Node
考点2:在链表查找中,使用的是类似二分法,这样可减少一半的循环次数
考点3:只有在增删在首尾节点时,效率较高,当链表过长时,插入点在1/2处时,效率依旧过慢,因为需要查找一半的元素节点

总结

不同点

  • ArrayList底层是数组结构;LinkedList底层是链表结构
  • ArrayList存在容量,即有扩容算法;LinkedList无此概念
  • ArrayList访问元素速度快,增删慢;LinkedList特殊位置下增删快,查找慢

相同点

  • 二者都是线程不安全
  • 都是有序的
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值