为了春招,系统地复习一下Java相关的知识,应该不会写源码解析,完全是给自己看的,如果足够幸运,有幸进入大厂,重新写下给别人看的
注意点
- ArrayList不是线程安全的,如果要在多线程情况下使用,使用
List list = Collections.synchronizedList(new ArrayList(...));
或者Vector
,但二者都不好,最好使用JUC下的copyonwriteArrayList
; - ArrayList底层是通过数组实现的,这里有一个注意点,这个底层数组有一个修饰符
transient
,这个关键字一般很难见到,但以防万一,还是看看,这篇博客 - ArrayList源码有一些默认值需要注意一下:
- 底层数组默认长度(不是存储元素的个数,了解一下容量和长度的区别) 是 10 ;
- ArrayList的扩容是监测到底层数组满了以后才扩容,没有负载因子
- ArrayList的扩容大小是原数组的1.5倍,至于为什么是1.5倍,具体是数学方面的预估,总之1.5倍能有较好的性能和较少的内存消耗
- 上面说到了注意ArrayList集合的长度和容量的区别,其实也没什么,集合的长度是集合中存储的元素的个数,而容量是底层数组的长度,二者不同的主要原因是每次扩容后的大小是原来的1.5倍,最坏的情况,在扩容后只用了一个扩展后的空间,这样浪费了很多空间.
- 这里有一个小知识,就是ArrayList的
trimToSize()
方法,可以将容量缩减到集合长度,删掉多余的空间
- 这里有一个小知识,就是ArrayList的
- 动态数组的实现无非是通过数组复制来扩容,这里介绍一下ArrayList的数组复制方法.这个方法是
Arrays.copyof()
,而这只是开始,点进去这个方法,发现这个方法使用的是System.arraycopy()
,然后再点进去,这个方法是System类下的一个native方法,这才知道这个方法是本地方法,是用c语言实现的,那就先这样吧. - 前面简单说了一下ArrayList是线程不安全的,然后说了一下解决办法,现在再说一下ArrayList中一个用于并发控制的变量modcount,其实不止ArrayList,其他集合类中都有这个变量.
- 顾名思义,modcount就是修改次数的意思,在ArrayList中,这个变量的改变发生在添加元素,删除元素,清空等等情况下
- 这个变量怎么和并发有关呢?ArrayList中的内部类
Itr
中有一个expectedModCount
,这个值初始值就是当时ArrayList的modcount值,而Itr是一个迭代器,这样其实就很好想了,就是迭代器在使用时,也就是说一般的遍历时,如果这个ArrayList发生了修改操作,造成modcount != expectedModCount
这个时候就发生了并发错误,也会报错,抛出的异常是ConcurrentModificationException
- 这个异常也有一定的说法,这又要说到Java的快速失败机制–fail-fast,这个是源码中对快速失败的解释
注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭代器的快速失败行为应该仅用于检测 bug。
简单来说就是当多个线程对一个ArrayList(集合类)进行操作时,对有可能发生的并发错误抛出ConcurrentModificationException
,详细的可以看这篇博客
- 上面说的是遍历时的并发,但是更多情况下ArrayList的并发问题是多线程使用时扩容和数据不一致引起的.因为ArrayList中所有操作都没有加锁,所以在多线程操作中数据不一致情况是很常见的,而且是不可预见的.其次是扩容的问题,这个也比较容易想像.总之多线程情况下,我能想象到的就是,如果对一个ArrayList不做任何修改,只是查询,应该是没有问题的,除此以外,都不要使用,可以使用Vector
- 最后说一下另一个线程安全的替代类CopyOnWriteArrayList,这个集合类的核心概念就是,任何对于底层数组的操作,都会copy当前数组,然后在复制的数组上修改,最后再覆盖.其实就是写时复制的理念,学过操作系统这门课都知道进程的写时复制,意思差不多.