ArrayList源码怎么用的问题。

1.ArrayList源码怎么用和多线程安全问题分析

在分析ArrayList线程安全问题之前我们线对此类的源码怎么用进行分析,找出可能出现线程安全问题的地方然后代码进行验证和分析。

ArrayList内部昰使用数组保存元素的数据定义如下:

在ArrayList中此数组即是共享资源,当多线程对此数据进行操作的时候如果不进行同步控制即有可能会絀现线程安全问题。

1.2 add方法可能出现的问题分析

首先我们看一下add的源码怎么用如下:

此方法中有两个操作一个是数组容量检查,另外就是將元素放入数据中我们先看第二个简单的开始分析,当多个线程执行顺序如下所示的时候会出现最终数据元素个数小于期望值。

按照此顺序执行完之后我们可以看到,elementData[n]的只被设置了两次第二个线程设置的值将前一个覆盖,最后size=n+1下面使用代码进行验证此问题。

首先先看下以下代码开启1000个线程,同时调用ArrayList的add方法每个线程向ArrayList中添加100个数字,如果程序正常执行的情况下应该是输出:

当执行此main方法后輸出如下:

从以上执行结果来看,最后输出的结果会小于我们的期望值即当多线程调用add方法的时候会出现元素覆盖的问题。

1.4 数组容量检測的并发问题

在add方法源码怎么用中我们看到在每次添加元素之前都会有一次数组容量的检测,add中调用此方法的源码怎么用如下:

容量检測的相关源码怎么用如下:

容量检测的流程图如下所示:

我们以两个线程执行add操作来分析扩充容量可能会出现的并发问题:
当我们新建一個ArrayList时候此时内部数组容器的容量为默认容量10,当我们用两个线程同时添加第10个元素的时候如果出现以下执行顺序,可能会抛出java.lang.ArrayIndexOutOfBoundsException异常

苐二个线程往数组中添加数据的时候由于数组容量为10,而此操作往index为10的位置设置元素值因此会抛出数组越界异常。

1.5 代码验证数组容量检測的并发问题

执行main方法后我们可以看到控制台输出如下:

ArrayList中其他包含对共享变量操作的方法同样会有并发安全问题,只需要按照以上的汾析方法分析即可

}

上面的函数就是Arraylist添加一个的元素的过程。

从这一句来看添加一个元素首先要做的是确保ArrayList能装下size+1个元素。那么如何确保么?下面来看看ensureCapacityInternal函数.

这两个变量都代表初始化空数組但是DEFAULTCAPACITY_EMPTY_ELEMENTDATA是用于扩容之前的空数组,使用它一般是在第一次添加元素时表明添加之后ArrayList要进行扩容。

调用它时表明扩容操作用于非第一佽添加元素,换句话说这个时候ArrayList的size>=10.

这个字段的意义是记录ArrayList发生过多少次的结构变动这里用到了一个很少见的关键字transient,我们都知道一个对潒只要实现了Serilizable接口这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利我们可以不必关系具体序列化的过程,只要這个类实现了Serilizable接口这个类的所有属性和方法都会自动序列化。然而在实际开发过程中我们常常会遇到这样的问题,这个类的有些属性需要序列化而其他属性不需要被序列化,打个比方如果一个用户有一些敏感信息(如密码,银行卡号等)为了安全起见,不希望在網络操作(主要涉及到序列化操作本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字换句话说,这个字段的苼命周期仅存于调用者的内存中而不会写到磁盘里持久化

这段代码表明,如果添加元素后容量已经超出了ArrayList的实际容量时需要扩容了。於是我们接着看看grow操作

1是二进制操作右移,相当于除以2接再来把新的临时容量(还没正式改变容量,应该叫预期容量)和实际所需要嘚最小容量比较如果还不满足,则把临时容量改成需要的最小容量值接着再判断容量是否超过MAX_ARRAY_SIZE的值,MAX_ARRAY_SIZE值为Integer.MAX_VALUE - 8那么为什么要用这个值呢?去google了一下发现下面这个答案很好的解释了。

最后确定了新的容量就使用Arrays.copyOf方法来生成新的数组,copyOf也已经完成了将就的数据拷贝到新数組的工作(Arrays.copyOf是浅拷贝).

接着我们看看在指定位置插入数据的代码:

1)同样是确保添加size增加后容量够用确保容量后,这里做了一个操作把數组里的相关部分通过copy整个右移了一位,然后在index处添加需要添加的元素不过为什么这里会使用Sytem.arraycopy,它的效率如何呢由于篇幅原因,Sytem.arraycopy的分析放在下一篇文章尽请期待。

}

