数据结构十:哈希表

news2024/11/16 12:31:35

        本次将从概念上理解什么是哈希表,理论知识较多,满满干货,这也是面试笔试的一个重点区域。

目录

一、什么是哈希表

1.0 为什么会有哈希表?

1.1 哈希表的基本概念

1.2 基本思想

1.3 举例理解

1.4 存在的问题

1.5 总结

二、哈希函数的构造方法

2.1 哈希函数的理解

2.2构造方法

2.2.1 直接定址法

2.2.2 数字分析法

2.2.3 平方取中法

2.2.4 折叠法

2.2.5 除留余数法(最常用)

2.2.6 随机数法

2.2.7 总结

三、处理哈希冲突的方法

3.1 开放定址法

3.1.1 线性探测法

3.1.2 二次探测法

3.1.3 随机探测法

 3.1.4 总结

3.2 再哈希法

3.3 链地址法

3.3.1  链址法的插入元素操作

3.3.2  链址法的查找元素操作

3.3.4  链址法的删除元素操作

3.3.5  代码实现增删改查操作

3.3.6 总结 

3.4 公共溢出区法

四、一致性哈希+虚拟节点+布隆过滤器


一、什么是哈希表

1.0 为什么会有哈希表?

       之前学习的这么多抽象数据结构类型,例如顺序表,单链表..., 他们有一个共同的特点:
数据的存储地址和数据本身是没任何关系的,它是随机的,在这些结构中进行查找,因为不知道key(关键字)和记录在结构中存储位置的关系,所以只能让key和结构中的记录进行一一比较,但是不一定是每个记录都要与key进行比较。在顺序查找时,比较的结果有"=“和"≠"两种可能。在二分查找,比较的结果有”>“,”<“和”="三种可能。所以对于这种比较型查找,效率和比较的次数强关联,比较次数少,效率就高,比较次数多,效率就低。
       对于上诉所说的情况,有没有一种可能,我们不需要通过任何比较就能找到我们想要查询的记录,当然是有可能的。只需要在记录的存储位置和它的key建立一个确定的对应关系f(也可以称之为映射关系),使key与结构中唯一一个存储位置相对应。说到这里,初学者可能看得不是很明白,那就举个栗子:例如我在给别人自我介绍的时候,我说我来自重庆,此时在其他人脑海里就会联想到重庆火锅,小面等许多美食或者洪崖洞等地标性建筑。这就是一种映射关系,通过重庆这个关键字就能得到所对应的信息。

      因此在查找时,只需要通过这个对应关系,就能找到key所对应的值。如果结构中存在key和我们所想要查找的K相等的记录,则必定在f(K)的存储位置上,所以我们并不需要任何比较就能找到所查记录。对于这种映射关系,我们将其称之为哈希函数或者散列函数,按照这个思想建立的表,我们称之为哈希表或者散列表。

