zoukankan      html  css  js  c++  java
  • ArrayList、Vector和LinkedList

    List接口特点

    1、有序的 collection。

    2、可以对列表中每个元素的插入位置进行精确地控制。

    3、可以根据元素的索引访问元素,并搜索列表中的元素。

    4、列表通常允许重复的元素。

    5、允许存在 null 元素。

    6、实现List接口的常用类有LinkedList、ArrayList、Vector和Stack。

    ArrayList特点

    1、java.util.ArrayList<E> : List 接口的大小可变数组的实现类

    2、ArrayList 内部基于 数组 存储 各个元素。

    3、所谓大小可变数组,是指当 数组容量不足以存放新的元素时,创建新数组,并将原数组中的内容复制过来。

    4、源码分析

    //elementData中已存放的元素的个数,注意:不是elementData的容量
    private int size;
    //elementData的默认容量为10
    private static final int DEFAULT_CAPACITY = 10;
    //对象数组:ArrayList的底层数据结构,transient表示该字段不进行序列化操作
    transient Object[] elementData;
    //实例化一个空数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //实例化一个空数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    protected transient int modCount = 0;
    
    @Native public static final int   MAX_VALUE = 0x7fffffff;
    
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    /**
     *指定初始容量的有参构造
     */
    public ArrayList(int initialCapacity) {
             //如果初始容量大于0就,对象数组就指向到一个新的数组,大小为所指定的大小
            if (initialCapacity > 0) {
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) {
                //如果等于0就指向一个空数组
                this.elementData = EMPTY_ELEMENTDATA;
            } else {
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
    }
    /**
    * 无参构造
    */
    public ArrayList() {
             //对象数组就指向到一个空数组
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    /**
     * 向elementData末尾中添加元素
     */
    public boolean add(E e) {
           //确保对象数组elementData有足够的容量,可以将新加入的元素e加进去
            ensureCapacityInternal(size + 1);  
            //加入新元素e,size加1
            elementData[size++] = e;
            return true;
    }
    
    // minCapacity = seize+1,即表示执行完添加操作后,数组中的元素个数 
    private void ensureCapacityInternal(int minCapacity) {
            //判断是否是空数组
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                //用最小容量和10进行比较,取最大值赋值给最小容量
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
    
            ensureExplicitCapacity(minCapacity);
     }
    
    /**
     *确保数组的容量足够存放新加入的元素,若不够,要扩容
     */
    private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
            //如果数组个数(size+1)数组长度就需要进行扩容
            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) {
           // 如果数组个数小于0抛出OutOfMemoryError异常
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();       
           // 如果最数组个数大于最大容量 就返回最大值,否则返回最大容量
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
    }
    /**
     * 向elementData指定位置添加元素
     */
    public void add(int index, E element) {
           //指定位置检查
            rangeCheckForAdd(index);
            // 扩容检查
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            //通过拷贝使数组内位置为 index 到 (size-1)的元素往后移动一位
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);         
            // 找到位置添加元素
            elementData[index] = element;
            // 元素个数加一
            size++;
    }
    // 判断指定位置是否超出数组容量
    private void rangeCheckForAdd(int index) {
            if (index > size || index < 0)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    /**
     * @param      src      原数组.
     * @param      srcPos   原数组的起始位置.
     * @param      dest     目标数组.
     * @param      destPos  目标数组的起始位置.
     * @param      length   拷贝长度.
     */
    public static native void arraycopy(Object src,  int  srcPos,
                                            Object dest, int destPos,
                                            int length);
    
    public E remove(int index) {
           //指定位置检查
            rangeCheck(index);
            modCount++;       
           //保留要删除的值
            E oldValue = elementData(index);
           //要移动元素个数
    
    
            int numMoved = size - index - 1;
            if (numMoved > 0)
            //通过拷贝使数组内位置为 index+1到 (size-1)的元素往前移动一位
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            //清除末尾元素让GC回收
            elementData[--size] = null; // clear to let GC do its work
            //返回删除的值
            return oldValue;
    }

    ArrayList在无参的add方法中,每次插入新的元素时,判断数组是否为空,为空则建默认的容量10赋值给minCapacity,判断是否要扩容,第一次添加,数组的size是10,之后添加元素时,在ensureExplicitCapacity方法中判断数组元素个数即size+1(形参minCapacity)是否超过数组长度,超过则需要进行扩容操作,扩容是将旧的容量扩大到1.5倍,然后将数组拷贝到新的数组完成扩容操作。最后将元素添加,并size+1。ArrayList在指定位置添加元素时,是先检查指定位置是否在数组范围内,即数组中元素个数是1,则index得小于或者低于1。然后通过拷贝使数组内位置为 index 到 (size-1)的元素往后移动一位,腾出位置之后放入元素,数组个数加一。

    Vector特点

    1、java.util.Vector<E> :List 接口的内部用数组存放元素的实现类

    2、内部也是用数组存放元素。

    3、与 ArrayList 不同的是 扩容方式不同,Vector 每次增长的容量是固定的,大部分方法都被 synchronized 关键字修饰。

    4、Vector 是线程安全的。

    5、源码分析

    protected transient int modCount = 0;
    //elementData中已存放的元素的个数,注意:不是elementData的容量
    protected int elementCount;
    //对象数组:Vector的底层数据结构
    protected Object[] elementData;
    //增长系数
    protected int capacityIncrement;
    
    @Native public static final int   MAX_VALUE = 0x7fffffff;
    
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    /**
     *指定初始容量和增长系数的有参构造
     */
    public Vector(int initialCapacity, int capacityIncrement) {
            super();
            if (initialCapacity < 0)
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
             //如果初始容量大于0就,对象数组就指向到一个新的数组,大小为所指定的大小
            this.elementData = new Object[initialCapacity];
            //给增长系数赋值
            this.capacityIncrement = capacityIncrement;
    }
    /**
     *无参构造
     */
    public Vector() {
            //调用指定初始容量的有参构造,设置默认初始容量为10
    
    
            this(10);
    }
    /**
     *指定初始容量的有参构造
     */
    public Vector(int initialCapacity) {
            //调用指定初始容量和增长系数的有参构造,设置增长系数为0
            this(initialCapacity, 0);
    }
    
    /**
     * 向elementData末尾添加元素
     */
    public synchronized boolean add(E e) {
            modCount++;
            //确保对象数组elementData有足够的容量,可以将新加入的元素e加进去
            ensureCapacityHelper(elementCount + 1);
            //加入新元素e,elementCount加1
            elementData[elementCount++] = e;
            return true;
    }
    public synchronized void addElement(E obj) {
            modCount++;
            //确保对象数组elementData有足够的容量,可以将新加入的元素e加进去
            ensureCapacityHelper(elementCount + 1);
            //加入新元素e,elementCount加1
            elementData[elementCount++] = obj;
     }
    /**
     * 向elementData指定位置添加元素
     */
    public void add(int index, E element) {
            insertElementAt(element, index);
    }
    
    public synchronized void insertElementAt(E obj, int index) {
            modCount++;
           // 如果index大于数组个数抛出ArrayIndexOutOfBoundsException异常
    
            if (index > elementCount) {
                throw new ArrayIndexOutOfBoundsException(index
                                                         + " > " + elementCount);
            }
            //确保对象数组elementData有足够的容量,可以将新加入的元素e加进去
            ensureCapacityHelper(elementCount + 1);
            System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
            // 找到位置添加元素
            elementData[index] = obj;    
            // 元素个数加一
            elementCount++;
    }
    
    /**
     * @param      src      原数组.
     * @param      srcPos   原数组的起始位置.
     * @param      dest     目标数组.
     * @param      destPos  目标数组的起始位置.
     * @param      length   拷贝长度.
     */
    public static native void arraycopy(Object src,  int  srcPos,
                                            Object dest, int destPos,
                                            int length);
    
    private void ensureCapacityHelper(int minCapacity) {
            //元素个数如果大于数组长度就扩容 
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
    }
    
    private void grow(int minCapacity) {
          
            int oldCapacity = elementData.length;
            // 如果指定了增长系数就按照增长系数去增长,没有指定就增加一倍
            int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                             capacityIncrement : oldCapacity);
           //如果新的容量小于数组个数,将数组个数赋值给新容量
            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) {
           // 如果数组个数小于0抛出OutOfMemoryError异常
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
           // 如果最数组个数大于最大容量 就返回最大值,否则返回最大容量
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
    }

    Vector和ArrayList一样都是基于数组实现的,且默认长度都是10,不同的是ArrayList扩容方式是扩大为原来的1.5倍,Vector默认是扩大为原来的两倍。其次Vector的方法都是同步的(Synchronized),是线程安全的,而ArrayList的方法不是。

    LinkedList特点

    1、java.util.LinkedList<E> :List 接口的实现类和Queue接口子类Deque的实现类

    2、内部基于链表实现,增删快、迭代快,随机访问能力较差。

    3、链表中的每个节点都是 LinkedList.Node 类型的对象。

    4、LinkedList提供额外的get、remove、insert方法在LinkedList的首部或尾部。

    5、这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。

    6、LinkedList没有同步方法。如果多个线程想访问同一个List,则必须自己实现访问同步。

    7、源码分析

    // 链表中存放节点个数
    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;
            }
    }
    /**
     * 无参构造
     */
    public LinkedList() {
    }
    /**
     * 有参构造,将集合中元素插入链表
     */
    
    public LinkedList(Collection<? extends E> c) {
            this();
            addAll(c);
    }
    
    public boolean addAll(Collection<? extends E> c) {
            //以size作为插入下标,插入集合中所有
            return addAll(size, c);
    }
    
    /**
     * 以index为下标,添加所有到元素尾部
     */
    public boolean addAll(int index, Collection<? extends E> c) {
            //指定位置检查
            checkPositionIndex(index);
            //集合转换为数组
            Object[] a = c.toArray();
            //获取新增元素数量
            int numNew = a.length;
            //如果新增元素为0就不增加
            if (numNew == 0)
                return false;
            Node<E> pred, succ;    
            //如果index是size,在链表尾部追加数据
            if (index == size) {
                succ = null;//原节点为空,
                pred = last;//前置节点为尾节点,
            } else {
                //如果index!=size通过遍历获取到原节点
                succ = node(index);
                //原节点的前置节点通过pred进行保留
                pred = succ.prev;
            }
            //遍历数组,依次插入节点
            for (Object o : a) {
                @SuppressWarnings("unchecked") E e = (E) o;
                //生成新节点,前置节点为pred,后置节点为null
                Node<E> newNode = new Node<>(pred, e, null);
                //如果前置节点是null,则更新头节点
                if (pred == null)
                    first = newNode;
    
                else
                    //如果前置节点不为null,则更新前置节点的后置节点为新节点
                    pred.next = newNode;
                //设置当前节点为下次的前置节点,为下次插入做准备
                pred = newNode;
            }
            //在链表尾部加数据时,原节点为null,则更新尾节点为最后插入的节点
            if (succ == null) {
                last = pred;
            } else {
                //不是在链表尾部追加数据时,将最后插入的节点的后置节点更新为原节点
                pred.next = succ;
                //原节点的前置节点更新为最后插入的节点
                succ.prev = pred;
            }
            //更新节点个数
            size += numNew;
            modCount++;
            return true;
    }
    /**
     * 添加节点
     */
    public boolean add(E e) {
             //生成新节点 并插入到 链表尾部, 更新 last/first 节点。
            linkLast(e);
            return true;
    }
    //生成新节点 并插入到 链表尾部, 更新 last/first 节点。
    void linkLast(E e) {
            //用l记录尾节点
            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加1
            size++;
            modCount++;
    }
    /**
     * 添加节点到指定位置
     */
    public void add(int index, E element) {
             //指定位置检查
            checkPositionIndex(index); 
             //指定位置等于元素个数,在尾节点插入
            if (index == size)
                linkLast(element);
            else
                  //在中间插入
                linkBefore(element, node(index));
    }
    
    private void checkPositionIndex(int index) {
            if (!isPositionIndex(index))
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    private boolean isPositionIndex(int index) {
            return index >= 0 && index <= size;
    }
    
    void linkBefore(E e, Node<E> succ) {
            // 用pred记录指定位置节点(原节点)的前置节点
            final Node<E> pred = succ.prev;
            //生成新节点,前置节点为pred,后置节点为指定位置的节点(原节点)
            final Node<E> newNode = new Node<>(pred, e, succ);
            //将新节点作为原节点的前置节点
            succ.prev = newNode;
            //如果原节点的前置节点为空,更新头节点
            if (pred == null)
                first = newNode;
            else    
                //如果原节点的前置节点不为空则更新前置节点的后置节点为新节点
                pred.next = newNode;
            //更新节点个数
            size++;
            modCount++;
    }
    /**
     * 移除指定位置的节点
     */
    
    public E remove(int index) {
            //指定位置检查
            checkElementIndex(index);
            //移除节点
            return unlink(node(index));
    }
    /**
     * 移除节点
     */
    E unlink(Node<E> x) {
            // 记录节点信息
            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;
                //删除除节点的前置节点置为null
                x.prev = null;
            }
            // 删除节点是否为尾节点
            if (next == null) {
                // 更新尾结点为删除节点的前置节点
                last = prev;
            } else {    
               //更新删除节点的后置节点的前置节点为删除节点的前置节点
                next.prev = prev;
                //删除除节点的后置节点置为null
                x.next = null;
            }
            //删除除节点的元素值置为null
            x.item = null;
            //更新节点数并返回删除节点的元素值
            size--;
            modCount++;
            return element;
    }
    /**
     * 获取指定位置的节点的元素值
     */
    public E get(int index) {
             //指定位置检查
            checkElementIndex(index);
             //获取元素值
            return node(index).item;
    }
    /**
     *会根据index处于前半段还是后半段 进行一个折半,以提升查询效率
     */
    Node<E> node(int index) {
            //如果index< size/2从头结点开始遍历
            if (index < (size >> 1)) {
                Node<E> x = first;
                for (int i = 0; i < index; i++)
                    x = x.next;
                return x;
            } else {
                 //如果index>=size/2从尾结点开始遍历
                Node<E> x = last;
                for (int i = size - 1; i > index; i--)
                    x = x.prev;
                return x;
            }
    }

    LinkedList的批量增加是找到添加的位置,然后通过循环数组,依次执行插入节点。LinkedList的移除节点则是先根据index判断及诶单是否存在,再根据所移除节点判断是否为头节点和尾节点,更新移除节点的前置节点和后置节点的信息,然后在把移除节点的信息置为null。查询操作则是根据index是处于前半段还是后半段,进行折半,以此来提高查询效率。

    ArrayList和Vector

    1、联系

    • 都是List接口(List接口继承了Collection接口)的实现类
    • 都是基于数组实现的,位置都是有序的,并且数据允许重复

    2、区别

    • Vector类的方法大部分用synchronized修饰,是线程安全的,而ArrayList是线程不安全的,它的方法之前不是同步的
    • 扩容方式不同,Vector是默认增加容量一倍,或者是按照指定的容量增量进行增加。而ArrayList是增加容量为原来的二分之一

    ArrayList和LinkedList

    1、联系

    • 都是List接口(List接口继承了Collection接口)的实现类
    • 都是线程不安全的

    2、区别

    • ArrayList是基于动态数组实现的,LinkedList是基于链表实现的
    • 对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList是通过先折半然后遍历节点来获取指定节点,则ArrayList只需要通过索引就可以获取到
    • 对于新增和删除操作add和remove,LinkedList优于ArrayList,因为ArrayList要移动数据,而LinkedList只需要记录前后节点即可

    牛客网笔试题

    1、java 中的集合类包括 ArrayList 、 LinkedList 、 HashMap 等,下列关于集合类描述错误的是?

    正确答案: C 

    A、ArrayList和LinkedList均实现了List接口

    B、ArrayList访问速度比LinkedList快

    C、随机添加和删除元素时,ArrayList的表现更加快速

    D、HashMap实现Map接口,它允许任何类型的键和值对象,并允许将NULL用作键或值

    2、实现或继承了Collection接口的是()

    正确答案: B C E 

    A、Map

    B、List

    C、Vector

    D、Iterator

    E、Set

     3、ArrayLists和LinkedList的区别,下述说法正确的有?

    正确答案: A B C D 

    A、ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。

    B、对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。

    C、对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。

    D、ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间。

    参考博客链接

    https://blog.csdn.net/zxt0601/article/details/77341098

    转载请于明显处标明出处

    https://www.cnblogs.com/AmyZheng/p/9428922.html

  • 相关阅读:
    Android应用性能测试
    Jmeter 中使用非GUI启动进行压力测试
    软件工程结对作业01
    第六周进度条
    求一维数组最大最大子数组和
    第五周进度条
    第四周进度条博客
    软件工程个人作业03
    第四周四则运算3 PSP表格
    poj 2096 概率dp
  • 原文地址:https://www.cnblogs.com/AmyZheng/p/9428922.html
Copyright © 2011-2022 走看看