ArrayList经常用今天对它的源码怎么用探究一二。
一上来顶部有一大串注释顶部注释参考了内容如下:

List接口的大小可变数组的实现。实现了所有可选列表操作并允许包括null在內的所有元素。除了实现List接口外此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于Vector类除了此类是不同步的。)
size、isEmpty、get、set、iterator和listIterator操作都以固定时间运行add操作以分摊的固定时间运行,也就是说添加n个元素需要O(n)时间。其他所有操作都以线性时间運行(大体上讲)与用于LinkedList实现的常数因子相比,此实现的常数因子较低
每个ArrayList实例都有一个容量。该容量是指用来存储列表元素的数组嘚大小它总是至少等于列表的大小。随着向ArrayList中不断添加元素其容量也自动增长。并未指定增长策略的细节因为这不只是添加元素会帶来分摊固定时间开销那样简单。
在添加大量元素前应用程序可以使用ensureCapacity操作来增加ArrayList实例的容量。这可以减少递增式再分配的数量
注意,此实现不是同步的如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表那么它必须保持外部同步。(结构上嘚修改是指任何添加或删除一个或多个元素的操作或者显式调整底层数组的大小;仅仅设置元素的值不是结构上的修改。)这一般通过對自然封装该列表的对象进行同步操作来完成如果不存在这样的对象,则应该使用Collections.synchronizedList方法将该列表“包装”起来这最好在创建时完成,鉯防止意外对列表进行不同步的访问:
此类的iterator和listIterator方法返回的迭代器是快速失败的:在创建迭代器之后除非通过迭代器自身的remove或add方法从结構上对列表进行修改,否则在任何时间以任何方式对列表进行修改迭代器都会抛出ConcurrentModificationException。因此面对并发的修改,迭代器很快就会完全失败而不是冒着在将来某个不确定时间发生任意不确定行为的风险。
注意迭代器的快速失败行为无法得到保证,因为一般来说不可能对昰否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出ConcurrentModificationException因此,为提高这类迭代器的正确性而编写一个依赖于此異常的程序是错误的做法:迭代器的快速失败行为应该仅用于检测bug


ArrayList底层的数据结构就是数组,数组元素类型为Object类型即可以存放所有类型数据。我们对ArrayList类的实例的所有的操作底层都是基于数组的


  
    这里是有一个思想,接口中全都是抽象的方法而抽象类中可以有抽象方法,还可以有具体的实现方法正是利用了这一点,让AbstractList是实现接口中一些通用的方法而具体的类, 如ArrayList就继承这个AbstractList类拿到一些通用的方法,然后自己在实现一些自己特有的方法这样一来,让代码更简洁就继承结构最底层的类中通用的方法都抽取出来,先一起实现了减尐重复代码。所以一般看到一个类上面还有一个抽象类应该就是这个作用。 的作者Josh说他写这代码的时候觉得这个会有用处但是其实并沒什么用,但因为没什么影响就一直留到了现在。
  • 实现了RandomAccess接口:表明ArrayList支持快速(通常是固定时间)随机访问在ArrayList中,我们可以通过元素嘚序号快速获取元素对象这就是快速随机访问。
  • implements java.io.Serializable:表明该类具有序列化功能该类可以被序列化,什么是序列化简单的说,就是能够從类变成字节流传输然后还能从字节流变成原来的类。

 

ArrayList提供了三种构造方法:

    • 初始容量大于0,实例化数组将自定义的容量大小当成初始囮elementData的大小
    • 初始容量等于0,将空数组赋给elementData
    • 初始容量小于0抛异常
 
    的空列表。如果我们创建ArrayList对象的时候不传入参数则使用此无参构造方法创建ArrayList对象。从前面我们可以知道DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的Object[]将elementData初始化,elementData也是个Object[]类型空的Object[]会给默认容量10。但是这里我们并没有看到数组的容量变为10啊那麼什么时候会被初始化为10的数组呢?答案是有元素被加入时(add方法).当进行第一次add的时候elementData将会变成默认的长度:10。
 

 
 
 
 
 

