C++ 哈希的应用【布隆过滤器】

✨个人主页: 北 海
🎉所属专栏: C++修行之路
🎃操作环境: Visual Studio 2022 版本 17.6.5

成就一亿技术人


文章目录

  • 🌇前言
  • 🏙️正文
    • 1、字符串比较
    • 2、布隆过滤器的概念
    • 3、布隆过滤器的实现
      • 3.1、基本结构
      • 3.2、插入
      • 3.3、查找
      • 3.4、删除
      • 3.5、测试
      • 3.6、优化方案
    • 4、布隆过滤器小结
    • 5、海量数据面试题(哈希切割)
      • 5.1、题目一
      • 5.2、题目二
  • 🌆总结


🌇前言

注册账号是进行网络冲浪的第一步操作,而拥有一个具有个性且独一无二的用户昵称是非常重要的,很多人在填写昵称时,常常会看到 此昵称已存在 的提示,系统是如何快速知道当前昵称是否存在呢?总不能挨个去遍历对比吧,这时候就需要我们本文中的主角: 布隆过滤器

图示


🏙️正文

1、字符串比较

常见的字符串比较方法是 ASCII 码值进行比较,直到两个字符串同时结束,说明两者一致

比如字符串1 abcdef 和字符串2 azbmcy
显然两个字符串不一样

这种比较方法很直接,也很可靠,但缺点也很明显:需要对字符串进行遍历
一个字符串还好,如果是几千万个字符串呢?不但需要消耗大量存储空间,查找效率也很低,此时填写个昵称,服务器都要跑一会才有反映,这是用户所无法容忍的

因此人们想出了另一个方法,利用哈希映射 的思想,计算出 哈希值,存储这个值即可,可以借此 标识字符串是否存在
在进行字符串(昵称)比较时,只需要计算出对应的 哈希值,然后看看该位置是否存在即可

哈希值 也是一个整数啊,可以利用 位图 进行 设置,查找字符串时,本质上是在 查找哈希值是否在位图中存在

字符串有千万种组合,但字符是有限的,难免会出现 误判 的情况(此处的 哈希函数 为每个字符相加)

图示

为了尽可能降低 误判率,在 位图 的基础之上设计出了 布隆过滤器

接下来看看什么是 布隆过滤器

图示


2、布隆过滤器的概念

这里是 布隆 可不是 英雄联盟中的 弗雷尔卓德之心 布隆,毕竟他也不能解决字符串比较问题,他只是 召唤师峡谷 中的一个坦克,主要负责 过滤(吸收) 敌方的伤害

布隆过滤器 是由 布隆(Burton Howard Bloom)1970 年提出的一种 紧凑型的、比较巧妙概率型数据结构,特点是 高效地插入和查询

布隆过滤器 的核心在于通过添加 哈希函数降低误判率

举个例子,如果每个人的名字都只有一个字,那么肯定存在很多重名的情况,但如果把名字字数增多,重复的情况就会大大缓解

所以 布隆过滤器 其实很简单,无非就是映射字符串时,多安排几个不一样的 哈希函数,多映射几个 比特位,只有当每个 比特位 的为 1 时,才能验证这个字符串是存在的

图示


3、布隆过滤器的实现

3.1、基本结构

布隆过滤器 离不开 位图,此时可以搬出之前实现过的 位图结构

既然需要增加 哈希函数,我们可以在模板中添加三个 哈希函数 的模板参数以及待存储的数据类型 K

