• List集合解析:ArrayList、Vector、LinkedList


    ArrayList

    ArrayList是基于数组的实现,支持随机访问,可以动态扩容

    初始化:

    • 可以用带参的构造函数指定初始值,如果传入的参数是正数就创建指定容量的数组,如果是0就创建一个空数组,如果是负数,就抛出异常

    • 默认大小是10

    添加元素:

    • 添加元素时使用ensureCapacityInternal() 方法来保证容量足够,如果不够需要使用grow扩容,扩容的时候每扩容原数组容量的1.5倍

    • 每次添加的时候都会修改modCount的值。modCount++

    • 使用Arrays.copyof()把原数组的元素复制到新的数组中。

    • 扩容1.5倍

      • 如果新的容量小于需要的最小容量size+1,新容量就取最小容量size++(这种情况就是旧容量只有1,新容量1.5向下取模是1,小于添加元素后的最小容量2,这个时候新容量是size+1=2)

      • 如果新的容量大于MAX_ARRAY_SIZE=Integer.MAX_VALUE-8

        • 再判断需要的最小容量是否大于MAX_ARRAY_SIZE,如果是,新容量取值:Integer.MAX_VALUE,否则,新的容量取值:MAX_ARRAY_SIZE

      总结:

      最小容量(a) 1.5倍扩容后的新容量(b) MAX_ARRAY_SIZE(c) Integer.MAX_VALUE(d)

      if b<a 新容量=a;

      if b>c if a>c 新容量=d

      else 新容量=c

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

      

    删除元素:

    • 按照下标删除元素

      • 如果下标超出size范围,抛出异常IndexOutOfBoundsException

      • modCount++;

      • 判断要删除的元素是否在数组的结尾,如果在数组的结尾,就不需要调用System.arraycopy(),否则就需要调用System.arraycopy,通过numMoved=size-index-1判断

        • numMoved==0,是在数组结尾,elementData[--size] = null;

        • numMoved>0不是在结尾System.arraycopy(elementData,index+1,elementData,index,numMoved);

      • 返回删除的元素

      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;
      }
    • 根据元素进行删除

      • 遍历element数组,把第一个用equals判断相等的元素删除(如果要删除的元素是null,使用==判断),确定待删除元素的index,然后调用私有方法fastremove()跳过边界检查直接删除index位置上的元素,并且返回true;如果元素不存在返回false.

      public boolean remove(Object o) {
          if (o == null) {
              for (int index = 0; index < size; index++)
                  if (elementData[index] == null) {
                      fastRemove(index);
                      return true;
                  }
          } else {
              for (int index = 0; index < size; index++)
                  if (o.equals(elementData[index])) {
                      fastRemove(index);
                      return true;
                  }
          }
          return false;
      }

    System.arraycopy()和Arrays.copyOf()的区别

    • System.arraycopy():native方法,使用System.arraycopy()进行数组的拷贝非常灵活, 可以选择拷贝的原数组的起点和长度, Arrays.copyOf()就是使用System.arraycopy()实现的

      arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

    • Arrays.copyof():Arrays.copyOf(original, length) 将源数组original中的元素拷贝到一个新数组中并返回这个新数组, 新数组的长度是length。copyOf(int[] original, int newLength)

    modCount的作用:

    • modeCount用来记录ArrayList结构性变化的次数,包括添加元素和删除元素,在进行序列化或迭代的时候,需要比较操作前后的modCount是否改变,如果改变了就会抛出ConcurrentModificationException,这种迭代称为Fail-fast模式,常见的的使用fail-fast方式遍历的容器有HashMapArrayList等。

    • Fail-safe模式:这种遍历基于容器的一个克隆。因此,对容器内容的修改不影响遍历。常见的的使用fail-safe方式遍历的容器有ConcerrentHashMapCopyOnWriteArrayList

    Vector

    • 使用synchronized同步,单步操作是线程安全的

    • 可以通过构造函数传入初始容量和每次扩容的倍数,如果传入0和默认情况都是两倍扩容

    • Vector与ArrayList的比较

      • Vector 是同步的,开销就比 ArrayList 大,访问速度更慢,最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制;

      • Vector 每次扩容请求其大小的 2 倍(也可以通过构造函数设置增长的容量),而 ArrayList 是 1.5 倍。

      • 替代:可以使用 concurrent 并发包下的CopyOnWriteArrayList 类替代Vector实现同步

    CopyOnWriteArrayList:

    通过读写分离实现了并发操作的安全性

    • 读写分离:写操作在一个复制的数组上进行,读操作还是在原始数组中进行,读写分离,互不影响。

      • 写操作需要加锁,防止并发写入时导致写入数据丢失。

      • 写操作结束之后需要把原始数组指向新的复制数组。

    • 适用场景:适用于读多写少的场景

    • 性能分析:写操作每次都要拷贝数组,耗费内存;不能用于实时读的场景,因为写操作复制数组新增元素需要耗费时间,虽然能到达最终一致性,但是读写同时发生的时候读到的还是旧的数据。

    LinkedList

    • 基于双向链表的实现,使用Node表示链表节点信息。不支持随机访问,但是新增节点和删除节点的效率比ArrayList高

    • 与 ArrayList 的比较

      ArrayList 基于动态数组实现,LinkedList 基于双向链表实现。ArrayList 和 LinkedList 的区别可以归结为数组和链表的区别:

      • 数组支持随机访问,但插入删除的代价很高,需要移动大量元素;

      • 链表不支持随机访问,但插入删除只需要改变指针。

     

  • 相关阅读:
    linux less命令用法
    Spark-RDD 模型 以及运行原理
    Spark 架构原理介绍 以及 job、task、stag 概念
    Kafka 基本概念以及框架介绍
    负载均衡- TCP/ IP 基础知识
    JAVA多线程和并发基础面试题
    Java并发使用--线程池
    Java基础--并发编程
    事务实现原则(一) 事务的改变以及JDBC事务的设计
    Spark RDD Transformation和Action
  • 原文地址:https://www.cnblogs.com/learnjavajava/p/14893609.html
Copyright © 2020-2023  润新知