- HashMap
- 初始容量 和 负载因子,这两个参数是影响HashMap性能的重要参数。其中,容量表示哈希表中桶的数量 (table 数组的大小),初始容量是创建哈希表时桶的数量;负载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。若负载因子越大,那么对空间的利用更充分,但查找效率的也就越低;若负载因子越小,那么哈希表的数据将越稀疏,对空间造成的浪费也就越严重。系统默认负载因子为 0.75,这是时间和空间成本上一种折衷,一般情况下我们是无需修改的。
- HashMap 很少会用到equals方法,因为其内通过一个哈希表管理所有元素,利用哈希算法可以快速的存取元素。当我们调用put方法存值时,HashMap首先会调用Key的hashCode方法,然后基于此获取Key哈希码,通过哈希码快速找到某个桶,这个位置可以被称之为 bucketIndex。如果两个对象的hashCode不同,那么equals一定为 false;否则,如果其hashCode相同,equals也不一定为 true。所以,理论上,hashCode 可能存在碰撞的情况,当碰撞发生时,这时会取出bucketIndex桶内已存储的元素,并通过hashCode() 和 equals() 来逐个比较以判断Key是否已存在。如果已存在,则使用新Value值替换旧Value值,并返回旧Value值;如果不存在,则存放新的键值对<Key, Value>到桶中。因此,在 HashMap中,equals() 方法只有在哈希码碰撞时才会被用到。
- 随着HashMap中元素的数量越来越多,发生碰撞的概率将越来越大,所产生的子链长度就会越来越长,这样势必会影响HashMap的存取速度。为了保证HashMap的效率,系统必须要在某个临界点进行扩容处理,该临界点就是HashMap中元素的数量在数值上等于threshold(table数组长度*加载因子)。
- HashMap 的底层数组长度为何总是2的n次方?扩容:resize 包含初始化和扩容https://www.bilibili.com/video/av50449402?from=search&seid=1170539893142288868
- 不同的hash值发生碰撞的概率比较小,这样就会使得数据在table数组中分布较均匀,空间利用率较高,查询速度也较快;
- h&(length - 1) 就相当于对length取模,而且在速度、效率上比直接取模要快得多,即二者是等价不等效的,这是HashMap在速度和效率上的一个优化。
- 1.7之前时数组+链表,1.8之后数数组+红黑树,在插入元素时会判断时链表结构还是树结构(链表超过8,就会改成红黑树结构.1.8中新元素会插入到链表的尾部,还有就是避免死锁;在扩容是,不会再进行移位异或操作,直接看hash之后的某一位,如果为1,直接index=index+扩容前大小;若为0,直接解释index=原index),然后进行相应的操作。因为当插入元素过多时,链表长度会非常长,导致查询效率降低,红黑树提高查询效率
- 1.7在插入一个节点的时候,需要将其的next指针指向数组位置(hash之后的桶),然后整条链表中新插入节点作为头节点(数组桶的位置)
- 一个节点包含4个变量 key value next hashcode
- 在初始化时可以指定容量,15的话初始化16,30的话初始化32,初始化值永远是2的平方倍,在底层hash计算桶的位置时,是hash出来的h&(length-1)。在算出hashcode之后会右移或异或运算,让一个hashcode的高4位和第四位都参与运算,让结果更散列,提高元素分布的均匀性,插入效率高,查询效率低
- LinkedHashMap
- 将hashmap和双向链表向结合。每个节点除了key value hash next(前者就是hashmap的节点结构) before after(后者用于维护双向链表结构)在一个entey插入时,先拆入一个哈希表(数组+单链表),再将其插入到一个双向链表中。二者相互独立。
- hashmap写入慢,读取快;linkedhashmap是个有序链表读取慢,写入快;如果需要输出的顺序和输入的相同,那么用LinkedHashMap 可以实现,它还可以按读取顺序来排列.
- ConcurrentHashMap
- ConcurrentHashMap 可以支持 16 个线程执行并发写操作(如果并发级别设为16),及任意数量线程的读操作。
- HashMap进行扩容重哈希时导致Entry链形成环。一旦Entry链中有环,势必会导致在同一个桶中进行插入、查询、删除等操作时陷入死循环。原因就是当线程1对hash进行扩容时,链表的相对位置进行的调换,从而导致线程2再进行操作的时候出现了循环链表
- 对整个hashmap加锁导致同步--->hashtable
- 1.7里面有2个数组,sgement对象包含的数组就是真正存元素的数组(hashEntry)。可以理解为ConcurrentHashMap包含很多小的hashmap,所有小的hashmap共同使用一把锁;当插入一个新的kv时,先判断进入那个segment数组里,然后再判断再进入那个hashmap里。初始化数组(和hashmap一样的存元素的数组)大小和负载因子还有并发等级,其中包含先初始化segment数组,然后初始化里面的hashentry。当ssize(初始为1)<并发等级(默认为16)时,ssize左移移位,1-2-4-8-16。总是2的平方倍。在创建了segment数组对象后,当每一个hashentry进入时先加锁(segment对象继承了ReentrantLock接口),其余和hashmap类型类似;本质上,ConcurrentHashMap就是一个Segment数组,而一个Segment实例则是一个小的哈希表。由于Segment类继承于ReentrantLock类,从而使得Segment对象能充当锁的角色,这样,每个 Segment对象就可以守护整个ConcurrentHashMap的若干个桶,其中每个桶是由若干个HashEntry 对象链接起来的链表。通过使用段(Segment)将ConcurrentHashMap划分为不同的部分,ConcurrentHashMap就可以使用不同的锁来控制对哈希表的不同部分的修改,从而允许多个修改操作并发进行, 这正是ConcurrentHashMap锁分段技术的核心内涵。进一步地,如果把整个ConcurrentHashMap看作是一个父哈希表的话,那么每个Segment就可以看作是一个子哈希表,如下图所示;注意,假设ConcurrentHashMap一共分为2^n个段,每个段中有2^m个桶,那么段的定位方式是将key的hash值的高n位与(2^n-1)相与。在定位到某个段后,再将key的hash值的低m位与(2^m-1)相与,定位到具体的桶位。
- 1.8中取消segment对象,直接在数组中的每个链表的头节点加锁(synchronzied)
java基础——Map
最新推荐文章于 2021-02-16 06:11:39 发布