zoukankan      html  css  js  c++  java
  • ArrayList源码分析

    ArrayList集合是Collection和List接口的实现类。底层的数据结构是数组。
    数据结构特点 : 增删慢,查询快。线程不安全的集合!
    开发的时候,不建议无脑选用ArrayList
    ArrayList的特点:
    单列集合 : 对应与Map集合来说【双列集合】
    有序性 : 存入的元素和取出的元素是顺序是一样的
    元素可以重复 : 可以存入两个相同的元素
    含带索引的方法 : 数组与生俱来含有索引【下角标】

    ArrayList的数据结构源码分析
    //空的对象数组 
    private static final Object[] EMPTY_ELEMENTDATA = {}; 
    //默认容量空对象数组,通过空的构造参数生成ArrayList对象实例 
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //ArrayList对象的实际对象数组! transient Object[] elementData; // non-private to simplify nested class access //1、为什么是Object类型呢?利用面向对象的多态特性,当前ArrayList的可以存储任意引用数据类 型。 //2、ArrayList有一个问题,不能存储基本数据类型!就是数组的类型是Object类型

    ArrayList默认容量&最大容量

    //默认的初始化容量是10
    private static final int DEFAULT_CAPACITY = 10; 
    //大容量 : 2^31 - 1 - 8 = 21 4748 3639【21亿】 
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    为什么最大容量要-8呢?
    目的是为了存储ArrayList集合的基本信息,比如list集合的最大容量!

    为什么ArrayList查询快,增删慢?
    ArrayList的底层数据结构就是一个Object数组,一个可变的数组,对于其的所有操作都是通过数组来实
    现的。
    数组是一种,查询快、增删慢!
    查询数据是通过索引定位,查询任意数据耗时均相同。查询效率贼高!
    删除数据时,要将原始数据删除,同时后面的每个数据迁移。删除效率就比较低!
    新增数据,在添加数组的位置加入数组,同时在数组后面位置后移以为!添加效率极低!

     

    ArrayList初始化容量
    ArrayList底层是数组,动态数组!
    底层是Object对象数组,数组存储的数据类型是Object,数组名字为elementData。 
    transient Object[] elementData;
    创建ArrayList对象分析:无参数
    创建ArrayList的之后,ArrayList容量是多少呢?回答10是错误的!回答0是正确【限定条件,在
    JDK1.8中】
    如何 初始化 动态数组的容量?10个
    构造方法
    /** 
    * Constructs an empty list with an initial capacity of ten. 
    */ 
    //初始化的ArrayList的容量,是10个! 
    public ArrayList() { 
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 
    }
    //空数组! 
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    在执行add()方法的时候初始化!【懒加载】
    判断当前数组的容量是否有存储空间,如果没有初始化一个10的容量。 

     

    创建ArrayList对象分析:带有初始化容量构造方法
    //创建ArrayList集合,并且设置固定的集合容量
    public ArrayList(int initialCapacity) {
        //initialCapacity 手动设置的初始化容量
        if (initialCapacity > 0) {//判断容量是否大于0,如果大于0
            //创建一个对象数组位指定容量大小,并且交给ArrayList对象
            this.elementData = new Object[initialCapacity];
            //如果设置的容量为0,设置默认数组
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;//默认的元素数据数组{}
        } else {
            //如果不是0,也不是大于0的数,会抛出非法参数异常!
            throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
        }
    }
    注意 : 使用ArrayList的集合,建议如果知道集合的大小,最好提前设置。提示集合的使用效率!
    ArrayList扩容原理
    add方法先要确保数组的容量足够,防止数组已经填满还往里面添加数据造成数组越界:
    1. 如果数组空间足够,直接将数据添加到数组中
    2. 如果数组空间不够了,则进行扩容。扩容1.5倍扩容。
    3. 扩容 : 原始数组copy新数组中,同时向新数组后面加入数据
    注意 : new的ArrayList的对象没有容量的,在第一次添加的add,会进行第一次扩容。0 -> 10! 
    总结:
    1. 扩容的规则并不是翻倍,是原来容量的1.5倍
    2. ArrayList的数组最大值Integer.MAX_VALUE。不允许超过这个最大值
    3. 新增元素时,没有严格的数据值的检查。所有可用设置null 
    ArrayList线程安全问题及解决方案
    错误复现
    ArrayList 我们都知道底层是以数组方式实现的,实现了可变大小的数组,它允许所有元素,包括null。
    看下面一个例子:开启多个线程操作List集合,向ArrayList中增加元素,同时去除元素

     

     

     

     

    运行代码结果可知,会出现以下几种情况:
    ①打印null
    ②某些线程并未打印
    ③数组角标越界异常 

     

     

    导致ArrayList线程不安全的源码分析
    ArrayList成员变量

     

     

    ArrayList的Object的数组存所有元素。
    size变量保存当前数组中元素个数。
    出现线程不安全源码之一 : add()方法 
    add添加元素,实际做了两个大的步骤:
    1. 判断elementData数组容量是否满足需求
    2. 在elementData对应位置上设置值
    线程不安全的隐患【1】,导致③数组下标越界异常 
    线程不安全的隐患【2】,导致①Null、②某些线程并未打印 
     

     

     

    由此我们可以得出,在多线程情况下操作ArrayList 并不是线性安全的。
    那如何解决呢?
    3.3 解决方案
    第一种方案:使用Vector集合,Vector集合是线程安全的
    //线程安全问题解决方案1 
    protected static Vector<Object> vector = new Vector<>();
    第二种方案:使用Collections.synchronizedList。它会自动将我们的list方法进行改变,最后返回给我们
    一个加锁了List
    //线程安全问题解决方案2 
    //将集合改为同步集合 
    protected static List<Object> synList = Collections.synchronizedList(arrayList);
    第三种方案:使用JUC中的CopyOnWriteArrayList类进行替换。【】 
    //线程安全问题解决方案3 JUC 【最佳选择】 
    protected static CopyOnWriteArrayList<Object> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
    ArrayList的Fail-Fast机制深入理解
    什么是Fail-Fast机制?
    "快速失败"即Fail-Fast机制,它是Java中一种错误检测机制!
    当多钱程对集合进行结构上的改变,或者在迭代元素时直接调用自身方法改变集合结构而没有通知迭代
    器时,有可能会触发Fail-Fast机制并抛出异常【ConcurrentModificationException】。注意,是有可能
    触发Fail-Fast,而不是肯定!
    触发时机 : 在迭代过程中,集合的结构发生改变,而此时迭代器并不知情,或者还没来得及反应,便会
    产生Fail-Fast事件。
    再次强调,迭代器的快速失败行为无法得到保证!一般来说,不可能对是否出现不同步并发修改,或者
    自身修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。
    Java.util包中的所有集合类都是快速失败的,而java.util.concurrent包中的集合类都是安全失败的;快
    速失败的迭代器抛出ConcurrentModificationException,而安全失败的迭代器从不抛出这个异常。 
    ArrayList的Fast-Fail事件复现及解决方案

     

     

     

     

     

  • 相关阅读:
    spring框架里面处理中文匹配
    日常问题记录--使用fiddler自动响应jsonp结构的响应
    linux命令--pamp
    每天一个linux命令--nice命令
    阿里RAP+fiddler实现app原生应用的cgi数据mock----- (二)添加mock规则,随机返回4中类型(不同长度)的数据
    父子组件之间传递数据
    redux-API(二)
    redux数据流
    Redux 的基础概念-API
    react-redux要点梳理
  • 原文地址:https://www.cnblogs.com/Alwaysbecoding/p/14395821.html
Copyright © 2011-2022 走看看