1、常用的集合有哪些?
集合框架分为两类:Map和Collection,实现类分别有
1)Map:HashMap、TreeMap、HashTable和ConcurrentHashMap
2)Collection:List接口实现类有ArrayList和LinkedList;Set实现类有TreeSet和HashSet
2、HashMap和HashTable的区别?
1)HashMap是线程不安全的,HashTable是线程安全的,里面加了synchrognized,所以HashTable效率比较低
2)HashMap的键值可以为空,HashTable键值都不能为空
3)HashMap继承于AbstractMap,HashTable继承于Dictionary
3、HashMap的put具体流程?
先根据key值算出hashcode,然后和数组长度-1做与运算,得出数组下标;如果该位置上没有值,则判断HashMap的容量有没有超过溢出因子,一般是0.75,如果超出了,则需要做扩容,数组大小扩大为原来的两倍,把之前存的数据重新计算哈希值,放到新的数组里;如果数组对应下标上没有值,则直接放进去,如果有值,则放在该下标对应的链表上,jdk1.8之后,如果链表长度超过8,则转成红黑树,提高取数据时的效率,至此,put操作完成。还是下面的图清晰一点
4、HashMap扩容操作是怎么实现的?
先把数组长度扩大一倍,如果超过Integer.maxSize的话,则不再扩容;然后遍历所有的数据,对key的hash值和新的数组长度进行与运算,计算出新的下标位置,然后放入对应的位置。后面操作跟put差不多
5、HashMap如何解决哈希冲突?
哈希冲突是指不同的值算出来的哈希值是一样的,这样虽然哈希值一样,但实际上是两个不同的值
1)HashMap使用链表来解决哈希冲突,如果哈希值一样,那么就放到一个链表里面,查找的时候用equals方法直接比对值就能判断是不是想获取的
2)通过两次扰动(一次位运算和一次异或运算),把hash值的高位也拿过来运算,使hash值分布更加均匀。具体就是hashcode和hashcode右移16位的结果进行异或,代码:hashcode ^ (hashcode>>16)
3)引入红黑树,使查询更快
6、为什么不直接使用HashCode处理后的值做数组下标?
因为hashCode计算出来的值是int,范围是-2^31~(2^31-1),而HashMap数组长度范围是16~(2^31-1),一般来说,都是超过当前数组长度的,不能直接拿来用
1)如何解决?
把hashCode先做两次扰动,使低16位和高16位进行异或运算,保证哈希值低16的均匀,然后和数组长度-1进行与运算,就能获得当前数组长度范围内的值,用来当数组下标,这比直接取余效率要高,而且当数组长度为2的幂次方的时候,这样计算出来的值和取余出来的值是一样的
2)为什么数组长度要保证为2的幂次方?
因为当数组长度为2的幂次方的时候,与计算出来的值和取余计算出来的值是一样的,而且效率更高。第二个原因是,2的幂次方-1的值,二进制都是1111的形式,与运算之后,所有空间都能利用,而如果是奇数,则与运算出来的下标,有的数组位置永远也不用利用到,造成hash冲突严重,并且空间浪费
3)为什么是两次扰动?
扰动的目的是保证hashcode低16位均匀分布,手段是通过高16位和低16位进行异或,所以只需要一次位运算和一次异或运算两次扰动就可以实现了高低位同时运算,达到低16位的随机性和均匀性
7、HashMap在jdk1.7和jdk1.8有什么不同?
jdk1.8加入了红黑树,提高了查询、插入效率,减少因为哈希冲突导致的性能降低
链表的头插法改成了尾插法
8、为什么String、Integer适合作为HashMap的key值?
因为这两个是final的,也就是不可更改,每次计算出来的hashcode是一样的,但如果不是final的,那对象被修改后,根据这个对象算出来的hashcode跟put进去的时候不一样,那就取不到你放在里面的值了。
还有就是已经对hashCode和equals方法进行了很规范的重写,不需要担心hashcode分布不均匀
1)如果我要使用自己的Object作为key,应该怎么做?
重写hashCode方法和equals方法,hashCode方法一般是把所有变量拿来计算;equals方法一般要遵守自反性、对称性等原则,自动生成的也可以
9、ConcurrentHashMap和HashTable的区别是什么?
两者都是线程安全的,但是实现方式不太一样,jdk1.7及之前ConcurrentHashMap结构是segments+数组,segments是使用分段锁,每个分段分别使用锁,多线程的时候,同一个锁还是单线程执行,但是不同值是可以多线程操作的;
jdk1.8之后使用的CAS(自旋锁)+synchronized保证数据安全性,数据结构跟HashMap一样(数组+链表+红黑树),
而HashTable是整个使用了锁,多线程情况下,一次只能一个线程操作数据。相比之下,HashTable效率要低,所以如果是多线程使用Map,一般使用ConcurrentHashMap
10、java集合的快速失败机制“fail-fast”
是集合的一种错误检测机制,当多线程操作同一个集合遍历的时候,如果一个线程修改了集合的结构,比如删了一个元素,那么另一个线程再操作的时候就会抛出异常。
原理:使用迭代器的时候,有一个count值,每次next都会加1,但是如果结构发生改变,count值就和exceptCount不匹配,从而抛出异常。
解决方法:操作集合对象时加同步
11、ArrayList和Vector的区别?
1)ArrayList线程不安全,Vector是线程安全的
2)ArrayList扩容后是原来的1.5倍;Vector是原来的2倍
12、ArrayList和LinkedList的区别?
数据结构不一样,前者是动态数组,后者是双向链表。所以查询效率ArrayList更高,增删效率LinkedList效率更高。LinkedList比ArrayList占更多的内存
1)什么时候该用Array而不是ArrayList?
Array可以是基本数据类型,也可以是对象;ArrayList只能是对象
Array是固定长度;ArrayList会扩容
ArrayList提供更多的方法,比如addAll、迭代器之类的,用起来更方便
综上:当长度固定,操作不多且是基本数据类型数组的时候,使用array
13、HashSet是如何保证数据的不可重复性?
HashSet里面用的是HashMap,不过只用了它的键,值都是一样的,因为HashMap的键是不可重复的,所以HashSet也是不可重复的。add的时候调用的是HashMap的put方法,如果返回为null,说明数据存在,add方法返回false
14、BlockingQueue是什么?
阻塞队列是专门用来解决生产者消费者问题的