【C++】位图|布隆过滤器|海量数据处理面试题

news2024/11/15 19:33:15

文章目录

  • 一.位图
    • 1. 位图的概念
    • 2. 位图的使用
    • 3. 位图的实现
  • 二.布隆过滤器
    • 1. 布隆过滤器
    • 2. 布隆过滤器的实现
  • 三.海量数据处理面试题
    • 1.位图
    • 2.布隆过滤器
    • 3.哈希切割

一.位图

1. 位图的概念

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

2. 位图的使用

首先我们来看一道题目:

给定40亿个不重复的无符号整数,没有进行排序。现在给一个无符号整形,如何快速判断一个数是否存在这40亿个数中。

现在有三种方法:

  1. 遍历,时间复杂度O(N)

  2. 排序后使用二分查找,时间复杂度为:排序(O(N logN)) + 二分查找(O(logN))

  3. 位图

如果我们使用位图解决该的问题,我们只需要开辟一个40亿个 bit 的空间(如果直接存放40亿的整数约占16G,开辟40亿bit约占512MB).

使用直接定址法进行映射,如果该位置是0,则表示该数据不存在,如果是1表示该数据存在。

如下图:

在这里插入图片描述

3. 位图的实现

接下来是位图的接口展示:

template<size_t N>
class bit_set
{
public:
	//默认构造
	bit_set()
	{}
 
	//将映射的地方改为1
	void set(size_t x)
	{}
 
	//删除数据
	void reset(size_t x)  
	{}
 
	//判断x在不在
	bool test(size_t x)
	{}
private:
	vector<char> _bits;
};

我们可以设置一个非模板参数来控制开辟空间的大小,在构造函数中进行空间的开辟。

bit_set()
{
	_bits.resize(N / 8 + 1, 0);
}

接下来就是 set 的编写了,目的就是将映射的地址改为1即可,我们使用/8求出该值在第几个char上,再进行模8求出在第几位上,再进行进行位移+或的方式进行即可:

//将映射的地方改为1
void set(size_t x)
{
	//1.除8再模8
	size_t i = x / 8;     //求在第几个char处
	size_t j = x % 8;     //求在第几位上
	_bits[i] |= (1 << j); 
}

reset表示删除该数,我们直接将该bit位上的数据置为0即可,我们找到该位将1左移到该位置上,然后使用取反操作,这样除了第j位的都是1,再进行与操作,即可完成数据的删除。

在这里插入图片描述

void reset(size_t x)  //删除这个数据
{
	size_t i = x / 8;
	size_t j = x % 8;
	_bits[i] &= ~(1 << j);    //左移取反再 与
}

test接口就是将传入的数据的映射位直接返回即可。

bool test(size_t x)//判断x在不在
{
	size_t i = x / 8;
	size_t j = x % 8;
	return _bits[i] & (1 << j);
}

二.布隆过滤器

1. 布隆过滤器

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

在这里插入图片描述

如上图,x、y、z都映射了3处,但是发现 x 和 z 以及 y 和 z 有相同的映射处,这就说明布隆过滤器是存在不准确的情况。

再观察W,w不是过滤器中的值,进行检测映射后发现一个位置为0,则能表示w不在过滤器中。这便能得出结论。

误判情况:

  • 存在:不准确,有可能是其它数据也映射到了此处。

  • 不存在:准确,表示该值并没有把其应该映射的位置进行修改。

布隆过滤器的存在的误判是被允许的,因为在很多场景需要快速地进行判断。

  • 比如游戏中的起网名,服务器不可能将你的游戏 ID 拿到数据库中进行查询,而是直接将你的游戏 ID 在过滤器中进行查询,如果过滤器查询结果是 ID 已存在,系统则提示你 ID 被占用。即使这个ID在数据库中并不存在,但是这样的操作节省了服务器的运行压力。
  • 再比如网络失信名单,将身份证号在失信名单过滤器中进行查询,如果查询结果显示为失信人员,则再由服务器将身份证在数据库中进行二次查询;而如果显示非失信人员时,直接返回结果即可

所以,布隆过滤器是非常适合字符串的快速查询,即使存在缺陷,但是我们可以采取多次映射的方式,即使用不同的字符串哈希算法,来降低误判的几率。

理论而言:一个值映射的位越多或表的长度越长,误判概率越低。但是也不能映射太多,不然会导致布隆过滤器优势丧失。

