C++【位图/布隆过滤器—海量数据处理】

news2024/11/15 18:31:41

文章目录

  • 一、位图
    • (1)位图概念介绍
    • (2)简单模拟实现
    • (3)位图应用
  • 二、布隆过滤器
    • (1)关于布隆过滤器概念及介绍
    • (2)布隆过滤器的使用场景
    • (3)模拟实现
    • (4)布隆过滤器天生不支持删除reset
    • (5)BF总结
  • 三、海量数据处理
    • (1)问题1/2
    • (2)问题3/4
    • (3)问题3
  • 四、所有源码(含BF)

一、位图

(1)位图概念介绍

先看下面的一道题
1.有40亿个不重复的无符号整数,无序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。
如果我们放到哈希表或红黑树中或用排序和二分查找这两种方法。
前两种方法不可行,因为40亿个整数占用大约16G的内存空间,第一要排序需要先把数放到内存,只能用文件归并排,但是不能文件中不能搞二分查找即不能用下标去访问;第二如果放到红黑树但是同样放不进去,如果放到树里面,给一棵树查找一次,但是这里是很多数据,来一个树先读2G查找再释放掉,再来一个树放进去查,不断的查,与其这样不如读的时候判断一下没必要放树里面,直接暴力查找了,还有额外的消耗表里面的结点不光有数据还是有指针。所以上俩种方法不行主要原因就是内存不够

我们可以用一种直接定址法,我们可以最少用1字节即char标记一个数在不在,一个char数组最少消耗4G,我们还可以最少,即开比特位,比如一个字节开8个比特位,我们也可以开int的,如下图,0到7映射到第一个cha人,8到15映射到第二个char,依次映射,40亿个数,如果是一个整数去存储需要16G,现在是按位去存储,用位去标识,缩小了32倍,也可以这么说,这是40亿个整数看成40亿个比特位,除以8大概就是相当于5亿字节,需要512MB,这里东西就叫位图
位图:它是一种直接定址法的哈希映射,用来判断整型的在不在的问题,用每一位来存放某种状态,适用于海量数据,数据无重复的场景。
在这里插入图片描述

(2)简单模拟实现


	template<size_t N>
	class bitset
	{
	public:
		bitset()
		{
			_b.resize(N / 8 + 1, 0);
		}
		void set(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			_b[i] |= (1 << j);

		}
		void reset(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			_b[i] &= ~(1 << j);
		}
		bool test(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			return _b[i] & (1 << j);
		}
	private:
		vector<char> _b;

	};

库里面这个函数是有的,我们是不能去按位去开数组的,我们可以用vector数组存储char类型控制char。
我们需要实现里面三个核心接口set和set以及test,set把x映射的那个比特位设置成1,reset把它设置成0,test判断在不在。
初始化构造:我们还需要空间,我们要N个比特位我们需要开N/8,但这样少开一个比特位需要加上1,然后初始都为0。
先实现set
但是我们怎么去找到对应的比特位?
1、一个字节是8比特位,我们是算它在第几个8比特位,我们可以直接除8算出i即在第i个char数组位置,接着算在第几个8比特第几个上面,可以直接模8算出j即char位置第几个比特位。
2、然后我们把char的第j位设置为1,我们需要进行位运算,我们需要把j位设置成1,其他位不能影响需要用到或,因为或有一个特点0和任何数或还是任何数,我们还需用1进行左移j位,左移是向高位移,最后再或等,这样设置完毕。如下图
在这里插入图片描述

实现reset
同样先算出i和j,想让它第j位设置为0,先左移再取反,但是不能那个影响其它位,就需要按位与等,因为1和1与还是1,0和1与还是0。
实现判断test
同样先算出i和j,对对应的位置直接与,两种可能性,与之后除了第j位其他位都为0,如果第j位是0,那么结果就是0返回假,如果第j为不是0,那么结果是非0
值,非0值即为真不管是1还是其他非0数,都返回真。注意位运算优先级是很低的需要加括号。
我们测试一下:
在这里插入图片描述
那么开头那个问题就可以解决。

