浅谈List

本文深入探讨了Java中的List集合,重点关注ArrayList、LinkedList和CopyOnWriteArrayList。ArrayList基于数组实现,提供快速访问但插入删除较慢;LinkedList采用链表结构,插入删除高效但访问慢;CopyOnWriteArrayList在并发环境下确保线程安全,适合读多写少的场景。
摘要由CSDN通过智能技术生成

1. List集合

1.1 ArrayList

1.11 基本特点
  • ArrayList是一个实现了List接口的可变数组

  • 可以插入null

  • 它的size, isEmpty, get, set, iterator,add这些方法的时间复杂度是O(1),如果add n个数据则时间复杂度是O(n).

  • ArrayList不是synchronized的,所以线程不安全

  • 关注点结论
    ArrayList是否为空允许
    ArrayList是否允许重复数据允许
    ArrayList是否有序有序
    ArrayList是否线程安全非线程安全
1.12 源码分析
  • 构造函数

      //默认容量
        private static final int DEFAULT_CAPACITY = 10;
      //空数组
        private static final Object[] EMPTY_ELEMENTDATA = {};
      //默认空数组
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
      //存放数据的数组
        transient Object[] elementData
    
        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);
          }
      }
    
      public ArrayList() {
          this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
      }
    
      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;
          }
      }
  • add方法

    1. 去add后的大小和默认值(10)的最大值
    2. 查看这个最大值是否超过了数组的长度
    3. 超过则扩容,newCapacity = oldCapacity + (oldCapacity >> 1)
    4. 判断newCapacity 是否超过数组长度最大值,超过newCapacity 则取Integer.MAX_VALUE
    5. 创建新数组 进行复制
      public boolean add(E e) {
          ensureCapacityInternal(size + 1);  // Increments modCount!!
          elementData[size++] = e;
          return true;
      }
    
      private void ensureCapacityInternal(int minCapacity) {
          if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
              minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
          }
    
          ensureExplicitCapacity(minCapacity);
      }
    
      private void ensureExplicitCapacity(int minCapacity) {
          modCount++;
    
          // overflow-conscious code
          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);
      }
    
    //数组所能允许的最大长度;如果超出就会报`内存溢出异常` -- 可怕后果就是宕机
    //根本作用在于只是为了避免一些机器内存溢出,是否-8其实并不重要,实际最大长度还是Integer.MAX_VALUE
    //之所以要-8是因为有些版本的虚拟机会保留8个字节长度的header,下面以HotSpot为例
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
      private static int hugeCapacity(int minCapacity) {
          if (minCapacity < 0) // overflow
              throw new OutOfMemoryError();
         //注意:最大容量是 Integer.MAX_VALUE 而不是 MAX_ARRAY_SIZE=Integer.MAX_VALUE-8
          return (minCapacity > MAX_ARRAY_SIZE) ?
              Integer.MAX_VALUE :
              MAX_ARRAY_SIZE;
      }
  • remove方法

    直接使用System.arraycopy把需要删除index后面的都往前移一位然后再把最后一个去掉

    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;
    }
1.13 ArrayList的优缺点
  • 优点:
    1. ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,因此查找也就是get的时候非常快
    2. ArrayList在顺序添加一个元素的时候非常方便,只是往数组里面添加了一个元素而已
  • 缺点:
    1. 删除元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能
    2. 插入元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能
1.14 ArrayList和Vector的区别
  • 区别:ArrayList是线程非安全的,Vector是线程安全的;扩容方式有区别,Vector里有个增长系数参数,可以构造方法中设置,如果没有设置则默认为0。在扩容的时候如果这个增长系数为0,则新容量是旧容量的两倍,否则就是旧容量加上这个增长系数作为新容量

  • 解决线程安全性的办法:

    1. Collections.synchronizedList方法把你的ArrayList变成一个线程安全的List,比如:

      List<String> synchronizedList = Collections.synchronizedList(list);
      synchronizedList.add("aaa");
      synchronizedList.add("bbb");
      for (int i = 0; i < synchronizedList.size(); i++){
       System.out.println(synchronizedList.get(i));
      }
    2. 使用Vector