namespace Yohifo
{template<size_t N,class K,class Hash1,class Hash2,class Hash3>class BloomFilter{public://……private:Yohifo::bitset<N> _bits;	//位图结构};
}

显然,这三个 哈希函数 的选择是十分重要的,我们在这里提供三种较为优秀的 哈希函数(字符串哈希算法),分别是 BKDRHashAPHash 以及 DJBHash

函数原型如下(写成 仿函数 的形式,方便传参与调用):

struct BKDRHash
{size_t operator()(const std::string& str){size_t hash = 0;for (auto e : str){hash = hash * 131 + (size_t)e;}return hash;}
};struct APHash
{size_t operator()(const std::string& str){size_t hash = 0;for (auto e : str){if (((size_t)e & 1) == 0){hash ^= ((hash << 7) ^ (size_t)e ^ (hash >> 3));}else{hash ^= (~((hash << 11) ^ (size_t)e ^ (hash >> 5)));}}return hash;}
};struct DJBHash
{size_t operator()(const std::string& str){if (str.empty())return 0;size_t hash = 5381;for (auto e : str){hash += (hash << 5) + (size_t)e;}return hash;}
};

因为 布隆过滤器 中最常存储的数据类型是 字符串,并且三个 哈希函数 我们也已经有了,所以可以将 布隆过滤器 中模板添加上 缺省值

template<size_t N,class K = std::string,class Hash1 = BKDRHash,class Hash2 = APHash,class Hash3 = DJBHash>

如何创建一个布隆过滤器

BloomFilter<100> bf;    //最大值为 100 的布隆过滤器

3.2、插入

插入 无非就是利用三个 哈希函数 计算出三个不同的 哈希值,然后利用 位图 分别进行 设置 就好了

void set(K& key)
{size_t HashI1 = Hash1()(key) % N;   //% N 是为了避免计算出的哈希值过大_bits.set(HashI1);size_t HashI2 = Hash2()(key) % N;_bits.set(HashI2);size_t HashI3 = Hash3()(key) % N;_bits.set(HashI3);
}

注意: 布隆过滤器的插入操作是一定会成功的,因为不管是什么字符串,都可以在其对应的位置留下痕迹

3.3、查找

查找 某个字符串时,需要判断它的每个 哈希值 是否都存在,如果有一个不存在,那么这个字符串必然是不存在的

