哈希的应用——布隆过滤器

文章目录

  • 前言
  • 1. 布隆过滤器提出
  • 2. 布隆过滤器概念
  • 3. 布隆过滤器的插入
    • 多哈希函数映射减少冲突
    • 结构定义及set(插入)函数实现
  • 4. 布隆过滤器的查找
    • test(查找)函数实现
    • 布隆过滤器允许误判
  • 5. 布隆过滤器的适用场景
  • 6. 如何选择布隆过滤器的长度和哈希函数的个数
  • 7. 测试
  • 8. 布隆过滤器删除(reset)的思考
  • 9. 布隆过滤器的优缺点分析
    • 布隆过滤器的优点
    • 布隆过滤器的缺陷
  • 10. 源码
    • bitset.h
    • BloomFilter.h
    • Test.cpp

前言

上一篇文章,我们学习了位图,位图在某些场景下是非常适用的,非常快捷方便。
但是,在文章的最后,我们也提出了位图的一些缺陷——比如位图只能映射整型数据,其它类型的数据则不行。
因为位图里面的元素去映射的其实就是下标嘛,而下标的话都是整型啊。

那有没有什么 办法可以解决呢?
这就是我们今天要学的布隆过滤器(Bloom Filter)
在这里插入图片描述

1. 布隆过滤器提出

我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经看过的内容。问题来了,新闻客户端推荐系统如何实现推送去重的?
其实就是用服务器记录了用户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那些已经存在的记录。

那这就涉及到一个问题:面对海量的数据,如何进行快速的查找筛选呢?

1. 用哈希表或红黑树存储用户记录,缺点:空间问题,因为它们除了存储数据之外还有额外存储一些指针,结点颜色这些东西,而且数据量过大的时候可能直接就存不下了。
2. 用位图存储用户记录,缺点:位图一般只能处理整形,如果内容编号是字符串,就无法处理了。(这也是我们上面提到的问题)
3. 就是我们这篇文章要重点学的——将哈希与位图结合,即布隆过滤器(不仅可以提升查询效率,也可以节省大量的内存空间)

2. 布隆过滤器概念

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”(允许误判),它是用多个哈希函数,将一个数据映射到位图结构中的多个位置(即它的底层还是位图)。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。
在这里插入图片描述

那接下来我们就来详细讲解一下布隆过滤器

3. 布隆过滤器的插入

上面提到布隆过滤器其实就是用哈希函数把数据映射到位图结构中。

现在有这样一个位图结构:

在这里插入图片描述

例如现在我们要插入一些元素——“百度”、“美团”、“Google”,一些字符串,那字符串没法直接映射到位图中,怎么办?

那这没什么难的,我们直接玩过的东西,可以搞一个仿函数把字符串转成整型,然后就可以往位图里面映射了。

那转成整型之后确实可以映射了,但是有没有存在什么问题呢?

是不是会存在冲突啊。
我们的位图之所以没有考虑冲突的问题因为我们说了位图适用的是海量数据,数据无重复的场景,而且位图是一种直接定址的映射。通常是用来判断某个数据存不存在的。

那我们可以使用字符串哈希等一些方法减少冲突,当然不能完全避免

而且字符串往整型的映射本身就是一个大范围到小范围的映射。
就比如一个长度为10的字符串,大家算一下有多少种?
char有256种取值,那就是256的10次方,而整形只有2^32个。
而且这还只是长度为10的一种情况,那…

多哈希函数映射减少冲突

那布隆过滤器呢采用这样一种方法来进一步的减少冲突:

比如现在我们插入了3个值
在这里插入图片描述
这时还没有发生冲突,然后再插入一个值
在这里插入图片描述
这时候B站就和美团发生了冲突。
那布隆就想到了这样一个方法来降低冲突:
既然一个值映射一个位置容易发生冲突,那我就用多个哈希函数让一个值同时映射到多个位置,就可以再进一步减少冲突。(因为如果一个元素映射多个位置的话那就需要这多个位置同时被多个元素映射才算冲突)

