Java8 ArrayList & LinkedList

作者: 没有颜色的菜 | 来源:发表于2018-09-13 19:03 被阅读21次

    前言

    为什么会想写这东西呢?这是我们都非常常用的数据结构,然而平时除了添加,遍历操作,很少使用其他功能,即使是这样,我们也存在万般理由搞清楚他们的 API,这对于我们在编程时如何选择二者具有良好的指导意义。所以,我觉得好好看看这二者的区别,之前一直以为只是 ArrayList 和 LinkedList 只是数组和链表的区别,我已经大错特错了, Java8 的实现远远不止于此,LinkedList 还有双端队列的功能,之前一直没注意到,最近发现 ArrayDeque,却没有看到 LinkedDeque,恍然想起 LinkedList 实现了 Deque 接口,这才恍然大悟~~

    ArrayList & LinkedList

    他们的区别从实现的接口上也可以看出来, LinkedList 多实现了一个接口,下次面试官再问到,我们可以谈谈这个问题,面试官会另眼相看的,面试官就喜欢深入研究的

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

    ArrayList 原理

    初始大小为 10,transient Object[] elementData 用来存储元素

        /**
         * Default initial capacity.
         */
        private static final int DEFAULT_CAPACITY = 10;
    
        /**
         * Shared empty array instance used for empty instances.
         */
        private static final Object[] EMPTY_ELEMENTDATA = {};
    
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
     
        transient Object[] elementData; // non-private to simplify nested class access
    
        private int size;
        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);
            }
        }
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    

    add 方法,首先检查容量,检查的时候首先在是否是空的,获取一个最小容量,才去执行扩容,为 elementData 赋值

        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            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++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    

    扩容策略,每次增长 1/2

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

    如果你想在数组中确定某一个元素的话,那么需要遍历,有两种遍历方式,可根据场景自行选择

        public int indexOf(Object o) {
            if (o == null) {
                for (int i = 0; i < size; i++)
                    if (elementData[i]==null)
                        return i;
            } else {
                for (int i = 0; i < size; i++)
                    if (o.equals(elementData[i]))
                        return i;
            }
            return -1;
        }
    
        public int lastIndexOf(Object o) {
            if (o == null) {
                for (int i = size-1; i >= 0; i--)
                    if (elementData[i]==null)
                        return i;
            } else {
                for (int i = size-1; i >= 0; i--)
                    if (o.equals(elementData[i]))
                        return i;
            }
            return -1;
        }
    

    toArray 通常需要进行类型强转

        public Object[] toArray() {
            return Arrays.copyOf(elementData, size);
        }
    

    获取某个索引的元素,我们看到也是强转类型

        E elementData(int index) {
            return (E) elementData[index];
        }
    
        /**
         * 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);
        }
    

    还有 set 方法,覆盖之前的值,返回旧值

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

    插入,需要移动之后的所有元素

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

    删除某个索引的元素,同样需要移动元素

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

    删除某个 object,看到了 fastRemove,只不过是去除了边界验证

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

    LinkedList

    由于是链表,很多首尾节点的插入删除操作,方便了很多

    属性

    很简单的三个属性

        transient int size = 0;
    
        /**
         * Pointer to first node.
         * Invariant: (first == null && last == null) ||
         *            (first.prev == null && first.item != null)
         */
        transient Node<E> first;
    
        /**
         * Pointer to last node.
         * Invariant: (first == null && last == null) ||
         *            (last.next == null && last.item != null)
         */
        transient Node<E> last;
    

    node 静态内部类,根据 Effective Java 的描述,这种设计是比较好的。

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

    我们简单看一些方法即可,由于实现了 Deque,大部分操作非常相似

    add 在尾部添加元素,无需边界检查

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

    获取元素,需要注意的是为了加快速度,会判断更接近后边还是前边,这样能省一般时间

        public E get(int index) {
            checkElementIndex(index);
            return node(index).item;
        }
        
        Node<E> node(int index) {
            // assert isElementIndex(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;
            }
        }
    

    而 indexOf(Object o) 就没这个幸运了,必须老老实实从头开始

        public int indexOf(Object o) {
            int index = 0;
            if (o == null) {
                for (Node<E> x = first; x != null; x = x.next) {
                    if (x.item == null)
                        return index;
                    index++;
                }
            } else {
                for (Node<E> x = first; x != null; x = x.next) {
                    if (o.equals(x.item))
                        return index;
                    index++;
                }
            }
            return -1;
        }
    

    一些关于队列的操作,注意方法的返回值,如果出现为空是否会抛出异常呢,像 element pop remove 会抛异常,而 peek,poll 则不会,这些都要注意,否则一不小心就会引发惨案!!!

        public E peek() {
            final Node<E> f = first;
            return (f == null) ? null : f.item;
        }
    
        /**
         * Retrieves, but does not remove, the head (first element) of this list.
         *
         * @return the head of this list
         * @throws NoSuchElementException if this list is empty
         * @since 1.5
         */
        public E element() {
            return getFirst();
        }
    
        public E getFirst() {
            final Node<E> f = first;
            if (f == null)
                throw new NoSuchElementException();
            return f.item;
        }
    
        /**
         * Retrieves and removes the head (first element) of this list.
         *
         * @return the head of this list, or {@code null} if this list is empty
         * @since 1.5
         */
        public E poll() {
            final Node<E> f = first;
            return (f == null) ? null : unlinkFirst(f);
        }
    
        public E pop() {
            return removeFirst();
        }
    

    其它的便不贴代码了,都很好理解了

    小结

    总的来说,对这两个东西的用法更加清晰了,来龙去脉摸得更准确了~

    相关文章

      网友评论

        本文标题:Java8 ArrayList & LinkedList

        本文链接:https://www.haomeiwen.com/subject/doodgftx.html