1、Java容器类之间的继承关系

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2、fail-fast 机制是java集合(Collection)中的一种错误机制。

当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

3、Java容器的基本概念

Java容器类库是用来保存对象的,他有两种不同的概念:

  • Collection:独立元素的序列,这些元素都服从一条或多条规则。List、Set以及Queue都是Collection的一种,List必须按照顺序保存元素,而Set不能有重复元素,Queue需要按照排队规则来确定对象的顺序。
  • Map:Map是键值对类型,允许用户通过键来查找对象。Hash表允许我们使用另一个对象来查找某个对象。

4、List、set和map

  • list(对付顺序的好帮手):List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象;
  • set(注重独一无二的性质):不允许重复的集合。不会有多个元素引用相同的对象。
  • map(用Key来搜索的专家):使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。

arrylist底层通过数组实现,支持随机访问,但是插入删除操作需要移动元素会特别耗时,linklist底层通过链表实现查找元素需要遍历链表,插入删除操作简单。

6、ArrayList和Vector

arraylist是非同步的(线程不安全)在单线程操作的情况下适合使用,vector是同步的(线程安全)在操作时会在同步代码上花费较多时间。

7、HashMap和HashTable区别

  • 差异:
    • hashmap是非线程安全的,hashtable是线程安全的,所以hashmap比hashtable的执行效率高
    • hashmap支持null值,其键key只能有一个为null,对于值value可以有多个null。hashtable如果键key为null会直接抛出异常。
    • hashmap初始化时会分配16的内存大小,之后扩容为当前大小的2倍(2x)。hashtable初始化时会分配11的内存大小,之后扩容时会分配当前大小的2倍加1(2x+1)。
    • hashmap在jdk8之后解决哈希冲突时如果链表长度大于8会直接将链表转换成红黑树存储,小于6时又会将红黑树转换成链表存储。hashtable内没有这种机制,都是使用链表存储。
    • hashmap继承AbstractMap类,hashtable继承了Dictionary类,由于Dictionary类已经被废弃,所以hashtable也已经废弃。
    • Hashtable比HashMap多提供了elments() 和contains() 两个方法。
      • elments() 方法继承自Hashtable的父类Dictionnary。elements() 方法用于返回Hashtable中的value的枚举。
      • contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实上,contansValue() 就只是调用了一下contains() 方法。
  • 相同:
    • hashtable和hashmap都是通过哈希表实现的
    • 两者都实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口。
  • 注意:
    当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。

8、ConcurrentHashMap

  • 底层采用分段的数组+链表实现,线程安全。
  • 通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)
  • Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
  • 有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁
  • 扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容
  • 它是HashTable的替代,比HashTable的扩展性更好。
  • 首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
  • jdk8之前一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。
  • jdk8中ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。Java 8在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为O(N))转换为红黑树(寻址时间复杂度为O(log(N)) synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。

9、Hashmap和HashSet

  • hashset是基于hashmap实现的
  • hashmap实现了map接口、存储键值对、调用put方法添加元素、使用key值计算hashcode。
  • hashset实现了set接口、仅存储对象、调用add方法添加元素、使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性。
  • 当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。

10、comparable 和 Comparator的区别

  • comparable接口实际上是出自java.lang包 它有一个 compareTo(Object obj)方法用来排序
  • comparator接口实际上是出自 java.util 包它有一个compare(Object obj1, Object obj2)方法用来排序

    一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo()方法或compare()方 法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写compareTo()方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的 Collections.sort()。

11、集合框架底层数据结构总结

  • List
    • Arraylist: Object数组
    • Vector: Object数组
    • LinkedList: 双向链表(JDK1.6之前为循环链表,JDK1.7取消了循环)
  • Set
    • HashSet(无序,唯一): 基于 HashMap 实现的,底层采用 HashMap 来保存元素
    • LinkedHashSet: LinkedHashSet 继承于 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 HashMap 实现一样,不过还是有一点点区别的
    • TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树)
  • Map
    • HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
    • LinkedHashMap: LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:《LinkedHashMap 源码详细分析(JDK1.8)》
    • Hashtable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
    • TreeMap: 红黑树(自平衡的排序二叉树)

12、如何选用集合?

主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用Map接口下的集合,需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap.当我们只需要存放元素值时,就选择实现Collection接口的集合,需要保证元素唯一时选择实现Set接口的集合比如TreeSet或HashSet,不需要就选择实现List接口的比如ArrayList或LinkedList,然后再根据实现这些接口的集合的特点来选用。