在学习hashmap的具体实现前,我们需要先了解数据存储的两种最基本的格式:链表和数组。
在磁盘存储数据的时候,最小的存储单元是一个字节,一个字节打大小是8个bite,但是如果每次存储数据的时候每次都这么一小个一小个的读,显然是十分浪费时间的,如果我们定义个较大一点的单元,那么我们就能较大的提高效率。我们一般定义的存储单元是4kb
链表和数组在存储上的优缺点。
数组需要的是一连串的存储空间,但是链表不需要。并且,一个单元是4kb的话,那么我们需要的数组的所使用的空间不可能每次都是刚好的使用这么多,所以磁盘使用的还是链表的形式存储。
对于这两个存储方式,我们知道他们的时间复杂度都是比较久的,那么我们有没有什么方法来加快链表的读取数据的时间呢?
使用二叉树的存储方式。
但是要注意二叉树的查找的时间复杂度是在logn级别到n的,最坏的情况有到O(n)的情况。
equals方法和“ = = ”的区别,首先这两种比较方法都是为了比较两个对象是否相等,“ = = ”号的使用是简单的两个值的比较,而equals方法在Object类中写的方法,是比较两个对象的地址是否相等,但是在String类中,对这个方法进行了重写String类的equals就仅仅只需要比较两个对象的值是否相等了。
hash表的使用简单来说,hash表就是数组和链表的结合,在一个有限数组里,按照hash算法将每个数据加入到这个数组里面。
对于hash算法,首先是先使用获取这个数据的地址使用hashcode方法将其转化为一个int类型的数据,然后再使用位运算和亦或^运算将其转化为的数据在经过&运算成为一个属于在数组范围内的一个数字,然后把它存在表中。
注意,在&运算之前得到的那个数,与表的长度-1做&运算,&运算起到决定性作用的是那个比较小的数,所以这样的运算保证了最后得到的那个数一定是在数组的范围内的数。
哈希冲突
如果两个不同的元素,通过哈希函数得出的实际存储地址相同怎么办?也就是说,当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。前面我们提到过,哈希函数的设计至关重要,好的哈希函数会尽可能地保证 计算简单和散列地址分布均匀,但是,我们需要清楚的是,数组是一块连续的固定长度的内存空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式
这是一个简易的模型,对于这个模型,有一个问题是,每一个数组单元格下面的数据我们还是得一个一个的往下找,所需要的时间还是需要一定的时间的。
我们使用对每一个单元格进行限制大小的操作,每次添加数据如果到一定检查到一定大小以后就往后面一个添加,或者对这个数组进行扩容,使用这样的操作来对读取效率进行提升。
但是对于底下的链表来说即使是一个一个读取,时间也是比较慢的,我们能否使用二叉树的方法来对这些链表来进行提升?
所以hashmap对数据量到达一定数量后的链表将这些链表进行转化为平衡二叉树来进行优化。
平衡二叉树:任何一个节点左右子树的深度的差不能超过1,超过了的时候需要进行转化。
仅仅是平衡树仍然是效率不够的,还有对平衡树的进一步的优化。