1.15 为什么ArrayList的elementData是用transient修饰
  • 因为序列化ArrayList的时候,ArrayList里面的elementData未必是满的,所以没有必要序列化整个elementData呢,因此ArrayList中重写了writeObject方法:
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
  • 每次序列化的时候调用这个方法,先调用defaultWriteObject()方法序列化ArrayList中的非transient元素,elementData不去序列化它,然后遍历elementData,只序列化那些有的元素,这样:
    1. 加快了序列化的速度
    2. 减小了序列化之后的文件大小

1.2 LinkedList

双向链表

1.21 与ArrayList对比
  • ArrayList:数组形式访问List链式集合数据,元素可重复,访问元素较快
  • LinkedList:链表方式的List链式集合,元素可重复,元素的插入删除较快
1.22 特性
关注点结论
LinkedList是否允许空允许
LinkedList是否允许重复数据允许
LinkedList是否有序有序
LinkedList是否线程安全非线程安全
1.23 源码分析
  • 查找使用二分法
    /**
     * Returns the (non-null) Node at the specified element 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;
        }
    }

1.3 CopyOnWriteArrayList

java.util.concurrent包下的并发安全类,任何可变操作都伴随着copy动作

1.31 特性
关注点结论
CopyOnWriteArrayList是否允许为空允许
CopyOnWriteArrayList是否允许重复数据允许
CopyOnWriteArrayList是否有序有序
CopyOnWriteArrayList是否为线程安全线程安全
1.32 源码分析
  • 实例化CopyOnWriteArrayList

    public class CopyOnWriteArrayList<E>
      implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
      private static final long serialVersionUID = 8673264195747942595L;
    
      /** The lock protecting all mutators */
      transient final ReentrantLock lock = new ReentrantLock();
    
      /** The array, accessed only via getArray/setArray. */
      private volatile transient Object[] array;    
      ...
    
      public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }
    final void setArray(Object[] a) {
        array = a;
    }
    }

    对于CopyOnWriteArrayList来说,底层就是一个Object[] array,然后实例化一个CopyOnWriteArrayList

  • 添加操作(add)

    public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
      Object[] elements = getArray();
      int len = elements.length;
      Object[] newElements = Arrays.copyOf(elements, len + 1);
      newElements[len] = e;
      setArray(newElements);
      return true;
    } finally {
      lock.unlock();
    }
    }

    步骤:

    1. 加锁
    2. 拿到原数组,得到新数组的大小(原数组大小+1),实例化出一个新的数组来
    3. 把原数组的元素复制到新数组中去
    4. 新数组最后一个位置设置为待添加的元素(因为新数组的大小是按照原数组大小+1来的)
    5. 把Object array引用指向新数组
    6. 解锁
1.33 优缺点及使用场景
  • 缺点:修改代价十分昂贵,每次修改都伴随着一次的数组复制
  • 优点:在并发下不会产生任何的线程安全问题,也就是绝对的线程安全
    • 读写分离:取CopyOnWriteArrayList的时候读取的是CopyOnWriteArrayList中的Object[] array,但是修改的时候,操作的是一个新的Object[] array,读和写操作的不是同一个对象,这就是读写分离
    • 最终一致:对CopyOnWriteArrayList来说,线程1读取集合里面的数据,未必是最新的数据。因为线程2、线程3、线程4四个线程都修改了CopyOnWriteArrayList里面的数据,但是线程1拿到的还是最老的那个Object[] array,新添加进去的数据并没有,所以线程1读取的内容未必准确。不过这些数据虽然对于线程1是不一致的,但是对于之后的线程一定是一致的,它们拿到的Object[] array一定是三个线程都操作完毕之后的Object array[],这就是最终一致。
  • 使用场景:CopyOnWriteArrayList中元素的增加,CopyOnWriteArrayList的修改代价将越来越昂贵,因此,CopyOnWriteArrayList适用于读操作远多于修改操作的并发场景中
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值