通俗的理解一下Java中的集合和并发集合

这是一篇用于速记和通俗理解的文章,有不严谨的地方欢迎指出来,反正我不一定改。
先来挂一张Java集合的鸟瞰图(俯视图?框架图?)。
通俗的理解一下Java中的集合和并发集合_第1张图片
图片转载自:面试常被问到的 Java 集合知识点(详细)

接下来我们通俗的理解一下

我们使用Java语言进行开发,设计了类,那么肯定是需要一种东西来装载这些类的对象。于是Java设计了 集合 Map 来做这件事。
PS:Java容器里只能放对象,对于基本类型(int, long, float, double等),需要将其包装成对象类型后(Integer, Long, Float, Double等)才能放到容器里。很多时候拆包装和解包装能够自动完成。这虽然会导致额外的性能和空间开销,但简化了设计和编程。
在这里插入图片描述
集合(Collection)负责存储一个元素集合,而图(Map)负责存储键/值对映射。
但是集合和Map能表达的很有限。

  • 我们对集合内元素的放置提出了要求:
    • 来收集有序的且允许有重复元素的Collection,于是有了 List
    • 来收集无序的且不包含重复元素的Collection,于是有了 Set
  • 我们对集合的存取提出了要求:
    • 希望读取的对象,顺序进入,顺序取出,即先进先出,于是有了 Queue
    • 希望读取的对象,顺序进入,逆序取出,即先进后出,于是有了 Stack
  • 我们对Map内元素的放置提出了要求:
    • 如果按照 hash 的方式进行放置,于是有了 HashMap
    • 如果按照 二叉平衡树 的方式进行放置,于是有了 SortedMapTreeMap
      通俗的理解一下Java中的集合和并发集合_第2张图片

好了,在上面,我们一共提到了六大种容器,分别是:List、Set、Queue、Stack、HashMap、TreeMap。

  • 对于List,我们提出了更进一步的要求:
    • 按照数组方式实现List,于是有了 ArrayList
    • 按照双向循环链表方式实现List,于是有了 LinkedList
  • 对于HashMap,我们提出了更进一步的要求:
    • 既能够保留hash的存取方式,又能够像LinkedList一样记录元素的插入顺序,于是有了 LinkedHashMap
    • 对于一些缓存的场景,我们希望旧数据能自己消失,于是有了 WeakHashMap
  • 对于TreeMap,我们提不出更进一步的要求了。
  • 对于Set,我们提出了更进一步的要求:
    • 按照Hash的方式存取数据,于是有了 HashSet,本质上是包装了 HashMap
    • 按照AVL的方式存取数据,于是有了 TreeSet,本质上是包装了 TreeMap
    • 既有Hash的存取方式,又能够像LinkedList一样记录元素的插入顺序,于是有了 LinkedHashSet,,本质上是包装了 LinkedHashMap
  • 对于Queue,我们提出了更进一步的要求:
    • 按照 优先队列 的方式存取数据,于是有了 PriorityQueue
    • 按照一个既可以当队列又可以当栈的方式存取数据,于是有了双端队列:DeQueue
    • DeQueue如果按照数组的方式实现,于是有了 ArrayDeque
  • 对于Stack,我们提不出更进一步的要求了,但是我们希望你用DeQueue来实现栈和队列。

专题说说面试常见的容器

  • HashMap
    • JDK 1.7 之前使用头插法、JDK 1.8 使用尾插法
    • 当碰撞导致链表大于 TREEIFY_THRESHOLD = 8 时,就把链表转换成红黑树
    • 转化前,桶的数量必须大于64,小于64的时候只会扩容
    • 链表长度低于6,就把红黑树转回链表
  • ConcurrentHashMap
    • 没有初始化,就调用 initTable() 方法来进行初始化
    • 没有 hash 冲突就直接 CAS 无锁插入
    • 需要扩容,就先进行扩容
    • 存在 hash 冲突,就加锁来保证线程安全,两种情况:一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入
    • 该链表的数量大于阀值 8,就要先转换成红黑树的结构,break 再一次进入循环
    • 添加成功就调用 addCount() 方法统计 size,并且检查是否需要扩容
    • 扩容方法 transfer():默认容量为 16,扩容时,容量变为原来的两倍,helpTransfer():调用多个工作线程一起帮助进行扩容,这样的效率就会更高
    • hash 值,定位到该 table 索引位置,如果是首结点符合就返回,如果遇到扩容时,会调用标记正在扩容结点 ForwardingNode.find()方法,查找该结点,匹配就返回,以上都不符合的话,就往下遍历结点,匹配就返回,否则最后就返回 null。

上述ConcurrentHashMap部分内容引用于 互联网架构师

进入并发快车道

对于上述提及的容器,很多是不可以用在高并发环境下的,会有线程安全问题,对于常用的容器,我们在Java并发包下,列出了并发版本的容器。

  • 先说说HashMap

    • HashMap比较快,但是线程不安全,于是引入了 HashTable,通过Synchronize加锁的方式,变得线程安全,但是牺牲了效率,于是又引入 ConcurrentHashMap ,通过分段锁的方式,既保证了安全性有提升了效率。
    • ConcurrentHashMap,在 JDK 1.7 中采用 分段锁的方式;JDK 1.8 中直接采用了CAS(无锁算法)+ synchronized
      • JDK 1.7 中使用分段锁(ReentrantLock + Segment + HashEntry),相当于把一个 HashMap 分成多个段,每段分配一把锁,这样支持多线程访问。锁粒度:基于 Segment,包含多个 HashEntry。
      • JDK 1.8 中使用 CAS + synchronized + Node + 红黑树。锁粒度:Node(首结点)(实现 Map.Entry)。锁粒度降低了。
    • HashMap最多只允许一条记录的键为null,允许多条记录的值为null,而 HashTable不允许
    • HashMap 需要重新计算 hash 值,而 HashTable 直接使用对象的 hashCode
  • 还有ArrayList

    • ArrayList不是线程安全的,于是有了 Vector,通过 Synchronize 加锁的方式,变得线程安全。
  • 再说说Queue

    • BlockingQueue、BlockingDeque、
    • ArrayBlockingQueue、LinkedBlockingDeque、LinkedBlockingQueue、
    • ConcurrentLinkedDeque、ConcurrentLinkedQueue、
    • DelayQueue、PriorityBlockingQueue
  • 最后说说List

    • CopyOnWriteArrayList、CopyOnWriteArraySet

你可能感兴趣的:(软件工程师基础技能,java,队列,栈,stack)