首页 > 编程知识 正文

java哈希值和地址值,java 字符串哈希值

时间:2023-05-04 21:32:56 阅读:16185 作者:4257

jddxtz表1. jddxtz表的定义jddxtz表(Hash table,也称为散列表)是基于键码值(Key value )直接访问的数据结构。 也就是说,通过将键码值映射到表中的某个位置来访问记录,从而减少搜索时间。 该映射函数称为哈希函数,存储记录的数组称为哈希表。key(键值)和存储地址的映射

jddxtz表可以理解为每个索引中存储位置对应的数组。 jddxtz表的索引由关键字(数据本身)和jdxtz函数获得,而不是像常规数组索引那样由0到length-1获得。而当使用jddxtz表进行查询的时候,就是再次使用jddxtz函数将key转换为对应的数组下标,并定位到该空间获取value,如此一来,就可以充分利用到数组的定位性能进行数据定位。

2.jddxtz冲突或不同的key值生成相同的地址,用公式表示的话key1key2,但是f(key1(=f ) key2),即不同的key值用jddxtz函数转换得到的存储器地址相同

解决jddxtz冲突:

1.开放定址法

如果发生冲突怎么办? 查找哈希表中的可用空间,找到可用空间并插入。 就像你去店里买东西,发现东西卖完了一样,我该怎么办? 让卖下一个东西的商家买吧。

我没有做过深入的实验,所以贴在书上的说明:

2.链地址法

上述开放地址法的原理是,发生冲突时,沿原jddxtz地址查找并插入下一个空闲地址,但也存在空间不足无法解决冲突、无法插入数据的问题。 因此,需要因子(插入数据/空格)=1。

如果由于链地址法原理而发生冲突,他会在原始地址中创建新空间,并将其作为链表的节点插入到该空间中。

例如,我有很多数据{1、12、26、337、353…},但我的jdxtz算法是h(key )=key mod 16,第一个数据1的jddxtz值f(1)=1,1 第四数据337被计算为jddxtz值是1,虽然遇到冲突,但是仍然可以找到该一个节点的最后连接点并插入。 与353相同。

3358www.Sina.com/一般由对象数组和链表构成jddxtz表,链表解析3358www.Sina.com/。

3.构造jddxtz表

jddxtz冲突

那么,能否综合两者的特性,制作出容易寻址、也容易插入删除的数据结构呢? 答案是肯定的。 这是我们建议的jddxtz表。 jddxtz表有几种不同的实现方法。 以下采用最常见的方法——拉链法。 我们可以理解为“链表的排列”。 图:

左边显然是数组。 数组中的每个成员都包含指向链表头的指针。 当然,这个链表可能是空的,但也可能有很多元素。 我们根据元素的一些特征将元素分配给不同的链表,也是根据这些特征找到正确的链表,然后从链表中找到该元素。

数组的特点是:寻址容易,插入和删除困难;

创建用于管理多个链表的混列表。 创建jddxtz函数。 根据散列函数决定关键字应该放在哪个链表中,调用链表类的添加方法。 对于查询,根据jddxtz函数检索其存储位置,并查找该位置的链表。 创建实体类,即链表的类型。 创建链表类,封装增删评审方法。 创建hash表,然后单击多个链表class hashtable { privateemplinkedlist [ ] arr; 私密int size; //初始化生成器公共隐藏(intsize )//大小为7的数组arr=new EmpLinkedList[size]; this.size=size; 不仅需要初始化//数组,还需要初始化链表for (inti=0; i size; I ) { arr [ I ]=new EMP链接列表(; }//员工publicvoidadd(EMP )inthashfun=hashfun ) EMP.id ),根据散列函数确定应该将员工编号放在哪个链表上; ARR[hashfun].add(EMP; //自定义散列函数publicinthashfun(intid ) { return id % size; (jddxtz表格公共语音列表) ) ) ) ) )。

for (int i = 0; i < size; i++) { arr[i].list(i); } } //根据id删除员工 public void del(int no) { int id = hashFun(no); arr[id].del(no); } // 根据Id查找雇员 public void findById(int id) { int hashFun = hashFun(id); Emp emp = arr[hashFun].findById(id); if (emp == null) { System.out.println("没有该员工!"); } else { System.out.printf("员工id:%d,姓名:%s", emp.id, emp.name); } }}//创建一个雇员class Emp { public int id; public String name; public Emp next; public Emp(int id, String name) { this.id = id; this.name = name; }}//创建一个链表类,封装增删改查方法class EmpLinkedList { // 创建一个头结点,直接指向第一个Emp private Emp head;// 默认为null // 根据雇员ID查找 public Emp findById(int id) { // 当头结点为空时,说明链表为空! if (head == null) { return null; } Emp temp = head; while (true) { // 找到ID if (temp.id == id) { break; } // 链表遍历完毕没有找到 if (temp.next == null) { temp = null; break; } temp = temp.next; } return temp; } /* * 添加雇员 */ public void add(Emp emp) { // 如果是添加的是第一个雇员,将头结点直接指向当前添加的节点 if (head == null) { head = emp; return; } Emp temp = head;// 定义辅助节点 // 不是第一个节点,遍历链表,将待添加的节点放在最后一个节点的后面 // 循环遍历找到最后一个节点 while (true) { if (temp.next == null) { break; } temp = temp.next; } // 将添加的节点放在找到的最后节点的后面 temp.next = emp; } // 遍历节点 public void list(int no) { if (head == null) { System.out.println("第" + (no + 1) + "链表为空!"); return; } Emp temp = head;// 定义辅助节点,用于遍历 while (true) { System.out.printf("第" + (no + 1) + "链表的id是%d,姓名是%st", temp.id, temp.name); if (temp.next == null) { break; } temp = temp.next; } System.out.println(); } // 删除雇员 public void del(int no) { if(head == null) { System.out.println("该链表为空!"); return; } // 当头结点的id等于要查找的id,直接删除 if (head.id == no) { head = head.next; return; } Emp temp = head;// 定义辅助节点 boolean flag = false; while (true) { if (temp.next == null) { break;// 说明没有找到该点 } if (temp.next.id == no) { // 删除操作 flag = true; break; } } if (flag) { // 找到该id,删除 temp.next = temp.next.next; } else { System.out.println("没有找到该员工!"); } }} 4.关于jddxtz表的性能