比如现在我们让每个插入的元素映射2个位置:

那怎么做到映射多个位置呢?
很简单,让一个元素分别通过多个哈希函数计算映射地址就行了,然后将这个多个结果对应的位置都置成1。
这样只有这多个位置都为1,才算这个元素是存在的。
在这里插入图片描述
大家看,现在我们让每个元素映射两个位置,这样的话即使某些元素的其中一个映射位置与别人发生了冲突,但是也没有构成冲突。
这样的话冲突的概率就会更小一点。

结构定义及set(插入)函数实现

先来定义一下布隆过滤器的结构:

在这里插入图片描述
这里我们给3个哈希函数,实际应用中看具体情况。N代表插入的数据个数。

那我们来写一下set:

那set的话就是用三个哈希函数计算出来三个映射地址,然后把这三个比特位都置成1
在这里插入图片描述

那我们可以找三个字符串哈希用一下(大家可以自己去网上查找)

struct BKDRHash
{size_t operator()(const string& s){size_t hash = 0;for (auto ch : s){hash += ch;hash *= 31;}return hash;}
};struct APHash
{size_t operator()(const string& s){size_t hash = 0;for (long i = 0; i < s.size(); i++){size_t ch = s[i];if ((i & 1) == 0){hash ^= ((hash << 7) ^ ch ^ (hash >> 3));}else{hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));}}return hash;}
};struct DJBHash
{size_t operator()(const string& s){size_t hash = 5381;for (auto ch : s){hash += (hash << 5) + ch;}return hash;}
};

然后模板参数的地方,我们可以直接给缺省值:

在这里插入图片描述
K我们默认给一个string类型,布隆过滤器面对的场景大多都是字符串。

4. 布隆过滤器的查找

test(查找)函数实现

那我们查找的时候如何判断一个元素在不在呢?

那其实就是去判断它映射的位置是否都置成了1就行了。
如果全部为1,就是在,如果有一个不为1,就是不存在。
在这里插入图片描述

布隆过滤器允许误判

那这里我要问大家一个问题:判断在和不在那种情况会存在误判?

🆗,要告诉大家的是,对于布隆过滤器来说:
判断在是可能不准确的,可能会误判;而判断不在是一定准确的。

为什么呢?

如果是不在的话,那么只要有一个映射的位置为0,那他就一定不在,这是不会出错的。
而判断在的话,就可能出现这样的情况:
在这里插入图片描述
大家看,上面的4个字符串是已经插入到布隆过滤器里面的值,已经把它们映射的位置都设置成了1.
现在有一个待插入元素“腾讯”,还没有插入,但是它映射的几个位置已经被之前插入的其它元素设置为1了。
那这时我们去查找“腾讯”的话,实际它是没有插入的,但是test的时候,会发现它映射的几个位置都已经被set成了1,那这时候就会误判“腾讯”是存在的。

总结一下:

布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特位一定为1。
所以可以按照以下方式进行查找:分别计算每个哈希值对应的比特位置存储的是否为零,只要有一个为零,代表该元素一定不在哈希表中,否则可能在哈希表中。
注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,可能并不存在,因为这里可能发生误判。

5. 布隆过滤器的适用场景

那正由于会出现上面误判情况的原因,所以布隆过滤器的适用场景是有限的,即它适用于一些允许误判的场景

比如:

我们下载一个游戏,比如说王者荣耀,然后我们注册一个新账号的时候要给自己起一个昵称,或者使用什么改名卡修改昵称的时候
可能会出现这样的场景
在这里插入图片描述
我们输入一个昵称之后,系统提示你这个昵称已经存在了,让你换一个。
那这种情况就是允许你误判的,你输入一个昵称之后,系统提示已经存在,那这里就有两种情况:
第一种就是真的已经被用过了;
第二种就是实际没有被使用过,但是它误判了。
那这种情况即使它误判了,其实也没什么影响,因为对于用户来说,他也不知道自己输入的昵称到底有没有被用过,系统提示被用过了,那用户就会认为真的被用过了(即使是误判了),就再换一个。

