HashMap相关的研究和思考
问题:
https://www.cnblogs.com/LexMoon/p/HashMapDep.html
1.扩容思考:
https://cloud.tencent.com/developer/article/1420754
数据库分片扩容、redis扩容、es扩容具体都是怎么做的?从hashmap的扩容上能学到什么?
ngnix负载均衡算法中的哈希算法是干啥用的?
shardingjdbc的分片算法怎么搞的?
xxl-job的分片处理算法怎么实现?
hashmap1.7采用的是插入前置检查扩容,同步懒扩容策略。
1.8则采用的是插入后检查扩容,同步预扩容策略。
扩容的标准是实时维护的map size大于数组的length*loadFactor(默认0.75)时以及该元素对应的当前数组位置不为null时就会扩容为原来的2倍。
2.红黑树、B+树、倒排索引的选型思路
https://www.cnblogs.com/cjsblog/p/10327673.html
数据库数据删除过多导致索引树失衡的原因
红黑树在线演示:
https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
3.java hashmap和redis 哈希表的区别和实现考虑:https://www.cnblogs.com/TuringLi/p/12803247.html
https://blog.csdn.net/xiaodoukuaile/article/details/53162042
redis通过渐进式rehash的方式(解决节点过多造成的读性能下降问题)+最简单的数据+单链表方式实现高效的读写操作
https://www.cnblogs.com/gaopengfirst/p/10062980.html
java hashmap则是通过红黑树解决节点过多时造成的读写性能下降问题
4.解决hash冲突的办法
开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
再哈希法
链地址法
建立一个公共溢出区
https://blog.csdn.net/vking_wang/article/details/14166593
5.hashmap的链表转换为红黑树的长度为啥是8呢?
一般真到不了
https://blog.csdn.net/baidu_37147070/article/details/98785367
首先可以肯定当链表长度不断变长时,肯定会对查询性能有一定的影响,因此需要转换成红黑树。但是 TreeNodes 占用空间是普通 Nodes 的两倍,并且我们需要避免频繁的在链表和红黑树之间来回转换,所以我们需要一个阈值来确定什么时候进行转换。根据源码注释所说,在理想情况下随机 hashCode 算法下所有 bucket 中节点的分布频率会遵循泊松分布,在源码中可以看到一个 bucket 中链表长度达到 8 个元素的概率为 0.00000006,所以官方选择 8 作为整个限制是通过严谨科学的概率统计得来的。另外在treeifyBin中有一个优化,链表长于8时的树化处理会再检查一下当前数组的长度是否在64以下,是的话直接扩容继续采用数组+链表的数据结构,只有数组长度超过64时才会树化。
https://blog.csdn.net/ccnt_2012/article/details/81114920
通过这点我们学会思考如何利用泊松分布和概率统计提升程序的时间、空间利用率:比如分布式锁的循环等待时长定为多少合适?
6.分治算法
https://www.cnblogs.com/xsyfl/p/6921687.html
7.设计模式
运用了组合模式,好处是啥
组合模式的具体应用案例:文件系统(文件夹包含多个文件,但是他们都当做是文件进行处理,只是文件夹有额外的处理逻辑)
统一容器和子节点的访问接口,在保证层级关系及容器的特殊处理功能的同时,将二者抽象为组件统一管理,简化了客户端使用组件的逻辑,降低了使用成本。
Map接口相当于统一组件接口,HashMap相当于容器,内部类Node则相当于子节点,HashMap和Node都拥有统一的操作方法
8.线程安全问题在哪?怎么解决?
扩容时可能会导致死循环
9.学习不能仅仅学基础,还要多去思考,从个个角度发问,从真实场景发问,才能吃透,学会,同时掌握学习的能力。
10.为啥重写对象的equals方法时,必须重写他的hashcode方法?
因为hashmap获取时是通过对象的hashcode找到存放的数组位置,然后再遍历散列表equals找到对象的,所以只有如果不重写hashcode方法,很有可能相同的对象KEY(实际并不是一个对象,只是equals返回true而已)存储时被认为不是一个对象导致重复存储,或者获取时获取不到的情况
11.为啥数组的长度必须是 2的N次方?
因为定位数组坐标的算法采用的是 hashcode&(length-1);这个算法要求被与的数必须是一个奇数才能同时达到数组坐标不越界和取模算法相同的效果,这种算法的效率较取模算法 10万次计算规模下 性能至少提升 3倍以上
12.添加元素时是如何维护链表的?
1.7版本首先将该元素插入到原有链表的头部,然后再将该元素替换到原来头部元素所在的数组对应的位置(下移链表),插入头部其实也考虑到很多场景下新插入的元素使用的概率会更高。
1.8版本则是将该元素插入到原有链表的尾部,因为增加了红黑树的遍历处理,所以直接省去了原来的替换操作,直接插入到了链表的尾部
13.hashmap支持null元素?
是的,但null元素无法做equals和hashcode比较,所以是单独处理的分支,null只能放在数组第一个位置,另外如果其他分配到数组第一个位置的元素还是按照统一新增的流程处理,查找时直接通过 ==(注意null==null返回true这点和SQL不太一样) 或者 非空equals比较找到对应的元素
14.为什么扩容是 2 倍,而不是50%,75%什么的?
A. 取模用与操作(hash & (arrayLength-1))会比较快,所以数组的大小永远是 2 的 N次方,所以扩容后的大小必须是 2 的幂。
15.为什么是数组+链表?
数组拥有快速访问的特性,根据坐标访问操作的时间复杂度是O(1),所以很快,但是根据KEY查询元素还是得遍历的,时间复杂度是O(N),写操作复杂度则更高,因为可能涉及到扩容,扩容则涉及到重新申请新的数组内存,数组都是连续的一整块内存,同时还涉及到迁移旧数组的元素到新数组的元素的过程,所以非常复杂,而链表则具备快速插入(挂到头结点就可以了),扩容更不是问题因为内存不必是连续的所以非常随意,插入性能是O(1),数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。所以有没有什么办法可以结合数组的快速访问特点和链表的快速插入特点整一个牛逼的数据结构呢,于是散列表(哈希表)出现了。hashmap便是散列表的一种实现。