Java集合类主要由两个根接口Collection和Map派生出来的,Collection派生出了三个子接口:List、Set、Queue
List:有序可重复集合,可直接根据元素的索引来访问;
Set:无序不可重复集合,只能根据元素本身来访问;
Queue:队列集合;
Map:代表的是存储key-value对的集合,可根据元素的key来访问value。
底层的数据结构:
ArrayList底层是一个数组
LinkedList底层是一个双向链表
线程安全:
ArrayList与LinkedList都不是线程安全的,线程安全可以使用Vector或者用
Collections.synchronizedList(new ArrayList())创建list集合
扩容:
ArrayList默认初始容量为10,超过最大值会扩容最大值的一半,新建新数组复制老数组集合,比较低效,并且会产生额外的垃圾对象
LinkedList则只需要在固定序列插入节点数据就好,效率高
效率:
对于查询效率,ArrayList直接使用底层的数组索引来寻找值,效率高
而LinkedList并没有相关算法来找固定索引对象,需要遍历取节点数据
但对于插入和删除数据而言,由于ArrayList底层的数据结构导致,插入和删除开销比较大,对于中间或开头位置的数据需要通过数组移位的形式进行数据随机插入
LinkedList随机插入时只需要对前后2节点的指针进行修改就好了
1. 线程是否安全: HashMap 是非线程安全的,HashTable 是线程安全的
2. 效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。
3. 对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛出 NullPointerException。
4. 初始容量大小和每次扩充容量大小的不同 :
①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。
5. 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。
线程安全性考虑:
HashMap和TreeMap都是线程不安全,线程安全可考虑用HashTable和ConcurrentHashMap
是否可允许NULL值KEY:
HashMap允许Key为NUll,TreepMap不允许Key为Null(因为底层compare方法实现2个key的排序)
从底层结构出发:
HashMap底层结构采用数组+链表结构,key使用Hash算法实现,不允许重复,value不限制
TreeMap底层基于红黑树实现,put的时候需要比较元素的大小来进行节点插入
从排序上:
HashMap的存储是无序存储
TreeMap是有序存储
扩容问题:
由于HashMap底层是一个node数组结构,当元素个数达到容量*负载因子是会发生扩容的情况,容量为最大的2倍
TreeMap底层是树的结构,不用扩容,直接添加节点就好
各方面的效率:
由于TreepMap需要维护底层树的结构,所以查询、删除和插入的等方面效率都比较低
插入需要比较节点的位置才能完成插入操作
查询也一样
使用场景:
HashMap适合使用无序的key-value场景
TreeMap适合用于有序的key-value场景
Hashtable是在put和get上都用synchronized ,是对整个hash表进行加锁操作,当一个线程占有这个锁时,其他线程必须阻塞等待其释放锁
而ConcurrentHashMap是如下实现:
②jdk1.6的实现:ConcurrentHashMap是采用Segment分段锁的方式,默认16块,它并没有对整个数据结构进行锁定,而是对segment块进行局部锁定
③jdk1.8的实现: 直接用 Node 数组+链表+红黑树的数据结构来实现,put的时候并发控制使用 synchronized 和 CAS 来操作,get方法不加锁的底层是Node的元素val和指针next是用volatile修饰对象 , volatile关键字来保证可见性、有序性