(3)位图应用

我们再看几个问题:
2.给100亿个整数,设计算法只出现一次的整数。
部分核心代码

template<size_t N>
	class twobitset
	{
	public:
		void set(size_t x)
		{
			if (_b1.test(x) == false && _b2.test(x) == false)
			{
				_b2.set(x);
			}
			else if (_b1.test(x) == false && _b2.test(x) == true)
			{
				_b1.set(x);
				_b2.reset(x);
			}
		}
		void one_print()
		{
			for (size_t i = 0; i < N; ++i)
			{
				if(_b2.test(i))
				{
				     cout << i << endl;
                }
			}
		}
	public:
		bitset<N> _b1;
		bitset<N> _b2;
	};

100亿个整数不影响我们开空间,因为可能有重复的,我们可以搞2个位图。出现0次就是00,出现1就是01次,出现1次以上就是10。
直接运用刚才的两个位图,直接复用,两个位进行组合。
_b1和_b2都test一下如果都是00表示没有出现过,就把_b2设置成1即01表示出现了1次,如果是_b1为0,_b2为1就把_b1设置为为,_b2设置为0即10表示出现2次。
接着写个打印函数去找出现1次,N是个范围,只需要遍历,只需要判断_b2是真,就是出现1次,因为01,打印即可
如图:
在这里插入图片描述

3.给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
第一种:可以把其中一个文件的值,读到内存的一个位图中,再读取另一个文件,判断在不在上面位图中,在就是交集。但是找出的交集存在重复的值,还要再次去重。可以改进,每次找到交集,都将上面的位图对应的值设置为0解决重复问题。
第二种:更好的是放到两个位图中,把文件1放到位图1,把文件2放到位图2。
读取文件1的数据映射到位图1,读取文件2的数据映射到位图2,用for循环遍历范围N,如果位图1和位图2都在就是交集。
如果数据量大就选第二种方法,反之第一种。
在这里插入图片描述

4.位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数。
这道类似第二道,用第二道题的思想,出现0次用00表示,出现1用01表示,出现2次用10表示,出现3次以上用11表示。不超过2次的所有整数就去找01和10。

总结:位图也是一种哈希结构,效率很高速度快,O(1),而且还节省内存。
缺点就是:只能映射整型,统计次数也有限。其他类型string,double等不能映射。下面的布隆过滤器就是解决这种问题。

二、布隆过滤器

(1)关于布隆过滤器概念及介绍

如果是大量字符串,位图是没法完成映射的,如果用哈希或红黑树,会有大量消耗,有附带消耗。我们可以用仿函数转成整型,间接映射,但是这样会有一个冲突问题,假如字符串是汉字,字符串的长度是8,会有256^8中组合,会存在多对一冲突。
而布隆过滤器的思想不是解决冲突,而是降低冲突概率,一个值映射一个位置容易误判,映射多个位置就可以降低误判率,即将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特位一定为1。所以可以按照以下方式进行查找,分别计算每个哈希值对应的比特位置存储的是否为零,只要有一个为零,代表该元素一定不在哈希表中,否则可能在哈希表中。

(2)布隆过滤器的使用场景

