- vector相当于ArrayList有什么优势,为什么会有这个类的存在。
- 为什么List是vector/stack的父类,不是queue的父类。
- Vector、Stack、HashTable为什么被废除?
集合罗列
- 不重复集合:HashSet, LinkedHashSet(按插入顺序迭代), TreeSet(排序集合),ConcurrentSkipListSet, CopyOnWriteArraySet(线程安全set)
- 列表:ArrayList(底层使用数组), LinkedList(底层使用链表), CopyOnWriteArrayList(线程安全), Vector,Stack(后进先出)
- Map: HashMap, Hashtable, LinkedHashMap, TreeMap ,ConcurrentHashMap, ConcurrentSkipListMap
- 队列queue:ArrayBlockingQueue , ConcurrentLinkedQueue, DelayQueue, LinkedBlockingDeque,, PriorityBlockingQueue, PriorityQueue, SynchronousQueue
- 双端队列deque:LinkedList,ArrayDeque
- 栈:stack
总结
- 备注:Vector、Stack和HashTable是legacy,已经不建议使用。
- 集合的功能分类方式:底层使用数组/链表,是不是同步,是不是有序。
ArrayList实现
- 底层使用Object[]。
- 数组默认大小10
- 超出容量的add,remove,add(int index, E element)都是使用的数组copy实现的。 每次扩容大小(jdk1.8):是原数组大小的1.5倍或者需要的最小值(可能1.5倍不能满足扩容需求)。
HashMap实现
简介
- key和value是绑定在一起的,构成一个entry。HashMap就是一个entry类型的数组,entry除了有用户存入的key和value,还有一个entry类型的指针,指向其他entry。对key的hashCode进行hash,可以得到数组角标;所有hashCode相同的object在数组的同一位置,使用链表连接。
- 向hashMap中添加一个key\value的流程:
key的hashCode不会直接用来求角标的位置,hashMap中会有一个int hash(int hashCode)方法,key的hashCode经过hash之后的值才会被用来计算数组角标。
- hashmap的相关参数
- initial_capacity = 16(jdk1.7/1.8)
- loadfactor = 0.75(jdk1.7/1.8) : 当map.size/entryArr.size>loadfactor时会引发hashmap扩容。因为当桶的数量远小于数据的数量时,大多数元素会存在于一条链表上,导致查询效率降低。
- 每次扩容大小:resize(2 * table.length)(1.7)
- hash和hashCode是两个值,hashCode是写在Object中的hashCode()方法的返回值,hash是hashmap中对hashCode扰动后的结果值。在同一个桶位上的对象的判断语句是(k = e.key) == key || (key != null && key.equals(k))),即是同一个对象,或者equals相等的对象。对于1.8版本优化后的红黑树,对于同一桶位的不同entry,红黑树的排序会首先根据hashCode,如果hashCode相同,则map希望key实现Comparable接口,如果没有实现这个接口则不能起到优化的结果。
- end
hashmap的结构,1.7和1.8有哪些区别
https://blog.csdn.net/qq_36520235/article/details/82417949
https://blog.csdn.net/qq_27093465/article/details/52270519
- 8版本会在链表的大小超过8时改用红黑树。
- hashmap1.7版本在resize时会有逆序和死锁的问题,1.8不会。
- hash(hashCode)方法做了改变。在计算hash值的时候,JDK1.7用了9次扰动处理=4次位运算+5次异或,而JDK1.8只用了2次扰动处理=1次位运算+1次异或。
- 扩容时的rehash算法改变:resize的基本算法是hash&length-1,结果分为两种一部分会待在原始位置,还有一部分的数组位置是(原始位置+旧容量大小)。
- 对于原来的key, 1.7版本时会再算一次hashCode(不知道1.7为什么会重新计算?),1.8不会。
- 1.7计算hash&length-1,1.8根据resize时的规律只判断length-1的最高位就可以得到结果。
- 对于同一bucket内的不同entry,1.7是单个进行处理,1.8是先将entry分成;两个链表,然后再将两个链表放到不同的bucket中去。
- 1.7版本在resize完之后需要再添加newVal,1.8版本resize完成的同时新值将被添加完毕。
- resize时hashMap1.8版本的红黑树怎么处理?
1.7中节点只有entry类型,1.8就有了Node(implements Map.Entry)和TreeNode(extends LinkedHashMap.entry(extends HashMap.Node<K,V>))两种类型,所以红黑树节点是链表节点的子类,所以只要使用在逻辑中使用公共的接口,没有再单独写逻辑。但是在putVal()时还是有单独的逻辑的。
hashmap1.7的resize死锁问题原因:
https://www.cnblogs.com/kxdblog/p/4323892.html
假设旧数组有A、C节点,新数组有B、D节点:
- thread1:执行至e.next=newTable[i],改变了next指针,A指向了B
- thread2:执行至最后一行,将旧数组的头指针改为了错的next(B)
- 这时如果完再执行t1,则旧数组的头指针会被还原为正确的C。但是如果t1还没有执行,t2又执行了一轮,就会出现B.next=B;或者t1只又执行了一句newTable[i]=e,这时候t2开始执行,就会形成A和B的循环。
- 总结:正常的流程下新链表应该是C->A->B->D,但是在线程2的正常执行的时候,线程1通过扰乱导致a.next=b,也就是线程2将b当成了原先的c,就出现了B(原先应该是C)->A->B,循环!
HashSet的实现
HashSet的底层实现使用的是HashMap<E,Object>,hashSet.add(e)内部调用的是hashMap.put(e, PRESENT), PRESENT是一个static的引用,指向一个Object类型的对象。
BitSet
https://blog.csdn.net/kongmin_123/article/details/82225172
bitset通常用于对海量数据的处理。
从表面上看可以认为是一个boolean[ ],每个对象对应一个角标,角标为true代表对象存在,角标为false代表不存在。
但是如果底层实现也是boolean的话,Boolean是1byte(8bit),一个标志其实是只需要1bit就可以了,所以会有存储的浪费。java中又没有bit类型,所以bitset的内部实现是long[ ]。通过位操作可以得到相应的值。bitset并不继承自java.util.collection,所以bitset并没有提供集合的接口,bitset最常用的就是set\get。实现布隆过滤器时通常会使用bitset( https://blog.csdn.net/iam333/article/details/38084137 )。