哈希表(三级)

关于查找我已经讲了三种了,一种是线性表的查找,一种是树的,查找树,下面我们讲另外一种结构,它是哈希表


前面的我们查找不管你是顺序查找,还是折半查找,还是树里面查找,都要做一个操作,都需要进行比较,拿着我要找的关键字,

跟表里面或者树里面进行比较,看是不是相同,那既然要比较的话,就涉及到比较的效率,理想的方法是,不需要进行比较,就可以

直接找到我们要找的内容,有人说这怎么可能呢,有没有这样的可能,是有的,对于我们的数组来说,如果我们要按照索引来说的话,

索引查询,我要查询第5个,我要查询第10个,是不是可以直接定位,直接定位,只要计算一次公式,他有数组的首地址,有每个元素的

长度,只要套一个公式,既可以直接定位到索引是5索引是10的元素,找第5个,找第10个,找第1个,找第1万个,花的时间都是一样的,

并且一次计算就可以得到,效率会特别的快,但是我们如果按照内容来找的话,我不是找5个元素,也不是找第10个元素,我是找值是

10的元素,那他就不是索引的值了,是内容,那我们就会逐个的比对,顺序查找效率是比较低的,折半查找要限制条件,要求是有序的,

我们按照内容查找的时候也能和按照索引查找这么高的效率,不需要比较,直接定位,这就是我们哈希表要做的一件事情,所以我们

就知道哈希表他有多重要了,有多神奇了,那怎么来理解哈希表呢,哈希表底层它是采用一个什么样的结构,结构和他的神奇是

有一定的关系,光有这个结构还不行,还有他具体的算法,我们看一下哈希表是怎么添加数据的,和哈希表是怎么查询数据的,

通过这两个具体的操作,知道明白哈希表为什么神奇,它是怎么快的,最后我们还有一些细节的内容,讲里面的一些细节,比如说怎么

减少冲突啊,怎么构建哈希码啊,在JAVA里面有两个重要的方法,使用哈希表的话有两个重要的方法,这两个方法到底有什么样的

作用,那我们就开始了
1. 先来看哈希表的结构和特点:

哈希表英文单词是这个单词hashtable,也按照英译的话也是哈希表,按照意义译的话也是散列表

他有个特点,快,非常快,神奇的快,他底层是什么结构啊,底层有多种结构,多种实现方式,下面我给出的是最容易理解的,最常用的

一种方式,就是顺序表加链表,他的主结构大家看,主结构它是一张顺序表,我们你这里从0到12一共有13个数,那这个13个不存放

具体的内容,每个后面会引一个链表,会引链表的,主结构是一个顺序表,每个顺序表的节点,它是用来引链表的,如果我有一个值是

11,我有一个值是11,那经过我的运算之后,11的位置不往数组里面放,后面拉一个链表,数组里面的指针指向他就行了,这是我们

哈希表的主结构,哈希表最容易理解的主结构是一个数组,每个元素可以拉链表,这是一个,那下面我们来看一下哈下表是怎么来

添加数据的,那我们这里有一个哈希表初始操作状态,我们来理解一下,这上面是什么,上面就是我们要添加的数据,23,36,一直到

47,我们要加这些数,而下面我们这一块就是我们的哈希表,我们是不是创建了一个哈希表他的主结构它是多少啊,是不是11,从0

到10,一共有11个元素,注意画的这个是什么啊,只不过这个是纵向画的,我们这边是横向来画的,注意就是这条线往下,就是哈希表

的主结构了,目前都是空的,刚开始的情况下你可以认为有没有值啊,有,都是null,这个地方都是null,没有什么值的,我们就不再

一个一个标记了,那下边我们就要看,我们就要把这些数放在哈希表里面,怎么快的,首先你想一下数组里面要想加一个数,你如果

不是加到最后,加到中间的话,就要移动,我们这会不需要移动,你看是怎么来做的,我们要加这个23,这一列是是什么啊,这是我们

要加入的内容,加入的数据,只不过这一列比较特殊,正好是整数,不管是什么数据,到最后你都得生成一个哈希码,生成一个哈希码

