【集合】List、Set、Map区别?ArrayList、HashMap、ConcurrentHashMap原理与区别

206 阅读4分钟

面试题开路

面试的时候,经常会被问:

  • List、Set、Map、底层用什么实现的?有哪些典型实现?
  • ArrayList、Linkedlist、Hashset区别?
  • HashMap、Hashtable、ConcurrentHashMap实现原理?

要解决这个问题,一定要回到原理开始说起,说清楚的结构也很简单:

  1. 数据结构
  2. “增删改查”效率,这个主要包含“随机访问效率” + “增删改效率”2个指标来看;
  3. 内存空间占用
  4. 线程安全

最后还要来一个“总结”

List、Set、Map的关系

image.png

抛开其他的因素,看表中的关系,就知道!!

开始分析,这里如果有表格就更好了

ArrayList

  1. 数据结构:动态数组,有序
  2. “增删改查”效率:因为是数组(有下标),所以查找快。 增删很慢,动一个会带来元素移动(其他的元素要改下标)。插入时扩容还需要复制数组;
  3. 内存空间:低
  4. 线程安全:不保证线程安全;

LinkedList

image.png

  1. 数据结构:双向链表,有序
  2. “增删改查”效率:因为是链表,增删快,只需要改指前和指后的指针就行了,但查找很慢,需要通过指针一个个地循环;
  3. 内存空间:比ArrayList大,因为有item+双指针
  4. 线程安全:不保证线程安全;

HashMap

  1. 数据结构:1.8之后是数组+链表+红黑树,无序
  2. “增删改查”效率:相对很巴适,一直在优化
  3. 内存空间:缝合怪,这还用说
  4. 线程安全:不保证线程安全

这里有好几个考点

  1. HashMap的底层原理 image.png

  2. HashMap的putVal实现 image.png

  3. HashMap的get实现

  • 通过key的hash值找到该key映射到的桶
  • 如果该桶上首个元素first的key就是要查找的key,即直接命中,则直接返回
  • 如果该桶上的首个元素first的key不是要查找的key,则需要查看后续节点
  • 如果first属于树节点,则该桶位已经升级成了红黑树,将从红黑树中找我们需要的key
  • 否则,后续节点就是链表形式,通过遍历链表来寻找该key 这里感兴趣可以去看源码
  1. HashMap是如何解决Hash冲突的

image.png

  1. 什么是hashCode

Object类中有一个方法public native int hashCode(); (用native修饰,是一个本地方法,通常用c或c++写成,在java中可以去调用它)

调用这个方法会生成一个int型的整数,和调用它的对象地址和内容有关。把任意长度 的二进制映射为固定长度较小的二进制值。

  1. 为什么扩容是2倍? HashMap两个重要的参数:初始容量大小和加载因子,初始容量大小是创建时给数组分配的容量大小,默认值为16,用数组容量大小乘以加载因子得到一个值:12。内容超过12就会进行扩容。

一旦数组中存储的元素个数超过该值就会调用rehash方法将扩容到原来的2倍。扩容会生成一个新数组,原来的所有数据需要重新计算哈希码值重新分配到新的数组,所以扩容的操作非常消耗性能。

先看一下HashMap中的putVal方法(存值的)和resize方法(扩容的),之所以HashMap扩容是2的n次幂和这两个方法有千丝万缕的联系。 putVal方法可以看出来HashMap在存值时会先把key的hash值和扩容后的长度进行一次按位与运算; resize方法可以看出来扩容时会新建一个tab,然后遍历旧的tab,将旧的元素进行e.hash & (newCap - 1)的计算添加进新的tab中,也就是(n - 1) & hash的计算方法; HashMap以2倍扩容,目的就是减少hash碰撞,使元素分配均匀


ConcurrentHashMap

  1. 线程安全:保证线程安全 其他跟HashMap一样

这里的考点

  1. ConcurrentHashMap的底层原理

JDK1.7 先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。 image.png

JDK1.8(TreeBin: 红黑二叉树节点 Node: 链表节点)

采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,只有hash冲突的时候,才会占用锁。这样只要hash不冲突,就不会产生并发,效率又提升N倍。(这里看不懂的话 前面看HashMap是如何解决Hash冲突的) image.png

HashSet

  1. 数据结构:HashSet 基于 HashMap 来实现的,不允许有重复元素的集合
  2. “增删改查”效率
  3. 内存空间
  4. 线程安全:不保证线程安全

HashTable

  1. 数据结构:基于 HashMap 实现,基本已被淘汰
  2. 线程安全:线程安全 单线程转为使用HashMap,多线程使用ConcurrentHashMap。