【C++】哈希思想的应用——位图、布隆过滤器和哈希切割

news2024/12/22 23:59:54

前言:

       前面我们学习了unordered_map和unordered_set和哈希表哈希桶等,并且我们自己用哈希桶封装了unordered_map和unordered_set。我们知道哈希的查找效率非常高为O(1),本章我们将延续哈希的思想,共同学习哈希的应用。

目录

(一)位图

1、概念

 2、海量数据处理的思路

3、位图的模拟实现

4、位图的应用

应用一:

应用二:

 应用三:

 (二)布隆过滤器

1、概念

2、布隆过滤器的实现

 3、布隆过滤器的测试

4、为什么布隆过滤器无法实现数据的删除

5、优缺点

6、应用

(三)哈希切割 (思想)


(一)位图

1、概念

概念:

所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用
来判断某个数据存不存在的。

在STL官方库中就有位图:文档入口--->位图文档

主要的接口如下图:

  •  set是置位的意思,就是通过哈希函数找到对应的比特位,然后置为1;
  • reset是复位的意思,就是通过哈希函数找到对应的比特位,然后置为0。

这里我们对于位图有了初步的理解,下面我们通过几道题目来深入理解一下。

这里只是介绍,下面我们在模拟实现中会对位图有更详细的解释。

 2、海量数据处理的思路

我们来看一道面试题:

大家看到这道题,绝大部分人的第一思路就是历遍比较查找,但是这是40亿个数啊...

1、我们先来算一下40亿个数存储起来的大小:

先用10亿Byte为例:

  • 10亿Byte=10亿/1024KB=10亿/1024/1024MB=10亿/1024/1024/1024G≈0.93G

40亿个数,每个数4Byte,那么40亿个数的大小就是16G左右。

2、我们能否通过之前学的容器将这么多的数据存储进去:

第一种方法:

我们用vector存放这些数,然后历遍查找。

首先对于40亿数查找效率O(N)很低,把这些数都读出来比较至少也得要16G连续的内存,不太现实。

第二种方法:

使用查找效率高的容器存放:

  • 很显然这更不可能,原因也很简单
    • 以红黑树为例,每存4个Byte还要有4个Byte的消耗(颜色 + 三叉链)
    • 哈希表也是类似的道理,但是比红黑树消耗小一点

3、我们能否通过排序的方式:

  • 首先一点内排序肯定是不行的,数据量太大了
  • 通过外排序 + 二分查找的方式(也是不可以的)
    • 16G可以开出来,但是连续的16G就开不出来
    • 文件不能随机读取,只能挨个挨个读取,所以是不支持二分查找的
    • 同时外排序的话消耗也很大,这里的数据量也非常大

4、直接定址法——用一个比特位标识映射值在不在

通过上面分析,发现我们之前学的一些方法都不太能行得通。

所以我们要用到了今天学习的位图。

  • 既然这么多数据我们存不了,我们的目的是判断其在不在这40亿个数据中
  • 我们只需要标识一下该数字在不在就可以,这里采用用一个比特位标识
  • 比特位是1就表示其在,比特位是0就表示其不在
    • 那么我们知道无符号整数最大就是4,294,967,295,我们只需要给0 ~ 4,294,967,295个比特位
    • 就能将所有的无符号整数的范围都包含在内
    • 接下来直接哈希映射,直接定址法映射

 

注意:

这里开的是42亿九千万个(整形最大值个)比特位,而不止题目中的40亿个

  • 因为开的是范围,而不是个数

用一个比特位标识的原因

  • 用一个比特位只能有两个值(0和1),正好表示两种状态
  • 如果用一个int来标识的话,那么一个int能标识256种状态

这就是用位图的方式来解决该问题。
 

3、位图的模拟实现

1、位图类的成员:

       我们是通过每一个比特位来表示该位映射的值存不存在的,所以类中的成员我们引入一个vector来存放状态值。但是,vector中存放的是什么类型的数据比较好呢?

      我们知道,比特位是最小的存储单位,我们的数据类型最小是char,1个字节,占有8个比特位,我们还是存储最小的数据类型比较好,这样便于后面的数据映射。

2、位图如何开空间:

      因为我们vector存放的数据类型最小是char,是一个字节(只能8个8个这样开比特位空间),但是我们开的是比特位的整数个(可能不是8的整数个),所以没有直接精确控制到比特位个数的开空间方法,故采用以下方式:

 3.位图如何插入标识位:

这里的插入并不是直接将数据插入到位图中,而是将数据对应的哈希地址所在的比特位标识成1。

 

  • 因为我们用的是char为一个vector的数据类型,所以我们先定位在哪一个char中
  • 例如:18,我们先定位它在哪一个char中,所以我们18 / 8,先定好位
  • 再用18 % 8定位其具体在这个char中的哪一个比特位
  • 这道题定位是下标为2的char中(也就是第三个char类型数值中),第二个比特位
  • 注意:我们想把第二个比特位修改成1,需要用到的是移位和或运算,但是把00000001向左移位2后再和该处char存放的值进行或运算(其实修改位置的数据和我们惯性思维是有出入的,他对于每一个char类型存的比特位对应的值是从右向左)
  • 请看图:

4、位图如何实现删除: 

和上面插入一个道理,只不过用到的位运算不一样:

  • 同样的道理我们先定位到要删除的那个位置通过:/、%操作
  • 只需要将1左移要标记的位置之后,先取反,再与一下

 

5、如何实现查找:

  • 只需要将1左移要标记的位置之后,直接与所在的char所表示的整数值与一下即可

由图也就验证了开了四十二亿个比特位,大概是五百多MB。

具体代码:


template<size_t N>
class bitset
{
public:
	bitset()
	{
		//+1保证足够的比特位,最多浪费8个
		_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;
};

 

4、位图的应用

应用一:

  • 我们知道无符号整数最多也就四十多亿个,100亿个必然有大量重复的。
  • 我们来计算一下100亿个整数是多大,100亿个整数就是400亿个字节,那么大概就是40G的大小。

很显然用之前学的容器内存是放不下40G这么大的数据的,那我们如何用位图来解决?

  • 先来分析一下,该用几个为来标识
  • 分类一下:出现0次,出现1次,出现两次及以上的
  • 那我们此时就要用两个比特位来标识
  • 那我们是对之前的位图进行修改吗,这里我们并没有,而是直接复用两个位图

每个位图还是原来的大小(四十二亿九千万个),因为计算机表示的整数也就4,294,967,295个,其余的也都是重复。

  • 我们知道100亿个整数的范围肯定是在0 ~ 4,294,967,295之间的,所以我们开两个位图,重复的数对应的位置在每个表中是一样的。
  • 此时我们就需要标识:00(一次都没出现的),01(只出现一次的),其他(出现两次及以上的)

template<size_t N>
class twobitset
{
public:
	void set(size_t x)
	{
		//00->01
		if (_bs1.test(x) == false && _bs2.test(x) == false)
		{
			_bs2.set(x);
		}

		else if (_bs1.test(x) == false && _bs2.test(x) == true)
		{
			_bs2.reset(x);
			_bs1.set(x);
		}
	}


	void Print()
	{
		for (size_t i = 0; i < N; ++i)
		{
			if (_bs2.test(i))
			{
				cout << i << endl;
			}
		}
	}
private:
	bitset<N> _bs1;
	bitset<N> _bs2;
};



void test_twobitset()
{
	int a[] = { 3, 45, 53, 32, 32, 43, 3, 2, 5, 2, 32, 55, 5, 53,43,9,8,7,8 };
	twobitset<100> bs;
	for (auto e : a)
	{
		bs.set(e);
	}

	bs.Print();
}

应用二:

因为只给了1个G的内存,我们首先想到的是用到两个位图,正好1个G。

解决办法:

在找交集之前我们一定要先做的是去重,对每个文件都要去重,不然就会找到重复交集。

  • 方法一:分别将两个文件映射到两个位图中,挨个挨个比,利用对应算法找两个去重之后位图的交集
  • 方法二:分别将两个文件映射到两个位图中,直接将两个位图与一下,剩下的就是交集了
     

 应用三:

  • 这题和应用一是一样的,只是多记录和判断了一次,因为不超过2次有:0、1、2。
  • 分类一下:出现0次,出现1次,出现2次,出现两次以上的
  • 同样是复用了两个位图

 (二)布隆过滤器

在之前我们讲到了位图,他能迅速判断一个数是否在海量数据中。位图是直接映射,也不存在哈希冲突,空间消耗几乎没有,并且快,直接是O(1),但是位图只是适合于整形的查找,并不适用于浮点数字符串甚至是一些自定义类型的查找。

1、概念

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

2、布隆过滤器的实现

比如说是10亿个字符串是否在一个文件中。

我们来判断一下能否用红黑树哈希表来存储这10亿个字符串呢?