 bool test(const K& key){//过滤不存在的情况,至于是否存在,还得进一步判断size_t HashI1 = Hash1()(key) % N;if (_bits.test(HashI1) == false)return false;size_t HashI2 = Hash2()(key) % N;if (_bits.test(HashI2) == false)return false;size_t HashI3 = Hash3()(key) % N;if (_bits.test(HashI3) == false)return false;//经过层层过滤后,判断字符串可能存在return true;}

查找 函数可以很好的体现 过滤 的特性

如何判断一个人是否存在
不能盲目去查找,而是应该根据姓名,查询身份证号、住址等个人信息,如果这些信息都没有,那么就说明这个人不存在,因为这些信息足够过滤出结果了;如果出现重名或信息重复的情况,则需要进一步判断,这就是说明 通过过滤判断 “存在” 是不准确的,但判断 “不存在” 是准确的

布隆过滤器判断 “不在” 是准确的,判断 “在” 是不准确的

比如,字符串1映射了 1、6、7 号位置,字符串2映射了 2、4、5 号位置,字符串3映射了 1、3、4 号位置,虽然这三个字符串不会相互影响,但如果此时字符串4映射的是 1、2、3 号位置,会被误断为 存在,理论上 字符串存储位置越密集,误判率越高

图示

所以对于一些敏感数据,如果要判断是否存在,不能只依靠 布隆过滤器,而是使用 布隆过滤器 + 数据库 的方式进行双重验证

当然,如果 布隆过滤器 判断字符串不存在,那么就是真的不存在,因为这是绝对准确的

布隆过滤器 能容忍误判的场景:注册时,判断昵称是否存在

3.4、删除

一般的 布隆过滤器 不支持删除,一旦进行了删除(重置),会影响其他字符串

图示

表面上只删除了 “腾讯”,但实际上影响了 “百度”,在验证 “百度” 是否存在时,会被判断为 不存在,此时只有三个字符串,如果有更多呢?造成的影响是很大的,所以对于一般的 布隆过滤器,是不支持删除操作的

如何让布隆过滤器支持删除?
关于共用同一份资源这个问题,我们以前就已经见过了,比如 命名管道,当我们试图多次打开同一个 命名管道 时,操作系统实际上并不会打开多次,因为这样是很影响效率的,实际每打开一次 命名管道,其中的 计数器++,当关闭 命名管道 时,计数器--,直到 计数器0 时,命名管道 才会被真正关闭

这不就是 引用计数 的思想吗?

我们可以给每一个 比特位 带上一个 引用计数器,用来表示当前位置存在几个映射关系,这样 布隆过滤器 就能支持 删除 操作了

但这未免也太本末倒置了,位图 的优点是 高效且空间利用率高,如果给每一个 比特位 都挂上一个 引用计数器,会导致 位图 占用的内存资源膨胀,浪费很多不必要的空间,并且 删除 操作需求不大,没必要添加

3.5、测试

接下来测试一下 布隆过滤器 是否有用

void TestBloomFilter1()
{BloomFilter<100> bf;    //最大值为 100 的布隆过滤器bf.set("aaaaa");bf.set("bbbbb");bf.set("ccccc");bf.set("ddddd");bf.set("eeeee");std::cout << "bbbbb: " << bf.test("bbbbb") << std::endl;std::cout << "ddddd: " << bf.test("ddddd") << std::endl;std::cout << "============" << std::endl;std::cout << "aaaa: " << bf.test("aaaa") << std::endl;  //相似字符串std::cout << "CCCCC: " << bf.test("CCCCC") << std::endl;std::cout << "zzzzz: " << bf.test("zzzzz") << std::endl;    //不相似字符串std::cout << "wwwww: " << bf.test("wwwww") << std::endl;
}

图示

可以正确进行判断,接下来看看 设置 的每个字符串的 哈希值 是多少

图示

同时在三个 哈希值 的叠加下,误判 的概率被大大降低了,尽管如此,在判断字符串存在时,仍然存在较高的 误判率,可以通过下面的程序计算 误判率

测试方法:插入约 10 w 个字符串(原生),对原字符串进行微调后插入(近似),最后插入等量的完全不相同的字符串(不同),分别看看 原生近似原生不同 字符串之间的误判率

void TestBloomFilter2()
{//测试误判率//构建一组字符串 + 一组相似字符串 + 一组完全不同字符串//通过 test 测试误判率const size_t N = 100000;	//字符串数std::string str = "https://blog.csdn.net/weixin_61437787?spm=1000.2115.3001.5343";//构建原生基本的字符串std::vector<std::string> vsStr(N);for (size_t i = 0; i < N; i++){std::string url = str + std::to_string(i);vsStr[i] = url;	//保存起来,后续要用}//构建相似的字符串std::vector<std::string> vsSimilarStr(N);BloomFilter<N> bfSimilarStr;for (size_t i = 0; i < N; i++){std::string url = str + std::to_string(i * -1);vsSimilarStr[i] = url;bfSimilarStr.set(url);}//构建完全不一样的字符串str = "https://leetcode.cn/problemset/all/";std::vector<std::string> vsDiffStr(N);BloomFilter<N> bfDiffStr;for (size_t i = 0; i < N; i++){std::string url = str + std::to_string(i);vsDiffStr[i] = url;bfDiffStr.set(url);}//误判率检测:原生 <---> 近似double missVal = 0;for (auto e : vsStr){if (bfSimilarStr.test(e) == true)missVal++;}//误判率检测:原生 <---> 不同double diffVal = 0;for (auto e : vsStr){if (bfDiffStr.test(e) == true)diffVal++;}std::cout << "原生 <---> 近似 误判率:" << missVal / N * 100 << "%" << std::endl;std::cout << "原生 <---> 不同 误判率:" << diffVal / N * 100 << "%" << std::endl;
}

图示

显然,此时存在很高的误判率

3.6、优化方案

可以从两个方面进行优化:

  1. 增加哈希函数的个数(不是很推荐)
  2. 扩大布隆过滤器的长度,使数据更分散

因此我们可以控制 布隆过滤器 的长度,降低 误判率

如何理解空间扩大后,误判率会降低?

想想 地广人稀的西伯利亚地狭人稠的香港,人口越稠密,找人时越有可能发生误判

图示

那么如何选择 布隆过滤器 的长度,做到 平衡误判率与空间占用呢

《详解布隆过滤器的原理,使用场景和注意事项》

图示
图示

经过计算得出,长度为 3~8 时,效果最好

  • 实际位图的大小为 N * _len

对原来的 布隆过滤器 进行修改,结合 误判率 与 空间,选择较为折中的 6 作为 布隆过滤器 的长度

template<size_t N,class K = std::string,class Hash1 = BKDRHash,class Hash2 = APHash,class Hash3 = DJBHash>
class BloomFilter
{static const int _len = 6;   //布隆过滤器的长度static const int _size = N * _len; //位图的大小
public:void set(const K& key){size_t HashI1 = Hash1()(key) % _size;   //% N 是为了避免计算出的哈希值过大_bits.set(HashI1);size_t HashI2 = Hash2()(key) % _size;_bits.set(HashI2);size_t HashI3 = Hash3()(key) % _size;_bits.set(HashI3);}bool test(const K& key){//过滤不存在的情况,至于是否存在,还得进一步判断size_t HashI1 = Hash1()(key) % _size;if (_bits.test(HashI1) == false)return false;size_t HashI2 = Hash2()(key) % _size;if (_bits.test(HashI2) == false)return false;size_t HashI3 = Hash3()(key) % _size;if (_bits.test(HashI3) == false)return false;//经过层层过滤后,判断字符串可能存在return true;}private:Yohifo::bitset<_size> _bits;	//位图结构
};

此时再来看看之前的测试:

图示

误判率降至 5% 左右

对于 用户登录时检测昵称是否存在 这件事上,已经足够用了

如果想要最求更高的准度,可以使用 布隆过滤器 + 数据库 双重验证


4、布隆过滤器小结

总的来说,作为 哈希思想 的衍生品,布隆过滤器 实现了字符串的 快速查找与极致的空间利用,在需要判断字符串是否存在的场景中,判断 “不在”,是值得信赖的

优点:

  • 查找效率极高,为 O(K),其中 K 表示哈希函数的个数
  • 哈希函数之间并没有直接关系,方便进行硬件计算
  • 数据量很大时,布隆过滤器可以表示全集
  • 可以利用多个布隆过滤器进行字符串的 交集、并集、差集运算
  • 在可以容忍误判率的场景中,布隆过滤器优于其他数据结构
  • 布隆过滤器中存储的数据无法逆向复原,具有一定的安全性

缺点:

  • 存在一定的误判性
  • 无法对元素本身进行操作,仅能判断存在与否
  • 一般不支持删除功能
  • 采取计数删除的方案时,可能存在 计数回绕 的问题

实际应用场景:

  • 注册时对于 昵称、用户名、手机号的验证
  • 减少磁盘 IO 或者网络请求,因为一旦一个值必定不存在的话,我们可以不用进行后续昂贵的查询请求

总之,能被 布隆过滤器 拦截(过滤)下来的数据,一定是不存在的


5、海量数据面试题(哈希切割)

5.1、题目一

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

query查询语句,比如 网络请求、SQL 语句等,假设一个 query 语句占 50 Byte,单个文件中的 100 亿个 query500 GB 的空间,两个文件就是 1000 GB

下面来看看解法

近似解法:借助布隆过滤器,先存储其中一个文件的 query 语句,这里给每个 query 语句分配 4 比特位,100 亿个就占约 1 GB 的内存,可以存下,存储完毕后,再从另一个文件读取 query 语句,判断是否在 布隆过滤器 中,“在” 的就是交集。因为 布隆过滤器 判断 “在” 不准确,符合题目要求的 近似算法

精确解法:对于这种海量数据,需要用到哈希分割,我们这里把单个文件(500 GB 数据)分割成 1000 个小文件,平均每个文件大小为 512 Mb,再将小文件读取到内存中;另一个文件也是如此,读取两个大文件中的小文件后,可以进行交集查找,再将所有小文件中的交集统计起来,就是题目所求的交集了

图示

此时存在一个问题:如果我们是直接平均等分成 1000 个小文件的话,我们也不知道小文件中相似的 query 语句位置,是能把每个小文件都进行匹配对比,这样未免为太慢了

所以不能直接平均等分,需要使用 哈希分割 进行切分

i = HashFunc(query) % 1000

不同的 query 会得到不同的下标 i,这个下标 i 决定着这条 query 语句会被存入哪个小文件中,显然,一样的 query 语句计算出一样的下标,也就意味着它们会进入下标相同的小文件中,经过 哈希切割 后,只需要将 大文件 A 中的小文件 0大文件 B 中的小文件 0 进行求 交集 的操作就行了,这样能大大提高效率

图示

但是,此时存在一个 问题:如果因哈希值一致,而导致单个小文件很大呢?

此时如果小文件变成了 1GB、2GB、3GB 甚至更大,就无法被加载至内存中(算法还有消耗)

解决方法很简单:借助不同的哈希函数再分割

即使在同一个小文件中,不同的 query 语句经过不同的 哈希函数 计算后,仍可错开,怕的是 存在大量重复的 query,此时 哈希函数 就无法 分割 了,因为计算出的 哈希值 始终一致

图示

所以面对小文件过大的问题,目前有两条路可选:

  1. 大多都是相同、重复的 query,无法分割,只能按照大小,放到其他小文件中
  2. 大多都是不相同的 query,可以使用 哈希函数 再分割

这两条路都很好走,关键在于如何选择?
小文件中实际的情况我们是无法感知的,但可以通过特殊手段得知:探测

对于大于 512 Mb 的小文件,我们可以对其进行读取,判断属于情况1、还是情况2

  • 首先准备一个 unorder_set,目的很简单:去重
  • 读取文件中的 query 语句,存入 unordered_set
  • 如果小文件读取结束后,没有发生异常情况,说明属于情况1:大多都是相同、重复的 query 语句,把这些重复率高的数据打散,放置其他 512 Mb 的小文件中
  • 如果小文件读取过程中,出现了一个异常,捕获结果为 bad_alloc,说明读取到的大多都是不重复的 query 语句,因为我们内存只有 1 GB,抛出的异常是 内存爆了,异常的抛出意味着这个小文件属于情况2,可以使用其他的 哈希函数 对其进行再分割,分成 512 Mb 的小文件

如此一来,这个文件就被解决了,核心在于:利用哈希切割将数据分为有特性的小文件、利用抛异常得知小文件的实际情况

5.2、题目二

给一个超过 100 GB大小的 log file, log 中存着 IP 地址, 设计算法找到出现次数最多的 IP 地址?

这题本质上也是在考 哈希分割,将 log file 文件中的 IP 地址看作上一题中的 query 语句,得知文件大小约为 500 GB

因为这里没有内存限制,我们可以将其分为 500 个小文件,每个小文件大小为 1 GB

图示
这里分为小文件的目的是 让相同的 IP 分至同一个小文件中

针对较大的小文件,依然采取 其他哈希函数继续分割分给其他小文件的做法

读取单个小文件时,利用 unordered_map 统计 IP 地址的出现次数,读取完毕后,遍历 unordered_map 即可得知出现次数最多的 IP 地址

与上题条件相同,如何找到 Top KIP ?如何直接用 Linux 系统命令实现?

涉及 Top K 的问题都可以通过 优先级队列(堆) 解决,在第一问的基础上,构建一个大小为 K小堆,将高频出现的 IP 地址入堆,筛选出 Top KIP 即可

至于如何利用 Linux 命令解决?

sort log_file | uniq -c | sort -nrk1,1 | head -K

解释:

  • sort log_file 表示对 log_file 文件进行排序
  • uniq -c 表示统计出其中每个 IP 的出现次数
  • sort -nrk1,1 表示按照每个 IP 的出现次数再进行排序
  • head -k 表示选择前 kIP 地址显示

注意: 以上操作都需要借助管道 | 因为它们都是有关联性的


🌆总结

以上就是本次关于 C++ 哈希的应用【布隆过滤器】的全部内容了,在本文中我们主要学习了布隆过滤器的相关知识,再一次对哈希思想有了更深层次的理解(多组映射),在简单模拟实现布隆过滤器之后,顺便解决了几道海量数据面试题,从中学到了哈希分割这一重要思想,哈希是一个被高频使用的工具,因为它实在是太香了,想要玩的更溜,还需要勤加练习


星辰大海

相关文章推荐

C++ 进阶知识

C++ 哈希的应用【位图】

C++【哈希表的完善及封装】

C++【哈希表的模拟实现】

C++【初识哈希】

C++【一棵红黑树封装 set 和 map】

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

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

相关文章

一、Postfix[安装与配置、smtp认证、Python发送邮件以及防垃圾邮件方法、使用腾讯云邮件服务]

Debian 11 一、安装 apt install postfix 二、配置 1.dns配置 解释&#xff1a;搭建真实的邮件服务器需要在DNS提供商那里配置下面的dns 配置A记录mail.www.com-1.x.x.x配置MX记录www.com-mail.www.com 解释&#xff1a;按照上面的配置通常邮件格式就是adminwww.com其通过…

Python 教程之标准库概览

概要 Python 标准库非常庞大&#xff0c;所提供的组件涉及范围十分广泛&#xff0c;使用标准库我们可以让您轻松地完成各种任务。 以下是一些 Python3 标准库中的模块&#xff1a; 「os 模块」 os 模块提供了许多与操作系统交互的函数&#xff0c;例如创建、移动和删除文件和…

C#,中国福利彩票《刮刮乐》的数学算法(02)——时来运转

1 中国福利彩票 中国福利彩票始于1987年7月27日&#xff0c;以“团结各界热心社会福利事业的人士&#xff0c;发扬社会主义人道主义精神&#xff0c;筹集社会福利资金&#xff0c;兴办残疾人、老年人、孤儿福利事业和帮助有困难的人”、即“扶老、助残、救孤、济困”为宗旨。随…

【C++进阶:哈希--unordered系列的容器及封装】

本课涉及到的所有代码都见以下链接&#xff0c;欢迎参考指正&#xff01; practice: 课程代码练习 - Gitee.comhttps://gitee.com/ace-zhe/practice/tree/master/Hash unordered系列关联式容器 在C98中&#xff0c;STL提供了底层为红黑树结构的一系列关联式容器&#xff0c;在…

拓扑排序详解(带有C++模板)

目录 介绍&#xff1a; 实现原理&#xff1a; 简答来说&#xff1a; 例子 模板&#xff08;C&#xff09; 介绍&#xff1a; 拓扑排序&#xff08;Topological Sorting&#xff09;是一种针对有向无环图&#xff08;DAG&#xff09;的节点进行排序的算法。DAG是一个图&…

PHP数据库

PHP MySQL 连接数据库 MySQL 简介MySQL Create 免费的 MySQL 数据库通常是通过 PHP 来使用的。 连接到一个 MySQL 数据库 在您能够访问并处理数据库中的数据之前&#xff0c;您必须创建到达数据库的连接。 在 PHP 中&#xff0c;这个任务通过 mysql_connect() 函数完成。 …

Linux环境安装MySQL(详细教程)

1、下载MySQL MySQL官网&#xff1a;MySQLhttps://www.mysql.com/ 下载社区版&#xff08;免费&#xff0c;但不提供技术支持&#xff09; 简单说明一下rpm和tar包的区别&#xff1a; tar 只是一种压缩文件格式&#xff0c;所以&#xff0c;它只是把文件压缩打包 rpm&#xf…

【字节跳动青训营】后端笔记整理-3 | Go语言工程实践之测试

**本文由博主本人整理自第六届字节跳动青训营&#xff08;后端组&#xff09;&#xff0c;首发于稀土掘金&#xff1a;&#x1f517;Go语言工程实践之测试 | 青训营 目录 一、概述 1、回归测试 2、集成测试 3、单元测试 二、单元测试 1、流程 2、规则 3、单元测试的例…

我的第一个flutter项目(Android Webview)

前言&#xff1a;flutter开发环境搭建Flutter的开发环境搭建-图解_☆七年的博客-CSDN博客 第一个flutter简单项目&#xff0c;内容是一个主界面&#xff0c;其中&#xff1a; 1.内容点击数字自增 2.跳转一个空页&#xff0c; 3.跳转一个WebView界面 其中涉及添加主键&#xf…

QT: 用定时器完成闹钟的实现

闹钟项目&#xff1a; widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTimerEvent> #include <QTime> #include <QDebug> #include <QTextToSpeech> #include <QMessageBox> #include <QTimer>QT_BEGIN…

Quartz项目搭建与任务执行源码分析

数据库准备 准备一个MySQL数据库&#xff0c;版本为8.0&#xff0c;然后创建一个库&#xff0c;并从quartz官方的版本包中找到名称为tables_mysql_innodb.sql的脚本执行进去&#xff08;脚本内容文后也有提供&#xff09;。 项目依赖说明 创建一个Maven项目&#xff0c;引入…

gitignore文件使用方法(gitignore教程)(git status --ignored)(git check-ignore -v <file>)

文章目录 Gitignore文件使用描述Gitignore基本语法1. 基本语法★★★★★2. 配置方法 匹配示例示例1示例2示例3 其他命令git status --ignored&#xff08;用于显示被Git忽略的文件和文件夹的状态&#xff09;git check-ignore -v <file>&#xff08;用于检查指定文件是否…

一个灵活、现代的Android应用架构

一个灵活、现代的Android应用架构 学习Android架构的原则&#xff1a;学习原则&#xff0c;不要盲目遵循规则。 本文旨在通过示例演示实际应用&#xff1a;通过示范Android架构来进行教学。最重要的是&#xff0c;这意味着展示出如何做出各种架构决策。在某些情况下&#xff0…

网络层IP协议的基本原理 数据链路层ARP协议 域名解析以及一些重要技术

目录 1 网络层IP协议协议头格式网段划分DHCPCIDR&#xff1a;基于子网掩码的划分方式特殊的IP号IP地址的数量限制私有IP地址和公网IP地址路由路由表 2 数据链路层 — 局域网的转发问题以太网认识以太网以太网帧格式局域网通信原理 MTUMTU对IP协议的影响MTU对UDP协议的影响MTU对…

人类文明进入下个纪元奇点:UFO听证会-恒温超导发现-GPT大模型

今年以来&#xff0c;科技领域出圈的事件频繁发生&#xff0c;每一个事件都意味着一个领域的重大突破的可能。这些事件是UFO听证会、恒温超导LK99的论文、GPT类大模型的广泛应用&#xff0c;我常将这些事件串在一起思考&#xff0c;细思极恐&#xff0c;一种”火鸡与农场主“的…

C语言手撕顺序表

目录 一、概念 1、静态顺序表&#xff1a;使用定长数组存储元素。 2、动态顺序表&#xff1a;使用动态开辟的数组存储 二、接口实现 1、对顺序表的初始化 2、对数据的销毁 3、对数据的打印 4、检查是否需要扩容 5、尾插 6、头插 7、尾删 8、头删 9、在pos位置插入x …

使用ComPDFKit PDF SDK 构建iOS PDF阅读器

在当今以移动为先的世界中&#xff0c;为企业和开发人员创建一个iOS应用程序是必不可少的。随着对PDF文档处理需求的增加&#xff0c;使用ComPDFKit这个强大的PDF软件开发工具包&#xff08;SDK&#xff09;来构建iOS PDF阅读器和编辑器可以让最终用户轻松查看和编辑PDF文档。 …

IDEA 模块不加载依旧是灰色 没有变成小蓝色的方块

Settings > Build, Execution, Deployment > Build Tools > Maven > Ignored Files下降对应的模块勾选掉 但通常在Maven的配置中&#xff0c;您会找到一个名为“ignoredFiles”的列表&#xff0c;其中包含被忽略的文件和目录。您可以通过取消选中所需的文件或目录…

本地非文字资源无法加载

目录 方法A.静态/动态绑定路径 方法B.require导入&#xff08;运行时加载&#xff09; 方法C.import导入&#xff08;x&#xff09;&#xff08;编译时加载&#xff09; 方法D.ref直接操作元素赋值&#xff08;x&#xff09; 相关知识 import和requir区别 模板路径&#…

基于opencv与机器学习的摄像头实时识别数字!附带完整的代码、数据集和训练模型!!

前言 使用摄像头实时识别数字算是目标检测任务&#xff0c;总体上分为两步&#xff0c;第一步是检测到数字卡片的位置&#xff0c;第二步是对检测到的数字卡片进行分类以确定其是哪个数字。在第一步中主要涉及opencv的相关功能&#xff0c;第二步则使用机器学习的方式进行分类…