1.1 哈希表的基本概念

       哈希表(Hash table,也叫散列表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过键 key 和映射函数 Hash(key) 计算出对应的值 value,把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做哈希函数(散列函数),存放记录的数组叫做哈希表(散列表)。

         通过上面的概念,那样我们可以通过查找关键字不需要比较就可获得需要的记录的存储位置。这就是一种新的存储技术 —>散列技术

          散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f (key)。查找时,根据这个确定的对应关系找到给定值key的映射f (key), 若查找集合中存在这个记录,则必定在f (key) 的位置上。这里我们把这种对应关系f称为散列函数,又称为哈希(Hash) 函数。按这个思想,采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。那么关键字对应的记录存储位置我们称为散列地址

1.2 基本思想

        哈希表的关键思想是使用哈希函数,将键 key 映射到对应表的某个区块中。

我们可以将算法思想分为两个部分:

1.  向哈希表中插入一个关键码值:哈希函数决定该关键字的对应值应该存放到表中的哪个区块,并将对应值存放到该区块中。

2.  在哈希表中查找一个关键码值:使用相同的哈希函数从哈希表中查找对应的区块,并在特定的区块搜索该关键字对应的值。

      因此,从上面理解,散列技术既是一种存储方法,也是一种查找方法。 然而它与线性表、树、图等结构不同的是,前面几种结构,数据元素之间都存在某种逻辑关系,可以用连线图示表示出来,而散列技术的记录之间不存在什么逻辑关系,它只与关键字有关联。因此,散列主要是面向查找的存储结构。散列技术最适合的求解问题是查找与给定值相等的记录。对于查找来说,简化了
比较过程,效率就会大大提高。但万事有利就有弊,散列技术不具备很多常规数据结构的能力。

1.3 举例理解

        下面要将一组数利用散列技术存储起来,那么应该如何存储?通过上面我们可以知道,哈希表的关键在于构造合适的哈希函数,这里我们将哈希函数构造为:数据本身对10取余,那么必然会得到0-9之间的数,得到的这个数其实就是将这个数据存放在数组/哈希表中的地址(也就是数组的下标)

      上面讲了如何存储,那么,我又应该如何去查找一个数到底在不在这个数组呢?如果在,又该如何返回这个数的位置呢?(下标)传统的数组查找方法,当然是:从头开始对比遍历整个数组,比较数据是否与我们要找的数相等,如果相等返回下标,很明显,它的时间复杂度比较高为:O(n),那么哈系表又是如何解决这个问题的呢?对于我们待查找的数据,同样通过哈希函数映射到存储的位置,然后直接在该位置查找是否存在该数据,如果该位置存在,代表找到了,如果没有找到,那它就是不存在,其他位置也不用继续找了,它是通过缩小查找范围,从而降低时间复杂度,相比遍历,大大的提高了效率。

1.4 存在的问题

      对于查找来说,简化了比较过程,效率就会大大提高。但万事有利就有弊,那就是哈希冲突。继续通过上面的例子来理解:上边的数据比较巧,一个格子刚好放一个值,那接下开看下面这组数据:

哈希冲突:理想情况下,每一个关键字通过哈希函数计算出来的存储位置都是不同的,但是这种想法不现实。实际使用中,经常会出现,两个或多个关键字不同,但是经过哈希函数计算得到的存储地址是一样的,也就是说Key1!=Key2,但是F(Key1) == F(Key2), 这种现象就是发生了哈希冲突,key1和key2 也叫做哈希函数的同义词。

       像上面的那个例子,22和32对10取余后结果都是2,代表他们二者都要存放在2的下标位置,但是下标为2的位置已经被22占了,这便产生了哈希冲突,从数学上简单理解:对于数学上的函数,都是自变量通过对应法则映射为函数值,它们是一对一的关系,但是这里便让它出现了多对一的关系,这里称作哈希冲突!生活上理解就是:两个人同时抢占一个位置,但是前面那个人已经把座位占了。

1.5 总结

       一般情况下,哈希冲突只能可能的减少,无法达到完全避免的效果。因为,哈希函数是从关键字集合到地址集合的映像。表越长,数据量越少,冲突的概率就越低,表越短,数据量越大,冲突的概率就越高。在建立哈希表时不仅要设定一个好的哈希函数,也要设定一种好的处理冲突的方法。

       综上所述,可如下描述哈希表:根据哈希函数和处理哈希冲突的方法,将一组记录的关键字映射到一个有限且连续的一段区间内,并以关键字处理得到地址作为记录在表中的存储位置,这种表就成为哈希表,这个过程称为哈希造表或散列,所得的存储位置称为哈希地址或者散列地址。

二、哈希函数的构造方法

2.1 哈希函数的理解

     从上面我们知道,哈希函数(Hash Function):将哈希表中元素的关键键值映射为元素存储位置的函数。 构成哈希表的关键在于:哈希函数/散列函数的构造;在哈希表的实际应用中,关键字的类型除了数字类,还有可能是字符串类型、浮点数类型、大整数类型,甚至还有可能是几种类型的组合。一般我们会将各种类型的关键字先转换为整数类型,再通过哈希函数,将其映射到哈希表中。如果关键字是字符串如何处理?其实无论是英文字符,还是中文字符,也包括各种各样的符号,它们都可以转化为某种数字来对待,比如ASCII 码或者Unicode码等,因此也就可以使用上面的这些方法。

一个哈希函数的好不好,取决于以下三点

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果哈希表允许有m个地址时,其值域必须在0 到m-1之间
  • 哈希函数计算出来的地址能均匀分布在整个空间中,刚才提到冲突带来的问题,最好的办法就是尽量让散列地址均匀地分布在存储空间中,这样可以保证存储空间的有效利用,并减少为处理冲突而耗费的时间。
  • 哈希函数应该比较简单

2.2构造方法

      接下来我们就要介绍几种常用的散列函数构造方法。估计设计这些方法的前辈们当年可能是从事间谍工作,因为这些方法都是将原来数字按某种规律变成另一个数字而已。构造哈希函数的方法有很多,但能尽可能的避免哈希冲突才能称得上好的哈希函数。换言之,就是关键字通过哈希函数的处理得到一个随机的地址,以便使一组关键字的哈希地址均匀的分布在整个地址区间,从而减少哈希冲突。


2.2.1 直接定址法

        取关键字或关键字的某个线性函数值为哈希函数,即:H(key) = key或者H(key) = a·key+b,其中a和b是常数。

优点:简单、均匀,也不会产生冲突;

缺点:需要事先知道关键字的分布情况,适合查找表较小且连续的情况。

        由于这样的限制,在现实应用中,此方法虽然简单,但却并不常用。

       适用场景:适合查找较小数据范围且连续的情况

   

2.2.2 数字分析法

     假设关键字是一个N进制数, 并且哈希表中的可能出现的关键字都是事先知道的,则可以去关键字的若干数位组合成哈希地址。

       数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法。

2.2.3 平方取中法

    如果关键字的每一位都有某些数字重复出现频率很高的现象,可以先求关键字的平方值,通过平方扩大差异,而后取中间数位作为最终存储地址。

适用场景:事先不知道数据并且数据长度较小的情况

2.2.4 折叠法

折叠法事先不需要知道关键字的分布,适合关键字位数较多的情况。

2.2.5 除留余数法(最常用)

      取关键字被某个不大于哈希表表长m的数p除后所得的余数为哈希地址。即:H(key) = key % p,p ≤ m。这是一种简单,也是最常用的构造哈希函数的方法。它不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。这也是一种简单且常用的哈希函数方法。其关键点在于 p 的选择。根据经验而言,一般 p 取素数或者 m,这样可以尽可能的减少冲突。

2.2.6 随机数法

选择一个随机函数,取关键字的随机函数值为它的哈希地址,即:H(key) = random(key),其中random为随机函数。通常,当关键字长度不等时采用此方法构造哈希函数比较恰当。

2.2.7 总结

      总之,现实中,应该视不同的情况采用不同的哈希函数。我们只能给出一些考虑的因素来提供参考:

  1. 计算哈希函数所需要的时间(包括硬件指令的因素)
  2. 关键字的长度
  3. 哈希表的大小
  4. 关键字的分布情况
  5. 记录的查找频率

三、处理哈希冲突的方法

       在前面哈希表的概念中提及到好的哈希函数能够减少哈希冲突,那我们肯定会不可避免地发生哈希冲突,那又该如何解决呢?

3.1 开放定址法

      所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址只要散列表足够大,空的散列地址总能找到,并将记录存入。总之,开放定址法只要在散列表未填满时,总是能找到不发生冲突的地址,是我们常用的解决冲突的办法。

         Hi = H(key)+di)%m i = 1, 2, 3…,k(k ≤ m-1)其中:H(key)为哈希函数,m为哈希表表长,d为增量序列,可以有下列3种取法:

  1. di = 1, 2, 3, …, m - 1,称为线性探测法
  2. di = 1², 2², 3², …, k² (k ≤ m/2),称为二次探测法
  3. di = 伪随机数序列,称为随机探测法