  • 这肯定是不能的,因为10亿个Byte就是1G
  • 假设每个字符串是10个Byte,那么光这么多字符串就已经占了十几个G了
  • 还有红黑树和哈希表自带的消耗,那几十个G就没了,所以肯定不行

此时一个叫布隆的人,正如概念中所提到的运用了位图的思想,将字符串转化成一个整数,然后映射到位图当中。 

如图,我们通过某哈希函数,把字符串转成数值,然后通过位图标识:

 

但是,把字符串通过哈希函数转成数值这一过程可能会导致一种情况的发生:

不同字符串对应同一个比特位。如图中“美团”和“B站”字符串。

这种情况的发生我们是没有办法避免的,但是我们可以降低这种事件的发生:

由于上述情况的发生,我们可以得出结论:

  • 如果给我们一个字符串通过哈希函数发现标识位是1,说明他可能之前不在
  • 但是如果是0,说明他之前肯定不在! 

  • 有人就专门研究并统计了,误判率影响的因素--->布隆过滤器误判率影响报告

 我们截取一下公式:

通过上述公式,我们哈希函数个数k取3得到:

4.35 * n = m

也就是说在3个哈希函数的时候,没插入一个元素,就需要5个比特位来标识。

布隆过滤器是复用位图的:


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;
	}
};

// N最多会插入key数据的个数
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 * _X;
		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 * _X;

		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 _X = 6;
	bitset<N* _X> _bs;
};

 3、布隆过滤器的测试

测试1:

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(time(0));
	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 = "zhihu.com";
		//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;
}

我们分别对相似字符串和不相似字符串的误判率进行测试:

综上,测试可见:布隆过滤器开的越大,误判率就越低。

4、为什么布隆过滤器无法实现数据的删除

因为布隆过滤器采用的是多组映射的方式,所以要是直接删除的话可能会影响其他的值存不存在的标识,所以布隆过滤器的删除是不能直接删除的。

但是通过其他一些方法改造,可以实现:

  • 通过计数删除,映射一个值就在位图的标记位上计数
  • 用到该位置就++,显然标识位不能再用比特位了
  • 1bit标识一个位置
  • 8bit标识一个位置 0 ~ 255
  • 16bit标识一个位置 0 ~ 65535
  • 还有溢出的风险
  • 一个数删除之后,判断还在,说明是误判了。

此种方法的缺陷:

  • 1.无法确认元素是否真正在布隆过滤器中
  • 2.存在计数回绕
     

5、优缺点

布隆过滤器优点:

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

布隆过滤器缺点: 

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

6、应用

1.注册的时候,快速判断一个昵称是否使用过:

  • 将系统所有的昵称都映射到布隆过滤器中
  • 不在:说明没人有用过
  • 在:再去数据库查确认一遍(因为在的话存在误判)

2.黑名单:

  • 不在:通行
  • 在:再次去系统确认(绝不放过一个坏人,但有可能误抓好人)

3.过滤层,提高查找数据效率: 

(三)哈希切割 (思想)

哈希切割:

  • 给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?
  • 与上题条件相同,如何找到top K的IP?

问题:

  • 位图和布隆是Key的模型,无法解决Key_ Value的模型
  • 只能哈希表和红黑树,但是内存又不够

解决方案一:

  • 如何统计次数,很显然100G的log中的IP肯定是不能放在红黑树或者哈希表中的。
  • 这里我们采用将文件分割,假设将每个文件分成100份,每个文件就是1G就可以放在红黑树或者哈希表中了。
  • 不过这里是存在问题的
  • 会存在大量相同的IP会被分到不同的文件当中
  • 如果要想统计个数,那么最后要合并还是会存在放不下的问题

解决方案二:

  • 综合方案一的问题,我们就要将相同的IP放在同一个文件当中

然后再用红黑树和哈希表来统计:

注意:

  • 不同的ip也有可能进入同一个文件,但是相同的ip一定是进入同一个文件。

存在问题:

某个文件太大了,哈希表和红黑树中放不下

  • a. 某个相同的ip太多 - 这时候存储大概率是够的,因为相同的ip存在map中只是统计次数,不额外占用空间。
  • b. 映射冲突到这个编号文件的ip太多 - 但是冲突的太多的话,还是会大量文件存在小文件中,依旧会存在一个小文件太大的情况。

解决办法:是针对小文件再分割,再用其他哈希函数,进行哈希分割,再切分成小文件。

  • 大量相同时,都集中在一个小文件,再去切分是不起作用的,切分完之后还是在同一个文件里
  • a不会抛异常(大量相同的ip) ,b会抛异常(大量冲突不同的ip)

布隆过滤器找交集:

给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法。