首先要找到它的特点,它能容忍误判的场景,比如我们在注册时,快速判断昵称是否使用过。如果没注册过,会立刻给你反馈,说明是准确的;如果它注册过有两种可能性,把昵称放到这个布隆中,第一它真的被用过了,第二它没被用过,存在了误判,但是从用户使用场景上是不知道的,可以允许误判,昵称用户感知不到。
如果是昵称,10亿个用户是存在数据库里面的,数据库的数据本质在磁盘上,快速不判断是不去找磁盘的,因为磁盘IO太慢了,所以我们把昵称全部读到布隆过滤器里面,节省空间,在布隆就直接反馈昵称注册过,不在布隆就反馈没注册过,但是在是会存在误判的,有可能真没被注册过。
如果是手机号,判断不在就直接返回没注册过,不在是准确的,判断在,可能会存在误判,明明没有注册过,这时候要去数据库里面磁盘上确认一下,然后再返回这个结果,以数据库的结果为准。这个跟直接去数据库查找相比,从整体而言效率是高的。因为布隆是在内存当中时间复杂度是O(1),把不在的都快速过滤掉,如果在的话再去找数据库,单拿在的场景多消耗了一点,整而言效率高,减少了数据库的访问。
布隆过滤器为啥叫这个名字,它是先提前做一层过滤,不在就直接走了,在的话再去数据库确认一下再返回。它的优点是快节省内存,缺点存在误判。
如下图:
在这里插入图片描述
大部分使用布隆过滤器的数据类型都是用字符串,如果用整型就用位图。

减少磁盘IO和网络请求,一旦一个值必定不存在,就不用进行后面的查询。BF实践当中一般都是做数据过滤,判断在不在,如果不在就不用再往后请求了,如果在继续再往后面请求,如果再次请求数据都在数据库里面,甚至数据库在远程服务器中,还要走一层网络,成本还是蛮高的。

(3)模拟实现

主要先上部分核心代码,后面有原码。

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 * _M;
			size_t hash1 = Hash1()(key) % len;
			_b.set(hash1);
			size_t hash2 = Hash2()(key) % len;
			_b.set(hash2);
			size_t hash3 = Hash3()(key) % len;
			_b.set(hash3);
		}
		bool test(const K& key)
		{
			size_t len = N * _M;
			size_t hash1 = Hash1()(key) % len;
			if (!_b.test(hash1))
				return false;

			size_t hash2 = Hash2()(key) % len;
			if (!_b.test(hash2))
				return false;

			size_t hash3= Hash3()(key) % len;

			if (!_b.test(hash3))
				return false;

			return true;
		}
	private:
		static const size_t _M = 6;
		bitset<N*_M> _b;
	};

我们在模板里面增加三个hash函数算法,可以在网上搜字符串哈希函数算法,我所取的这个三个hash函数的散列质量及效率是别人进过测试后排在前三的。在set函数里面先给一个哈希映射的第一个位置,把key转成可以去摸的整型值,摸上N,同理3个hash函数,set3个位置。
如果判断在不在,三个位置都要在才在即真,只有一个位置不在就是不在即假。
有一个关键问题:在和不在谁会存在误判?
在是不准确的,会存在误判,如果判断一个位置不在,说明至少有一个位置为0,上面说到只要有一个不在就是不在;如果判断在的话,这个位置不可能为0,三个位置都为1。比如一个字符串,本来不在,但是它映射的位置都跟别人冲突了即都被被人映射了,所以导致认为它在,即误判。

hash函数个数,代表一个值映射几个位,哈希函数越多,误判率越低,但是希函数越多,平均空间越多。
这是下面别人通过实验总结出来的公式,来降低误判率,此图链接来源于:链接。
在这里插入图片描述

以上的测试结果可以看出布隆过滤器的长度会直接影响误报率,布隆过滤器越长其误报率越小。哈希函数的个数也需要考虑,但治不了本。因为n插入个数和BF长度存在一个倍数,我们适当增加倍数_M,来验证一下,6是最好的。如图:
在这里插入图片描述
找的是不在的字符串去测试,因为本来就在测试时它肯定在,它不在的有可能能会被判断成在,这就是误判,结果是在是不准确的,因为本来不在它会判断成在。

(4)布隆过滤器天生不支持删除reset

因为会对别人造成影响以及其他影响(即使用计数法(由多个比特位控制)也非常不好,不确定删除哪个数据以及本来不在误判成在的数据,把它删了其他的又找不到了),如下图,删除nza,会把2号位置置成0,再查找azn,查找时就不在了,有关联影响。
在这里插入图片描述

