Java集合主要从三个方面来说明:
1. Java集合基本概念和对比
1. 集合的继承关系图
2. 集合继承关系说明
Java集合主要分类对比:
单例集合:Collection, 单列集合,主要存储一个值
List: List 集合元素有序,可以重复
ArrayList:底层是数组,查询快,增删慢,线程不安全, 每次扩容是1.5 倍扩容
LinkedList: 底层是链表, 增删快,查询慢, 线程不安全
Vector: 底层是数组,查询快,增删慢, 线程安全,安全主要是底层是每个方法有synchronized修饰, 0.75 扩容
Set: set集合元素无序,但是不可重复
HashSet: 底层主要有hash算法实现,通过hashcode() 和equals() 方法实现元素不可重复
LinkedHashSet: 有序的set
TreeSet: 底层由二叉树实现的有序Set
键值对集合:Map, 是双列结合,主要表现为键值对存在
HashMap:最常用的Map实现类,其中元素无序,线程不安全,允许一个Key 为null,多个value为null
TreeMap:与HashMap 最大的区别就是有序,他是按照某种特定的规则有序,底层是红黑树实现有序的,但是线程也是不安全的,
2. HashMap
1. HashMap的底层数据结构
JDK1.7 : 数据 + 链表
JDK1.8 : 数据 + 链表 + 红黑树(在jdk1.8 之后,当链表长度大于8 时,就裂变为红黑树,目的是提高性能)
2. HsahMap 的主要特点和其他Map的对比
HashMap 是一个无序的键值对双列集合,在HashMap中允许一个Key 为Null, 多个value 为null, 线程不安全。如果在Put 键值对的时候有key 已存在,就会覆盖原来key 的value. 从HashMap的主要特点就可以进行选择Map集合, 如果元素无序,为了更高的性能不需要考虑线程安全时就选择HashMap, 如果需要有序的Map集合就用TreeMap,来重写排序原则或者使用原来的自然排序,如果需要线程安全的就使用 ConcurrentHashMap.
3. HashMap 的原理
HashMap 底层原理: HashMap 底层主要是有数组和链表组成,数组是HashMap的主体结构,链表主要是为了解决Hash冲突存在的。当定位到的数组没有链表结构,则对于查询、新增都很快,如果定位的数组有链表,新增的时间复杂度为O(n),每次新增首先判读key 是否存在,如果存在就覆盖新值,不存在就新增。查询的时候需要遍历整个链表,通过key 对象的eauals方法的逐一对比。所以性能考虑,HashMap的链表出现越少,性能越好。
HashMap 主要通过Entry 来存key-value储键值对,每一个Key_value 键值对组成一个Entry 实体,Entry类实际上是一个单向的链表结构,它具有next 指针,指向下一个Entry 实体,依次来解决hash冲突的问题,因为HashMap是按照Key的hash值来计算Entry在HashMap中存储的位置的,如果hash值相同,而key内容不相等,那么就用链表来解决这种hash冲突。
4. HashMap 扩容
初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂
扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容)当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀
5. HashMap 源码
自己去品尝,每个人品尝的味道都不一样,总之一句话,酸爽。
3. ConcurrentHashMap
1. 底层原理实现
1. JDK 1.7(数组 + 链表, 由分段锁实现线程安全问题)
jdk1.7 是由Segment 数组、HashEntry 组成,HashEntry和 HashMap 一样,仍然是数组 + 链表。Segment是一种 可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
2. JDK 1.8(数组+链表+红黑树, 用CAS无锁算法 + synchronized来实现线程安全)
JDK 1.8 : 结构和HashMap的结构一样,其中抛弃了分段锁,采用了CAS+synchronized来保证并发安全,1.7 中存放数据的 HashEntry 改为 Node,但作用都是相同的。其中val和next用了volatile 修饰,保证了在内存中的可见性。
2. 常用方法
put方法:
根据key计算出hashcode,在定位到Node,如果当前Node为空表示可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
判断是否需要扩容;
如果都不满足,使用synchronized锁写入数据;
判断是否需要转换成红黑树。
get方法:
根据计算出来的hashcode寻址,如果就在桶上就直接返回值;
如果是红黑树就按照红黑树的方式获取值;
如果不满足就按照链表的方式遍历获取值。
哪里写的不对,哪里需要补充请大家多多指教,谢谢!