那这种场景用布隆过滤器其实就挺合适的:

假如这个游戏现在有10亿用户,这些用户的数据(可能包括昵称、账号、密码这些东西)存储在数据库里面,数据库通常存在磁盘上。
那我们去查找判断的时候为什么不直接去数据库查找呢?
因为太慢了,效率太低。
所以就可以这样做:
在这里插入图片描述
我们就把所有的昵称存到布隆过滤器里面,然后用户注册新的昵称或者修改昵称的时候,就可以快速的反馈给用户昵称是否存在。

那大家想手机号码这样的信息能不能也存到布隆里面?

如果是手机号码的话有没有注册过用户自己是不是应该知道啊,那如果再误判的话是不是就被用户喷了啊。
但是其实也是可以借助布隆过滤器处理的,而且这种情况反而更能体现布隆“过滤器”的价值。
怎么做呢?
还是把用户的手机号都放到布隆里面
在这里插入图片描述
然后新用户注册的时候,如果这个手机号不在布隆里面,那就可以直接返回,因为我们上面分析了判断不在是一定准确的。
那如果反馈的信息是已存在,那这时候就可能出现了误判,那这时候我们再去数据库里面进行一个确认,然后再返回。
在这里插入图片描述
那这样的话其实就可以认为布隆过滤器实现了一个很好的“过滤”的作用。
它能够把大多数不在的情况快速的“过滤”掉,只剩下少数在的情况需要去数据库里面查询确认,那这样与全部到数据库里面查找还是效率高了很多的可以避免不必要的查询操作,节省时间和资源。

所以对于布隆过滤器来说:

相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。
常见的实践有:利用布隆过滤器减少磁盘 IO 或者网络请求,因为一旦一个值必定不存在的话,我们可以不用进行后续昂贵的查询请求。

6. 如何选择布隆过滤器的长度和哈希函数的个数

那大家思考一下,如果我们现在有N个待插入数据,那布隆过滤器底层的位图我们要开多大呢?哈希函数要选择多少个呢?

就开N个吗?好像不行,因为一个值就要映射多个位置啊。
然后哈希函数多一点的话,误判率肯定会小一点,但是哈希函数也不能搞太多,太多的话一个值映射的位置就会变多,那使用的空间就会变大。

那怎么样选择比较合适呢?有人给出了这样的公式:

k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个数,p 为误报率
则:
在这里插入图片描述

那按照我们上面写的,我们给了3个哈希函数:

则K为3,然后ln2大概为0.69
那么可以得出
在这里插入图片描述
m=4.3*n,即布隆过滤器的长度等于元素个数的4到5倍是比较合适的,一个元素分配4到5个空间

那我们就按照这个关系去改造一下我们实现的布隆过滤器,因为前面我们都没有考虑这个:

在这里插入图片描述
相关的地方也要改一下
在这里插入图片描述

7. 测试

我们来搞一点数据测试测试

先来看一下set:

在这里插入图片描述
每次set我们可以打印一下它映射的3个位置
在这里插入图片描述
运行一下
在这里插入图片描述
目前我们这些数据是没什么冲突的。

然后test我们也测试一下:

在这里插入图片描述
在这里插入图片描述
🆗,没问题,不过我们的数据量也比较小。

所以呢,我这里也有一个写好的测试的程序,我们来测试几把:

void test_bloomfilter2()
{srand((unsigned int)time(nullptr));const size_t N = 10000;BloomFilter<N> bf;std::vector<std::string> v1;std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";for (size_t i = 0; i < N; ++i){v1.push_back(url + std::to_string(i));}for (auto& str : v1){bf.set(str);}// v2跟v1是相似字符串集,但是不一样std::vector<std::string> v2;for (size_t i = 0; i < N; ++i){std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";url += std::to_string(999999 + i);v2.push_back(url);}size_t n2 = 0;for (auto& str : v2){if (bf.test(str)){++n2;}}cout << "相似字符串误判率:" << (double)n2 / (double)N << endl;// 不相似字符串集std::vector<std::string> v3;for (size_t i = 0; i < N; ++i){string url = "https://www.cctalk.com/m/statistics/live/16845432622875";url += std::to_string(i + rand());v3.push_back(url);}size_t n3 = 0;for (auto& str : v3){if (bf.test(str)){++n3;}}cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl;
}

