上次咱们讲了常用的几个Map的原理,接下来咱们接着探讨下Map的兄弟,几个常用List的底层实现原理,首先要说的肯定是ArrayList吧,这用的多吧,来来来,上干货。
首先明确一点,Arraylist是基于动态数组实现的,就如同他名字一样,Array=数组,那动态数据是什么意思呢?我们都知道,数组一旦确定了大小是不可变的,动态数据说的通俗点就是可以变化长度的普通数组(其实底层实现还是数组,只不过用了复制转移等方法来实现数组长度变化),咱们接着看看Arraylist的底层数组到底是怎么操作的吧。
首先我们先看看Arraylist是怎么新加元素的吧add("111"),其实就是给数组里增加了一个元素,很简单如图:
插入元素呢add(2, "000"),我们基于数组实现的所以Arraylist是可以支持在指定下标插入元素的,整个过程是这样子的,比如我们要在数组下标2的位置插入一个000,他会首先把当前数组下标2位置以及之后的元素都往后面复制一遍(System.arraycopy方法),然后再把数组下标2的位置填上要插入的元素,底层的样子应该是这个样子的:
接着再来看看删除元素的时候是怎么操作的remove("333"),同样的道理在删除元素的下标位置之后的元素全部前移复制(System.arraycopy方法)最后一个元素会null,gc会把的null元素回收掉。
扩容呢,Arraylist如果初始不设置数组大小,默认的是10,当我们新增的数据超过10的时候就需要扩容,默认的扩容是1.5倍的扩容。细心的小伙伴又发现了,如果我本次存的数据是20大小怎么办,1.5倍=15不够了,是的没错,这里其实是个三目运算法,如果20大于15就取20作为这次扩容后的数组的大小,如果本次需要的小于15就取15作为本次扩容后的数组的大小,扩容完怎么办,要把原来数组里面的10个元素复制过来到新数组,就完了如图。
有人就问了,不能无限制的扩容吧,最大是多少,贴一段源码看看,第20行,当想扩容的大小.>最大数组长度,会调用一个hugeCapacity方法,用最小需要容量和最大数组长度比较,如果最小需要容量>最大数组长度则用Integer.MAX_VALUE作为数组长度,否则取最大数组长度作为新数组长度,有点绕,大家可以根据源码一起理解下。
Arraylist底层因为是数组,可以通过下标快速的访问查询等,但是,是不是发现问题了,每次插入元素,删除元素,或者扩容的时候,都要复制一波元素,是不是明白为啥说,删除,特殊点插入的效率就低,比较适合顺序添加、随机访问的场景这句话了。
再稍微扩展一点,有一个底层实现和Arraylist及其相似的Vector,只不过Arraylist是线程不安全的(可以把你的list变成List<String> synchronizedList = Collections.synchronizedList(list);来实现线程安全),方法都不是同步的,Vector是线程安全的,主要的不同点就是这,还有个不同点是扩容不一样,一个是默认1.5倍,Vector是两倍。其他和LinkList的比较在下一篇文章里介绍。
最后,大家想获取更多知识的,可以继续关注公众号,不定时推送。分享了这么牛逼的知识,还不请小编喝个水吗,哈哈哈,欢迎土豪直接赏赞,谢谢,您的支持就是小编最大的动力。