map详解:Map接口和它的实现也是集合框架的一部分,但Map不是集合,集合也不是Map。
实现类:hashmap:hashmap允许存放多个空值,最多允许存放一个空键,他是用来存放key-value的集合,每一对
key-value也叫做一个Entry,这些键值是存放在一个数组中。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//默认的桶数组大小16 桶容量:hashmap的长度
static final int MAXIMUM_CAPACITY = 1 << 30;//极限值(超过这个值就将threshold修改为Integer.MAX_VALUE(此时桶大小已经是2的31次方了),表明不进行扩容了)
static final float DEFAULT_LOAD_FACTOR = 0.75f;//负载因子 负载因子:(loadFactor)
关键字:newThr(threshold 极限值)超过这个值就会进行扩容,hashmap的初始newThr=16*0.75=12,每次扩容变为之前的两倍。
随意可以猜出负载因子是干嘛的了吧,我是这样理解的,为了不等hashmap放不下再扩容,就提前扩容了,如hashmap默认初始值如果超过了12就开始扩容
这个负载因子可以改变,每次扩容是需要时间的,如果我们不需要追求时间效率,同时内存容量不大时,可以修改一下负载因子,这样就不会每次扩容时都
有没用的空间就就进行扩容了,这样的话可以省内存,但是当我们如果修改loadFactor很大,比如1那么就会导致用完了才取扩容,扩容这段时间没有空间无法
向下进行,扩容完后才能开始,这样就比较浪费时间,典型的时间换空间。网上的说法是会导致查找时间效率很低,这个更有说服力。
当我们扩大负载因子时,内存利用率虽然上升了,但是你想一下你留下来空白的空间少了自然计算时index的冲突就会很高,那么由于hashmap它的每个index位置
都是一条链表的头结点位置,如果出现位置冲突那么就会用到链表的头插法(至于为什么用头插法,说的是作者认为组后插入的元素查找几率会更高,
放在前面可以提高查询效率,链表一个一个查询时非常耗时间的,这也是如果多次插入重复的key的value,后面始终会覆盖前面value的原因)
横向在每个数组元素上都一个链表结构,当数据被Hash后,得到数组下标,把数据放在对应下标元素的链表上。所以最高效的方式就是每个数据位置的链表
上存储尽可能少的元素,也就是减少位置冲突,取值时不必要频繁的在链表上一个一个查找效率自然高。
数组加链表这也是hashmap解决冲突的办法,集合了数组和链表两种极端的数组存储方式,尽量发挥它们各自的优势。
取key的hashCode值、高位运算、取模运算。
这里的存储结构你可以想象是一根树干树干的不同位置挂了不同的藤条,藤条上有不同的瓜。所以查找元素get时自然查找效率就低了,不仅要从树干从左往右
还需要树干上的链条从上到下。横向是数组存储,纵向每个数组又是不同的链表头。jdk1.7之前元素的index是采用hash code和hashmap长度做取模运算得到,
jdk1.8就采用位运算,更加高效,jdk1.8增加了红黑树。
为什么hashmap的初始值大小不是10或者9呢,扩容也是2的幂次方,原因很简单就是键值元素的存储位置是用hash算法来找index的,
index=(length-1)*(key的hashcode),这里我们知道计算机的与运算1*1=1 0*1=0,哈哈知道怎么回事了吧,因为16-1=15=1111(二进制),如果是10
那么就是10-1=9=1001,当中0的次数很多,自然用元素hashcode来做运算时,最后结果index相同的概率就极大,要尽量减少位置冲突,当然要用全是1的了
这样最后的index主要就是取决于元素的hashcode的后面几位数,如果hashcode分布均匀,存放位置自然就很少冲突了。