简单解释一下,大家应该很容易看懂。这段代码其实就是搞大量的字符串,N可以控制插入字符串的数量。
三个vector v1、v2、v3里面插入的字符串数量都是N,我们搞了一个url字符串,v1里面插入的都是这个url+to_string(i),i在循环过程是不断变化的嘛。
v1set到一个布隆过滤器里面
v2插入的都是这个url+to_string(999999 + i),所以v2跟v1是相似字符串集,但是不一样
然后v3就用了另一个url+to_string(i + rand()),所以 v3跟v1不相似字符串集
然后我们先后遍历v2,v3,判断它们里面的每个字符串在不在布隆过滤器里面,最后得到两个误判率(分别对应字符串相似和不相似的情况下)

我们来运行一下:

首先N等于1万的时候,我们看一下
在这里插入图片描述
大家看,相似不相似都没差多少。但是这个误判率其实还是比较高的,超过了10%。
来10万个试一下(嫌慢可以换成release)
在这里插入图片描述
现在基本在10~20%

那我们如何能控制一下这个误判率呢?

可以适当增加哈希函数的数量,但这个有点治标不治本。
我们可以去扩大空间比较好,之前我们设置的倍数是5,那我们扩大到6试一下
在这里插入图片描述
🆗,还是有一个明显的下降。
来增到7呢?
在这里插入图片描述
会再降低一点,不会再降太多了。
来直接到10
在这里插入图片描述
就降低到1%左右了。
所以去扩大这个空间还是比较有效的降低误判率。

8. 布隆过滤器删除(reset)的思考

大家会发现我们上面没有实现reset,布隆过滤器可以置成reset(删除)吗?

🆗,传统的布隆过滤器是不支持删除操作的。

为什么不支持呢?

因为你删除一个元素之后可能会对其它元素的查找造成影响。
举个栗子
在这里插入图片描述
这里我们如果先把美团删除的话,那就把美团映射的两个位置置成0嘛,然后查找B站的时候,会发现B站映射的两个位置有一个为0,那就会误判B站是不存在的,但是B站我们并没有删除。

那有没有什么办法能让他支持删除呢?有人提出这样一种方法:

就是不再让布隆过滤器的每个位置存储0或1,而是让它直接存储这个位置被set的次数
在这里插入图片描述
这样
在这里插入图片描述
那这样我们把美团删除的话,就把它映射两个位置的次数减一,然后再查找B站就不受影响了。
但这样做的话就涉及到你要给每个位置分配几个位的问题,因为你分配的bit数量不同,那它能存储的次数的范围就不同。
所以这样就会存在一些问题:
就是你的位数如果给的不合适,可能某一次次数更新之后就会溢出,造成计数回绕(计数器值增加到达其最大范围后,再次增加会导致计数器值重新回到初始状态),而且这样做使用的空间肯定会变多
除此之外还存在一个问题:
就是我们查找一个元素的时候无法确认该元素是否真的存于布隆过滤器中。
因为我们删除一个元素的时候一定要确保它是存在的,再去删除(减减对应位置的次数),不存在是不能删除的,但是判断一个元素是否在布隆过滤器中是可能误判的。
所以我们在删除一个元素的时候无法确认它是否存在。

所以我觉得不能认为这种计数的方法可以实现删除,可以说它提供了实现删除的可能。

9. 布隆过滤器的优缺点分析

布隆过滤器的优点

  1. 增加和查询元素的时间复杂度为:O(K),(K为哈希函数的个数,一般比较小,所以可以认为是O(1)),与数据量大小无关
  2. 哈希函数相互之间没有关系,方便硬件并行运算
  3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
  4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有着很大的空间优势
  5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
  6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算

