• List集合框架


    Vector和ArrayList以及LinkedList区别和联系,以及分别的应用场景?

    1:Vector

    Vector的底层的实现其实是一个数组

     protected Object[] elementData;

    他是线程安全的,为什么呢?

    由于经常使用的add()方法的源码添加synchronized,所以说他是一个同步方法 ,就连不会对数据结构进行修改的get()方法上也加了synchronized

       public synchronized boolean add(E e) {
            modCount++;
            ensureCapacityHelper(elementCount + 1);
            elementData[elementCount++] = e;
            return true;
        }

    如果不手动指定它的容量的话,它默认的容量是10

      /**
         * Constructs an empty vector so that its internal data array
         * has size {@code 10} and its standard capacity increment is
         * zero.
         */
        public Vector() {
            this(10);
        }

    2.LinkedList

    LinkedList的底层其实是一个双向链表,每一个对象都是一个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;
            }
        }

    它是线程不安全的,所有的方法都有加锁或者进行同步

       public boolean add(E e) {
            linkLast(e);
            return true;
        }
    
    
        /**
         * Links e as last element.
         */
        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++;
        }

    3.ArrayList

    这里先简单介绍一下,下面会对ArrayList的扩容机制进行分析

    ArrayList是线程不安全的,如果不指定它的初始容量,那么它的初始容量是0,当第一次进行添加操作的时候它的容量将扩容为10

    三种集合的使用场景

    1. Vector很少用,有其他线程安全的List集合
    2. 如果需要大量的添加和删除则可以选择LinkedList   原因是:它查询的时候需要遍历整个链表,插入和删除的时候无需移动节点
    3. 如果需要大量的查询和修改则可以选择ArrayList           原因:底层为数组,删除和插入需要移动其他元素,查询的时候根据下标来查

    我们想要使用线程安全的List集合,你有什么办法?

    1:可以使用Vector

    2.自己重写类似于ArrayList的但是线程安全的集合

    3.可以使用Collections(工具类)中的方法,将ArrayList变成一个线程安全的集合

    4.可以使用java.util.concurrent包下的CopyOnWriteArrayList,它是线程安全的

    那你说说CopyOnWriteArrayList是怎么实现线程安全的?

    它是juc包下的,专门用于并发编程的,他的设计思想是:读写分离,最终一致,写时复制

    它不能指定容量,初始容量是0.它底层也是一个数组,集合有多大,底层数组就有多大,不会有多余的空间

    最常使用的add()方法的源码

    /**
         * Appends the specified element to the end of this list.
         *
         * @param e element to be appended to this list
         * @return {@code true} (as specified by {@link Collection#add})
         */
        public boolean add(E e) {
        //获取一把锁
            final ReentrantLock lock = this.lock;
        //加锁
            lock.lock();
            try {
           //获取当前集合(数组)
                Object[] elements = getArray();
            //获取当前集合的长度
                int len = elements.length;
           //复制一个新的数组,由于是添加操作,新数组的长度比原数组长度大1
                Object[] newElements = Arrays.copyOf(elements, len + 1);
            //原数组的长度就是新数组最大下标,将要添加的元素添加到最后
                newElements[len] = e;
            //更改引用,新数组替代原数组
                setArray(newElements);
                return true;
            } finally {
        //释放锁
                lock.unlock();
            }
        }

    remove()方法的实现逻辑也是大同小异,只不过需要移动元素,新数组是减1

        /**
         * Removes the element at the specified position in this list.
         * Shifts any subsequent elements to the left (subtracts one from their
         * indices).  Returns the element that was removed from the list.
         *
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        public E remove(int index) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                Object[] elements = getArray();
                int len = elements.length;
                E oldValue = get(elements, index);
                int numMoved = len - index - 1;
                if (numMoved == 0)
                    setArray(Arrays.copyOf(elements, len - 1));
                else {
                    Object[] newElements = new Object[len - 1];
                    System.arraycopy(elements, 0, newElements, 0, index);
                    System.arraycopy(elements, index + 1, newElements, index,
                                     numMoved);
                    setArray(newElements);
                }
                return oldValue;
            } finally {
                lock.unlock();
            }
        }

    CopyOnWriteArrayList的缺点

    底层是数组,删除插入的效率不高,写的时候需要复制,占用内存,浪费空间,如果集合足够大的时候容易触发GC

    数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。【当执行add或remove操作没完成时,get获取的仍然是旧数组的元素】。CopyOnWriteArrayList读取时不加锁只是写入和删除时加锁

    应用场景:读操作远大于写操作的时候

    CopyOnWriteArrayList和Collections.synchronizedList区别

    CopyOnWriteArrayList和Collections.synchronizedList是实现线程安全的列表的两种方式。两种实现方式分别针对不同情况有不同的性能表现,其中CopyOnWriteArrayList的写操作性能较差,而多线程的读操作性能较好。而Collections.synchronizedList的写操作性能比CopyOnWriteArrayList在多线程操作的情况下要好很多,而读操作因为是采用了synchronized关键字的方式,其读操作性能并不如CopyOnWriteArrayList。因此在不同的应用场景下,应该选择不同的多线程安全实现类。

    说一下ArrayList的扩容机制

    废话不多说,直接撸源码,红色的方法名代表会有解析

    无参构造方法

      /**
         * Constructs an empty list with an initial capacity of ten.
         */
        public ArrayList() {
        //其实就是空数组
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    
    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    底层的数组

      /**
         * The array buffer into which the elements of the ArrayList are stored.
         * The capacity of the ArrayList is the length of this array buffer. Any
         * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
         * will be expanded to DEFAULT_CAPACITY when the first element is added.
         */
        transient Object[] elementData; // non-private to simplify nested class access
    transient 这个关键字的用处是:ArrayList实现了Serializable接口,用transient修饰的字段或者对象不会进行实例化

    扩容是再添加元素时才会出现的情况,有的情况是不指定初始容量第一次添加元素时,直接看add()方法

        /**
         * Appends the specified element to the end of this list.
         *
         * @param e element to be appended to this list
         * @return <tt>true</tt> (as specified by {@link Collection#add})
         */
        public boolean add(E e) {
            //先将集合的大小加一,代表有一个元素要加进来,开口有没有它的容身之处
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            //将新元素添加到集合中
            elementData[size++] = e;
            return true;
        }
       

    跳转到ensureCapacityInternal方法中进行验证

     private void ensureCapacityInternal(int minCapacity) {
            //DEFAULTCAPACITY_EMPTY_ELEMENTDATA初始化的值,也就是空
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
             //如果是为空的话,默认的DEFAULT_CAPACITY=10传入的minCapacity哪个大取哪个
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            ensureExplicitCapacity(minCapacity);
        }    

    继续调用ensureExplicitCapacity方法,传入判断之后的值,第一次add的话这个就是默认的10

       private void ensureExplicitCapacity(int minCapacity) {
        //对集合操作的次数
            modCount++;
    
            // overflow-conscious code
            //传入的参数减去数组的长度是否大于0,大于0的话就代表要进行扩容了
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }

    判断传入的参数(第一次为10)减去数组的长度是否大于0,大于0的话调用grow扩容方法,数组的长度是elementData.length也可以说是容量,集合的大小是size,两个值是不同的

     /**
         * Increases the capacity to ensure that it can hold at least the
         * number of elements specified by the minimum capacity argument.
         *
         * @param minCapacity the desired minimum capacity
         */
        private void grow(int minCapacity) {
            // overflow-conscious code
         //旧的容量为当前数组的长度
            int oldCapacity = elementData.length;
         //新的容量为旧容量1.5倍,>>1代表右移一位,也就是÷2
            int newCapacity = oldCapacity + (oldCapacity >> 1);
         //新容量-旧容量是否小于0,一般是不指定容量,第一次add时才会进
            if (newCapacity - minCapacity < 0)
          //新容量等于传入的参数
                newCapacity = minCapacity;
          //如果新的容量超过了集合的阈值
            if (newCapacity - MAX_ARRAY_SIZE > 0)
          //调用hugeCapacity方法进行在一步的计算
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
          
        //底层的数组进行copy后长度变为新的容量
            elementData = Arrays.copyOf(elementData, newCapacity);
        }

    当新容量大于集合的阈值时,调用hugeCapacity方法

       private static int hugeCapacity(int minCapacity) {
        //为负数的话抛出异常,一般没这个可能
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
    //三元表达式:新容量大于集合容量阈值时,新的容量为Integer的最大阈值,否则为集合的阈值
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }
    MAX_ARRAY_SIZE的值其实为Integer.MAX_VALUE-8,为什么要减8呢?因为数组也是一个对象,对象需要一定的内存存储对象头信息,对象头信息最大占用内存不可超过8字节。

    整个ArrayList扩容的机制就如上所示,自己理解的,有不对之处还望指教

    你能自己重写一个ArrayList吗?

    这个就考验你对源码的理解程度,我自己对照着ArrayList写了一个,就是主要的增删改查还有扩容,写的比较拙劣,做个参考嘛

    package com.yjc.list;
    
    import java.util.AbstractList;
    import java.util.Arrays;
    import java.util.List;
    
    public class MyList<E> extends AbstractList<E> implements List<E> {
        //定义无参构造方法
        public MyList(){
            this.elementData=EMPTY_ELEMENT_DATA;
        }
    
        //定义带参构造方法
        public  MyList(int capacity){
            //验证容量是否合法
            if (capacity>0) {
                this.elementData = new Object[capacity];
            }else if(capacity==0){
                this.elementData=EMPTY_ELEMENT_DATA;
            }else{
                //为负数则抛出异常
                throw  new IllegalArgumentException("参数"+capacity+"不合法,参数不能为负数");
            }
        }
    
        //定义底层数据结构
        transient public   Object [] elementData;
    
        //定义初始化容量
       private static  final  Integer DEFAULT_CAPACITY=10;
       //集合的最大容量
       private  static final Integer MAX_CAPACITY=Integer.MAX_VALUE-8;
       //创建一个空的数组,
       private static final Object[] EMPTY_ELEMENT_DATA = {};
        //用于记录当前数组的大小
       private int size;
    
    
        public boolean add(E e) {
            ensureCapacityInternal(size+1);
            //将size+1空间判断是否够用
            elementData[size++]=e;
            return true;
        }
    
        //用于判断数组是否够用
       private void ensureCapacityInternal(Integer capacity){
            //代表是第一次添加数据
            if (elementData==EMPTY_ELEMENT_DATA){
                capacity=DEFAULT_CAPACITY;
            }
            if(capacity-elementData.length>0){
                //扩容
                grow(capacity);
            }
    
       }
    
        private void grow(Integer capacity) {
            //获取原数组长度
            int oldCapacity=elementData.length;
            //右移两位,相当于除以2
            int newCapacity=oldCapacity+(oldCapacity>>1);
            //不指定初始大小的时候,第一次执行add方法会走到这
            if (newCapacity-capacity<0){
                newCapacity=capacity;
            }
            //代表超过集合的最大容量
            if (newCapacity-MAX_CAPACITY>0){
                newCapacity=(capacity>MAX_CAPACITY)?Integer.MAX_VALUE:MAX_CAPACITY;
            }
            elementData= Arrays.copyOf(elementData,newCapacity);
        }
    
        @Override
        public E set(int index, E element) {
            checkIndex(index);
            Object oldValue=elementData[index];
            elementData[index]=element;
            //返回旧值
             return (E) oldValue;
        }
    
        @Override
        public E remove(int index) {
            checkIndex(index);
            Object oldValue=elementData[index];
            size--;
            for (int i = index; i <size-1; i++) {
                elementData[i]=elementData[i+1];
            }
            return (E) oldValue;
        }
    
        @Override
        public void add(int index, E element) {
            checkIndex(index);
            ensureCapacityInternal(size+1);
    
            for (int i = size+1; i> index; i--) {
              elementData[i]=elementData[i-1];
            }
            elementData[index]=element;
            size++;
    
        }
    
        @Override
        public int size() {
            return this.size;
        }
    
        @Override
        public E get(int index) {
            checkIndex(index);
            return (E) elementData[index];
        }
         private   void checkIndex(int index){
         //验证下标是否正确
         if ((index>=size)||(index<0)){
             throw  new IndexOutOfBoundsException("输入的下标不正确,当前集合大小为:"+size);
         }
     }
    }
  • 相关阅读:
    java中的接口
    java中的多态
    java中的继承
    抽象和封装
    表单验证
    13、迭代器与生成器
    10、end关键字和Fibonacci series: 斐波纳契数列
    9、字典
    8、元组
    2、Python_Day_1_作业
  • 原文地址:https://www.cnblogs.com/rzbwyj/p/12501818.html
Copyright © 2020-2023  润新知