关于java集合框架(二):List

上一次整理详细说了各种Set的区别,这次再来整理以下各List。

首先是LinkedList:

允许有null元素,主要用于创建链表数据结构
没有同步方法,如果多个线程同时访问一个List,则必须自己实现访问同步,解决方法是在创建List的时候构造一个同步的List:
List list = Collections.synchronizedList(new LinkedList<>());
LinkedList查找效率低。

为什么查找效率低呢?

我们来看看LinkedList的查找方法LinkedList.get(index i)的底层源码

public class Test{
    public static void main(String[] args) {
        LinkedList<String> linkedList = new LinkedList<String>();
        linkedList.add("a");
        linkedList.add("b");
        linkedList.add("c");

        System.out.println(linkedList);
        System.out.println(linkedList.get(1));
    }
}

对于这段测试代码,输出结果是

[a, b, c]
b

使用IDEA对linkedList.add的源码进行查看,得到如下代码:

    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

这段代码里用到了node方法(即查找编号所对应的链表节点),我们再来看看这个node的代码:

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

这里index<(size >> 1)        >>指的是二进制右移一位,对于十进制数据来说,可以近似相当于对原始数据除以2。(整除,不要小数部分)

所以这里就是指如果先把整体对半切开,如果index在前半部分的(<size/2),那就从第一个节点开始向后查找;而如果index在后半部分的,那就从最后一个节点开始向前查找。查找的方式是利用循环迭代,一个个向后(向前)查找,这种查找方式便是查找缓慢的原因(对于一个较大的链表来说,这样的查找方式会导致循环很多次)。

然后是ArrayList:

该类也是实现了List的接口,实现了可变大小的数组,随机访问和遍历元素时,提供更好的性能。该类也是非同步的,在多线程的情况下不要使用。超出容量时ArrayList 增长当前长度的50%,插入删除效率低。

下面是ArrayList增长长度的源码

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

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

可以看到是oldCapacity + (oldCapacity >> 1)(即old+old/2,增加50%)。

对于查找速度快,是因为ArrayList是以数组方式存储的,所以访问元素只需要根据元素的下表访问对应的元素即可。下面依旧列出源码:

public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
E elementData(int index) {
        return (E) elementData[index];
    }

可以看到是直接return对应元素(以数组方式存储的数据,地址连续,下标也就代表着对应指针增量,数组名为头指针,所以可以根据下标直接取对应元素,但相应得,扩容时,程序会开辟一块全新的存储空间,并将原始数据复制进新的地址空间里,这相比链表来说效率是更低的。)

至于插入删除为何效率低,在java集合框架(一)中已有说到,这里不再重复说明。

常用的List便是以上两种。

posted on 2019-07-08 14:35  尤达  阅读(154)  评论(0编辑  收藏  举报

导航