布隆过滤器的缺陷

  1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
  2. 不能获取元素本身
  3. 一般情况下不能从布隆过滤器中删除元素
  4. 如果采用计数方式删除,可能会存在计数回绕问题且无法判断要删除的元素是否存在

10. 源码

bitset.h

#pragma once
#include <vector>template <size_t N>
class bitset
{
public:bitset(){_bits.resize(N / 8 + 1, 0);}void set(size_t x){size_t i = x / 8;size_t j = x % 8;_bits[i] |= (1 << j);}void reset(size_t x){size_t i = x / 8;size_t j = x % 8;_bits[i] &= ~(1 << j);}bool test(size_t x){size_t i = x / 8;size_t j = x % 8;return _bits[i] & (1 << j);}
private:vector<char> _bits;
};

BloomFilter.h

#pragma once
#include "bitset.h"
#include <time.h>
#include <string>struct BKDRHash
{size_t operator()(const string& s){size_t hash = 0;for (auto ch : s){hash += ch;hash *= 31;}return hash;}
};struct APHash
{size_t operator()(const string& s){size_t hash = 0;for (long i = 0; i < s.size(); i++){size_t ch = s[i];if ((i & 1) == 0){hash ^= ((hash << 7) ^ ch ^ (hash >> 3));}else{hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));}}return hash;}
};struct DJBHash
{size_t operator()(const string& s){size_t hash = 5381;for (auto e : s){hash += (hash << 5) + e;}return hash;}
};template<size_t N,
class K = string,
class Hash1 = BKDRHash,
class Hash2 = APHash,
class Hash3 = DJBHash>
class BloomFilter
{
public:void set(const K& key){size_t len = N * _mul;size_t hash1 = Hash1()(key) % len;_bs.set(hash1);size_t hash2 = Hash2()(key) % len;_bs.set(hash2); size_t hash3 = Hash3()(key) % len;_bs.set(hash3);//cout << hash1 << " " << hash2 << " " << hash3 << " " << endl << endl;}bool test(const K& key){size_t len = N * _mul;size_t hash1 = Hash1()(key) % len;if (!_bs.test(hash1)){return false;}size_t hash2 = Hash2()(key) % len;if (!_bs.test(hash2)){return false;}size_t hash3 = Hash3()(key) % len;if (!_bs.test(hash3)){return false;}return true;}private:static const size_t _mul = 6;bitset<N*_mul> _bs;
};void test_bloomfilter1()
{BloomFilter<100> bs;bs.set("sort");bs.set("bloom");bs.set("hello world hello bit");bs.set("test");bs.set("etst");bs.set("estt");cout << bs.test("sort") << endl;cout << bs.test("bloom") << endl;cout << bs.test("hello world hello bit") << endl;cout << bs.test("etst") << endl;cout << bs.test("test") << endl;cout << bs.test("estt") << endl;cout << bs.test("ssort") << endl;cout << bs.test("tors") << endl;cout << bs.test("ttes") << endl;
}void test_bloomfilter2()
{srand((unsigned int)time(nullptr));const size_t N = 10000;BloomFilter<N> bf;std::vector<std::string> v1;std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";for (size_t i = 0; i < N; ++i){v1.push_back(url + std::to_string(i));}for (auto& str : v1){bf.set(str);}// v2跟v1是相似字符串集,但是不一样std::vector<std::string> v2;for (size_t i = 0; i < N; ++i){std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";url += std::to_string(999999 + i);v2.push_back(url);}size_t n2 = 0;for (auto& str : v2){if (bf.test(str)){++n2;}}cout << "相似字符串误判率:" << (double)n2 / (double)N << endl;// v3跟v1不相似字符串集std::vector<std::string> v3;for (size_t i = 0; i < N; ++i){string url = "https://www.llllll.com/m/statistics/live/16845432622875";url += std::to_string(i + rand());v3.push_back(url);}size_t n3 = 0;for (auto& str : v3){if (bf.test(str)){++n3;}}cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl;
}

