ArrayList是一个动态数组,它的底层采用Object数组实现 LinkedList是一个可以在任何位置进行高效地插入和移除操作的序列,它的底层是双向链表实现的
ArrayList因为是动态数组,所以它的容量是会自动扩展的,它的初始容量默认是10,但是它不是一开始就初始化,而是在调用add方法后才进行初始化,add方法在经过层层调用后,就会通过核心的grow方法检测容量,如果容量不够,将容量扩展为1.5倍,并且最大容量为Integer.MAX_VALUE-8(grow方法会调用copyOf方法创建一个容量为1.5倍的新数组,并把原来的值复制进去,如果容量已经超过Integer.MAX_VALUE-8(这里的8用于存储对象头信息),就会进行最后一次扩展,直接扩展到MAX_VALUE) 而LinkedList则是双向链表实现的,从它的源码上来看应该也不存在容量不足的情况,它的容量取决于内存的大小。
插入和删除是否受元素位置的影响: ① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e) 方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在开头插入和删除元素的话(add(int index, E element) )时间复杂度就为 O(n)。因为之后的每个元素都要执行向后位/向前移一位的操作。 ② LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)。
是否支持快速随机访问: LinkedList 不支持高效的随机元素访问, 而 ArrayList 支持,通过它实现了RandomAccess这个标记性接口就可以看出来,因此ArrayList使用普通for循环比使用迭代的增强的foreach循环来遍历要快。
HashMap使用哈希算法,在put和get方法中,HashMap使用Key的hashCode()和哈希算法来找出存储key-value对的索引。如果已经存在,它使用equals()方法来检查传递的key是否已经存在,如果存在,它会覆盖value,如果不存在,它会创建一个新的entry然后保存。HashMap默认的初始容量是16,加载因子是0.75,在put方法放入数据时,如果实际容量大于初始容量 X 加载因子,并且put的位置上有元素,才会进行扩容,扩容会将容量 X 2,并且重新计算hash值存放。当同一个位置的元素大于初始容量 X 加载因子时,则桶中的数据结构从链表改为红黑树。
在JDK8及以后的版本中,HashMap引入了红黑树结构,其底层的数据结构变成了数组+链表或数组+红黑树。添加元素时,若桶中链表个数超过8,链表会转换成红黑树。之前有写过篇幅分析选择数字8的原因,内容不够严谨。最近重新翻了一下HashMap的源码,发现其源码中有这样一段注释: Because TreeNodes are about twice the size of regular nodes, we use them only when bins contain enough nodes to warrant use (see TREEIFYTHRESHOLD). And when they become too small (due to removal or resizing) they are converted back to plain bins. In usages with well-distributed user hashCodes, tree bins are rarely used. Ideally, under random hashCodes, the frequency of nodes in bins follows a Poisson distribution (http://en.wikipedia.org/wiki/Poissondistribution) with a parameter of about 0.5 on average for the default resizing threshold of 0.75, although with a large variance because of resizing granularity. Ignoring variance, the expected occurrences of list size k are (exp(-pow(0.5, k) / factorial(k)). The first values are: 0: 0.60653066 1: 0.30326533 2: 0.07581633 3: 0.01263606 4: 0.00157952 5: 0.00015795 6: 0.00001316 7: 0.00000094 8: 0.00000006 more: less than 1 in ten million 翻译过来大概的意思是:理想情况下使用随机的哈希码,容器中节点分布在hash桶中的频率遵循泊松分布(具体可以查http://en.wikipedia.org/wiki/Poisson_distribution),按照泊松分布的计算公式计算出了桶中元素个数和概率的对照表,可以看到链表中元素个数为8时的概率已经非常小,再多的就更少了,所以原作者在选择链表元素个数时选择了8,是根据概率统计而选择的。
HashMap有两个参数影响其性能:初始容量和加载因子。容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动扩容之前可以达到多满的一种度量。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行扩容、rehash操作(即重建内部数据结构),扩容后的哈希表将具有两倍的原容量。 通常,加载因子需要在时间和空间成本上寻求一种折衷。加载因子过高,例如为1,虽然减少了空间开销,提高了空间利用率,但同时也增加了查询时间成本;加载因子过低,例如0.5,虽然可以减少查询时间成本,但是空间利用率很低,同时提高了rehash操作的次数。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少rehash操作次数,所以,一般在使用HashMap时建议根据预估值设置初始容量,减少扩容操作。 选择0.75作为默认的加载因子,完全是时间和空间成本上寻求的一种折衷选择,至于为什么不选择0.5或0.8,笔者没有找到官方的直接说明,在HashMap的源码注释中也只是说是一种折中的选择。