3.1.1 线性探测法

       例如,在长度为10的哈希表中已经填入有关键字分别为35、56和97的记录(哈希函数H(key) = key % 10),现在有第四个记录,其关键字为65,有哈希函数得到哈希地址为5,产生冲突。若采取线性探测再散列,得到下一个地址6,让然冲突,再求下一个地址,还是冲突的,知道哈希地址为8,不冲突后将该记录放入,处理哈希冲突的过程也就结束。

3.1.2 二次探测法

       从上述线性探测再散列的过程中我们可以发现一个现象:当表中i,i+1,i+2位置已经被填上的时候,当下次插入数据的哈希地址为i,i+1,i+2和i+3时都会填入到i+3的位置,这种在处理冲突过程中发生的第一个哈希地址不同的记录去争夺同一个后继哈希地址的现象称为“二次聚集”即在处理同义词(哈希地址相同的记录)的冲突中有添加了非同义词的冲突,显然这种对查找不利。


       举个栗子:比如在一张表中,表的大小为150,如果前面100个地址所对应的空间都填入了记录,现在需要插入一个哈希地址为1的记录,经过线性探测再散列的方式,最终会将这个记录填入到哈希地址为101的空间。如果在查找这个记录时,就需要从表的起始位置开始查找,直到哈希地址为101为止。本来我们是想通过哈希表进行1次查找就能找到记录,但实际上却查找了101次,效率大大得降低了。
      相对于线性探测法的方式,二次线性探测法能降低二次聚集,大家可以思考一下为什么。另外增加平方运算的目的是为了不让关键字都聚集在某一块区域。我们称这种方法为二次探测法。
