关于java:Java-List集合解析

List概述

List汇合的元素以线性形式存储
1.能够容许反复的对象。
2.能够插入多个null元素。
3.是一个有序汇合,放弃了每个元素的插入程序,输入的程序就是插入的程序。

List实现类

ArrayList

ArrayList底层是用数组实现的存储,Object[] elementData
查问效率高,增删效率低,线程不平安
ArrayList()提供了三种构造方法。
  ArrayList():结构一个初始容量为10的空列表。

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

  ArrayList(int initialCapacity):结构具备指定初始容量的空列表。

    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 c):结构一个蕴含指定汇合的元素的列表,依照它们由汇合的迭代器返回的程序。

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

ArrayList要害源码解析

add()办法
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // 如果minCapacity大于elementData的长度,则进行扩容解决
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        //容量默认扩充1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            //数组容量存在最大值
            newCapacity = hugeCapacity(minCapacity);
        
        //数组复制
        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;
    }    
  1. int newCapacity = oldCapacity + (oldCapacity >> 1);每次扩容是原数组的1.5倍
  2. elementData = Arrays.copyOf(elementData, newCapacity);创立一个长度是原数组容量1.5倍的新的数组,将原数组的数据复制到新数组中,elementData指向新的数组地址
  3. 扩容也是无限的,存在最大值Integer.MAX_VALUE,然而在某些虚拟机上数组容量达到MAX_ARRAY_SIZE = Integer.MAX_VALUE – 8时会内存溢出

4.最重要的一个点:如果咱们能够当时预计出数据量,那么最好给ArrayList一个初始值,这样能够缩小其扩容次数,从而省掉很屡次内存申请和数据搬移操作。(不指定初始值,至多会执行一次grow办法,用于初始化外部数组)。

remove()办法
    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;
    }

System.arraycopy(elementData, index+1, elementData, index,numMoved);复制一个index+1开始到最初的数组,而后把它放到index开始的地位

ArrayList为什么是线程不平安的

add()办法是非原子操作
1.多个线程进行add操作时可能会导致elementData数组越界
2.多线程环境下执行时,可能就会产生一个线程的值笼罩另一个线程增加的值

Vector

在ArrayList的所有办法加了synchronized关键字,线程平安。

LinkedList

ArrayList底层是用双向链表实现的存储,
查问效率低,增删效率高,线程不平安

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

LinkedList有一个空参构造函数和一个汇合参数构造函数:


    public LinkedList() {
    }

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

LinkedList要害源码解析

get()办法:

    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
    
    Node<E> node(int index) {
        // assert isElementIndex(index);

        //查找地位 index 如果小于节点数量的一半(size>>1示意把size右移1位,相当于size/2)则从头节点开始查找,否则从尾节点查找
        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为什么是线程不平安的

add()办法是非原子操作
多个线程进行add操作时可能会节点笼罩

Stack

Stack 继承自Vector,底层也是用数组实现的存储,线程平安,恪守 LIFO(后进先出)的操作程序

Stack要害源码解析

    //入栈,增加一个元素到数组开端
    public E push(E item) {
        addElement(item);

        return item;
    }
    
    //出栈,删除数组最初一个元素并返回
    public synchronized E pop() {
        E       obj;
        int     len = size();

        obj = peek();
        removeElementAt(len - 1);

        return obj;
    }

    //获取最初一个元素,不删除
    public synchronized E peek() {
        int     len = size();

        if (len == 0)
            throw new EmptyStackException();
        return elementAt(len - 1);
    }

参考:
一篇文章带您读懂List汇合(源码剖析)

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理