Hash的低级理解
以前大家在接触框架结构的时候都接触过Hashmap,对于hash相信很早的时候大家都有一定的接 触,但是那时候我们对于hash的理解似乎只局限于名字和他的一些我们应用时的方面。
在接触之前对于hash我一直都是觉得很困惑的东西,一直以为hash是一种很复杂的计算,或者那时候以为是一种和特别的属性,因为最先接触也是理解的就是hashcode,唯一标示,曾经因为这个以为所谓的hash就是与事物特性有关的一些方法,因为现在接触了并且应用的就是hashset所以谈一下通过hashset对于hash 的理解吧
在我们平时用到的set结合中就是简单吧我们要添加的数据加入到set集合中,对于自己实现的set我们可以用数组也可以用链表来实现,对于数组和链表之间的差别和适合的应用环境大家都应该已经清楚了,通常对于少数数据的情况下不管对于数组还是链表我们所用的时间都是很少的,并没有太大的优劣性,但是如果对于较大的数据量进行操作的时候我们应该采取什么样的方法来实现呢?
假设现在又一个很大的数据量的数据要我们对其进行各种操作,当然这种也分情形
1、若数据量固定我们只需要查找里面的信息,这种情况下仅仅数组就相对于简单的多
2、如果我们只是对里面的数据进行插入删除,这种情况下仅仅用链表来实现就简单的多了
以上两种情况是两种极端的情况
问题是:因为大多时候我们对于数据可能要同时有着两种操作,就是既要查找又要插入删除,但是基于我们的理解查找的话用数组实现是最简单的,而对于插入删除我们会悠闲选择链表实现;
解决这个问题大多人都会想到我们把数组和链表结合,链表跟数组的结合有两种方式
一、数组实现的链表;
二、链表实现的数组;、
第一种就是把数组作为链表的没一个节点这种方面最大的好处就是继承了链表的特点:内存的存储是离散的,它不需要连续大量的内存,但是因为数组开始的时候就被分配了固定的内存其实需要的空间并没有节省,这样我们字对其进行操作的时候就会有可能有大量的内存空间的浪费。
第二种方法是整体上还是数组的特性,查找速度快,而数组的每一个元素是链表组成,这样实现的好处很明显,如果我们在未知数据的情况下,不断地加入,对于内存的申请是通过我们加入数据的增加而增加的,因为数组的没一个元素是链表,所以整个数据在内存中的存储其实还是表“离散”的特征。我们对于内存的浪费就会大大减少。
所以比较来看 我们对于新的又数组和链表的结合方式应该选择第二种,现在要考虑的是怎么结合?
明显我们要用数组和链表结合的方式从结构上可以看出其实相当于“分组“存储,没一个数组的元素相当于一组的属性,而里面的链表相当于存储的具有相同的属性的数据。这样我们在选择同一个数组元素里链表数据分配的时候就要有一个分组的标准,例如:如果要存储的是一个学校学生的数据,我们就可以按照院系来存储,也就是每个数组元素代表的属性就是一个院系,里面的链表中的数据就是这个院系的学生。
这样按照这种思路基本上的hash 我们已经理解里面的结构,或者说我们都基本有一个自己的hash的定义了,但是同时在存储数据的时候我们还要注意的是因为我们使用链表实现的数组,我们这么做主要就是为了既方便于数据的查找也方便与数据的插入和删除,但是在我们存放数据的时候根据不同的分组方式会不会有问题出现呢?例如:
我们现在要存入的是十万个int 的数据,而我们分组是根据与数组长度取余来分组,这样就有可能会产生:
1、 一种情况刚好所有的数据取余后的结构都是一样的,这样我们构建的自己的hash就会出现数组中只有一个元素的链表不为空,而其他的元素都为空,这样我们构建的就纯属是一个链表,就丢失了数组查找方便的特性。
2、另一种就是刚好数据取余后的结果都是不同的,这样没一个数组的元素中的链表就有可能都只有一个元素,这样就会变成一个纯数组,就失去了链表插入删除方便的属性。
这个问题怎么解决呢?
我们需要的是数组中的链表都是均匀的,这样才能兼顾数组和链表的特性。要解决这个问题主要有两个方面
一、合理的选取分组方式,合理的分组方式是很重要的,如果一种合理的分组方式能够刚好符合数据的处理,这样我们在数据的添加是就会省去很多麻烦,减少很多rehash(后面会说到),大大缩减了处理时间,提到了效率;
二、当我们找不到很好的能够适应数据分组方式的时候,判断如果有可能会出现以上两种极端 的情况下我们就要进行rehash(就是指要重新建立hash表),rehash我们一般采取的就是增加数组的长度,这样在对分组我们可以进行更详细,或者分散在同一属性聚集的数据。这里对于数组长度的增加也要合理的选取,这也是跟hash效率有关的很重要的一个方面,就以我实现的方法举例吧
"对于一个大量的数据进行于数组长度的取余分组"
这个我们判断在数据位置的情况下还是很有可能出现极端情况的,而其中的rehash如果我们选取的是每次增加数组长度的一半,这样在我们rehahs 的时候因为我们要吧原来hash 中的数据重新加入到新的hash 中,我们就需要遍历以前的hash然后逐个元素加入;而另一种我们采取的就是直接数组翻倍,这样的好处就是因为是取余,如果只是数组防备我们就可以直接复制数组中的链表。而链表在新数组中的位置就是原来的位置加上数组长度!这种方法就大大减少了遍历数据的时间和重新计算分组的时间,当然这种方法也是有缺点的,对于未知的数据,如果我们每次都是数组翻倍,很有可能我们翻倍后在需要加入的只有一个数据了,这样的话我们就会大大浪费空间内存,如果从内存来看的话这种方法就不如第一种了。当然对于追求效率还是内存要我们可以根据需要采取不同的rehash方式。
因为对于一种新事物的理解我们是要建立在不断地应用不断地体会中,所以对于hash 的理解也就局限于这些吧,因为毕竟刚刚接触,其他的看的都是一些实现 的方法,对于里面的具体的实现还是看源代码理解会更深一些。