List、Set、Map区别:
List:有序、可重复
ArrayList:Object[]
Vector:Object[]
LinkedList:双向链表(1.6之前是循环的,1.7之后是非循环的)
Set:无序、不可重复
HashSet (无序,唯一): 基于 HashMap 实现的,底层采⽤ HashMap 来保存元素
LinkedHashSet : 是 HashSet 的⼦类,并且其内部是通过LinkedHashMap 来实现的
TreeSet(有序,唯⼀): 红⿊树(⾃平衡的排序⼆叉树)
Map:key:无序、不可重复;value:无序、可重复
HashMap : JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突⽽存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较⼤的变化,当链表⻓度⼤于阈值(默认为 8)(将链表转换成红⿊树前会判断,如果当前数组的⻓度⼩于 64,那么会选择先进⾏数组扩容,⽽不是转换为红⿊树)时,将链表转化为红⿊树,以减少搜索时间
LinkedHashMap :继承⾃ HashMap ,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红⿊树组成。另外, LinkedHashMap 在上⾯结构的基础上,增加了⼀条双向链表,使得上⾯的结构可以保持键值对的插⼊顺序。同时通过对链表进⾏相应的操作,实现了访问顺序相关逻辑。
Hashtable : 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突⽽存在的
TreeMap : 红⿊树(⾃平衡的排序⼆叉树)
如何选⽤集合?
根据键值获取到元素值时: Map 接⼝下的集合:
需要排序时: TreeMap
不需要排序时:HashMap
需要保证线程安全:ConcurrentHashMap
只需要存放元素值时: Collection 接⼝的集合
需要保证元素唯⼀时:Set 接⼝的集合⽐如 TreeSet 或 HashSet
不需要保证元素唯⼀时:List 接⼝的⽐如 ArrayList
或 LinkedList
有哪些集合是线程不安全的?怎么解决呢?
Arraylist, LinkedList , Hashmap , HashSet , TreeSet , TreeMap , PriorityQueue 都不是线程安全的。
解决:使⽤线程安全的集合来代替。
如果你要使⽤线程安全的集合的话,java.util.concurrent 包中提供了很多并发容器供你使⽤:
- ConcurrentHashMap : 可以看作是线程安全的 HashMap
- CopyOnWriteArrayList :可以看作是线程安全的 ArrayList ,在读多写少的场合性能⾮常好,远远好于 Vector .
- ConcurrentLinkedQueue :⾼效的并发队列,使⽤链表实现。可以看做⼀个线程安全的LinkedList ,这是⼀个⾮阻塞队列。
- BlockingQueue : 这是⼀个接⼝,JDK 内部通过链表、数组等⽅式实现了这个接⼝。表示阻塞队列,⾮常适合⽤于作为数据共享的通道。
- ConcurrentSkipListMap :跳表的实现。这是⼀个 Map ,使⽤跳表的数据结构进⾏快速查找。
comparable 和 Comparator 的区别
comparable 接⼝实际上是出⾃ java.lang 包 它有⼀个 compareTo(Object obj) ⽅法⽤来排序
comparator 接⼝实际上是出⾃ java.util 包它有⼀个 compare(Object obj1, Object obj2) ⽅法⽤来排序
⼀般我们需要对⼀个集合使⽤⾃定义排序时,我们就要重写 compareTo() ⽅法或 compare() ⽅法,
⽐较 HashSet、LinkedHashSet 和 TreeSet 三者的异同
HashSet 是 Set 接⼝的主要实现类 ,HashSet 的底层是 HashMap,线程不安全的,可以存储 null值;
LinkedHashSet 是 HashSet 的⼦类,能够按照添加的顺序遍历;
TreeSet 底层使⽤红⿊树,能够按照添加元素的顺序进⾏遍历,排序的⽅式有⾃然排序和定制排序。
HashMap 和 Hashtable 的区别
- 线程是否安全: HashMap 是⾮线程安全的,HashTable 是线程安全的,因为 HashTable 内部的⽅法基本都经过 synchronized 修饰。(如果你要保证线程安全的话就使⽤
ConcurrentHashMap 吧!); - 效率: 因为线程安全的问题,HashMap 要⽐ HashTable 效率⾼⼀点。另外,HashTable 基本被淘汰,不要在代码中使⽤它;
- 对 Null key 和 Null value 的⽀持: HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有⼀个,null 作为值可以有多个;HashTable 不允许有 null 键和 null 值,否则会抛出 NullPointerException。
- 初始容量⼤⼩和每次扩充容量⼤⼩的不同 :
① 创建时如果不指定容量初始值,Hashtable 默认的初始⼤⼩为 11,之后每次扩充,容量变为原来的 2n+1。HashMap 默认的初始化⼤⼩为16。之后每次扩充,容量变为原来的 2 倍。
② 创建时如果给定了容量初始值,那么 Hashtable
会直接使⽤你给定的⼤⼩,⽽ HashMap 会将其扩充为 2 的幂次⽅⼤⼩(HashMap 中的tableSizeFor() ⽅法保证)。也就是说 HashMap 总是使⽤ 2 的幂作为哈希表的⼤⼩。 - 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较⼤的变化,当链表⻓度⼤于阈值(默认为 8)(将链表转换成红⿊树前会判断,如果当前数组的⻓度⼩于 64,那么会选择先进⾏数组扩容,⽽不是转换为红⿊树)时,将链表转化为红⿊树,以减少搜索时间。Hashtable 没有这样的机制。
HashMap 和 TreeMap 区别
TreeMap 和 HashMap 都继承⾃ AbstractMap ,但是需要注意的是 TreeMap 它还实现了NavigableMap 接⼝和 SortedMap 接⼝。
实现 NavigableMap 接⼝让 TreeMap 有了对集合内元素的搜索的能⼒。
实现 SortMap 接⼝让 TreeMap 有了对集合中的元素根据键排序的能⼒。默认是按 key 的升序排序,不过我们也可以指定排序的⽐较器。
HashSet 如何检查重复
当你把对象加⼊ HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加⼊的位置,同时也会与其他加⼊的对象的 hashcode 值作⽐较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调⽤ equals() ⽅法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让加⼊操作成功。(摘⾃我的 Java 启蒙书
《Head fist java》第⼆版)
hashCode()与 equals()的相关规定:
- 如果两个对象相等,则 hashcode ⼀定也是相同的
- 两个对象相等,对两个 equals ⽅法返回 true
- 两个对象有相同的 hashcode 值,它们也不⼀定是相等的
- 综上,equals ⽅法被覆盖过,则 hashCode ⽅法也必须被覆盖
- hashCode()的默认⾏为是对堆上的对象产⽣独特值。如果没有重写 hashCode(),则该 class 的两个对象⽆论如何都不会相等(即使这两个对象指向相同的数据)。
ConcurrentHashMap 和 Hashtable 的区别
ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的⽅式上不同。
底层数据结构: JDK1.7 的 ConcurrentHashMap 底层采⽤ 分段的数组+链表 实现,JDK1.8 采⽤的数据结构跟 HashMap1.8 的结构⼀样,数组+链表/红⿊⼆叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采⽤ 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突⽽存在的;
实现线程安全的⽅式(重要): ① 在 JDK1.7 的时候,ConcurrentHashMap(分段锁) 对整个桶数组进⾏了分割分段(Segment),每⼀把锁只锁容器其中⼀部分数据,多线程访问容器⾥不同数据段的数据,就不会存在锁竞争,提⾼并发访问率。 到了 JDK1.8 的时候已经摒弃了Segment 的概念,⽽是直接⽤ Node 数组+链表+红⿊树的数据结构来实现,并发控制使⽤synchronized 和 CAS 来操作。(JDK1.6 以后 对 synchronized 锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② Hashtable(同⼀把锁) :使⽤ synchronized 来保证线程安全,效率⾮常低下。当⼀个线程访问同步⽅法时,其他线程也访问同步⽅法,可能会进⼊阻塞或轮询状态,如使⽤ put 添加元素,另⼀个线程不能使⽤ put 添加元素,也不能使⽤get,竞争会越来越激烈效率越低。