这有一篇相关的证明博客:详解布隆过滤器的原理,使用场景和注意事项

根据上面博客的中的内容,使用越多的字符串哈希函数其冲突率会逐渐降低。

在这里插入图片描述

接下来我们分析我们应该如何设计m和k,即过滤器长度哈希函数的个数

在这里插入图片描述

所以,接下来的布隆过滤器的实现,比如我们要标记N个数,则应开辟4.2*N以上的空间(方便计算取5)

在这里插入图片描述

2. 布隆过滤器的实现

布隆过滤器的底层使用的位图来进行记录数据,这次模拟实现使用3套哈希函数,所以要设置5个模板参数(1.数据个数;2.数据类型;3.哈希函数1;4哈希函数2;5.哈希函数3)

1.哈希函数

注意:这次是使用字符串类型进行测试,所以哈希函数都是字符串的哈希函数;如果想让过滤器支持自定义类型直接编写对应的哈希函数即可。

各种字符串哈希函数:各种字符串Hash函数

这里直接使用几种常见的字符串哈希函数进行用于传参即可,如下:

struct HashString1
{
	size_t operator()(const string& key)
	{
		size_t val = 0;
		for (auto ch : key)
		{
			val = val * 131 + ch;
		}
		return val;
	}
};
 
struct HashString2
{
	size_t operator()(const string& key)
	{
		size_t hash = 5381;
		for (auto ch : key)
		{
			hash += (hash << 5) + ch;
		}
		return hash;
	}
};
struct HashString3
{
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (size_t i = 0; i < key.size(); i++)
		{
			if ((i & 1) == 0)
			{
				hash ^= ((hash << 7) ^ key[i] ^ (hash >> 3));
			}
			else
			{
				hash ^= (~((hash << 11) ^ key[i] ^ (hash >> 5)));
			}
		}
		return hash;
	}
};

2.标记数据

过滤器的标记则是使用传入的哈希函数算出映射位置,然后调用位图得 set 进行标记即可。

void Set(const K& key)
{
	//将哈希函数映射处进行标记
	size_t hash1 = Hash1()(key) % (_ratio * N);
	size_t hash2 = Hash2()(key) % (_ratio * N);
	size_t hash3 = Hash3()(key) % (_ratio * N);
 
	_bits.set(hash3);
	_bits.set(hash1);
	_bits.set(hash2);
}

3.查询数据

查询数据其实就是找对应的映射位置,如果3个映射位置有一个为0,则表示数据不存在,并且该结果准确,如果三个都为1,则表示该数据可能存在,这是布隆过滤器不可避免的问题。

实现方式是根据哈希函数求出对应的3个映射位置,然后使用位图的 test,如果有一处为0则返回false,反之返回true

bool Test()
{
	//检测对应的3处标记为位
	size_t hash1 = Hash1()(key) % (_ratio * N);
	size_t hash2 = Hash2()(key) % (_ratio * N);
	size_t hash3 = Hash3()(key) % (_ratio * N);
	//3处都不为零返回真,1处为假则返回假
	if (_bits.test(hash1) && _bits.test(hash2) && _bits.test(hash3))
		return true;
	return false;
}

5.误判率的检测

接下来是一段测试误判率的代码

void TestBloomFilter2()
{
	srand(time(0));
	const size_t N = 100000;
	BloomFilter<100000, string, HashString1, HashString2, HashString3> bf;
	cout << sizeof(bf) << endl;
 
	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(1234 + i));
	}
 
	for (auto& str : v1)
	{
		bf.Set(str);
	}
 
	// 相似
	std::vector<std::string> v2;
	for (size_t i = 0; i < N; ++i)
	{
		std::string url = "http://www.cnblogs.com/-clq/archive/2021/05/31/2528153.html";
		url += std::to_string(99999999 + 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";
		url += std::to_string(rand() + i);
		v3.push_back(url);
	}
 
	size_t n3 = 0;
	for (auto& str : v3)
	{
		if (bf.Test(str))
		{
			++n3;
		}
	}
	cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl;
}

三.海量数据处理面试题

海量数据处理是指基于海量数据的存储和处理,正因为数据量太大,所以导致要么无法在短时间内迅速处理,要么无法一次性装入内存。

  • 对于时间问题,就可以采用位图、布隆过滤器等数据结构来解决。
  • 对于空间问题,就可以采用哈希切割等方法,将大规模的数据转换成小规模的数据逐个击破。

