java集合-List简单介绍

Posted woshijinlei

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java集合-List简单介绍相关的知识,希望对你有一定的参考价值。

List为一个接口,直接继承Collection接口,对比他们的接口变化:除了Collection接口中size(),isEmpty()等方法,其增加了基于下标index的一系列方法,摘抄部分接口方法:

get(int)

set(int, E)

add(int, E)

remove(int)

indexOf(Object)

lastIndexOf(Object)

subList(int, int)

我们简单看一下ArrayList,

首先看一下成员变量

    private static final int DEFAULT_CAPACITY = 10;
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    transient Object[] elementData; 
    private int size;

elementData为内部实际存储元素的数组,即ArrayList是基于数组的存储结构,我们get(int),set(int,E)等方法,实质也是操作数组来实现;
看一下加入元素方法:

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

主要做了输入数据合法性检查,容器扩容检查,数组复制,数组插入位置赋值,size尺寸增加;

我们从ensureCapacityInternal()方法开始:

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

关于容器扩容检查的几个方法,我们看一下主要流程,最终参数minCapacity的值为size+1,通过grow()方法,newCapacity其中一种尝试是赋值为elementData数组长度的1.5倍,并通过Arrays.copyOf()方法实现elementData数据最终扩容;

完成扩容检查之后,我们进行数组复制,因为此时elementData数组已经扩容,所以可以从index位置统一移动到index+1位置,预留出index位置;

最后我们把index位置赋值为我们add的元素,整个即完成来ArrayList在特定位置增加元素的过程;

 我们在简单看一下LinkedList:

LinkedList除了具备List接口的所有特性,它还实现了Deque接口,可以先看List接口相关的;我们看一下成员变量:

    transient int size = 0;

    transient Node<E> first;

    transient Node<E> last;

成员变量只有三个,主要存储了首尾Node元素,看一下Node结构:

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

是一个私有数据存储类,包含实际存储的元素item以及指向前后Node的变量;是一种基于链表结构的容器;

我们也看一下加入元素方法:

    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

主要包括输入参数检查,如果是末尾添加linkList(),如果不是末尾添加linkBefore();

主要看一下linkBefore()方法,输入参数node(index):

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

从成员变量我们知道,LinkedList不像ArrayList那样维护一个内部数组,而是通过包装类Node持有存入的元素和在它之前和之后的Node引用;所以当我们想要找到某一个元素时,只能够从成员变量的first开始位置的Node或者结束位置last的Node变量逐个逐个查找;通过node()方法可以看到,分析当index小于容器长度一半时,是从first位置开始遍历,由第一个Node的next引用可以找到第二个Node,第二个Node的next引用找下一个,直到小于index时的Node;

回到linkBefore()方法,我们已经找到index位置的Node,在以此为基础add进入一个新的的Node,即add进新的元素;

    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

我们简单看一下,主要目的是new一个新的Node对象newNode,newNode需要保存add进入容器的元素element,将prev和next分别赋值上一个Node和下一个Node,上一个Node即从传入的Node参数获取,下一个即传入参数Node本身,同时跟新传入Node的上一个引用指向新插入的newNode,最终完成了LinkedList指定位置add元素的整个流程;

从两种List集合的add方法的分析过程可以看出,删除和插入元素ArrayList涉及到内部数组的赋值移动,而LinkedList又涉及到内部遍历,效率都不是很好;如果我们想拿到具体位置的元素,ArrayList可以通过index索引直接得到,但是LinkedList需要从头部或者尾部逐个遍历才能拿到具体位置的元素;如果操作首尾元素,而linkedList则更具有效率;

以上是关于java集合-List简单介绍的主要内容,如果未能解决你的问题,请参考以下文章

java集合-List简单介绍

201621123062《java程序设计》第九周作业总结

java面试题之简单介绍一下集合框架

Java集合:List列表

深入理解JAVA集合系列四:ArrayList源码解读

深入理解JAVA集合系列四:ArrayList源码解读