但在另一方面,用线性探测法处理冲突可以保证做到:只要哈希表未被填满,总能找到一个不发生哈希冲突的地址。而二次探测法只有哈希表长为m形如4j+3(j为整数)的素数是才有可能。

3.1.3 随机探测法

在冲突时,对于位移量d; 采用随机函数计算得到,我们称之为随机探测法。

 3.1.4 总结

         二次探测和线性探测都属于闭散列,其原理都一样,两者的主要区别就是探测的方式不同,线性探测是如果要插入的位置已有元素,会一个一个往后查找到新的空位置。而二次探测是通过该位置的哈希冲突次数的平方来向后查找新的位置将产生哈希冲突的数据分散,使不堆积在一起。但是这两种方法都有很大的缺陷,就是空间利用率低。在这个基础上,引进一种新的技术来解决哈希冲突----链地址法(开散列)

3.2 再哈希法

        每当发生散列地址冲突时,就换一个散列函数计算,相信总会有一个可以把冲突解决掉。这种方法能够使得关键字不产生聚集,当然,相应地也增加了计算的时间。

3.3 链地址法

       思路再换一换,为什么有冲突就要换地方呢,我们直接就在原地想办法不可以吗?于是我们就有了链地址法。

链地址法(Chaining):将具有相同哈希地址的元素(或记录)存储在同一个线性链表中。链地址法是一种更加常用的哈希冲突解决方法。相比于开放地址法,链地址法更加简单。

       哈希表中的每个元素都是一个指向有效结点的指针,存储的是链表的第一个有效结点,特点:具有相同的哈希地址会存放在同一链表中,每个链表中的元素都具有相同的哈希地址。

结构如下:

      该哈希表示由指针数组来组成的,每个数组中的元素都是一个链表的头指针。从该表中我们可以看出,产生哈希冲突的元素并不会占用其他元素的位置,每个链表中的元素都是哈希冲突的元素。

3.3.1  链址法的插入元素操作

        当我们插入时,计算出哈希地址,就可以直接定位到该key对应的链表的头结点,但是由于不能存放相同的key,我们需要遍历该链表中是否存在相同元素,如果不存在才进行插入。插入时我们可以进行头插或者尾插,这里采用头插法时间复杂度更低。

3.3.2  链址法的查找元素操作

      查找:查找一个元素,我们可以先计算出要查找元素的哈希地址,直接定位到指定的链表的头结点,然后遍历该条链表,如果当前节点的值和要查找的元素的值相同,则表示查找,返回所找到的结点,如果没有找到则返回空

3.3.4  链址法的删除元素操作

      可以先通过查找,查看要删除的值是否存在哈希表中,如果不存直接返回false,存在则需要先计算当前删除的结点的所在链表的哈希地址,找到后遍历该链表,并用一个prev结点记录要删除的前一个结点,当遍历找到我们删除的结点时,要先判断该节点是否为链表的头结点,如果为头结点,则将要删除的结点的下一个结点置为头结点,如果不是头结点则将记录的前结点prev的下一个结点的next置为要删除结点的下一个结点。最后将有效元素-1并删除要删除的结点.