1.位图

题目一:给定100亿个整数,设计算法找到只出现一次的整数。

我们标记整数时可以将其分为三种状态:

  1. 出现0次。
  2. 出现1次。
  3. 出现2次及以上。

一个位只能表示两种状态,而要表示三种状态我们至少需要用两个位,因此我们可以开辟两个位图,这两个位图的对应位置分别表示该位置整数的第一个位和第二个位。

我们可以将这三种状态分别定义为00、01、10,此时当我们读取到重复的整数时,就可以让其对应的两个位按照00→01→10的顺序进行变化,最后状态是01的整数就是只出现一次的整数。

为了方便演示,下面我们直接从vector中读取若干整数进行模拟处理:

#include <iostream>
#include <vector>
#include <assert.h>
#include <bitset>
using namespace std;

int main()
{
	//此处应该从文件中读取100亿个整数
	vector<int> v{ 12, 33, 4, 2, 7, 3, 32, 3, 3, 12, 21 };
	//在堆上申请空间
	bitset<4294967295>* bs1 = new bitset<4294967295>;
	bitset<4294967295>* bs2 = new bitset<4294967295>;
	for (auto e : v)
	{
		if (!bs1->test(e) && !bs2->test(e)) //00->01
		{
			bs2->set(e);
		}
		else if (!bs1->test(e) && bs2->test(e)) //01->10
		{
			bs1->set(e);
			bs2->reset(e);
		}
		else if (bs1->test(e) && !bs2->test(e)) //10->10
		{
			//不做处理
		}
		else //11(理论上不会出现该情况)
		{
			assert(false);
		}
	}
	for (size_t i = 0; i < 4294967295; i++)
	{
		if (!bs1->test(i) && bs2->test(i)) //01
			cout << i << endl;
	}
	return 0;
}

需要注意以下几点:

  1. 存储100亿个整数大概需要40G的内存空间,因此题目中的100亿个整数肯定是存储在磁盘当中的,代码中直接从vector中读取数据是为了方便演示。
  2. 为了能映射所有整数,位图的大小必须开辟为232位,也就是代码中的4294967295,因此开辟一个位图大概需要512M的内存空间,两个位图就要占用1G的内存空间,所以代码中选择在堆区开辟空间,若是在栈区开辟则会导致栈溢出。

题目二:给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件的交集?

方案一:(一个位图需要512M内存)

  1. 依次读取第一个文件中的所有整数,将其映射到一个位图。
  2. 再读取另一个文件中的所有整数,判断在不在位图中,在就是交集,不在就不是交集。

方案二:(两个位图刚好需要1G内存,满足要求)

  1. 依次读取第一个文件中的所有整数,将其映射到位图1。
  2. 依次读取另一个文件中的所有整数,将其映射到位图2。
  3. 将位图1和位图2进行与操作,结果存储在位图1中,此时位图1当中映射的整数就是两个文件的交集。

说明一下: 对于32位的整型,无论待处理的整数个数是多少,开辟的位图都必须有 2 32 个比特位,也就是512M,因为我们要保证每一个整数都能够映射到位图当中,因此这里位图的空间消耗是固定的。

题目三:一个文件有100亿个整数,1G内存,设计算法找到出现次数不超过2次的所有整数。

该题目和题目一的方法是一样的,在该题目中我们标记整数时可以将其分为四种状态:

  1. 出现0次。
  2. 出现1次。
  3. 出现2次。
  4. 出现2次以上。

一个整数要表示四种状态也是只需要两个位就够了,此时当我们读取到重复的整数时,就可以让其对应的两个位按照00→01→10→11的顺序进行变化,最后状态是01或10的整数就是出现次数不超过2次的整数。

#include <iostream>
#include <vector>
#include <bitset>
using namespace std;

int main()
{
	vector<int> v{ 12, 33, 4, 2, 7, 3, 32, 3, 3, 12, 21 };
	//在堆上申请空间
	bitset<4294967295>* bs1 = new bitset<4294967295>;
	bitset<4294967295>* bs2 = new bitset<4294967295>;
	for (auto e : v)
	{
		if (!bs1->test(e) && !bs2->test(e)) //00->01
		{
			bs2->set(e);
		}
		else if (!bs1->test(e) && bs2->test(e)) //01->10
		{
			bs1->set(e);
			bs2->reset(e);
		}
		else if (bs1->test(e) && !bs2->test(e)) //10->11
		{
			bs2->set(e);
		}
		else //11->11
		{
			//不做处理
		}
	}
	for (size_t i = 0; i < 4294967295; i++)
	{
		if ((!bs1->test(i) && bs2->test(i)) || (bs1->test(i) && !bs2->test(i))) //01或10
			cout << i << endl;
	}
	return 0;
}