由于jddxtz表高效的特性,查找或者插入的情况在大多数情况下可以达到O(1),时间主要花在计算hash上,当然也有最坏的情况就是hash值全都映射到同一个地址上,这样jddxtz表就会退化成链表,查找的时间复杂度变成O(n),但是这种情况比较少,只要不要把hash计算的公式外漏出去并且有人故意攻击(用兴趣的人可以搜一下基于jddxtz冲突的拒绝服务攻击),一般也不会出现这种情况。

5.扩容方案

装载因子,也叫负载因子(load factor),它表示散列表的装满程度。装载因子α=元素个数/散列表长度。

当表的实际装载因子达到默认的负载因子值(负载极限)时,就会触发jddxtz表的扩容。

当jddxtz表的装载因子过大,jddxtz冲突一般较严重,其增删改查的性能也随之降低。
这个时候,我们会采取扩容机制,增大jddxtz表的容量。

Java中的实现

我们以HashMap为例,来探究一下Java中关于jddxtz表扩容的实现。

首先是,每次扩容时,jddxtz表的容量增加为原先的两倍。
于是在扩容被触发时(实际装载因子达到默认装载因子时),需要对原先的表进行rehash。所以这时增加一个元素的性能是比较差的,因为要等待原先的表rehash之后才能增加该元素。

其冲突解决的方式是链地址法。

当某个箱子的链表长度大于8时,Java的处理方案是将其转化为红黑树。当链表长度小于6时,从红黑树转换为链表。

这是因为初始默认的箱子个数(jddxtz表容量)为16,而根据泊松分布,当负载因子达到0.75时,某个箱子的链表长度为8的概率为0.00000006,这种可能性是小到可以忽略的。我们甚至可以断言,如果有了这种情况的出现,那一定是jddxtz函数设计的不合理所导致的。

java中的hashmap,当数据数量达到阈值的时候(0.75),就会发生rehash,hash表长度变为原来的二倍,将原hash表数据全部重新计算hash地址,重新分配位置,达到rehash目的

红黑树的插入删除、查询的性能都比较良好。所以Java的这种转化机制一定程度上避免了不恰当的jddxtz函数导致的性能问题。

Redis中的实现

redis中的hash表采用的是渐进式hash的方式

Redis 是一个高效的 key-value 缓存系统,也可以理解为基于键值对的数据库。

Redis也是采取链地址法解决jddxtz冲突。

我们知道,Java发生扩容的瞬间,是需要先将原jddxtz表中所有键值对都转移到新的jddxtz表中,这个过程是比较慢的,此时插入该元素的性能相当低。

而Redis对于这一部分,采取的是分摊转移的方式。即当插入一个新元素x触发了扩容时,先转移第一个不为空的桶到新的jddxtz表,然后将该元素插入。而下一次再次插入时,继续转移旧jddxtz表中第一个不为空的桶,再插入元素。直至旧jddxtz表为空为止。这样一来,理想情况下,插入的时间复杂度是O(1)。

redis中的hash则是执行的单步rehash的过程

在Redis的实现中,新插入的键值对会放在箱子中链表的头部,而不是在尾部继续插入。

这种方案是基于两点考虑:
一是由于找到链表尾部的时间复杂度为O(n),且需要额外的内存地址来保存链表的尾部位置,而头插法的时间复杂度为O(1)。

二是处于Redis的实际应用场景来考虑。对于一个数据库系统来说,最新插入的数据往往更可能频繁地被获取,所以这样也能节省查找的耗时。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。