3.3.5  代码实现增删改查操作

哈希表的设计:首先它是一个数组,数组的每个元素都是一个由带头结点串起来的单链表。

#define MAX_SIZE 12

//1.有效结点
typedef struct Node
{
	ELEM_TYPE data;
	struct Node* next;
}Node,*PNode;


//结点数组(数组的每一个元素是结点类型,是单链表的头结点)
typedef struct List_Hash
{
	struct Node arr[MAX_SIZE];   //该数组一共12个元素,每一个元素都是一个头结点
}List_Hash,*PList_Hash;

实现的主要函数

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "Hash.h"

//初始化
void Init_List_Hash(struct List_Hash * lh);

//插入
bool Insert_List_Hash(struct List_Hash * lh, ELEM_TYPE val);

//删除
bool Del_List_Hash(struct List_Hash * lh, ELEM_TYPE val);

//查找
struct Node* Search_List_Hash(struct List_Hash *lh, ELEM_TYPE val);

//打印
void Show(struct List_Hash * lh);

具体实现: 


//初始化
void Init_List_Hash(struct List_Hash * lh)
{
	for(int i=0; i<12; i++)
	{
		lh->arr[i].next = NULL;
	}
}

//插入
bool Insert_List_Hash(struct List_Hash * lh, ELEM_TYPE val)
{
	//1.购买新节点
	struct Node * pnewnode = (struct Node *)malloc(1 * sizeof(struct Node));
	pnewnode->val = val;

	//2.找到合适的插入位置
	int index = val % MAX_SIZE;

	//3.插入
	pnewnode->next = lh->arr[index].next;
	lh->arr[index].next = pnewnode;

	return true;
}

//删除
bool Del_List_Hash(struct List_Hash * lh, ELEM_TYPE val)
{
	//1.先通过哈希函数判断在哪一个下标对应头结点后边
	int index = val % MAX_SIZE;

	//2.在这个头结点后面进行遍历,找val值节点是否存在,直接用p指向
	struct Node *p = Search_List_Hash(lh, val);
	if(p == NULL)
	{
		return false;
	}

	//3.让指针q,指向待删除节点的上一个节点
	struct Node *q = &lh->arr[index];
	for( ; q->next != p; q=q->next);

	//4.跨越指向+释放
	q->next = p->next;
	free(p);
	p = NULL;

	return true;
}

//查找
struct Node* Search_List_Hash(struct List_Hash *lh, ELEM_TYPE val)
{
	int index = val % MAX_SIZE;
	struct Node *p = lh->arr[index].next;

	for(; p!=NULL; p->next)
	{
		if(p->val == val)
		{
			return p;
		}
	}

	return NULL;
}

//打印
void Show(struct List_Hash * lh)
{
	for(int i=0; i<MAX_SIZE; i++)
	{
		printf("%d : ", i);

		struct Node *p = lh->arr[i].next;
		for(; p!=NULL; p=p->next)
		{
			printf("-> %d ", p->val);
		}

		printf("\n");
	}


}

 功能测试:

int main()
{
	struct List_Hash BigHead;

	Init_List_Hash(&BigHead);

	Insert_List_Hash(&BigHead, 67);
	Insert_List_Hash(&BigHead, 15);
	Insert_List_Hash(&BigHead, 29);
	Insert_List_Hash(&BigHead, 16);
	Insert_List_Hash(&BigHead, 12);
	Insert_List_Hash(&BigHead, 22);
	Insert_List_Hash(&BigHead, 25);
	Insert_List_Hash(&BigHead, 34);
	Insert_List_Hash(&BigHead, 47);
	Insert_List_Hash(&BigHead, 48);
	Insert_List_Hash(&BigHead, 37);
	Insert_List_Hash(&BigHead, 56);

	Show(&BigHead);


}

 结果展示: 

3.3.6 总结 

