集合-容器
Collection单列集合
Set
- SortedSet
TreeSet
使用二叉树的原理对新 add()的对象按照指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入的二叉树指定的位置。
Integer 和 String 对象都可以进行默认的 TreeSet 排序,而自定义类的对象是不可以的, 自己定义的类必须实现 Comparable接口,并且覆写相应的 compareTo()函数,可以正常使用。
在覆写 compare()函数时,要返回相应的值才能使 TreeSet 按照一定的规则来排序。比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
HashSet
-
应用场景:去重
-
与HashMap的关系:
HashSet底层封装的是HashMap,HashMap的key是不能重复的,而这里HashSet的元素又是作为了map的key,当然也不能重复了。所有元素添加会放到HashMap的key中,value值使用new Object对象作为value;
若要将对象存放到HashSet中并保证对象不重复,应根据实际情况将对象的hashCode方法和equals方法进行重写。
-
-
特点
- 数据不能重复;
- 可以存储null值,数据不能保证插入有序;
LinkedHashSet
- 继承了HashSet
- 应用场景:实现了对数据进行去重,并且对集合类数据实现访问有序 ;
List
ArrayList
ArrayList的默认长度是多少?
它的默认初始化长度为10;同时它也是支持动态扩容的,通过这个方法进行动态扩容新增加的容量大小为原容量大小的50%。 底层调用的就是Arrays.copyOf(elementData, newCapacity)。
LinkedList
LinkedList:可知该链表是双向链表,即可以从头遍历到尾,也可以从尾遍历到头。同样它也是线程不安全的,在这里最可能的造成的并发原因就是链表成环。
Vector
Queue
Map双列集合
Map中的集合核心特性
自动扩容
最小可用原则,容量超过一定阈值便自动进行扩容。
-
扩容是通过resize方法来实现的。扩容发生在putVal方法的最后,即写入元素之后才会判断是否需要扩容操作,当自增后的size大于之前所计算好的阈值threshold,即执行resize操作。
-
通过位运算<<1进行容量扩充,即扩容1倍,同时新的阈值newThr也扩容为老阈值的1倍
扩容时,总共存在三个问题:
-
哈希桶数组中某个位置只有1个元素,即不存在哈希冲突时,则直接将该元素copy至新哈希桶数组的对应位置即可。
-
哈希桶数组中某个位置的节点为树节点时,则执行红黑树的扩容操作。
-
哈希桶数组中某个位置的节点为普通节点时,则执行链表扩容操作,在JDK1.8中,为了避免之前版本中并发扩容所导致的死链问题,引入了高低位链表辅助进行扩容操作。
针对扩容时出现的问题的解答:
-
为什么JDK1.7扩容时会产生并发死锁问题,也就是我们常温的为什么hashmap是线程不安全的?
主要原因是:并发,即多线程同时访问HashMap
hashmap是线程不安全造成的影响主要有两个方面:1 、高并发下,hashmap会出现扩容的死锁问题;2、数据会丢失,会造成数据的脏读
怎么产生的环形链表死循环问题?
JDK1.7 使用的是数组+单链表的数据结构会先进行扩容再插入,执行的是头插法,先将原位置的数据移到后一位,再插入数据到该位置;会出现逆序和环形链表死循环问题
JDK1.8 使用的是数组+链表+红黑树的数据结构(当链表的深度达到8的时候,也就是默认阈值,就会自动扩容把链表转成红黑树的数据结构来把时间复杂度从O(n)变成O(logN)提高了效率),会先进行插入再进行扩容,执行的是尾插法,直接插到链表尾部/红黑数树,不会出现逆序和环形链表死循环问题
线程不安全是因为:数组确定了就不会修改,想扩容则申请新的数组,把老数据进行迁移,涉及的源码:Entry[] newTable = new Entry[newCapacity]; 当迁移时,单线程迁移没有问题,当有多个线程需要扩容时,都申请了数组,则可能数据迁移不成功,造成JVM内存溢出,并造成大量GC,则当线程迁移时,造成死锁。
在扩容时,在哈希冲突的时候,产生的链表形成环,当有key在成环链表中时,则成死循环。
- JDK1.7单线程下的扩容
-
JDK1.7多线程下的扩容:
在JDK1.8及以后,用的ConcurrentHashMap解决死锁的问题,ConcurrentHashMap是线程安全的对死锁问题进行了改进,采用四组指针,分为高位指针和低位指针,hashcode & 数组容量长度,分成两部分迁移,避免头插链表成环。
ConcurrentHashMap 的线程安全机制:CAS + 锁
ConcurrentHashMap保证线程安全的原理思路:
-
T1线程加元素A,CAS判断节点是否为空,为空则用CAS把节点放入
-
T1线程再加元素B,CAS再判断节点是否为空,不为空则加锁后放入,T1线程都可访问
-
T2线程,get(key)时,当没有锁,则直接拿
扩容原理:ConcurrentHashMap中的put()方法比hashmap()对一个for循环的原因,多个线程需插入值,但CAS算法只能有一个成功,ConcurrentHashMap中加入for循环保证不成功的线程继续放入值,而不失效**(自旋)**
初始化与懒加载
初始化的时候只会设置默认的负载因子,并不会进行其他初始化的操作,在首次使用的时候才会进行初始化。
当new一个新的HashMap的时候,不会立即对哈希数组进行初始化,而是在首次put元素的时候,通过resize()方法进行初始化。
resize()中会设置默认的初始化容量DEFAULT_INITIAL_CAPACITY为16,扩容的阈值为0.75*16 = 12,即哈希桶数组中元素达到12个便进行扩容操作。
最后创建容量为16的Node数组,并赋值给成员变量哈希桶table,即完成了HashMap的初始化操作。
哈希计算
哈希表