它是一个整数类型,而对于整数来说,哈希码就取他自己就可以了,所以这个X就是hashcode就是哈希码,不是那个函数哈希码,他就

代表一个哈希码,对于整数来说,哈希码取他自己就可以了,整数的哈希码取自身即可,因为它本身也是一个整数,所以下面一大堆就

在这里解决了,每个哈希码我们又写了一遍他自己,然后y=k(x),它是一个哈希函数,这是需要一个哈希函数的,比如我们往哈希表里

放数据,他不需要比较,但是他需要计算,X是谁啊,X是那个哈希码,y是什么意思啊,y就是要存放的地址,只要计算这一次,马上就能

找到地址,你把这个数放到这个地址,就可以了,不需要大量的比较,也不需要移动,那我们制定的这个函数是什么意思,让这个哈希码

除以11,取余数,为什么我们这里除以11,因为我们哈希表的主结构,长度是11,如果你对11取余数的话,不正好是0到10吗,那正好落在

我们指定的下标位置,那实际上我们这么来做,所以大家初步已经知道了,哈希码不需要比较,不需要移动,只需要计算,计算得到地址

哪有这么快,我们按照索引在数据里面找元素也要计算,也要定位,再往下我们挨个来看,我们现在解决第一个问题,看这儿了,第一步
2. 哈希表是如何添加数据的?

第一步是计算哈希码,整数的哈希码是他自己

第二步是套到哈希函数里面去,得到地址

第三步然后存到哈希表:

一次添加成功,一次就成功了,什么是一次就成功了,我现在加23呗,23除以11取余数,后面的余数已经给大家写好了,告诉我23该怎么存,

23余数不是1吗,要不要把23擦掉把23存到这里,要不要,不要,这个地方不写值,这个地方写什么呢,我们要这么来写,这个引入一个链表,

引入一个节点,当然这个节点里面是分两部分的,这里面就放了一个23,放地址后面还有没有元素,后面是null了,后面没有节点了,如果

这个节点是0X1012的话,实际上我们是要把这个地址放到这儿的,0X1012,这个大家应该是没有任何问题的,链表直接加到这里就可以了,

23就加到这里,得到哈希码计算哈希函数,得到结果是1呗,写到这里就成了,添加这么做,36依次类似吗,余数是3,那你就在这又创建一个

节点,再创建一个节点,那这儿的值是多少,这儿的值是36,后面的值是null,同样,我们不再写具体的地址了,是这么来指的,48与此类似,

我们在这又创建一个,再创建一个啊,到这儿来,我们把这个复制一份吧,一会直接用它就可以了,我们在这放一个48,这边有一个null,

同样指向他,这么来指向,就可以了,77和他是一样的,余数是0,放到这儿来,这边放的是谁,这边放的是77,这边要写上一个null,把这个null

要擦掉,不是null了,指向他,还有吗,86,86的余数是9,选择一下,把它拖过来,9在这儿,那这边写的是86,我想大家已经感觉到他添加的

一个快速性了,添加基本都是常数级别的,直接一步两步三步,就到位了,特别的快,这是一个操作,再往下还有别的吗,76也类似,我们先

跳过去,76在这儿呢,这个67我们先把他放一下,这三个先排除,我们先把其他的一次性的写完,76在哪儿呢,76在这儿呢,76写到这,这边

写一个null,然后再画一个指针,指向他就可以了,其他的都是有冲突的呢,我们来看67吧,67该怎么办,我们讲的一个操作,一次添加成功

特别快,但是有时候你会发现需要多次添加成功,什么叫多次添加成功,67除以11余数是1,1的话怎么办,直接写到这儿,结果往这里写的

时候发现这里已经不是空了,不是空意味着下面已经有值了,有值了怎么办,那我们就在上面,就在这一块,把这个拿过来,拖到这儿,

一会可能还会再拖,我们就先来写一下,先拖到这儿,这边改写谁了,这边叫67,这边还是null,那这个就不是null,这么来指向他,

这个效率就有点降低了,第一次添加不成功,有冲突的,关键之不同但是得到的地址是一样的,当我们再来放一个23,23是另外一种情况,

