Map接口的实现类有HashMap,TreeMap,HashTable,ConcurrentHashMap
1.HashMap:
首先是HashMap的实现原理,我是参考的这两篇博客:深入Java集合学习系列:HashMap的实现原理,Jdk1.8中的HashMap实现原理
下面来记述一下HashMap中的重点:
1.在jdk1.8之前采用数组+链表的方式实现,1.8之后采用数组+链表+红黑树(链表长度大于8时)实现
2.HashMap中的参数:threshold(最多能容纳的键值对数量),loadFactory(阀值,threshold=length*loadfactory),modcount(记录HashMap内部结构发生变化的次数),size(实际存在的键值对数量)
3.HashMap的工作原理?答:通过hash的方法,通过put和get获取和存储对象。在存储对象时,将键值对传给put方法,它调用hashCode计算hash值从而得到bucket位置,进一步存储。获取对象时,将key传给get方法,它调用hashCode计算hash值从而得到bucket位置,当hashCode相同即发生冲突时,进一步调用equals方法来确定键值对(如果hashCode相对,equals不一定相等;equals相等hashCode一定相等)
4.你知道get和put的原理吗?equals()和hashCode()的都有什么作用?答:通过key的hashCode进行hash,并计算下标(n-1 & hash),从而获得bucket的位置,如果产生碰撞,则利用key.equals方法去链表或树中查找对应的节点
5.你知道hash的实现吗?为什么要这样实现?答:在java1.8中,通过hashCode的高16位异或低16位来实现的,为什么要这么设计呢?这样可以做到在bucket的n比较小的时候也能保证高低bit能参与到hash计算中。
6.如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?答:如果超过了负载因子(0.75),则会resize一个原来两倍长度的hashMap,并重新进行hash计算。
7.你了解重新调整HashMap大小存在什么问题吗?答:当重新调整hash的时候确实存在条件竞争,如果两个线程都发现hashMap需要调整大小,他们会同时试着调整大小,就会发生冲突,所以在多线程环境下使用currentHashMap来代替hashMap
8.为什么String, Interger这样的wrapper类适合作为键?答:因为String是final类型的,是不可变的,其他包装类也有这样的特点。为了要计算hashCode,就要防止键值改变,如果键值在放入时和获取时返回不同hashCode的话就不能从hashMap中找到你想要的对象,不可变性还有其他优点如线程安全。
9.hashMap的长度为什么是2的幂次方?答:一:通过将key的hash值和length-1进行与运算,实现了key的定位,如果长度为2的幂次方,那么length-1转化为二进制必定为11111....的形式,和h的二进制与操作效率会非常高并且不会造成空间浪费。如果不是2的幂次方,例如length为15,则length-1就是14,二进制位1110,再和h与操作最后一位都是0,而0001,0011,0101等二进制最后一位为1的情况就不能放置元素,空间浪费很大。
10.为什么JDK1.8之后采用红黑树而不是链表来处理冲突?答:遍历链表的时间复杂度为O(n),在JDK1.8之后引入红黑树,查找的时间复杂度为O(logn)
11.开放地址法:
基本思想:当地址发生冲突时,继续探测散列表的其他存储单元,直到找到空位置为止
12.拉链法的优势与缺点:
优势:
1. 拉链法处理冲突简单,无堆积现象,即非同义词绝不会发生冲突,因此平均查找长度较短
2. 由于拉链法中各链表上的节点空间是动态申请的,故它更适合于造表前无法确定表长的情况
3. 在拉链法构造的散列表中,删除节点的操作易于实现,只要简单的删去链表上相应的节点即可
缺点:
1. 指针需要额外的空间(相对于开放地址法而言,因为开放地址法中不需要使用链表)
2.HashTable:
hashTable主要看的这篇文章:【源码】Hashtable源码剖析
1.HashTable如何保证线程安全?答:HashTable底层使用synchronized来实现线程安全,所以hashTable在线程竞争激烈的情况下效率会非常低。
2.hashMap与hashTable的区别?答:一:hashTable使用synchronized方法来实现同步,线程安全;hashMap未使用同步线程不安全。二:hashTable不允许null值,hashMap允许null值。三:hashtable数组长度默认为11,增长方式为2*old+2,hashMap数组默认长度为16,增长方式为2*old。四:hash值的使用方式不同,hashTable是直接使用对象的hashCode,hashMap是重新计算hash值在进行与操作。
3.ConcurrentHashMap:
1.ConcurrentHashMap的具体实现知道吗?答:一:该类包含两个静态内部内HashEntry和Segment;前者用来封装映射表的键值对,后者用来充当锁的角色。二:Segemnt是一种可重入锁,每个Segment守护一个hashEntry数组中的元素,当对hashEntry中的元素进行修改时必须先获得对应的Segment锁。
2.ConcurrentHashMap如何保证线程安全?答:HashTable在多线程激烈环境下效率低下的原因就是它对所有的数据都加了锁,所有线程竞争用一把锁。ConcurrentHashMap使用分段锁技术,将数据分成一段一段存储,然后给每一段数据分配一把锁,当一个线程占用锁访问其中一段数据时,其他段的数据也能被其他线程访问。
3.ConcurrentHashMap与hashTable的区别?答:ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。HashMap 没有考虑同步,hashtable 考虑了同步的问题。但是 hashtable 在每次同步执行时都要锁住整个结构。 ConcurrentHashMap 锁的方式是稍微细粒度的。 ConcurrentHashMap 将 hash 表分为 16 个桶(默认值),诸如 get,put,remove 等常用操作只锁当前需要用到的桶。
4.ConcurrentHashMap如何做到Get方法不用加锁的(HashTable的Get方法需要加锁)?答:原因是get方法里使用的变量是volatile类型,能够在线程之间保持可见性,线程不会读到过期的值,但是只能被单线程写。之所以不会读到过期的值是因为java内存模型的happen-before原则,读volatile字段的写操作优于读操作,即使两个线程同时修改和读取同一变量也会拿到最新的值。
5.ConcurrentHashMap的put操作?答:put方法里需要对共享变量进行写入操作,所以为了线程安全,在操作共享变量时必须得加锁。Put方法首先定位到Segment,然后在Segment里进行插入操作。插入操作需要经历两个步骤,第一步判断是否需要对Segment里的HashEntry数组进行扩容,第二步定位添加元素的位置然后放在HashEntry数组里。
4.其他的关于集合的一些知识点:
1.ArrayList VS Vector:
ArrayList和Vector都实现了list接口,都是数组实现,Vector线程安全,arrayList线程不安全
Arraylist初始容量为10,加载因子为0.5,扩容增量:原容量的0.5倍+1;Vector初始容量为10,加载因子为1,扩容增量:原容量的1倍。
2.ArrayList VS LinkedList:
ArrayList底层是数组实现,适合查找和在末尾插入删除;LinkedList底层使用双向链表实现,适合中间插入和删除。
3.集合中常见的接口:总共有两大接口,Collection和Map,一个元素集合,一个是键值对集合;其中List和Set实现了Collection接口,一个是有序元素集合,一个是无序元素集合;而ArrayList和LinkedList实现了List接口;HashMap和HashTable实现了Map接口,HashTable是线程安全的,HashMap不是线程安全的。