hashmap是一个以key,value形式存储的集合,在JDK1.7中是以数组+链表的数据结构,在JDK1.8中是数组+链表+红黑树的数据结构,他在对数据操作时继承了数组的线性查找和链表的寻址修改
红黑树 :
- 除了插入操作慢,其他操作都是比链表要快的,当hash表中链表的长度超过8,数组长度大于64,就会从链表转为红黑树,小于6就会从红黑树转为链表;
- 而这样的设计是为了避免链表过长导致查询速度很慢(因为链表的查询是对整个链表进行遍历,效率是非常差的);
- 而红黑树是一个和二叉查找树类似,其优点就是平衡,左右子树的高度一致,以此来防止树退化为链表,保障查找的时间复杂度
红黑树的特性 :
- 每个节点要么是红色,要么是黑色,根节点永远是黑色
- 每个红色节点的两个子节点一定是黑色
- 红色节点不能连续(也就是 : 红色节点的孩子节点以及父节点都是黑色)
- 从任意节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点
- 所有的叶子节点都是黑色
- 注 : 树的结构发生变化时(插入或者删除操作),会破坏三,四调节,需要通过调整使得查找树重新满足红黑树的条件
hashmap是线程不安全的 :
- 在JDK1.7中会造成环形链和数据丢失的情况
- 在JDK1.8中hashmap的put过程会造成数据覆盖的情况
- put过程 :
- 首先将K,V封装到Node节点中,通过底层去调用K的hashCode()方法计算出hash值,判断是否发生哈希碰撞(计算出的哈希值相同)
- 然后通过哈希算法将hash值转换为数组的下标,下标位置上没有任何元素就将node节点添加到这个位置,如果下标对应的位置有链表,就会拿着K和链表上的每个节点的K进行equals,如果equals方法返回false,那么这个新的节点会被添加到链表末尾,如果返回的是true,那么这个节点有值的话将会被覆盖
- hashmap的存储过程 : 如果链表的长度大于8会转为红黑树,如果链表的长度小于6会从红黑树转为链表
- 计算出的哈希值相同就放入桶里面,如果桶满了会扩容2倍在重排
HashMap解决hash冲突的方式是用链表,通过hashcode去计算哈希值(哈希值是随机产生的),计算出来的值相同,会重新产生一个哈希地址在进行计算,以此类推,直到哈希值不同为止
jdk1.7和jdk1.8的区别 :
- 初始大小为16,每次都是扩容两倍,且一定是2的n次幂
- 扩容主要针对整个map,每次扩容时,原数组中的袁术一次重新计算存放位置,并重新插入
- 当map中元素总数超过数组的75%,会触发扩容操作,主要是为了减少链表的长度,使元素分配更均匀
在java中hashcode和equals方法是用来比较两个对象是否相等,重写这两个方法后
- hashcode方法返回对象的哈希值,用于哈希表中存储对象
- equals方法比较两个对象是否相等
- ==和equals的区别 :
- == : 在基本数据类型中比较的是数值
- equals在引用数据类型中比较的是地址值,重写后比较的是地址中的内容
- ==和equals的区别 :
- 为什么重写equals必须重写hashcode?
- 能够保证不违背hashcode方法中"相同对象必须有相同的哈希值"的约定
- hashcode本身是一个哈希函数,他遵循三个规则 :
- 一个对象多次调用hashcode方法应当返回相同的哈希值
- 两个对象如果通过equals方法比较,那么应当返回相同的哈希值
- 两个地址值不相同的对象调用hashcode方法不要求返回不相等的哈希值(因为两个不同对象的哈希值可能相同),但是要求拥有两个不相等哈希值的对象是不同对象
- 如果只重写了equals没有重写hashcode的话就会直接执行object中的hashcode方法,而object中的hashcode方法对比的是两个不同引用地址的对象,重写hashcode,他对比的是两个对象的所有属性的hashcode是否相同
其实hashmap主要的目的就是存储数据结构的,查询的方式通过哈希算法计算的
首先他的结构组成分为 :
- 数组结构 :
- 他是采用一段连续的存储单元来存储数据的
- 查询 : 由于数组元素下标是连续且自增的所以在做查询时可以直接通过下标找到对应的节点,一般在查询频繁的场景下使用最多
- 增删 : 当插入一个元素时,这个元素在数组中是没有下标的,需要将元素添加到数组中的某个位置,那么在该元素之后的下标都会向后移动,以至于后面的节点也要有相应的改变,删除会造成下标向前移动
- ArrayList : 就是一个基于数组结构的集合,查询快,增删慢,
- 他还有一个扩容机制 : 它的默认容量是10,在使用ArrayList做增删时,他会创建一个新的数组且这个数组是原数组容量的1.5倍,并将原数组中的元素拷贝一份到新的数组中去,所以一般我们使用ArrayList做增删时需要指定它的容量
- 他是采用一段连续的存储单元来存储数据的
- 链表结构 :
- 链表是一种物理存储单元上非连续,非顺序的存储结构,它的特点是增删快,查询慢
- 查询 : 它的查询需要通过头节点将整个链表都遍历一次,以至于查询效率很慢
- 增删 : 新增时上一个节点指向插入的节点,插入的节点指向下一个节点;只需要去改变指针的指向就可以完成增删操作
- LinkedList : 是基于链表结构的,查询慢,增删快
- 链表是一种物理存储单元上非连续,非顺序的存储结构,它的特点是增删快,查询慢
哈希算法 :
- 哈希算法(不可逆的,幂等性的算法)也叫作散列算法,也就是把任意长度值(key)通过散列算法变换成固定长度的key(地址),通过这个地址进行访问的数据结构,他通过把关键码值映射到表中一个位置来访问记录,从而加快查找速度
- 将lies计算出来的ascii码相加
- 然后除以10取模
- 为什么不直接存储,要进行取模?
- 因为数组是采用一段连续的存储单元来存储数据的,直接存储的话值会很大,其中会浪费很多的空间,取模的目的就是为了节省内存空间
- 取模会出现的问题 :
- 会发生哈希冲突(哈希碰撞) :
- lies的值通过ascii码计算的总和
- foes的值通过ascii计算的总和
- lies和foes取模之后的值相同,虽然他两是不同的key,但是数组存同一个下标元素时会进行覆盖,这就是哈希碰撞
- 哈希碰撞解决方式 :
- 使用链表解决 : 根据链表的指针,可以让lies指向foes,让foes去匹配下标,如果匹配lies不相等,则去匹配下一个节点foes,最终找到这个foes
- 这也是JDK1.8中引入红黑树的原因 : hashmap的存取过程
- 创建一个hasdmap集合并指定它的容量
- 往集合中添加元素时,当容量不够,就只能把这个数据放到链表上,链表是无线延长的,又因为链表的查询速度是比较慢的,那么哈希冲突也就会变得十分严重,查询末端数据的性能也就会变得很低(总结 : jdk1.7的hashmap需要解决链表过长查询效率低下的问题)
- 在jdk1.8中 : 使用红黑树去判断小中大(也就是左边的小于右边的),他的插入速度慢,而链表插入快,删除快
- 会发生哈希冲突(哈希碰撞) :
- 取模会出现的问题 :
- 因为数组是采用一段连续的存储单元来存储数据的,直接存储的话值会很大,其中会浪费很多的空间,取模的目的就是为了节省内存空间
- 为什么不直接存储,要进行取模?