2.布隆过滤器

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

题目要求给出近视算法,也就是允许存在一些误判,那么我们就可以用布隆过滤器。

  1. 先读取其中一个文件当中的query,将其全部映射到一个布隆过滤器当中。
  2. 然后读取另一个文件当中的query,依次判断每个query是否在布隆过滤器当中,如果在则是交集,不在则不是交集

题目五: 如何扩展BloomFilte使得它支持删除元素的操作?

布隆过滤器一般不支持删除操作,原因如下:

  • 因为布隆过滤器判断一个元素存在时可能存在误判,因此无法保证要删除的元素确实在布隆过滤器当中,此时将位图中对应的比特位清0会影响其他元素。
  • 此外,就算要删除的元素确实在布隆过滤器当中,也可能该元素映射的多个比特位当中有些比特位是与其他元素共用的,此时将这些比特位清0也会影响其他元素。

如果要让布隆过滤器支持删除,就必须要做到以下两点:

  1. 保证要删除的元素在布隆过滤器当中,比如在删除一个用户的信息前,先遍历数据库确认该用户确实存在。
  2. 保证删除后不会影响到其他元素,比如可以为位图中的每一个比特位设置一个对应的计数值,当插入元素映射到该比特位时将该比特位的计数值++,当删除元素时将该元素对应比特位的计数值–即可。

3.哈希切割

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

还是刚才那道题目,但现在要求给出精确算法,那么就不能使用布隆过滤器了,此时需要用到哈希切分。

  1. 首先需要估算一下这里一个文件的大小,便于确定将一个文件切分为多少个小文件。
  2. 假设平均每个query为20字节,那么100亿个query就是200G,由于我们只有1G内存,这里可以考虑将一个文件切分成400个小文件。
  3. 这里我们将这两个文件分别叫做A文件和B文件,此时我们将A文件切分成了A0 ~ A399共400个小文件,将B文件切分成了B0 ~ B399共400个小文件。

在切分时需要选择一个哈希函数进行哈希切分,以切分A文件为例,切分时依次遍历A文件当中的每个query,通过哈希函数将每个query转换成一个整型 i (0 ≤ i ≤ 399),然后将这个query写入到小文件Ai当中。对于B文件也是同样的道理,但切分A文件和B文件时必须采用的是同一个哈希函数

在这里插入图片描述

由于切分A文件和B文件时采用的是同一个哈希函数,因此A文件与B文件中相同的query计算出的 i 值都是相同的,最终就会分别进入到Ai和Bi文件中,这也是哈希切分的意义。

因此我们就只需要分别找出A0与B0的交集、A1与B1的交集、…、A399与B399的交集,最终将这些交集和起来就是A文件和B文件的交集。

在这里插入图片描述

那各个小文件之间又应该如何找交集呢?

  • 经过切分后理论上每个小文件的平均大小是512M,因此我们可以将其中一个小文件加载到内存,并放到一个set容器中,再遍历另一个小文件当中的query,依次判断每个query是否在set容器中,如果在则是交集,不在则不是交集。
  • 当哈希切分并不是平均切分,有可能切出来的小文件中有一些小文件的大小仍然大于1G,此时如果与之对应的另一个小文件可以加载到内存,则可以选择将另一个小文件中的query加载到内存,因为我们只需要将两个小文件中的一个加载到内存中就行了。
  • 但如果两个小文件的大小都大于1G,那我们可以考虑将这两个小文件再进行一次切分,将其切成更小的文件,方法与之前切分A文件和B文件的方法类似。

本质这里在进行哈希切分时,就是将这些小文件看作一个个的哈希桶,将大文件中的query通过哈希函数映射到这些哈希桶中,如果是相同的query,则会产生哈希冲突进入到同一个小文件中。

题目七: 给一个超过100G大小的log file,log中存着IP地址,设计算法找到出现次数最多的IP地址?如何找到topK的IP?