总结:

     相对于开放地址法,采用链地址法处理冲突要多占用一些存储空间(主要是链节点占用空间)。但它可以减少在进行插入和查找具有相同哈希地址的关键字的操作过程中的平均查找长度。这是因为在链地址法中,待比较的关键字都是具有相同哈希地址的元素,而在开放地址法中,待比较的关键字不仅包含具有相同哈希地址的元素,而且还包含哈希地址不相同的元素。 

3.4 公共溢出区法

四、一致性哈希+虚拟节点+布隆过滤器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1643929.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于TL431基准电压源的可调恒压恒流源的Multisim电路仿真设计

1、线性电源的工作原理 在我们日常应用里&#xff0c;直流电是从市电或电网中的交流电获取的。例如15V直流电压源、24V直流电压源等等。交流电变为直流电的过程大概分为一下几步&#xff1a; 首先&#xff0c;交流电通过变压器降低其电压幅值。接着&#xff0c;经过整流电路进…

01_SpringBoot简单搭建入门程序

目录 1、先创建一个java项目2、导入依赖3、将Java项目修改为SpringBoot项目4、编写一个测试的Controller5、测试(创建一个*.http的文件)方式1&#xff1a;方式2&#xff1a;可以直接在浏览器访问该地址方式3&#xff1a;使用postman也可以 1、先创建一个java项目 我的项目结构…

智能化工单助力业务运作:亿发解析工单系统的功能与适用场景

在现代企业管理中&#xff0c;工单系统扮演着至关重要的角色。面对市面上众多的工单系统&#xff0c;我们可以根据不同的依据进行分类&#xff0c;以更好地满足企业的需求。 1、按部署方式分类&#xff1a; 按照部署方式可以分为本地化部署工单系统和云端SaaS服务工单系统。 …

步态识别论文(6)GaitDAN: Cross-view Gait Recognition via Adversarial Domain Adaptation

摘要: 视角变化导致步态外观存在显着差异。因此&#xff0c;识别跨视图场景中的步态是非常具有挑战性的。最近的方法要么在进行识别之前将步态从原始视图转换为目标视图&#xff0c;要么通过蛮力学习或解耦学习提取与相机视图无关的步态特征。然而&#xff0c;这些方法有许多约…

【java9】java9新特性之Optional类改进

其实Optional类在Java8中就引入了&#xff0c;用于避免null检查和NullPointerException指针问题。 在Java9中&#xff0c;Optional类得到了进一步的改进&#xff0c;增加了一些新的方法&#xff0c;使其更加灵活和易用。 以下是一些Java9中对Optional类的改进&#xff1a; s…

ssm106学生公寓管理系统的设计与实现+jsp

学生公寓管理系统设计与实现 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本学生公寓管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短…

python数据分析——数据分析的统计推断

数据分析的统计推断 前言一、提出问题二、统计归纳方法三、统计推断四、统计推断步骤4.1.点估计4.2.区间估计4.2.1. 总体方差已知4.2.2总体方差未知 4.3. 假设检验4.4. 假设检验的假设4.5.显著性水平 五、检验统计量六、检验方法七、拒绝域八、假设检验步骤九、重要假设检验方法…

CGAL 网格简化

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 为了提高网格处理的效率,通常需要将过于冗长的3D数据集简化为更简洁而又真实的表示。尽管从几何压缩到逆向工程有许多应用,但简洁地捕捉表面的几何形状仍然是一项乏味的任务。CGAL中则为我们提供了一种通过变分几…

SpringBoot的ProblemDetails

1.RFC 7807 之前的项目如果出现异常&#xff0c;默认跳转到error页面。或者是抛出500 异常。 但是对于前后端分离的项目&#xff0c;Java程序员不负责页面跳转&#xff0c;只需要 把错误信息交给前端程序员处理即可。而RFC 7807规范就是将异常 信息转为JSON格式的数据。这个…

Golang | Leetcode Golang题解之第70题爬楼梯

题目&#xff1a; 题解&#xff1a; func climbStairs(n int) int {sqrt5 : math.Sqrt(5)pow1 : math.Pow((1sqrt5)/2, float64(n1))pow2 : math.Pow((1-sqrt5)/2, float64(n1))return int(math.Round((pow1 - pow2) / sqrt5)) }