23在这里画一下,现在来看56,56怎么了,56的余数还是1,那就应该往这里放,有值了,往这儿走,这儿也不是空,往下走,他这儿是空,

意味着它是这里的最后一个节点,然后我们在这里来写,写一个56,然后把这个内容去掉,去掉之后再拉一个链条,56这个呢现在是末

节点,它是null的,还有一个78,78怎么办,78余数还是1,那我们就得顺着这个位置一直往下找,往下列表找不行,我们这个78是23吗,

不是,是67吗,不是,是56吗,不是,那我们一直往下加,最后把78加到这儿,我们再连接起来最后一个状态,到这儿来,把这个去了,

那我们对添加操作就写到这儿,发现冲突是绝对避免不了的,难免会有冲突,如果冲突的概率比较低的话,最终他整体的速度还是可以的

那我们树的加载操作还差最后一步,23我们该怎么加,这我们已经讲了几种情况了,一次添加成功的,多次添加成功,多次添加成功就

出现了冲突,就会拿出我们要加的值,现有的值调用equals方法,进行比较,到最后也会相等,那就创建个新节点,存储数据,加到链表的

后面就行了,可是我们现在加的比较特殊,这是个23,23怎么办,之前已经加过23了,这个时候怎么办,23除以11的余数是1,往这儿一放,

要拿这个23和这个23进行比较,比较啊,从JAVA的角度来说是一个Integer对象,他最终比较是要调用equals来进行比较的,结果那这个

23和这个23进行一比,相同,或者你往里边加的是67,这里边是不是也有了,那怎么办,已经有重复的数据,那就不加了,所以最终导致我们

的哈希表里有没有重复的数据,没,是没有重复数据的,所以不添加,出现冲突,调用equals方法比较,有重复的就不添加了,通过这个添加的

过程,我们应该得到一个结论,整体的速度还是比较快的,计算就得到了位置,大家想一下数组里面是怎么添加的,他加在某个位置要大量的

移动,他的效率也要比较高,添加的时候要得出一个结论,添加的时候要快的,第二,这里面有没有重复的数据,没,有重复的就不加了,

并请问这里面的数据有没有顺序,无序的,这真的是没有顺序的,23,67,56,有什么顺序,没有顺序,所以我们要得到这三个结论,哈希表

添加数据特别的快,如果不考虑冲突的话,3步就可以了,常数级别的,数据元素是唯一的,不会重复,然后结论他还是无序的,哈希表的原理就

讲到这
下面我们来看,添加快了,我们来看一下哈希表是如何来查询数据的?

查询数据又分为3种情况:

1. 查询数据和添加数据过程是相同的,基本上相同的,但是又有不同之处,怎么不同啊,添加可能一次添加成功,也有可能多次添加成功,

我们的查询可能一次找到,有可能多次找到,但是我们添加的时候,如果有重复的,就不添加了,你查询的时候可能查询哈希表里面就没有的

值,我在这里找100,就没有100,根本就没有,我们看这个查询是怎么来实现的,我们举个例子来说,我要找23,我要在这里面找23,怎么办,

和刚才添加的过程是一样的,首先我要找到整数23的哈希码,整数的哈希码就是他自己,然后套到这个公式里面,余数是1,索引是1的位置找,

一下子就找到了23,你要这个48,计算哈希码就是他自己,套哈希函数得到结果4,来4这个位置直接找48,一次就找到了,也非常快,那我们

再来一个,再来一个多次找到的,找一个67,67是怎么找到的,67得到他的哈希码还是67,套入哈希函数,余数是1,那就来这个位置找67了,

一看这个值是67吗,不是,再往下找,67,你看,找到了,多次找到的,78和这个一样,只不过他要多比对,多比对几次,或者你把这个冲突

尽量避免的话,他的效率还是比较高的,再找一个100,100可怎么办,这里面有没有100,没有,我现在要在这里面找一个100了,100除以

11余数是几,是1吧,99加1,100怎么办,1来这个位置找,不是100,不是100,不是100,不是100,再往后没有了,100如果存在就肯定在这个

链表里了,而这个链表从头找到最后,是不是也没有找到100,那说明什么,那说明100是不存在的,我现在画的这个表,根现在这个表,