该题目同样需要用到哈希切分,切分步骤如下:

  • 我们将这个log file叫做A文件,由于A文件的大小超过100G,这里可以考虑将A文件切分成200个小文件。
  • 在切分时选择一个哈希函数进行哈希切分,通过哈希函数将A文件中的每个IP地址转换成一个整型 i(0 ≤ i ≤ 199),然后将这个IP地址写入到小文件Ai当中。
  • 由于哈希切分时使用的是同一个哈希函数,因此相同的IP地址计算出的 i 值是相同的,最终这些相同的IP地址就会进入到同一个Ai小文件当中。

在这里插入图片描述

经过哈希切分后得到的这些小文件,理论上就能够加载到内存当中了,如果个别小文件仍然太大那可以对其再进行一次哈希切分,总之让最后切分出来的小文件能够加载到内存。

  • 现在要找到出现次数最多的IP地址,就可以分别将各个小文件加载到内存中, 然后用一个map<string, int>容器统计出每个小文件中各个IP地址出现的次数,然后比对各个小文件中出现次数最多的IP地址,最终就能够得到log file中出现次数最多的IP地址。
  • 如果要找到出现次数top K的IP地址,可以先将一个小文件加载到内存中,选出小文件中出现次数最多的K个IP地址建成一个小堆,然后再依次比对其他小文件中各个IP地址出现的次数,如果某个IP地址出现的次数大于堆顶IP地址出现的次数,则将该IP地址与堆顶的IP地址进行交换,然后再进行一次向下调整,使其仍为小堆,最终比对完所有小文件中的IP地址后,这个小堆当中的K个IP地址就是出现次数top K的IP地址。

本文到此结束, 码文不易, 还请多多支持哦! ! !

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

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

相关文章

沁恒ch32V208处理器开发(一)开发环境

目录 简介&#xff1a;开发环境开发界面自定义风格 烧录工具支持范围烧录界面 简介&#xff1a; CH32V2x 系列是南京沁恒基于 32 位 RISC-V 指令集及架构设计的工业级通用微控制器。采用青稞 V4 内核&#xff0c;支持硬件中断堆栈&#xff0c;提升中断响应效率&#xff1b;CH3…

PyCharm新手入门指南

安装好Pycharm后&#xff0c;就可以开始编写第一个函数&#xff1a;Hello World啦~我们就先来学习一些基本的操作&#xff0c;主要包含新建Python文件&#xff0c;运行代码&#xff0c;查看结果等等。 文章主要包含五个部分&#xff1a; 一、界面介绍 主要分为菜单栏、项目目录…

3.2用互斥元保护共享数据

概述 于是&#xff0c;你有一个类似于上一节中链表那样的共享数据结构&#xff0c;你想要保护它免于竞争条件以及可能因此产生的不变量损坏。如果你可以将所有访问该数据结构的代码块标记为互斥的&#xff08;mutually exclusive)&#xff0c;岂不是很好&#xff1f;如果任何线…

flutter 没有open android module in Android studio 插件代码爆红

参考 1.结论 其实就是缺少这个文件 2.解决方案有两个 2.1 方案一 手动创建一个,命名规则是项目名字‘_android’‘.iml’ 内容如下: <?xml version"1.0" encoding"UTF-8"?> <module type"JAVA_MODULE" version"4">&l…

Python小白入门:类和面向对象思想的超详细知识点总结

目录 一、创建和使用类1.1 创建类1.2 根据类创建实例1.2.1访问属性1.2.2 调用方法1.2.3 创建多个实例 练习题 二、使用类和实例2.1 创建一个类2.2 给属性指定默认值2.3 修改属性值2.3.1 直接通过实例进行修改2.3.2通过方法进行设置2.2.3 通过方法对属性值进行递增 练习题 三、继…

【第一阶段】kotlin中反引号中的函数名特点

在kotlin中可以直接中文定义函数&#xff0c;使用反引号进行调用 eg: fun main() {2023年8月9日定义的函数(5) }private fun 2023年8月9日定义的函数(num:Int){println("反引号的用法$num") }执行结果 在Java中is,in可以定义方法&#xff0c;但是在kotlin中is,in是…

基于亚奈奎斯特采样和SOMP算法的平板脉冲响应空间插值matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ...................................................................... %fine regular gr…

RobotFramework的安装过程及应用举例

一、安装python3.8.0 二、安装wxPython C:\>pip install -U wxPython Collecting wxPythonObtaining dependency information for wxPython from https://files.pythonhosted.org/packages/00/78/b11f255451f7a46fce2c96a0abe6aa8b31493c739ade197730511d9ba81a/wxPython-…