假设每个query平均30byte,100亿query就是300G。

近似算法:

  • 将一个文件映射到布隆过滤器中,用另一个文件去找,利用布隆过滤器的快速性,不过可能存在误判的问题。
  • 所以这叫不精确的,近似算法。

精确算法:

  • 方法: 相同的小文件找交集,对应编号找交集

  • 通过哈希分割,A、B文件中相同的数据肯定被分到了相同的小文件
  • 对小文件找交集,那么就很容易了,用set就可以很快找到

感谢您的阅读!

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

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

相关文章

sh脚本工具集锦(文件批量操作、音视频相关)持续更新

1 文件夹目录下所有图片转换成视频文件 pic_2_videos.sh&#xff1a; #!/bin/bash # 放到图片文件夹目录下&#xff0c;把所有jpeg图片推成视频文件 # sh pic_2_videos.sh 0 # 0: pad to 1920*1080 ; 1 or other no pad pad_1920$1if [[ $pad_1920 0 ]] thenfilesls|grep jp…

【Flink】 FlinkCDC读取Mysql( DataStream 方式)(带完整源码,直接可使用)

简介: FlinkCDC读取Mysql数据源,程序中使用了自定义反序列化器,完整的Flink结构,开箱即用。 本工程提供 1、项目源码及详细注释,简单修改即可用在实际生产代码 2、成功编译截图 3、自己编译过程中可能出现的问题 4、mysql建表语句及测试数据 5、修复FlinkCDC读取Mys…

【C++】匿名对象 ① ( 匿名对象引入 | 匿名对象简介 | 匿名对象概念 | 匿名对象作用域 - 对象创建与销毁 )

文章目录 一、匿名对象引入二、匿名对象简介1、匿名对象概念2、匿名对象作用域 - 对象创建与销毁3、代码示例 - 创建并使用匿名对象 一、匿名对象引入 匿名对象引入 : 在上一篇博客 【C】拷贝构造函数调用时机 ② ( 对象值作为函数参数 | 对象值作为函数返回值 ) 中 , 讲到了 如…

【Java基础】- RMI原理和使用详解

【Java基础】- RMI原理和使用详解 文章目录 【Java基础】- RMI原理和使用详解一、什么RMI二、RMI原理2.1 工作原理图2.2 工作原理 三、RMI远程调用步骤3.1 RMI远程调用运行流程图3.2 RMI 远程调用步骤 四、JAVA RMI简单实现4.1 如何实现一个RMI程序4.2 JAVA实现RMI程序 一、什么…

小程序中如何查看指定会员的所有订单?

在小程序中&#xff0c;查看指定会员的所有订单可以通过如下方式实现。 1. 找到指定的会员卡。在管理员后台->会员管理处&#xff0c;找到需要查看订单记录的会员卡。也支持对会员卡按卡号、手机号和等级进行搜索。 2. 查看会员卡详情。点击查看详情进入该会员卡的详情页面…

GTS 中testPersistentProcessMemory fail 详解

0. 前言 GTS 在测试 case armeabi-v7a GtsMemoryTestCases 的时候出现下面异常&#xff0c;本文总结一下。 com.google.android.memory.gts.MemoryTest#testPersistentProcessMemory 1. error log 09-14 09:41:40.523 10182 13340 13359 E TestRunner: failed: testPersiste…

网攻西北工业大学的美国安局人员真实身份锁定!

14日&#xff0c;《环球时报》从国家计算机病毒应急处理中心和360获悉&#xff0c;在侦办西北工业大学网络攻击案过程中&#xff0c;我方成功提取了名为“二次约会”&#xff08;Second Date&#xff09;“间谍”软件的多个样本。在多国业内伙伴通力合作下&#xff0c;现已成功…

【ELK】日志分析系统概述及部署

目录 一、ELK概述 1、ELK是什么&#xff1f; 2、ELK的组成部分 2.1 ElasticSearch &#xff08;1&#xff09;分片和副本 &#xff08;2&#xff09;es和传统数据库的区别 2.2 Kiabana 2.3 Logstash &#xff08;1&#xff09;Log Stash主要组件 2.4 可添加的其它组件 …

Opencv之区域生长和分裂

区域生长 1.基本原理 区域生长法是较为基础的一种区域分割方法 它的基本思想我说的通俗些&#xff0c;即是一开始有一个生长点&#xff08;可以一个像素也可以是一个小区域&#xff09;&#xff0c;从这个生长点开始往外扩充&#xff0c;扩充的意思就是它会把跟自己有相似特征…

Python工程师Java之路(p)Maven聚合和继承

