• 从源码了解ArrayList和LinkedList


    面试题:ArrayList和LinkedList的区别?

    ArrayListLinkedList是List的两种基本实现,ArrayList是List的数组实现,LinkedList是List的双向链式实现。ArrayList可以指定位置访问,在查询、修改时效率比LinkedList高,添加、删除时需要后面的所有元素都移动,效率LinkedList低。LinkedList不能指定位置访问,查询、修改时需要遍历节点,效率比ArrayList低,添加、删除时不需要移动元素,只需要修改对应指针,效率比ArrayList高。两者都是线程不安全的。

    源码分析:

    1.ArrayList

    (1)类定义

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable

    Arraylist实现了RandomAccess(支持快速访问接口),Cloneable(复制接口),Serializable(序列化接口)。

    其主要继承和实现关系如下

    ArrayList-->AbstractList-->AbstractCollection-->Collection-->Iterable

    ArrayList-->List-->Collection-->Iterable

    上面那条线主要是优化get,set,add等基础方法的内部实现,下面这条线是List的基本线。

    (2)主要变量

    transient Object[] elementData;
    
    private int size;

    ArrayList主要的数据结构是Object数组,通过size记录其当前已有值的容量,size并非当前最大容量,当前最大容量是elementData.lenth,一些基本方法是通过直接调用数组的基本方法。

    (3)主要构造方法

    无参构造方法

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    private static final int DEFAULT_CAPACITY = 10;

    有参构造方法

    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);
       }
    }
    
    private static final Object[] EMPTY_ELEMENTDATA = {};

    无参构造方法的默认值大小是10,有参构造方法的参数为大于等于0的整数,如果参数为0,则默认一个空的Object数组,如果参数大于0,则为一个大小为指定大小的Object数组,构造方法构造的数组都未初始化,所以里面的值都为null。

    (4)主要方法

    查询

    public E get(int index) {
    
        rangeCheck(index);
        return elementData(index);
    
    }
    
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    修改

    public E set(int index, E element) {
        rangeCheck(index);
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

    删除

    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;
        return oldValue;
    }

     

    添加

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
       modCount++;
       // overflow-conscious code
       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);
    }

    ArrayList查询时直接调用数组的基本方法。

    ArrayList修改时直接调用数组的基本方法并设置值。

    ArrayList删除时,先删除元素,然后调用System.arraycopy的native方法将该元素后面的元素前移一位。

    ArrayList新增元素时会检测容量是否足够,如果容量不够,会进行扩容操作,扩容计算公式为oldCapacity + (oldCapacity >> 1),也就是原来大小的1.5倍,扩容的方式是调用Array.copyOf方法,其底层调用的是System.arraycopy的native方法,是复制了原来的值到一个新的object数组中。如果是指定位置新增还需将新增元素后的所有元素向后移一位。

    从上面可见,ArrayList查询和修改时操作少、效率比较高,删除和新增时操作多、效率比较低。

    (5)线程安全问题

    从上面add方法源码分析,ArrayList执行add方法时有两个地方导致其可能出现线程不安全:

    第一点:ensureCapacityInternal方法在多个线程中可能会都判断成未超过容量,都会进行+1操作,如果在临界值范围时有可能出现数组越界,

    第二点:size++并非原子操作,在多个线程中可能会出现一个线程覆盖另外一个线程的值。

    (6)CopyOnWriteArrayList

     CopyOnWriteArrayList主要继承和实现关系上只实现是List接口,次要继承和实现关系和ArrayList一致

    final transient ReentrantLock lock = new ReentrantLock();
    private transient volatile Object[] array;

    底层基本实现也是用的object数组,加上了volatile 关键字和ReentrantLock 重入锁。在set,add,remove,iterator等写相关的方法中,全程加锁,所以是线程安全的。

    2.LinkedList

    (1)类定义

    public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable

    LinkedList实现了Cloneable(复制接口),Serializable(序列化接口)。

    其主要继承和实现关系如下

    LinkedList-->AbstractSequentialList-->AbstractList-->AbstractCollection-->Collection-->Iterable

    LinkedList-->List-->Collection-->Iterable

    LinkedList-->Deque-->Queue-->Collection-->Iterable

    从继承关系上看出,前两条线和ArrayList是基本一致的,后面这条线是队列线,且这是个双端队列。

    (2)主要变量

    transient int size = 0;
    transient Node<E> first;
    transient Node<E> last;
    
    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;
        Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
        }
    }

    LinkedList主要存储结构是以节点的形式,内部定义了个静态的节点类,拥有向前和向后的指针,内部存储的是指定泛型。

    (3)主要构造方法

    public LinkedList() {
    }
    
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

    LinkedList主要构造方法除了默认值外,未设置其他任何值。

    (4)主要方法

    查询

    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
    
    Node<E> node(int index) {
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
           for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

    修改

    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

     

    删除

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

    添加

    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查询时,判断该元素属于前半段或者是后半段,前半段从头节点开始往后遍历到指定位置,如果是后半段从尾节点往前遍历到指定位置。

    LinkedList修改时,需要先查询到指定节点,直接修改元素。

    LinkedList删除时,需要先查询到指定节点,删除元素,并将相应的指针位置改变,手动设置未null,方便GC。

    LinkedList增加时,直接在尾结点后面新增,并将相应的指针位置改变,如果是指定位置新增,需要先查询到指定节点。

    linkedList除去直接增加不需要查询,其他操作都需要查询操作,而查询效率并不是很高,最差需要size/2的遍历。

    (5)线程安全性

    LinkedList指针操作并非线程安全,ConcurrentLinkedQueue是线程安全版本。

  • 相关阅读:
    此生对我影响最大的三位老师
    介绍自己
    介绍自己
    第三周作业
    第二周作业
    PTA编程总结3
    PTA编程总结2
    PTA编程总结1
    2019年春季学期第七周作业.
    2019年春季学期第六周作业.
  • 原文地址:https://www.cnblogs.com/hxlr/p/11419404.html
Copyright © 2020-2023  润新知