flutter开发实战-实现marquee根据文本长度显示文本跑马灯效果

flutter开发实战-实现marquee文本跑马灯效果 最近开发过程中需要marquee文本跑马灯效果&#xff0c;这里使用到了flutter的插件marquee 效果图如下 一、marquee 1.1 引入marquee 在pubspec.yaml中引入marquee # 跑马灯效果marquee: ^2.2.31.2 marquee使用 marquee使用也是…

k8s 自身原理 1

咱们从 pod 一直分享到最近的 Statefulset 资源&#xff0c;到现在好像我们只是知道如何使用 k8s&#xff0c;如何按照 k8s 设计好的规则去应用&#xff0c;去玩 k8s 仔细想想&#xff0c;对于 k8s 自身的内在原理&#xff0c;我们好像还不是很清楚&#xff0c;对于每一个资源…

【linux】-进程概念之环境变量的详解(命令行参数的介绍)

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …

介绍另外一个容器技术, Apptainer

一说到容器&#xff0c;我们往往会脱口而出&#xff0c; Docker&#xff0c; 实际上Docker 仅仅是Linux 容器化的一种&#xff0c; 今天介绍的Apptainer 就是另外一种容器技术。 那么Apptainer 具体是一个什么东西呢&#xff1f; 跟Docker 有什么区别呢&#xff1f; 首先&#…

剑指 Offer !!56 - II. 数组中数字出现的次数 II

剑指 Offer 56 - II. 数组中数字出现的次数 II 在一个数组 nums 中除一个数字只出现一次之外&#xff0c;其他数字都出现了三次。请找出那个只出现一次的数字。示例 1&#xff1a;输入&#xff1a;nums [3,4,3,3] 输出&#xff1a;4 示例 2&#xff1a;输入&#xff1a;nums …

智慧停车场:城市出行新潮流,轻松驶入未来

在如今城市日益拥堵的交通环境下&#xff0c;停车难题成为人们日常生活中的一大困扰。然而&#xff0c;随着科技的不断进步&#xff0c;智慧停车场的建设正为我们带来更为便捷、高效的出行体验&#xff0c;成为现代城市发展的重要一环。 智慧停车场利用先进的技术手段&#xff…

Nginx安装以及LVS-DR集群搭建

Nginx安装 1.环境准备 yum insatall -y make gcc gcc-c pcre-devel #pcre-devel -- pcre库 #安装openssl-devel yum install -y openssl-devel 2.tar安装包 3.解压软件包并创建软连接 tar -xf nginx-1.22.0.tar.gz -C /usr/local/ ln -s /usr/local/nginx-1.22.0/ /usr/local…

windows 安装免费3用户ccproxy ubuntu 代理上网

Windows 上进行安装 ubuntu 上进行设置 方法一 (临时的手段) 如果仅仅是暂时需要通过http代理使用apt-get&#xff0c;您可以使用这种方式。 在使用apt-get之前&#xff0c;在终端中输入以下命令&#xff08;根据您的实际情况替换yourproxyaddress和proxyport&#xff09;。 终…

水果音乐制作软件fl studio汉化中文修改版下载,FL Studio哪个版本更合适新手

水果音乐制作软件fl studio汉化中文修改版下载由兔八哥爱分享小编精心为大家整理了fl studio软件所有版本合集。水果音乐制作软件fl studio汉化中文修改版下载其中包含了fl studio、以及fl studio汉化补丁、fl studio修改程序、fl studio注册机等等&#xff0c;fl studio是一款…

【某SH公E司IN 数据挖掘工程师-提前批笔试2023-8-8】

题目&#xff08;回忆版&#xff09;&#xff1a; 给一棵树&#xff0c;每个节点要么是白色要么是红色&#xff0c;并且有节点编号&#xff0c;要求查询每个节点所在子树中红色节点的数量。 先是一个正整数 n&#xff0c;表示树有 n 个节点。 第二行给出 n-1 个数字&#xff0…

标准的OSI七层模型(其实了解tcp足矣)

七层模型&#xff0c;亦称OSI&#xff08;Open System Interconnection&#xff09;。参考模型是国际标准化组织&#xff08;ISO&#xff09;制定的一个用于计算机或通信系统间互联的标准体系&#xff0c;一般称为OSI参考模型或七层模型。 它是一个七层的、抽象的模型体&#x…