5.Spring Security-web权限方案

设置登录的用户名和密码 1.通过配置文件设置用户名密码 spring:security:user:name: xiankejinpassword: 123456 如果没有以上配置&#xff0c;那么就会在后台生成一个随机密码&#xff0c;用户名固定位user。 2.通过配置类设置用户名密码 Configuration public class Sec…

[C++核心编程-02]----C++引用详解和使用方法分析

前言 在C中&#xff0c;引用是一个别名&#xff0c;它允许将一个已存在的变量或对象用不同的名称来访问。引用在定义时必须初始化&#xff0c;并且一旦初始化就不能再绑定其他对象&#xff0c;因此引用在声明时被初始化后就不能再改变引用对象。引用使用&符号进行声明。 引…

打印菱形

使用拆分法&#xff1a;空格和*分开打印 #include <stdio.h> int main() { int n 0; //行数 scanf("%d", &n); int i 0; //打印上半行 for (i 0; i < n; i) { int j 0; for (j 0; j < n - i - 1;…

玩comfyui踩过的坑之使用ComfyUI_Custom_NODES_ALEKPET翻译组件问题

环境&#xff1a; 秋叶安装包&#xff0c;安装ComfyUI_Custom_NODES_ALEKPET组件或者直接下载网盘中的包&#xff0c;直接解压包到comfyui根目录/custom_nodes/&#xff0c;重启后&#xff0c;按指导文件操作。 注意&#xff1a;网盘指导包中有配置好的流程json文件&#xff0…

【011】网上鲜花商店(SSM+JSP)

【011】网上鲜花商店(SSMJSP&#xff09; 一、系统情况介绍 网上鲜花商店分为前台端和后台端&#xff0c;是致力于可以便捷购花而开发的一套系统&#xff0c;可以按照不同种类进行分类管理&#xff0c;清晰客观的展示鲜花的详情信息以及价格等等&#xff0c;适合新手学习开发…

C++多态特性详解

目录 概念&#xff1a; 定义及实现&#xff1a; 虚函数重写的两个例外&#xff1a; 1.协变&#xff1a; 2.析构函数的重写&#xff1a; final关键字&#xff1a; override关键字&#xff1a; 多态是如何实现的&#xff08;底层&#xff09;&#xff1a; 面试题&#xff1…

代码审计之浅谈RASP技术

前言&#xff1a; 想摆会烂&#xff0c;所以就落个笔吧。 其实本来是想写关于iast技术的&#xff0c;但是认真思考了下&#xff0c;感觉笔者自己本身也不太能讲清楚iast技术&#xff0c;怕误人子弟。 所以最后还是基于笔者的理解以及实际应用写一篇关于RASP技术的文章&#xf…

酷我音乐无广VIP破姐版,支持蝰蛇音效,PC端 v9.2.0.0

01 软件介绍 酷我音乐是一款优质在线音乐播放服务软件&#xff0c;拥有全面的音乐版权库存&#xff0c;中国好声音、蒙面歌王、燃烧吧少年等热门综艺的版权&#xff0c;包括无损音质选项&#xff0c;并涵盖广泛多样的音乐类型&#xff0c;如华语、英语、粤语音乐&#xff0c;以…

[暂未实现]APP签名不同保留数据覆盖安装记录

APP签名不同无法直接覆盖安装 使用adb可以卸载应用同时保留数据&#xff0c;但签名不同也无法覆盖安装&#xff08;安装原来签名的应用打开和卸载前一样&#xff09; 使用adb导出应用数据&#xff08;QQ&#xff09;db文件只有1kb&#xff0c;显然此方法也行不通

Superset二次开发之Legend功能优化

背景 Legend数据太长,影响整体图表体验,为改善用户体验,需要实现:1.数据省略展示,‘...’表示,鼠标悬停时,展示完整信息 2:文本内容从左向右滚动展示 柱状图优化 柱状图来自第三方Echarts插件,效果展示 功能核心在于红框的内容 option = {tooltip: {trigger: item,ax…