集合类源码学习

1.ArrayList

2.LinkedList

3.ArrayList和LinkedList的区别

  1. ArrayList是一个动态数组,它的底层采用Object数组实现
    LinkedList是一个可以在任何位置进行高效地插入和移除操作的序列,它的底层是双向链表实现的

  2. ArrayList因为是动态数组,所以它的容量是会自动扩展的,它的初始容量默认是10,但是它不是一开始就初始化,而是在调用add方法后才进行初始化,add方法在经过层层调用后,就会通过核心的grow方法检测容量,如果容量不够,将容量扩展为1.5倍,并且最大容量为Integer.MAX_VALUE-8(grow方法会调用copyOf方法创建一个容量为1.5倍的新数组,并把原来的值复制进去,如果容量已经超过Integer.MAX_VALUE-8(这里的8用于存储对象头信息),就会进行最后一次扩展,直接扩展到MAX_VALUE)
    而LinkedList则是双向链表实现的,从它的源码上来看应该也不存在容量不足的情况,它的容量取决于内存的大小。

  3. 插入和删除是否受元素位置的影响:
    ① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e) 方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在开头插入和删除元素的话(add(int index, E element) )时间复杂度就为 O(n)。因为之后的每个元素都要执行向后位/向前移一位的操作。
    ② LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)。

  4. 是否支持快速随机访问:
    LinkedList 不支持高效的随机元素访问,
    而 ArrayList 支持,通过它实现了RandomAccess这个标记性接口就可以看出来,因此ArrayList使用普通for循环比使用迭代的增强的foreach循环来遍历要快。

4.Interator

5.Vector和Stack

6. ArrayList和Vector的区别

  1. Vector的方法都是同步的,是线程安全的,而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。
  2. 当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间。

7.为什么现在都不提倡使用vector了

  1. 每个方法都加锁严重影响性能
  2. 使用vector不代表线程安全,在复合操作中还是会引发线程安全问题,所以在复合操作中还是要额外加锁,浪费资源
  3. 在遍历时还是会产生fail-fast问题

8.fail-fast和fail-safe的区别

  1. 快速失败,例如在arrayList中使用迭代器遍历时,有另外的线程对arrayList的存储数组进行了改变,比如add、delete、等使之发生了结构上的改变,所以Iterator就会快速报一个java.util.ConcurrentModificationException 异常(并发修改异常),这就是快速失败。
  2. 安全失败,在java.util.concurrent下的类,都是线程安全的类,他们在迭代的过程中,如果有线程进行结构的改变,不会报异常,而是正常遍历,这就是安全失败。
  3. 在concurrent下的集合类增加元素的时候使用Arrays.copyOf()来拷贝副本,在副本上增加元素,如果有其他线程在此改变了集合的结构,那也是在副本上的改变,而不是影响到原集合,迭代器还是照常遍历,遍历完之后,改变原引用指向副本,所以总的一句话就是如果在次包下的类进行增加删除,就会出现一个副本。所以能防止fail-fast,这种机制并不会出错,所以我们叫这种现象为fail-safe。

9.HashMap

参考1

参考2

10.HashMap是如何工作的

HashMap使用哈希算法,在put和get方法中,HashMap使用Key的hashCode()和哈希算法来找出存储key-value对的索引。如果已经存在,它使用equals()方法来检查传递的key是否已经存在,如果存在,它会覆盖value,如果不存在,它会创建一个新的entry然后保存。HashMap默认的初始容量是16,加载因子是0.75,在put方法放入数据时,如果实际容量大于初始容量 X 加载因子,并且put的位置上有元素,才会进行扩容,扩容会将容量 X 2,并且重新计算hash值存放。当同一个位置的元素大于初始容量 X 加载因子时,则桶中的数据结构从链表改为红黑树。

11.HashMap和HashTable的区别

  1. HashMap允许key和value为null,而HashTable不允许。
  2. HashTable是同步的,而HashMap不是。
  3. 在Java1.4中引入了LinkedHashMap,HashMap的一个子类,假如你想要遍历顺序,你很容易从HashMap转向LinkedHashMap,但是HashTable不是这样的,它的顺序是不可预知的。
  4. HashMap提供对key的Set进行遍历,因此它是fail-fast的,但HashTable提供对key的Enumeration进行遍历,它不支持fail-fast。
  5. HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
  6. 存放位置的计算方式不同,hashtable使用hash值对length取模,hashmap采用h&(length-1)与运算计算位置。
  7. HashTable被认为是个遗留的类,如果你寻求在迭代的时候修改Map,你应该使用CocurrentHashMap。

12.LinkHashMap,能顺序遍历的hashmap

13.TreeMap

14.HashMap和TreeMap的区别

  1. TreeMap是根据key进行排序的,它的排序和定位需要依赖比较器或覆写Comparable接口,也因此不需要key覆写hashCode方法和equals方法,就可以排除掉重复的key,而HashMap的key则需要通过覆写hashCode方法和equals方法来确保没有重复的key。
  2. TreeMap的查询、插入、删除效率均没有HashMap高,一般只有要对key排序时才使用TreeMap。
  3. TreeMap的key不能为null,而HashMap的key可以为null

15. hashmap链表转红黑树为什么选择数字8

在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,是根据概率统计而选择的。

16. hashmap默认加载因子为什么选择0.75

HashMap有两个参数影响其性能:初始容量和加载因子。容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动扩容之前可以达到多满的一种度量。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行扩容、rehash操作(即重建内部数据结构),扩容后的哈希表将具有两倍的原容量。 通常,加载因子需要在时间和空间成本上寻求一种折衷。加载因子过高,例如为1,虽然减少了空间开销,提高了空间利用率,但同时也增加了查询时间成本;加载因子过低,例如0.5,虽然可以减少查询时间成本,但是空间利用率很低,同时提高了rehash操作的次数。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少rehash操作次数,所以,一般在使用HashMap时建议根据预估值设置初始容量,减少扩容操作。 选择0.75作为默认的加载因子,完全是时间和空间成本上寻求的一种折衷选择,至于为什么不选择0.5或0.8,笔者没有找到官方的直接说明,在HashMap的源码注释中也只是说是一种折中的选择。

17.hashmap并发问题

  1. 数据覆盖丢失
  2. 死循环

18.ConcurrentHashmap (jdk1.6)

ConcurrentHashmap (jdk1.8)

19.CopyOnWriteArrayList

©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页