你看结构是一样的,只不过一个是横着画一个竖着画,通过我们刚才查找的这个过程,大家应该知道,得出什么结论,第一个哈希表的

查询顺序是比较快的,跟添加速度是一样的,查询到之后就可以删除了,比如我想把56删除了怎么办,我想把这个56删了,那你就直接

改指针就行了,改一下指针指向他就行了,更新就要看更新什么了,我找到67想把它改成77,那不能这么改的,为什么啊,当你把它改成

77的话,他是不是排在这个位置了,那不能随便改的,一改的话下次再改就找不着了,但是我们现在存着整数来说,可能这个哈希码

我们要看,我们要根据情况来看,如果一改这个值影响存储位置,那你就要考虑其他方法了,比如先删除再添加,也相当于是更新了,

其他的算法了,会有这种解决方案,更新就要好好考虑了,更新就影响哈希码,影响哈希码就要采用其他方法来解决了,但是你即使

先删除再添加,那速度也不慢,因为它引的是链表,便于删除也便于添加,这个就讲到这里了
讲到这里哈希表的主题内容就已经讲了,如果问到哈希表就从这三步来说,首先要说哈希表的特点是什么,特点是快,为什么快,

跟他的结构有关,然后再讲一下它是怎么添加的,把这个说明白,说明白之后得到这个结论,是非常快的,然后再讲一下它是怎么

查询的,和添加的过程是基本相同的,然后逐个来说明就可以了,讲到这哈希表就可以了,我们再讲一些细节,请问hashcode这个

方法是做什么的,JAVA里面有这个方法,是计算哈希码的,它是计算哈希码的,我们打开JAVA,hashcode是每个类都要有的一个方法,

它是从Object继承过来的,找一个方法,hashcode方法有没有,整数的哈希码就直接返回哈希码这个数,public int hashCode()

return Integer.hashCode(value);public static int hashCode(int value),return value;整数的哈希码取他自身

就可以了

不同数的哈希码肯定是不一样的,哈希码是通过一个计算得到一个整数,如果我们这里面存的是字符串,存的是学生,你必须把

字符串和学生变成一个整数值,算法放到hashcode里边就可以了,我们得到一个整数,这是他的一个内容,再往下看还有什么,这是

我们hashcode的一个作用,equals是干什么的,就是看这个图,hashcode是在这里使用的,equals是怎么用的,当你往这里放的时候,

里边出现了冲突,可能是添加,可能是查询,一般出现冲突之后需要比对了,你找的是67,那要和每个逐个比较看是不是这个内容,

那通过equals来比较,equals是什么,equals是出现了冲突之后,通过equals来比较,判断内容是否相同,这是一个内容
下面再讲一个内容,各种类型的哈希码应该如何获取?

1. 简单一句话就是调用hashcode方法,关键是我们使用者是调用hashcode方法,就得到他的哈希码了,这个hashcode的开发者呢,

在里面写什么代码,得到一个整数,那我们说了,如果是整数,取自身


2. double该怎么办,如果里面存的是double的话,那有人说那不简单,要整数取整呗,3.14,3.15,


3.145,我们都取整,取整不是找冲突吗,为什么,3.14,3.15,3.145一取整,是不是都是3,不同的数哈希码一样,那最终都得

存到一个位置,那明显是冲突的,所以如果你这么来设计hashcode的算法,那以后这种算法是很失败的,会导致冲突,

double肯定不能这么来写,那怎么来写呢,这我们不用操心,我们知道就可以了,好多数学家就是做这个的,学数学的,

学二进制的,他就是用来解决这些问题的,我们来看Double底层的

哈希码是怎么来做的,return Double.hashCode(value);这什么意思,再来看,public static int hashCode(double value),

long bits = doubleToLongBits(value);这什么意思,value就是3.14,先要把double变成一个long类,然后要得到一个long数,

return (int)(bits ^ (bits >>> 32));然后这个数要做什么,是不是麻烦,先要向右移动32位,然后要与原来的bits做异或运算,

这个里面就不看了,比较复杂,他做这么复杂的一个目的是什么,目的就是很简单,我这边不同的double数,经过你这个运算之后,