文章目录 依赖管理依赖传递可选依赖和排除依赖 继承与聚合 依赖管理 指当前项目运行所需的jar&#xff0c;一个项目可以设置多个依赖 <!-- 设置当前项目所依赖的所有jar --> <dependencies><!-- 设置具体的依赖 --><dependency><!-- 依赖所属群组…

gmssl v2 用 dgst 命令通过 sm2 签名出的结果,在别的工具上无法验签的问题分析

结论 通过分析发现&#xff0c;导致问题的原因是&#xff1a;gmssl v2 调用的算法不是 sm2 算法。 分析详情 具体情况如下所述 在 gmssl 调用 pkey_ec_init 函数时&#xff0c;默认会把 ec_scheme 设置为 NID_secg_scheme 签名的过程中会调用 pkey_ec_sign 函数&#xff0c…

【Redis】Redis的特性和应用场景 · 数据类型 · 持久化 · 数据淘汰 · 事务 · 多机部署

【Redis】Redis常见面试题&#xff08;3&#xff09; 文章目录 【Redis】Redis常见面试题&#xff08;3&#xff09;1. 特性&应用场景1.1 Redis能实现什么功能1.2 Redis支持分布式的原理1.3 为什么Redis这么快1.4 Redis实现分布式锁1.5 Redis作为缓存 2. 数据类型2.1 Redis…

Day_14 > 指针进阶(3)> bubble函数

目录 1.回顾回调函数 2.写一个bubble_sort函数 2.1认识一下qsort函数 ​编辑2.2写bubble_sort函数 今天我们继续深入学习指针 1.回顾回调函数 我们回顾一下之前学过的回调函数 回调函数就是一个通过函数指针调用的函数 如果你把函数的指针&#xff08;地址&#xff09;…

​Qt for Python 入门¶​

本页重点介绍如何从源代码构建Qt for Python&#xff0c;如果你只想安装PySide2。 与你需要运行&#xff1a;pip pip install pyside2有关更多详细信息&#xff0c;请参阅我们的快速入门指南。此外&#xff0c;您可以 查看与项目相关的常见问题解答。 一般要求 Python&#xf…

博客系统(升级(Spring))(四)(完)基本功能(阅读,修改,添加,删除文章)(附带项目)

博客系统 (三&#xff09; 博客系统博客主页前端后端个人博客前端后端显示个人文章删除文章 修改文章前端后端提取文章修改文章 显示正文内容前端后端文章阅读量功能 添加文章前端后端 如何使用Redis项目地点&#xff1a; 博客系统 博客系统是干什么的&#xff1f; CSDN就是一…

Activity生命周期递归问题查看

这类问题一般比较难分析&#xff0c;符合以下情况的才有可能分析出来&#xff1a; 能够复现并调试有问题时的堆栈以及对应的event log TaskFragment#shouldSleepActivities 方法导致递归 There is a recursion among check for sleep and complete pause during sleeping 关…

dlib库详解及Python环境安装指南

dlib是一个开源的机器学习库&#xff0c;它包含了众多的机器学习算法&#xff0c;例如分类、回归、聚类等。此外&#xff0c;dlib还包含了众多的数据处理、模型训练等工具&#xff0c;使得其在机器学习领域被广泛应用。本文将详细介绍dlib库的基本概念、功能&#xff0c;以及如…

删除数据库

MySQL从小白到总裁完整教程目录:https://blog.csdn.net/weixin_67859959/article/details/129334507?spm1001.2014.3001.5502 语法格式: drop database 数据库名称;这个命令谨慎使用,俗话说:删库跑路! 案列:删除testing数据库,并验证 mysql> show databases; -----------…

Kernel for SQL Database Recovery 21.1 Crack

SQL Server恢复工具 Kernel for SQL Database Recovery 21.1 具有针对不同 SQL Server 版本的全面恢复选项。它具有预览和选择功能来恢复精确的数据库对象。 好处 SQL 数据库恢复可为您带来多种好处。 完全恢复所有数据库组件 将损坏的 MDF/NDF 文件有效恢复到 Live SQL Serve…

HDMI 直通 ILA 调试实验

FPGA教程学习 第十四章 HDMI 直通 ILA 调试实验 文章目录 FPGA教程学习前言实验原理程序设计实验过程实验尝试总结TODO 前言 HDMI 输入直通到 HDMI 输出的显示&#xff0c;完成一个简单的 HDMI 输入输出检测。 实验原理 开发板 HDMI 输出接口芯片使用 ADV7511&#xff0c;HD…