(5)BF总结

布隆过滤器优点
1.增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无

2. 哈希函数相互之间没有关系,方便硬件并行运算
3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算
布隆过滤器缺陷
1.有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再
建立一个白名单,存储可能会误判的数据)
2.不能获取元素本身
3.一般情况下不能从布隆过滤器中删除元素
4.如果采用计数方式删除,可能会存在计数回绕问题

三、海量数据处理

(1) 哈希切割

(1)问题1/2

1.给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法
这里是找交集,不是整型的是字符串。
近似算法:就是之前说的,先把一个文件的数据放到BF中,再去找交集判断在不在,在就是交集,当然后面还有去重的需求。
精确算法:query本质就是一个字符串,假设单个query平均50字节,100亿个就是500G。我们可以如下图把文件A和B文件分别切成1000份(linux指令就可以切,写一个进程帮我们执行切文件的指令。),这样做还是要和每个文件找交集,所以我们可以用hash切分,用一个哈希函数计算出每个文件对应的i即文件号,然后让A0和B0,A1和B1依次找交集,只需要编号相同的小文件直接去找交集,因为一个一个的小文件就像一个桶,进入同一个桶都是冲突的值,A和B相同字符串会进去编号的相同小文件,而且我们用的是相同hash函数。
在这里插入图片描述

但是会有一个问题
某些小文件不是平均切分,可能会出现冲突过多,某个Ai,Bi小文件过大,太大加载不去内存,如果换个哈希函数再切,前提还是要算出这个两个文件多大,才决定你要切多少份,更重要的问题是继续换哈希函数可能切不动,因为有大量重复,而且这里还有两种可能:
第一种可能单个文件有大量重复的query字符串
第二种可能有大量不同的query。
第一种重复的值不管用什么哈希函数都切不动,第二种大量不同的字符串肯定可以继续用哈希函数切分,主要是怎么区分,要分别处理,
解决
我们可以这样直接使用一个unordered_set/set,依次读取文件query,插入set中
如果读取整个小文件query,都可以成功插入,那就是第一种,因为set插入key,如果有了返回false,没有继续插返回true,插入过程是不会失败的。
如果读取整个小文件query,插入过程抛异常,说明内存满了装不下,会抛bad_alloc异常,那就是第二种,要换其他哈希函数,再次分割,再求交集。

2.如何扩展BloomFilter使得它支持删除元素的操作
把每个映射的值改成引用计数,每个值由多个比特位组成,如01,10,11,分别代表1次,2次。3次,往上加,取决于用几个比特位。但其实没必要,会浪费空间,本身就不支持删除。

(2)问题3/4

3.给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?
还是一样的,哈希切分500份,依次读取数据,Hash函数计算出i,这个ip就是第i个小文件,直接用unordered_map/map统计出现次数。
如果某个过程中,出现抛异常,则说明单个文件小文件过大,冲突太多,需要重新换哈希函数,再次哈希切分这个小文件,比如这个单个小文件10G再切个30份,AA0到AA29,再生成小文件,和处理源文件的逻辑是一样的;没有异常正常统计,统计完一个小文件,记录最大的,clear,再统计下一个文件。

(3)问题3

4.与上题条件相同,如何找到top K的IP?
找次数最多IP,可以建一个K个数的小堆,小堆每一个位置是pair,key是ip,value是次数,如果比你大我就进去。
总结:相同的IP一定进入相同小文件,读取单个小文件,就可以统计IP出现次数。

四、所有源码(含BF)

bitset.h

#pragma once
#include<vector>
#include<string>
#include<iostream>
#include<ctime>
using namespace std;