得到一个整数,这个整数尽量要是不一样的,他就是为了这样的一个目的,为什么整数这么简单,其自身不同就是不同被


3. 如果是一个String的话,字符串的哈希码该怎么办,java,oracle,这跟整数也没关系,这个时候该怎么办,有人说我是有办法的,

比如说JAVA怎么办,JAVA是不是由4个字母组成的,那我就把4个字母的编码值都有它的unicode编码吗,它的编码不就是整数吗,

相加不就可以吗,也能得到一个整数,那也是一个很糟的方案,比如说,abc中国农业银行,cba中国篮球联赛,bac是什么,

我知道bat是什么意思,请问如果按照我们刚才的算法的话,a的编码是97,b的编码是98,c的编码是99,我们这三个一求,

把他们的编码相加,结果都是一样的,结果你这三个不同的字符串,他们的哈希码又是一样的,那一存又存在一个位置了,

又出现冲突了,这种方案又是不好的,那不好我们怎么办,比如我们这么来做,你不是顺序不同吗,abc 1*97+2*98+3*99,

abc这么来的,cba 1*99+2*98+3*97,这么一来数就不一样了,应该就不一样了,这是一种思路,但这是不是最好的思路,

我觉得最好的思路基本上就在这儿,我就看JAVA底层是怎么来实现的,人家肯定是采用非常好的算法,JAVA的hashcode

是怎么来做的,你看什么意思,JAVA的hashcode,int h = hash;这是一个h的值,if (h == 0 && value.length > 0),

value字符串底层是一个字符数组,取他的长度,把每个字符都取出来,char val[] = value; 

for (int i = 0; i < value.length; i++),h = 31 * h + val[i];

把每个字符取出来,然后乘以31,再加上value[i],就是h是谁,h是他的哈希值,总之他又是一套的算法,他的算法是这么来实现的,

保证不同的字符串生成的哈希码肯定是整数,并且值是不一样的,这大家明确了,下边怎么办,我要在这里来了一个类,

整数Double都是基本类型,我还来个学生
最后我们来看如何减少冲突?

冲突不能够百分百的避免,肯定会有冲突,没有谁说设计哈希表的时候不冲突的,我们只能减少这个冲突,所以我们来看,如何减少这个冲突,

第一个大家想一下,现在这个长度是不是11,现在这个读取的长度是不是11,我要往里面存20个数,有没有冲突,肯定没有冲突的,你存的数

比哈希表的长度还要长呢,那肯定是有冲突的,那如何避免,哈希表的长度和表中记录数的长度,概念叫填充因子,表的记录数,如果我要

往里面放20个数据,但是我哈希表的长度是11,这个一除大于1的,那肯定有冲突,肯定是有冲突的,那这个比例至少是小于1的,

等于1也是有冲突的,光小于1也不行,根据实际文献证明,填充因子在0.5左右的时候,性能是比较好的,也就是你的长度是11的话,

怎么办呢,你这里面的数最好不要超过5,6个,那这时候就会降低冲突,当然反过来说你降低冲突会浪费空间,那就会浪费空间,

所以大家大概知道这样一个理论,装填因子=表中的记录数/哈希表的长度,经验值是0.5,稍微高一些也可以,这是一个内容了,

冲突减少了,哈希表的总长度和表中要放的记录数是有关系的,这是一个了,哈希函数的选择,因为选一个比较好的哈希函数,

这个从理论上面有很多的方法,直接定址法,折叠法,除留余数法,大家可以去查询相关的资料,怎么去取一个好的哈希函数,

那我们这里使用的哈希函数是y=x%11,这个就是除留取余法,这个大家明确一下,我们的哈希函数是使用这种来做的



还有一个我们怎么来处理冲突,减少冲突,如果你能够把这个冲突处理好,可能能够进一步的减少这个冲突,该如何来处理这个冲突啊,

好多方法,链地址法,开放地址法,再散列法,建立一个公共的溢出区,把公共的数据放在溢出区里面,那我们刚才使用的是哪一种方法,