Test.cpp

#define _CRT_SECURE_NO_WARNINGS#include <iostream>
using namespace std;
#include "BloomFilter.h"int main()
{test_bloomfilter2();return 0;
}

在这里插入图片描述

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

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

相关文章

JVM 对象的内存布局

对象头 Mark word 标记字段 用于存储对象自身的运行时数据&#xff0c;如哈希码&#xff08;HashCode&#xff09;、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等 ClassPoint 类型指针 对象指向它的类型元数据的指针&#xff0c;Java虚拟机通过这个指针 来…

延时消息队列

目录 前言 一、延时队列实用场景 二、DelayQueue DelayQueue的实现 使用延迟队列 DelayQueue实现延时任务的优缺点 三、RocketMQ 原理 四、Kafka 原理 实现 DelayMessage定义 消息发送代码 消费者代码 参考 前言 延时队列的内部是有序的&#xff0c;最重要的…

Linux之NFS服务器

目录 Linux之NFS服务器 简介 NFS背景介绍 生产应用场景 NFS工作原理 NFS工作流程图 流程 NFS的安装 安装nfs服务 安装rpc服务 启动rpcbind服务同时设置开机自启动 启动nfs服务同时设置开机自启动 NFS的配置文件 主配置文件分析 示例 案例 --- 建立NFS服务器&#…

【Python】环境的搭建

前言 要想能够进行 Python 开发, 就需要搭建好 Python 的环境. 需要安装的环境主要是两个部分: 运行环境: Python开发环境: PyCharm 一、安装 Python 1.找到官方网站 官网&#xff1a;Welcome to Python.org 2.找到下载页面 点击download中的Windows 3.选择稳定版中的Win…

【计算机网络】HTTPS

文章目录 1. HTTPS的概念2. 加密常见的加密方式对称加密非对称加密 3. HTTPS的工作过程的探究方案1 —— 只使用对称加密方案2 —— 只使用 非对称加密方案3 —— 双方都是用非对称加密方案4 —— 非对称加密对称加密中间人攻击引入证书CA认证理解数据签名 方案5 —— 非对称加…

Java守护线程的理解及应用

在Java中有两类线程&#xff0c;分别是User Thread&#xff08;用户线程&#xff09;和Daemon Thread&#xff08;守护线程&#xff09; 。 用户线程很好理解&#xff0c;我们日常开发中编写的业务逻辑代码&#xff0c;运行起来都是一个个用户线程。而守护线程相对来说则要特别…

C#__资源访问冲突和死锁问题

/// 线程的资源访问冲突&#xff1a;多个线程同时申请一个资源&#xff0c;造成读写错乱。 /// 解决方案&#xff1a;上锁&#xff0c;lock{执行的程序段}:同一时刻&#xff0c;只允许一个线程访问该程序段。 /// 死锁问题&#xff1a; /// 程序中的锁过多&#xf…

vscode debug python launch.json添加args不起作用

问题 为了带入参数调试python 程序&#xff0c;按照网上搜到的教程配置了lauch.json文件&#xff0c;文件中添加了"args": [“model” “0” “path”] {// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更多信息&#xff0c;请访问: h…

深入浅出学Verilog--基础语法

1、简介 Verilog的语法和C语言非常类似&#xff0c;相对来说还是非常好学的。和C语言一样&#xff0c;Verilog语句也是由一连串的令牌&#xff08;Token&#xff09;组成。1个令牌必须由1个或1个以上的字符&#xff08;character&#xff09;组成&#xff0c;令牌可以是&#x…

day3_C++

day3_C 思维导图用C的类完成数据结构 栈的相关操作用C的类完成数据结构 循环队列的相关操作 思维导图 用C的类完成数据结构 栈的相关操作 stack.h #ifndef STACK_H #define STACK_H#include <iostream> #include <cstring>using namespace std;typedef int datat…

2023 年全国大学生数学建模竞赛题D 题 圈养湖羊的空间利用率思路详解+Python源码(二)

