HashMap为什么需要扩容?谈谈你的理解
为了提高查询效率(如果不扩容,只能任由链表一直增长或者树化);
当HashMap中的数据足够多的时候,他的查询效率变得很低,甚至退化成O(n),哪怕是使用红黑树也无法使查询效率有较大幅度提高的时候,也就是HashMap中的碰撞达到了负载因子的时候,就会触发HashMap的扩容;
扩容会扩容为之前的两倍,这样扩容之后,可以大幅提高查找效率,在不碰撞的情况下,查询还是O(1);
HashMap的扩容算法,描述一下
主要分两种情况,一种是第一次初始化,一种是已经初始化过的;
第一次初始化,他会根据你传入的值对HashMap进行初始化,初始化好之后,会把表返回,为什么是直接初始化呢?其实是因为调用构造方法的时候就已经对数据进行校验了,所以这个时候直接初始化就可以了;
如果已经经过初始化的话,首先它会根据传入现在表的值进行分析,如果扩容之后的大小超过了最大限制,那么HashMap就不会扩容了,而是把触发扩容的条件提高的Int的最大值,然后把旧表返回;
而如果可以扩容,那么会扩容为之前HashMap的两倍;扩容完成之后,HashMap会把旧表中的数据填充到新表中,数据的填充有三种情况:
1.只有一个值,这是最好的情况,因为可以直接计算位置,并添加就可以了,为什么要计算位置?因为现在数组扩容成之前的两倍了,如果高位是0,那么就是现在这个位置,如果高位是1,那就是另一个位置了,所以需要对数据的位置进行计算;
2.链表,链表的话和之前一个值的时候差不多,但是链表要遍历链表,并把一个链表分成高位链和低位链,这样分配之后,就可以计算并把数据插入了;
3.红黑树,会把数据先拿出来放到新表中,如果新表会不会有红黑树取决于数据放进去之后,而不是把整个红黑树移动过去;其实和链表也差不多,它也会先分成两个链,高位链和低位链,然后把链放到新表中;
HashMap和HashTable的区别
相同点:都是存储key-value键值对的
不同点:
HashMap容许Key-value为null,hashTable不容许;
hashMap没有考虑同步,是线程不安全的。hashTable是线程安全的,给api套上了一层synchronized修饰;
HashMap继承于AbstractMap类,hashTable继承与Dictionary类。
迭代器(Iterator)。HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。因此当有其它线程改变了HashMap的结构(增长或者移除元素),将会抛出ConcurrentModificationException。
容量的初始值和增长方式都不同:HashMap默认的容量大小是16;增长容量时,每次将容量变为"原始容量x2"。Hashtable默认的容量大小是11;增长容量时,每次将容量变为"原始容量x2 + 1";
添加key-value时的hash值算法不一样:HashMap添加元素时,是使用自定义的哈希算法。Hashtable没有自定义哈希算法,而直接采用的key的hashCode()。
HashMap和TreeMap的区别
在数组中我们是通过数组下标来对其内容索引的,而在Map中我们通过对象来对对象进行索引,用来索引的对象叫做key,其对应的对象叫做value。这就是我们平时说的键值对。
HashMap通过hashcode对其内容进行快速查找(哈希表通过把关键码值映射到表中一个位置来访问记录,不需比较便可直接取得所查记录,加快了查找的速度)。
而 TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。
- HashMap: 数组+列表+红黑树
- TreeMap: 红黑树
- HashMap 和 TreeMap 都不是线程安全的。没有关键字synchronized修饰,也没有JUC包类的同步机制。
- TreeMap 和 HashMap 都继承自 AbstractMap ,但是需要注意的是 TreeMap 它还实现了 NavigableMap 接口和 SortedMap 接口。
- 接口实现
实现 AbstractMap 类,覆盖了hashcode() 和 equals() 方法,以确保两个相等的映射返回相同的哈希值。
实现 NavigableMap 接口让 TreeMap 有了对集合内元素的搜索的能力。
实现 SortedMap 接口让 TreeMap 有了对集合中的元素根据键排序的能力。默认是按 key 的升序排序,不过我们也可以指定排序的比较器。 - 性能
HashMap:适用于在Map中插入、删除和定位元素。
Treemap:适用于按自然顺序或自定义顺序遍历键(key)。
ConcurrentHashMap的存储结构是怎样的?
Java7中ConcurrnetHashMap使用的分段锁,也就是每一个Segment 上同时只有一个线程可以操作,每一个Segment都是一个类似HashMap数组的结构,它可以扩容,它的冲突会转化为链表。但是Segment 的个数一但初始化就不能改变,默认 Segment 的个数是 16 个。
Java8中的ConcurrnetHashMap使用的Synchronized锁加CAS的机制。结构也由Java7中的 Segment 数组 + HashEntry 数组 + 链表进化成了Node数组 + 链表 / 红 黑树,Node是类似于一个 HashEntry的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表。
ConcurrentHashMap和HashMap的区别是什么?
1.HashMap是线程非安全的,ConcurrentHashMap是线程安全的。
2.ConcurrentHashMap将整个Hash桶进行了分段segment,也就是讲这个大的数组分成了几个小的片段segment,而且每个小的片段segment上面都有锁存在,那么在插入元素的时候需要先找到应该插入到哪个segment片段,然后再这个片段上面进行插入,而且这里还需要获取segment锁。
3.ConcurrentHashMap让锁的力度更精细一些,并发性能更好。
Concurrent HashMap线程安全吗,Concurrent HashMap如何保证线程安全?
1.Hash Table容器在竞争激烈的兵法环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,那加入容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段数据时,线程就不会存在竞争锁,从而可以有效提高有效提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个数据片段的时候,其他的数据也能被其他线程访问。
2.get操作的高效之处在于整个get过程不需要加锁,除非读到的值是空的才会加锁重读,get方法里将要使用的共享变量都定义成volatile,如用于统计当前Segment大小的count字段和用于存储值的HashEntry的value。定义成volatile的变量,能够在线程之间保持可见性,能够被多线程同时读,并且保证不会读到过期的值,但是只能被单线程写(友一种情况可以背多线程写,就是写入的值不依赖于原值),在get操作里只需要读不需要写共享变量count和value,所以可以不用加锁。
3.Pt方法首先定位到Segemnt,然后在Segment里进行插入操作,插入操作需要经历两个步骤,第一步判断是否需要对Segment里的HashEntry数组进行扩容,第二步定位添加元素的位置然后放在HashEntry数组里。
什么是红黑树?
官方解释:红黑树(Red Black Tree)是一种特殊的自平衡二叉树,简称RBT,即是一种特殊的平衡二叉树(AVL),都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。
说点人能听懂的:就是一种为了提高插入和删除操作的一种算法,最坏的时间复杂度:O(logn)
红黑树的五大性质
(1)结点是红色或者黑色。
(2)根结点是黑色。
(3)所有的叶子结点都是黑色。(叶子是NIL结点)
(4)每个红色结点的两个子节点都是黑色。(即不可能出现两个相邻的红色结点)
(5)从任一结点到每个叶子的所有路劲都包含相同数目的黑色结点。