冲突了就往链表后面加节点,我们用的就是链地址法,我们刚才处理冲突的方法叫链地址法,讲到这里我们就把哈希表相关的理论就讲了


总结一下:

如果问到哈希表了,哈希表最大的特点就是快,怎么快啦,添加快,查询快,怎么达到这一点的呢,跟他的结构有关,说一下他最流行,

最简单的结构,然后来讲一下它是如何来添加的,再讲一下查询的,讲查询和添加的时候要贯穿一条主线,就是快,几步就可以了,

要贯穿这条主线,讲到这基本上就可以结尾了,如果下面再继续交流的话,我们就可以把后面的这些内容说一下,我建议大家

再加上一句话,那句话啊,我们JAVA里面有一个类,叫HashSet,是不是还有一个叫HashMap,还有一个过时了,叫Hashtable,

他们底层用的是什么啊,都是哈希表,只要遇到Hash这四个单词,这4个字母哈希的意思,他的底层结构就是哈希,

关于哈希的理论和算法呢,就都给大家讲了
package com.learn.search;

/**
 * 如果我要往哈希表里面存学生的话
 * 他的哈希码该怎么办,
 * 这是一个复杂类型,
 * 但是他的属性是基本类型,
 * Student比较复杂,
 * 但是你这里不是有这么多的属性吗
 * 我得到你每个属性的基本哈希码,
 * 然后按照某种算法添加啊,
 * 不是可以得到一个整数了吗
 * 你的Class不是比较复杂吗
 * 
 * 学生的哈希码又该怎么办
 * 我来了一个学生的姓名,年龄都不一样的
 * @author Leon.Sun
 *
 */
public class Student {

	private int id;
	
	private String name;
	
	private int age;
	
	/**
	 * 因为我们这里写的是小写的double,
	 * 如果是大写的Double他就会直接调用它里面的hashCode方法
	 */
	private double score;

	/**
	 * 但是还有一种情况,
	 * 班级Class,
	 * 这个clazz当然不存在
	 */
	private Clazz clazz;

	/**
	 * 我们来产生以下它的hashCode方法
	 * 好复杂啊,
	 * 最终还是得到一个整数,
	 * 返回就可以了
	 * 我们又给大家回答了一个问题,
	 * 什么问题呢,
	 * 各种数据类型的哈希码该如何获取
	 * 这个理论大家知道
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		/**
		 * 年龄
		 */
		result = prime * result + age;
		/**
		 * 班级直接调用hashCode方法得到结果就行了
		 */
		result = prime * result + ((clazz == null) ? 0 : clazz.hashCode());
		/**
		 * id
		 * 整数直接取自身
		 */
		result = prime * result + id;
		/**
		 * 姓名
		 * 字符串的话就直接调用hashCode
		 */
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		long temp;
		/**
		 * 实际上和我们开始见到的double处理机制是一样的
		 */
		temp = Double.doubleToLongBits(score);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) obj;
		if (age != other.age)
			return false;
		if (clazz == null) {
			if (other.clazz != null)
				return false;
		} else if (!clazz.equals(other.clazz))
			return false;
		if (id != other.id)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (Double.doubleToLongBits(score) != Double.doubleToLongBits(other.score))
			return false;
		return true;
	}
	
}
package com.learn.search;

/**
 * Clazz里面还是基本类型的
 * 可以得到一个哈希码
 * 最终按照某个机制来就可以了
 * 我们哈希表里既用到hashcode也用到equals
 * equals我们经常用,
 * 所以我们同时来实现hashcode和equals
 * @author Leon.Sun
 *
 */
public class Clazz {

	/**
	 * 这里面可能比较简单
	 */
	private int id;
	
	private String name;

	/**
	 * 仔细看一下他的hashCode是怎么来的,
	 * 最终保证不同
	 */
	@Override
	public int hashCode() {
		/**
		 * 还是一个31
		 */
		final int prime = 31;
		int result = 1;
		/**
		 * 哈希码的整数就是他自己
		 */
		result = prime * result + id;
		/**
		 * 字符串的哈希码就是调用它的hashCode方法
		 */
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Clazz other = (Clazz) obj;
		if (id != other.id)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	
	
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值