昨天已经将E题第一二问的详解和思路源码都写了出来&#xff0c;大家如果想从E题下手的话推荐参考本人文章&#xff0c;个人认为E题在建模上是优于D题的&#xff0c;毕竟有给出数据而且有明确的建模思路&#xff0c;E题我直接提供了Python源码直接可以运行即可&#xff1a; 202…

STC15单片机特有的PWM寄存器和普通定时器实现PWM输出

STC15单片机特有的PWM寄存器和普通定时器实现PWM输出 🌿主要针对STC15W4型号特有的6通道15位专门的高精度PWM。 ✨STC15W4K32S4系列单片机具有6通道15位专门的高精度PWM(带死区控制)和2通道CCP(利用它的高速脉冲输出功能可实现11~16位PWM);(STC15F/L2K60S2系列单片机具有3通…

Android逆向学习(番外一)smali2java部分文件无法反编译的bug与修复方法

Android逆向学习&#xff08;番外一&#xff09;smali2java部分文件无法反编译的bug与修复方法 一、前言 昨天我和往常一样准备着android逆向&#xff08;四&#xff09;的博客&#xff0c;结果发现smali2java对某些文件无法进行逆向&#xff0c;我不知道windows会不会产生这…

视频汇聚/视频云存储/视频监控管理平台EasyCVR安全检查的相关问题及解决方法2.0

开源EasyDarwin视频监控TSINGSEE青犀视频平台EasyCVR能在复杂的网络环境中&#xff0c;将分散的各类视频资源进行统一汇聚、整合、集中管理&#xff0c;在视频监控播放上&#xff0c;TSINGSEE青犀视频安防监控汇聚平台可支持1、4、9、16个画面窗口播放&#xff0c;可同时播放多…

windows10使用wheel安装tensorflow2.13.0/2.10.0 (保姆级教程)

安装过程 安装虚拟环境安装virtualenv安装满足要求的python版本使用virtualenv创建指定python版本的虚拟环境 安装tensorflow安装tensorflow-docs直接下载使用wheel下载 在VSCode编辑器中使用虚拟环境下的python解释器&#xff0c;并使用tensorflow常见错误 注意&#xff1a; t…

反序列化中_wakeup的绕过

文章目录 前言绕过方法变量引用属性个数不匹配(cve-2016-7124)C绕过fast-destruct其余GC回收机制 前言 反序列化中_wakeup扮演着非常重要的角色&#xff0c;ctf碰到很多的题目都有涉及到_wakeup绕过&#xff0c;写下这篇博客来总结下大部分绕过方法&#xff0c;其中会有例题具…

大数据导论 笔记

一、大数据方向 1、技术发展 计算机网络云计算大数据时代人工智能&#xff08;本科&#xff1a;使用&#xff0c;研究生&#xff1a;推导&#xff0c;博士&#xff1a;创新&#xff09; 2023年 大数据模型 人工智能元年 2、基础课程 hadoop 大数据基础 三大件&#xff1a;HDF…

java实现调用百度地图

这里使用的springbootthymeleaf实现&#xff0c;所以需要有springboot技术使用起来更方便 当然&#xff0c;只使用html加js也可以实现&#xff0c;下面直接开始 首先我们需要去百度地图注册一个AK&#xff08;百度地图开放平台 | 百度地图API SDK | 地图开发&#xff09; 找到左…

学会用命令行创建uni-app项目并用vscode开放项目

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 创建 uni-app 项目 命令行创建 uni-app 项目 编译和运行 uni-app 项目&#xff1a; 用 VS Code 开发 uni…

教你如何快速阅读葡萄酒标签

我们经常被问及葡萄酒标签上写了什么&#xff0c;总体而言这些信息可以分为四个关键部分&#xff0c;第一品牌或生产商&#xff1b;第二国家或地区&#xff1b;第三葡萄品种&#xff1b;第四年份。 第一品牌或生产商&#xff0c;在寻找葡萄酒的制造商时&#xff0c;著名的品牌名…