1、说说HashMap 底层数据结构是怎样的?
1.7时HashMap 底层是hash数组和单向链表实现,jdk1.8后采用数组+链表+红黑树的数据结构。
2、说说HashMap 的工作原理
通过put和get存储和获取对象。当我们给put()方法传递键和值时,先对键做一个hashCode()的计算来得到它在bucket数组中的位置来存储Entry对象。当获取对象时,通过get获取到bucket的位置,再通过键对象的equals()方法找到正确的键值对,然后再返回值对象。
3、使用HashMap时,当两个对象的hashCode相同怎么办?
因为HashCode相同,不一定就是相等的(equals方法比较),所以两个对象所在数组的下标相同,"碰撞"就此发生。又因为HashMap使用链表存储对象,这个Node会存储到链表中。
4、HashMap的哈希函数怎么设计的
hash函数是先拿到通过key的hashCode,是32位的int值,然后让hashCode的高16位和低16位进行异或操作。两个好处:
1、一定要尽可能降低hash碰撞,越分散越好;
2、算法一定要尽可能高效,因为这是高频操作, 因此采用位运算;
5、HashMap遍历方法有几种?
1、Iterator迭代器
最常见的使用方式,可同时得到key、value值
2、使用foreach方式(JDK1.8 才有)
3、通过key的set集合遍历
6、解决hash冲突的有几种方法?
1、再哈希法:如果hash出的index已经有值,就再hash,不行继续hash,直至找到空的index位置,要相信瞎猫总能碰上死耗子。这个办法最容易想到。但有2个缺点:
比较浪费空间,消耗效率。根本原因还是数组的长度是固定不变的,不断hash找出空的index,可能越界,这时就要创建新数组,而老数组的数据也需要迁移。随着数组越来越大,消耗不可小觑。
get不到,或者说get算法复杂。进是进去了,想出来就没那么容易了。
2、开放地址方法:如果hash出的index已经有值,通过算法在它前面或后面的若干位置寻找空位,这个和再hash算法差别不大。
3、建立公共溢出区: 把冲突的hash值放到另外一块溢出区。
4、链式地址法: 把产生hash冲突的hash值以链表形式存储在index位置上。HashMap用的就是该方法。
优点是不需要另外开辟新空间,也不会丢失数据,寻址也比较简单。但是随着hash链越来越长,寻址也是更加耗时。好的hash算法就是要让链尽量短,最好一个index上只有一个值。也就是尽可能地保证散列地址分布均匀,同时要计算简单。
7、说说HashMap中put方法的过程
8、当链表长度>=8时,为什么要将链表转换成红黑树?
因为红黑树的平均查找长度是log(n),长度为8的时候,平均查找长度为3,如果继续使用链表,平均查找长度为8/2=4,所以,当链表长度>=8时 ,有必要将链表转换成红黑树。
9、说说resize扩容的过程
10、说说hashMap中get是如何实现的?
对key的hashCode进行hash值计算,与运算计算下标获取bucket位置,如果在桶的首位上就可以找到就直接返回,否则在树中找或者链表中遍历找,如果有hash冲突,则利用equals方法去遍历链表查找节点。
11、拉链法导致的链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?
之所以选择红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持“平衡”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。
12、红黑树
R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。
红黑树特征:
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。[注意:这里叶子结点,是只为空的叶子结点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑结点。
下图就是一个简单的红黑树。
13、JDK8中对HashMap做了哪些改变?
1.在java 1.8中,如果链表的长度超过了8,那么链表将转换为红黑树。(桶的数量必须大于64,小于64的时候只会扩容)
2.发生hash碰撞时,java 1.7会在链表的头部插入,而java 1.8会在链表的尾部插入
3.在java 1.8中,Entry被Node替代(换了一个马甲)。
14、HashMap中的key我们可以使用任何类作为key吗?
平时可能大家使用的最多的就是使用String作为HashMap的key,但是现在我们想使用某个自定义类作为 HashMap的key,那就需要注意以下几点:
如果类重写了equals方法,它也应该重写hashCode方法。
类的所有实例需要遵循与equals和hashCode相关的规则。
如果一个类没有使用equals(),你不应该在hashCode中使用它。
咱们自定义key类的最佳实践是使之为不可变的,这样hashCode值可以被缓存起来,拥有更好的性能。不可变的类也可以确保hashCode和equals在未来不会改变,这样就会解决与可变相关的问题了。
15、HashMap,LinkedHashMap,TreeMap有什么区别?
LinkedHashMap是继承于HashMap,是基于HashMap和双向链表来实现的。
HashMap无序;LinkedHashMap有序,可分为插入顺序和访问顺序两种。如果是访问顺序,那put和get操作已存在的Entry时,都会把Entry移动到双向链表的表尾(其实是先删除再插入)。
LinkedHashMap存取数据,还是跟HashMap一样使用的Entry[]的方式,双向链表只是为了保证顺序。
LinkedHashMap是线程不安全的。
TreeMap实现SortMap接口,能够把它保存的记录根据键排序。
默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。
16、说说什么是fail-fast?
fail-fast机制是Java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。 例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变,那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。这里的操作主要是指add、remove和clear,对集合元素个数进行修改。 解决办法 建议使用“java.util.concurrent 包下的类”去取代“java.util 包下的类”。可以这么理解:在遍历之前,把 modCount 记下来 expectModCount,后面 expectModCount 去 和 modCount 进行比较,如果不相等了,证明已并发了,被修改了,于是抛出ConcurrentModificationException异常。
17、HashMap是线程安全的吗?
不是,在多线程环境下,1.7会产生死循环、数据丢失、数据覆盖的问题,1.8中会有数据覆盖的问题,以1.8 为例,当A线程判断index位置为空后正好挂起,B线程开始往index位置的写入节点数据,这时A线程恢复现场,执行赋值操作,就把A线程的数据给覆盖了;还有++size这个地方也会造成多线程同时扩容等问题。
如何规避:使用线程安全的ConcurrentHashMap。
18、HashMap和ConcurrentHashMap的区别?
一个线程不安全,一个线程安全。
ConcurrentHashMap是Java中的一个线程安全且高效的HashMap实现。平时涉及高并发如果要用map结构,那第一时间想到的就是它。相对于hashmap来说,ConcurrentHashMap就是线程安全的map,其中利用了锁分段的思想提高了并发度。
ConcurrentHashMap在JDK1.8之前是采用分段锁来实现的Segment + HashEntry,Segment数组大小默认是16,2的n次方;JDK1.8之后,采用Node + CAS + Synchronized来保证并发安全进行实现,锁粒度降低了。
19、说说ConcurrentHashMap中锁机制
JDK 1.7中,采用分段锁的机制,实现并发的更新操作,底层采用数组+链表的存储结构,包括两个核心静态内部类Segment和HashEntry。
①、Segment继承ReentrantLock(重入锁)用来充当锁的角色,每个Segment对象守护每个散列映射表的若干个桶;
②、HashEntry用来封装映射表的键-值对;
③、每个桶是由若干个HashEntry对象链接起来的链表
JDK 1.8 中,采用Node + CAS + Synchronized来保证并发安全。取消类Segment,直接用table数组存储键值对;当HashEntry对象组成的链表长度超过 TREEIFY_THRESHOLD 时,链表转换为红黑树,提升性能。底层变更为数组 + 链表 + 红黑树。
20、常用的数据结构都有哪些?
数组、链表、队列、栈、树、堆、图
1、list:
ArrayList、LinkedList
2、map:
hashMap、treeMap、LinkedHashMap
3、queue:
ArrayBlockingQueue、PriorityBlockingQueue、LinkedBlockingQueue
21、常用的集合类都有哪些?
Map接口和Collection接口是所有集合框架的父接口:
1、Collection接口的子接口包括:Set接口和List接口
2、Map接口的实现类主要有:HashMap、TreeMap、Hashtable、 ConcurrentHashMap以及Properties等
3、Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
4、List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
Java容器分为Collection和Map两大类,Collection集合的子接口有Set、 List、Queue三种子接口。我们比较常用的是Set、List,Map接口不是 collection的子接口。
Collection集合主要有List和Set两大接口
- List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重 复,可以插入多个null元素,元素都有索引。常用的实现类有ArrayList、LinkedList和 Vector。
- Set:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素, 只允许存入一个null元素,必须保证元素唯一性。Set接口常用实现类是HashSet、 LinkedHashSet 以及TreeSet。
Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value不要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
Map的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、 ConcurrentHashMap
22、集合框架底层数据结构?
List
- Arraylist: Object数组
- Vector: Object数组
- LinkedList: 双向循环链表
Set
- HashSet(无序,唯一):基于HashMap实现的,底层采用HashMap来保存元素
- LinkedHashSet: LinkedHashSet 继承与HashSet,并且其内部是通过LinkedHashMap来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于Hashmap 实现一样,不过还是有一点点区别的。
- TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)
Map
-
HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
-
LinkedHashMap:LinkedHashMap 继承自HashMap,所以它的底层仍然是 基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。 同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
-
HashTable: 数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的
-
TreeMap: 红黑树(自平衡的排序二叉树)