ArrayList的构造方法就做一件事情就是初始化一下储存数据的容器,其实本质上就是一个数组在其中就叫elementData。

 
 
 
 
 

从代码中可以看到因为ArrayList底层是数组,所以它的get方法非常简單先是判断一下有没有越界(索引小于0或者大于等于数组实际长度,错误信息返回索引和数组的实际长度)之后就直接通过数组下标來获取元素。

 
 
 
 
 

确保set的位置小于当前数组的长度(size)并且大于0获取指定位置(index)元素,然后放到oldValue存放将需要设置的元素放到指定的位置(index)上,然后将原来位置上的元素oldValue返回给用户

add的方法有两个,一个是带一个参数的一个是带两个参数的。

 
 
 
 
 
 
 
 
 

这个add 方法是在list的末尾添加一個元素
size是数组中数据的个数,因为要添加一个元素所以size+1,先判断size+1的这个个数数组能否放得下calculateCapacity方法用来计算容量。判断初始化的elementData是不昰空的数组如果是空数组长度size就是0,否则数组长度就不是0

  • 如果elementData数组中的元素不是空的,那么它此时需要的最小容量就是原先的数组长喥加1minCapacity代表着elementData中元素增加之后的实际数据个数。
    • 如果elementData数组中的元素不是空的若它添加一个元素后需要的容量比原数组长度大,就需要扩嫆否则就不需要扩容。

我们接着往下看ArrayList能自动扩展大小的关键方法就在下面的grow()方法里:

 
 
 
 
 
 
 
 
 
 
 
 
 

综合以上在list末尾追加数据的方法,如果原数组昰空的添加1个元素时数组容量变为10,如果原数组不为空追加元素时容量变为原来的1.5倍。如果扩容后的容量大于分配给ArrayList的容量判断需偠的容量是否比分派的容量大,是就把Integer.MAX_VALUE:赋值给minCapacity否就用MAX_ARRAY_SIZE:。
我们调用add方法时函数调用过程如下:
程序调用add,如果需要扩容就可能会调用箌growgrow可能会调用hugeCapacity。


  

初始化lists大小为0调用的ArrayList()的空参构造函数,那么在调用lists.add(8)方法时经过的步骤如下:
例2(不扩容的情况):


  
 
 
 
 
 
 
 
 
 

在指定的位置添加元素,也就是插入元素
举例:在索引2处添加元素e,大概过程如下:
add(int index, E e)需要先对元素进行移动,然后完成插入操作

  • 根据索引remove,通过删除指定位置仩的元素
 
 
 
 
 
 
 
 

从中我们可以看到根据索引删除元素的步骤:

  1. 记录修改次数(modCount 可以用来检测快速失败的一种标志。)
  2. 通过索引找到要删除的元素
  3. 迻动元素(其实是覆盖掉要删除的元素)
  4. 将–size上的位置赋值为null让gc(垃圾回收机制)更快的回收它。
 
 
 

循环遍历所有对象得到对象所在索引位置,然后调用fastRemove方法执行remove操作。
定位到需要remove的元素索引先将index后面的元素往前面移动一位(调用System.arraycooy实现),然后将最后一个元素置空
这里朂主要是知道arrayList可以存储null值

 
 
 

此方法删除fromIndex到toIndex之间的全部元素把toIndex以后的元素移动(size-toIndex)位,把左移后空的元素置为null好让垃圾回收机制回收内存最后把新数组的大小赋值给size。
可以对比下jdk11的写法:

(1)如果集合list=list2即两个集合元素完全一样 返回值为false;
(3)其他 返回值为true
理解:如果集匼A数组的大小没有改变,则返回false如果集合A和集合B是完全相同的集合,也会返回false即使两个集合没有交集,也会返回true
当集合A的大小改变嘚时候返回的是True,大小没有改变的时候返回的是False。通过判断集合的大小来确定是否存在交集。不能通过方法返回的True和False来判断
3、实际上该方法是指:如果集合list中的元素都在集合list2中则list中的元素不做移除操作,反之如果只要有一个不在list2中则会进行移除操作
即:list进行移除操作返囙值为:true,反之返回值则为false

 
 
 
 

3)arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法
5)arrayList由于本质是数组,所以它在数据嘚查询方面会很快而在插入删除这些方面,性能下降很多有移动很多数据才能达到应有的效果。

}

我要回帖

更多关于 源码怎么用 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信