namespace nza
{
	template<size_t N>
	class bitset
	{
	public:
		bitset()
		{
			_b.resize(N / 8 + 1, 0);
		}
		void set(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			_b[i] |= (1 << j);

		}
		void reset(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			_b[i] &= ~(1 << j);
		}
		bool test(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			return _b[i] & (1 << j);
		}
	private:
		vector<char> _b;

	};


	void test1()
	{
		bitset<100> bs;
		bs.set(6);
		bs.set(15);
		bs.set(66);
		cout << bs.test(6) << endl;
		cout << bs.test(7) << endl;
		cout << bs.test(66) << endl;
		cout << endl;
	}



	template<size_t N>
	class twobitset
	{
	public:
		void set(size_t x)
		{
			if (_b1.test(x) == false && _b2.test(x) == false)
			{
				_b2.set(x);
			}
			else if (_b1.test(x) == false && _b2.test(x) == true)
			{
				_b1.set(x);
				_b2.reset(x);
			}
		}
		void one_print()
		{
			for (size_t i = 0; i < N; ++i)
			{
				if(_b2.test(i))
				{
				     cout << i << endl;
                }
			}
		}
	public:
		bitset<N> _b1;
		bitset<N> _b2;
	};

	void test2()
	{
		int a[] = { 6, 22, 99, 88, 6, 4, 3, 22, 5,};
		twobitset<100> tb;
		for (auto e : a)
		{
			tb.set(e);
		}
		tb.one_print();
		cout << endl;
	}
	






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

	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 * _M;
			size_t hash1 = Hash1()(key) % len;
			_b.set(hash1);
			size_t hash2 = Hash2()(key) % len;
			_b.set(hash2);
			size_t hash3 = Hash3()(key) % len;
			_b.set(hash3);
		}
		bool test(const K& key)
		{
			size_t len = N * _M;
			size_t hash1 = Hash1()(key) % len;
			if (!_b.test(hash1))
				return false;

			size_t hash2 = Hash2()(key) % len;
			if (!_b.test(hash2))
				return false;

			size_t hash3= Hash3()(key) % len;

			if (!_b.test(hash3))
				return false;

			return true;
		}
	private:
		static const size_t _M = 6;
		bitset<N*_M> _b;
	};


	void test_BF1()
	{
		BloomFilter<100> b;
		b.set("nza");
		b.set("zan");
		b.set("qwe");
		b.set("ewq");


		cout << b.test("nza") << endl;
		cout << b.test("zan") << endl;
		cout << b.test("qwe") << endl;
		cout << b.test("ewq") << endl;
		cout << b.test("kd") << endl;
	}
	void test_BF2()
	{
		srand(time(0));
		const size_t N = 10000;
		BloomFilter<N> bf;

		std::vector<std::string> v1;
		std::string url = "https://www.education.com/-kd/2023/06/12/66666.html";

		for (size_t i = 0; i < N; ++i)
		{
			v1.push_back(url + std::to_string(i));
		}

		for (auto& str : v1)
		{
			bf.set(str);
		}
		std::vector<std::string> v2;
		for (size_t i = 0; i < N; ++i)
		{
			std::string url = "https://www.education.com/-kd/2023/06/12/66666.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://editor.csdn.net/md?articleId=131012473";
			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

#include"bitset.h"

int main()
{
	nza::test1();
	nza::test2();
	nza::test_BF1();
	nza::test_BF2();
	return 0;
}


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

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

相关文章

干翻Mybatis源码系列之第十一篇:Mybatis拦截器获取被拦截对象的方法和参数

给自己的每日一句 不从恶人的计谋&#xff0c;不站罪人的道路&#xff0c;不坐亵慢人的座位&#xff0c;惟喜爱耶和华的律法&#xff0c;昼夜思想&#xff0c;这人便为有福&#xff01;他要像一棵树栽在溪水旁&#xff0c;按时候结果子&#xff0c;叶子也不枯干。凡他所做的尽…

DJ4-1 网络层概述

目录 一、网络层提供的功能 二、路由和转发 三、数据平面和控制平面 四、网络层的服务模型 一、网络层提供的功能 网络层实现主机与主机之间的通信 从发送方主机传输报文段到接收方主机&#xff1a; 发送方主机封装报文段 (segments) 为数据报 (datagrams)接收方主机递交…

Linux常用命令——gcov命令

在线Linux命令查询工具 gcov 测试程序的代码覆盖率的工具 补充说明 gcov命令是一款测试程序的代码覆盖率的工具。 语法 gcov(选项)(参数)选项 -h&#xff1a;显示帮助信息&#xff1b; -v&#xff1a;显示版本信息&#xff1b; -a&#xff1a;输出所有的基本块的执行计数…

SpringMVC 中的常用注解和用法

观前提示:本篇博客演示使用的 IDEA 版本为2021.3.3版本,使用的是Java8(又名jdk1.8) 电脑使用的操作系统版本为 Windows 10 目录 前言 Spring Boot Spring MVC 1. MVC 1.1 MVC 和 Spring MVC 之间的关系 2. 创建 Spring MVC 项目 创建一个 SpringMVC 项目 1. new projec…

PyQt学习笔记-使用通用数据库接口QtSql操作SQLite数据库

使用通用的数据库接口的好处是当数据库发生改变时&#xff0c;只需要修改初始化的配置即可&#xff0c;而不用修改对应的更多的代码。 一、QtSql类 QtSql类时的数据库操作接口类&#xff0c;包含如下类&#xff1a; QSql QSqlError QSqlQueryModel QSqlRelationalTableMo…

SQL基础入门-条件查询语句

前言 可以关注我的云原生社区&#xff1a;云原生社区 也可以关注我的英语社区&#xff1a;从零开始学英语 一. 创建数据库并写入数据 1.1 创建数据库 MySQL [school]> create database game; Query OK, 1 row affected (0.01 sec)MySQL [school]> use game Database c…

[PyTorch][chapter 40][CIFAR-10 数据集]

前言&#xff1a; CIFAR-10和CIFAR-100是8000万个微小图像数据集的标记子集。它们由Alex Krizhevsky、Vinod Nair和Geoffrey Hinto收集 目录&#xff1a; CIFAR-10数据集简介 在线下载方式 离线下载方式 一 CIFAR-10数据集简介 CIFAR-10数据集由10个类别的60000张32x32…

碳中和城市建筑能源系统(3):负荷篇(龙惟定)2022

碳中和城市建筑能源系统(3):负荷篇 摘要 本文是碳中和城市建筑能源系统系列文章的第三篇。碳中和城市能源系统要实现“两个替代”&#xff0c;即能源生产的可再生能源替代和能源消费的电力替代。因此有&#xff12;个关键点对负荷分析提出了要求&#xff1a;一是建筑电气化&a…

网络安全运维-数字取证篇

Volatility使用 使用工具&#xff1a;Autopsy、Volatility、Wireshark 这部分可分为数据分析与取证、内存取证两块内容 一、数据分析与取证 1、wirwshark图形化 wireshark基本操作 过滤器使用 ip.src x.x.x.x 选择源ip为x.x.x.x的数据包 tcp.port xx 选择源或目标端口…

WPF开发txt阅读器13:绑定快捷键实现翻页

文章目录 绑定快捷键翻页功能跳转到首尾章节跳转 txt阅读器系列&#xff1a; 需求分析和文件读写目录提取类&#x1f48e;列表控件与目录字体控件绑定&#x1f48e;前景/背景颜色书籍管理系统&#x1f48e;用树形图管理书籍语音播放&#x1f48e;播放进度显示&#x1f48e;快进…

python数据可视化玩转Matplotlib直方图、箱型图、密度图、正态分布、偏度和峰度

目录 1. 直方图、箱线图和密度图 1.1 直方图 1.2 箱线图 1.3 密度图 2. 正态分布 3. 偏度和峰度 结论 1. 直方图、箱线图和密度图 直方图、箱线图和密度图是数据分析中十分常用的图形。它们可以帮助我们更好地理解数据的分布情况&#xff0c;从而更好地进行数据分析和处…

M1和M2的剪刀差是什么意思?

Scissors difference between M1 and M2. 在市场上流通的货币的数量&#xff0c;用金融术语来讲叫货币供应量。 因为市场上的货币流动性各不相同&#xff0c;长期存款的流动性不如短期存款的强&#xff0c;短期存款的流动性不如现金的强。 所以在统计货币量的时候&#xff0c;标…

Linux优化命令之free命令

free 这里写目录标题 一、free命令描述&#xff1a;1.free命令的语法&#xff1a;2.free命令的选项&#xff1a;3.free命令的输出格式&#xff1a; 二、压力测试工具stress&#xff1a;1.工具简介&#xff1a;2.参数详解&#xff1a;3.下载压力测试工具&#xff1a; 三、模拟实…

osg环境搭建与使用

目录 环境安装 案例一&#xff1a; 案例二&#xff1a; 案例三&#xff1a; 案例四&#xff1a; 我的vs2022,window11 环境安装 看这个文章即可,博客很详细&#xff0c;按照这个没问题的 (5条消息) 【OSG】OSG环境部署 OSG3.6.5vs2017win10_x64&#xff08;超详细&…

STM32F407的介绍

文章目录 芯片STM32F407资源F407总线架构STM32F407系统框图STM32F407地址分配 芯片 STM32F407资源 内核 32位 高性能ARM Cortex-M4处理器时钟&#xff1a; 高达168MHz&#xff0c;实际还可以超频一点点 stm32f407的主频通过PLL倍频后能够达到168MHz&#xff0c;而且芯片内置一…

使用大白菜PE给苹果电脑安装win7ghost

如何安装大白菜苹果电脑&#xff1f;ghost (苹果电脑能用大白菜安装系统吗) 喜欢用苹果Mac电脑&#xff0c;开始后发现不习惯苹果的操作系统&#xff0c;还是习惯用Windows我们可以给苹果系统Mac电脑上安装Windows系统&#xff0c;享受苹果的外观&#xff0c;操作windows系统…

【Java】Java核心要点总结 66

文章目录 1. 成员变量 和 局部变量 的区别2 . 静态方法 和 实例方法 区别3. 基本数据类型 和 包装类 的区别4. 局部变量一定存储在栈中吗&#xff1f;5. 包装类型的缓存机制 1. 成员变量 和 局部变量 的区别 ● 语法形式 &#xff1a;从语法形式上看&#xff0c;成员变量是属于…

基于opencv与mediapipe的民族舞舞蹈动作识别

需要项目的请关注、私信 基于opencv与mediapipe的民族舞舞蹈动作识别 1、原理介绍1.1 Opencv1.2 Mediapipe 2、实验步骤2.1 导入工具包2.2 中文输入2.4 建立姿态位置信息库2.5 位置信息获取2.6 姿态识别 3 实验结果与评价 1、原理介绍 1.1 Opencv Opencv&#xff08;Open So…

Android Studio实现知乎日报App

项目目录 一、项目概述二、开发环境三、运行演示 一、项目概述 本系统基于 MVP RxJava Retrofit进行设计和开发&#xff0c;通过 Retrofit 实现了无网缓存&#xff0c;基于 MVP 模式对 Activity 和 Fragment 封装了两个基类&#xff0c;同样适用于非 MVP 的实现。运用 Recyc…

termux中apache+php的安装

如果 ssl.so.3 not found 需要 apk update 更新一下 然后&#xff0c;pkg install php 完成php 8.2安装 使用命令开启 存储 权限 termux-setup-storage apt install phpmyadmin apt install php-apache apache2 配置文件位于 cd $PREFIX/etc/apache2/ cd /data/data/com.te…