『 C++ - STL 』位图(BitMap)与布隆过滤器(Bloom Filter)

文章目录

    • 🧸 位图(BitMap)概念
    • 🧸 位图的实现
      • 🪅 总体框架
      • 🪅 位图的数据插入
        • 🧩 左移操作与右移操作的区别
      • 🪅 位图的数据删除
      • 🪅 位图的数据查找
      • 🪅 位图整体代码(供参考)
    • 🧸 布隆过滤器(Bloom Filter)概念
      • 🪅 哈希函数的个数及布隆过滤器的长度
    • 🧸 布隆过滤器的实现
      • 🪅 总体框架
      • 🪅 布隆过滤器的数据插入
      • 🪅 布隆过滤器的数据查找
      • 🪅 布隆过滤器的数据删除
      • 🪅 布隆过滤器整体代码(供参考)
      • 🪅 布隆过滤器的应用


🧸 位图(BitMap)概念

请添加图片描述

在之前的文章中提到了对于存储数据的数据结构分别为哈希表与红黑树;

STL当中底层为这两个数据结构所实现的容器分别为map,setunordered_map,unordered_set;

同时能知道这几个容器实际上是在内存当中存储数据的;

  • 腾讯曾经有一道面试题是这样的:

    存在40亿个不重复的无符号整数,其中这些数据并未排过序;给定一个无符号整数,如何快速判断这个数是否存在于这40亿个数中;

很显然,这个面试题问的是一个数据在不在的问题;

那么如何能够对数据进行判断在不在的问题?

  • 遍历数据逐个判断

    遍历数据逐个判断可以理解为一种暴力的解法,这种暴力的解法通过枚举的思路对数据逐个查询;

    但实际上该方法的时间复杂度为O(N)[由于需要遍历整组数据];

  • 排序后再利用二分查找的方式对数据进行查找

    排序所需要的时间复杂度大概为O(NlogN);

    利用二分查找的时间复杂度为O(logN);

以上面的两种方法确实可以达到在大量数据中判断一个数据是否存在;

除此之外还有两个问题?

本文主要围绕32位机器进行解析
  • 在内存当中如何对数据进行存储?

    已知一个无符号的整型大小为4byte,而一个字节为8bit;

    40亿个无符号整型的大小为4 * 108 * 40 = 160 * 108 byte,最终的换算结果为16GB;

    而若是使用上面提到的两种方法都不能很好的使数据完整的放置至内存当中;

    而若是使用char来存放数据的话则为16GB / 4 = 4GB,同时在此基础上使用其他算法将数据存入内存当中;

    那么实际上4GB放置在内存当中也会变得比较大;

    那么还有什么方式可以使得能够有效的存放数据?

  • 如何使得判断的效率变得更快?

    在上述的两种方法中,实际操作起来其总体效率都比较慢;

    如何提高判断的效率?

在之前的博客『 C++ - Hash 』闭散列与开散列哈希表详解及其实现 ( 万字 )-CSDN博客 中对于哈希函数提到了一种为直接定址法的方法;

这种方法根据直接定址来判断数据是否存在该组数据当中;

那么这种方法与该面试题有何种关联?

综上所述需要解决两个点:

  • 将数据"放置"在内存当中;
  • 根据对应算法使得提高判断效率;

实际上可以采用类似哈希函数中直接定址法的思路对问题进行解决;

在计算机当中,判断一个数据是否存在无非是一个==“是不是"的问题,而"是不是”==可以使用1或者0来进行判断;

10在内存当中只需要1bit;

所以可以开空间并使用bit为单位来判断这个数据是否存在;

以该图为例,设计一个以char为单位的数组;

已知char类型的大小为1byte,而1byte == 8bit;

若是存在一个数据为14,那么这个数据可以在对应的:

14/8char中的第14%8bit中;

那么已知size_t无符号整型的最大值为4294967295,也就是232-1;

若是需要判断一个无符号整型是否在40亿个数据当中也只需要开辟大约512MB的空间;而判断在不在也只需要直接映射即可,在判断的情况下只需要常数级别的时间复杂度也就是O(1);

这种数据结构被称为位图;

在C++的STL中也有对应的容器,名为std::bitset;


🧸 位图的实现

请添加图片描述

在上文当中提到了关于位图的概念,那么位图应该如何实现?


🪅 总体框架

请添加图片描述

对于位图而言总体框架是一个char类型的数组,当然也可以选用int类型作为数组的数据类型(根据需求进行修改);

template <size_t N>
class bitset {
 public:
  bitset() { _bits.resize(N / 8 + 1, 0); }

  void set(size_t x);

  void reset(size_t x);

  bool test(size_t x) ;

 private:
  std::vector<char> _bits;
};

为了位图能够合理的开辟内存在框架之中还使用了非类型模板参数N,并根据N来确定内存的大小;


🪅 位图的数据插入

请添加图片描述

实际上在物理意义上位图由于内存的限制不能使得数据真正意义上的存放至内存当中,只能根据映射的方式使其能够与位图中产生关联;

那么如何使得在一个char类型当中映射单个bit而不影响单个bit?

实际上使用按位或'|'即可使得在不影响其他位的情况下对该bit进行置1操作;

而数据只需要使用按位左移<<即可使得数据偏移至单byte中的对应bit使其能够进行上述操作;

  • 那么如何计算对应位置?

    由于数组的数据类型是char,且单个char的大小为1byte == 8bit;

    所以只需要对数据进行/8操作即可知道数据所在的char位置;

    而对数据进行%8操作即可知道数据映射在对应char位置中的bit位置;

    具体演示参照上文中的 “示例(点击跳转)” ;

  • 代码(供参考)

      void set(size_t x) {
        size_t i = x / 8;
        size_t j = x % 8;
        /*第i个char中的第j个比特位*/
        _bits[i] |= (1 << j);  // 运算符优先级
      }
    

当然在代码中应该时刻注意运算符的优先级(位运算的优先级往往是比较低的);


🧩 左移操作与右移操作的区别

请添加图片描述

  • 在上文当中对数据的单个bit映射位置的设置为什么是左移而不是右移,左移右移之间有什么区别?是否需要根据机器的情况来调整左移或者右移的操作?

左移和右移虽然听起来是一个方向的位置,若是按字面意思来解左移右移的话可能对应的则为左移是向左移动,而右移是向右移动;

而实际上在c/C++中,左移或者右移实际上是对于高低值进行移动;

  • 左移

    向高位移动;

  • 右移

    向低位移动;

无论是十六进制0x12ff40,或者十进制1024,更或者是二进制0101而言都是左高右低;

以该篇文章而言所实现的位图而言,以我们的角度而言他可能是这样的:

而实际上在内存当中是这样的:

所以并不需要根据机器的情况来调整左移或者右移的操作;


🪅 位图的数据删除

请添加图片描述

由于在位图当中没有真正的存储数据而是对数据与位图进行一种映射关系;

所以实质上对数据的删除只需要对对应的bit位置进行置0操作即可;

1左移至对应的位置并对该数据进行按位取反'~',最终将该数据与对应位置进行按位与'&'的操作即可;

  • 代码(供参考)

    void reset(size_t x) {
        size_t i = x / 8;
        size_t j = x % 8;
        _bits[i] &= ~(1 << j);
      }
    

🪅 位图的数据查找

请添加图片描述

对于数据的查找而言只需要将1左移至对应的偏移量最后与对应位置进行按位与'&'操作即可;

  • 代码(供参考)

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

按位与'&'操作的结果为1说明该数据存在,若是为0则不存在;


🪅 位图整体代码(供参考)

请添加图片描述

#pragma once

#include <iostream>
#include <vector>

namespace MyBitset {
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;
    /*第i个char中的第j个比特位*/
    _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:
  std::vector<char> _bits;
};
}  // namespace MyBitset

🧸 布隆过滤器(Bloom Filter)概念

请添加图片描述

在上文中所讲述的是位图所对应的内容;

位图在对于解决数据在不在的问题无论是在效率上还是在内存大小当中都是数据结构中的佼佼者;

但是对应的位图也存在着缺点:

  • 优点:

    • 内存需求小;

    • 效率高(时间复杂度为常数级);

  • 缺点:

    • 只能存储整型类型;

在位图当中普遍都是对大量的整型数据进行映射,判断其是否存在,而遇到其他类型则只能袖手旁观;

可能会有人想到使用哈希中的除留余数法并于位图进行结合;

但是这样的话以字符串类型string为例,使用除留余数法主要是依靠计算ASCII值对数据进行映射存储,而使用该种方法避免不了的为哈希冲突;

在1970年,伯顿·霍华德·布隆(Burton Howard Bloom)提出了一个概率型数据结构,这个数据结构则为今天的布隆过滤器(Bloom Filter);

这个数据结构是利用位图以及哈希算法来实现的;

在实现布隆过滤器中布隆并未想解决哈希冲突,而是降低了哈希冲突的概率;

以上图为例,若是只以一种哈希函数来对位图进行映射,那么当发生哈希冲突时,由于例如相同字符而字符顺序不同的两个字符串而言极容易发生冲突,其将会被对应的位图的去重性而无法判断该数据是否存在;

那么在一个哈希函数的基础上再加上几个哈希函数那么由于哈希函数的不同,对应每个哈希函数都需要映射一个位置,那么其将大大减小哈希冲突的概率;

以该图为例,该图演示了当使用两个哈希函数对数据进行映射时,其在一个哈希函数中可能发生冲突,但另一个哈希函数不一定发生同样的冲突,这种方式有效的降低了在位图当中的哈希冲突,即为布隆过滤器;


🪅 哈希函数的个数及布隆过滤器的长度

请添加图片描述

在上文当中提到了对于布隆过滤器的基本概念,其本质是通过多个哈希函数将同一个字符串映射至位图中的不同位置从而降低布隆过滤器中的整体的哈希冲突概率;

同时根据上文的内容当中可以得知,实际上布隆过滤器不仅与概念有关,而且与对应的哈希函数个数与布隆过滤器长度的选择有关;

  • 举个例子

    存在100string数据,若是在一个布隆过滤器当中使用4个哈希函数作为布隆过滤器的哈希函数,而在布隆过滤器的选择上开辟了400bit的空间;

    那么在这种情况下由于数据的插入将会引来大量的哈希冲突,大量的bit都将被置为1从而导致每次的判断都为可能存在;

    故实际上布隆过滤器的长度 m>=n*k ,其中m为长度,n为数据个数,k为哈希函数的个数;

而对于哈希函数的个数也是如此;

以降低误判率而言,哈希函数的个数越多越好,但实际上越多的哈希函数虽然能够有效降低布隆过滤器中的误判率,但随着哈希函数的个数越多,为了对应的降低误判率,其布隆过滤器的长度也要变得更大,从而增大了整体的内存开销;

其中p为误判率,k为哈希函数个数,m为布隆过滤器的长度,n为插入元素个数;

通常情况下,哈希函数的个数与布隆过滤器的长度都可以使用公式进行计算:

  • 哈希函数个数

    k = m n ln ⁡ ( 2 ) k = \frac{m}{n} \ln(2) k=nmln(2)

  • 布隆过滤器长度

    m = − n ln ⁡ p ( ln ⁡ 2 ) 2 m = - \frac{n \ln p}{(\ln 2)^2} m=(ln2)2nlnp


🧸 布隆过滤器的实现

请添加图片描述

在上文当中提到了布隆过滤器采用的是多个哈希算法与位图的结合;

在本文章中着重实现哈希函数数量为3的布隆过滤器;


🪅 总体框架

请添加图片描述

在上文当中提到了布隆过滤器的大致原理;

实际上就是采用多个哈希函数使同一个字符串根据不同的哈希函数规则映射至不同的位置,从而降低布隆过滤器中整体的哈希冲突;

而本文章中重点实现使用3个哈希函数实现布隆过滤器;

所使用的哈希函数分别为DKBRHash,APHashDJBHash;

#pragma once

#include "BitSet.h"


struct _BKDR__Hash {
  size_t operator()(const std::string& key) {
    size_t hash = 0;
    for (auto e : key) {
      hash *= 31;
      hash += e;
    }
    return hash;
  }
};

struct _AP__Hash {
  size_t operator()(const std::string& key) {
    size_t hash = 0;
    for (size_t i = 0; i < key.size(); i++) {
      char ch = key[i];
      if ((i & 1) == 0) {
        hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
      } else {
        hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
      }
    }
    return hash;
  }
};

struct _DJB__Hash {
  size_t operator()(const std::string& key) {
    size_t hash = 5381;
    for (auto ch : key) {
      hash += (hash << 5) + ch;
    }
    return hash;
  }
};

template <size_t N, class K = std::string, class Hash1 = _BKDR__Hash , class Hash2 = _AP__Hash,
          class Hash3 = _DJB__Hash>
class BloomFilter {
 public:
 void set(const K& key);

bool test(const K& key);
 private:
  const static size_t _X = 5;
  MyBitset::bitset<N*_X> _bits;  // N为数据量最多
};

在模板参数中定义一个非类型模板参数N,该非类型模板参数作为处理数据时的数据个数;

从该段代码中可以看出,其成员函数重点为一个位图_bits及对应的一个乘数因子_X;

其中乘数因子_X决定了最终布隆过滤器的长度;

  • 根据公式

    k = m n ln ⁡ ( 2 ) k = \frac{m}{n} \ln(2) k=nmln(2)

    可以推断出最终的布隆过滤器的长度大概为4.3n;

    此处实现为向上取整将乘数因子的大小控制在5(可根据项目条件酌情替换);

该处将哈希函数以仿函数的形式定义,并将该仿函数声明于模板参数中作为缺省参数,分别为Hash1,Hash2,Hash3;


🪅 布隆过滤器的数据插入

请添加图片描述

布隆过滤器的数据插入只需要调用对应的仿函数(哈希函数),并调用位图中的set接口将其映射至位图中的对应部分即可;

 void set(const K& key){
   size_t len = N * _X;
   size_t hash1 = Hash1()(key) % len;
   _bits.set(hash1);
   size_t hash2 = Hash2()(key) % len;
   _bits.set(hash2);
   size_t hash3 = Hash3()(key) % len;
   _bits.set(hash3);
 }

🪅 布隆过滤器的数据查找

请添加图片描述

关于布隆过滤器的数据查找而言只需要使用与插入同样的逻辑即可;

bool test(const K& key){
  size_t len = N * _X;
  size_t hash1 = Hash1()(key) % len;
  if(!_bits.test(hash1)){
    return false;
  }
  size_t hash2 = Hash2()(key) % len;
  if (!_bits.test(hash2)) {
    return false;
  }
  size_t hash3 = Hash3()(key) % len;
  if (!_bits.test(hash3)) {
    return false;
  }
  else{
    return true;
  }
}

当然实际上布隆过滤器的数据查找在判断是否存在中仍有误判的概率;

  • 数据存在

    数据存在是存在误判的概率,所以在布隆过滤器当中当数据返回结果判定为数据存在时其真正的意义是 “可能存在”;

    由于布隆过滤器是将同一个数据通过不同的哈希函数映射至位图中不同的位置;

    真正在对数据进行查找时,无法真正判断该数据是否存在于该位图当中;

    以该图为例,当一个数据不存在时,实际上其对应映射的位置可能被其他数据所映射,从而导致了返回其存在的误判;

    故实际上在布隆过滤器当中对==“存在”==的概念是不确定的;

  • 数据不存在

    相对的,对于布隆过滤器而言,数据不存在才是准确且值得信任的;

    当一个数据不存在时,其数据对应所映射的位置也必定为空,当其映射的位置其中的一个位置为空时则表示该数据不存在于位图当中;


    以该图为例;


🪅 布隆过滤器的数据删除

请添加图片描述

在布隆过滤器当中实际上是不能进行删除的,而对于有些布隆过滤器的变种而言其可以进行数据的删除操作,这里不进行阐述;

  • 为什么布隆过滤器不支持数据的删除操作?

    在上文中关于布隆过滤器的查找中的"数据是否存在"问题中对于误判有着详细的解释;

    由于无法百分百确认一个数据在布隆过滤器中的位图当中是否存在,所以在布隆过滤器当中不能对数据进行随意的删除;

与上文的例子相符,假设一个数据不存在,而该数据所对应映射的位置恰巧有其他数据的映射,那么则会判断该数据 “可能存在”;

而若是对这个 “可能存在” 的数据进行删除则会影响其他数据;

可能有些人设想在实现布隆过滤器当中使其加入一个关于计数的功能使其能够在布隆过滤器当中对数据进行计数删除操作;

而实际上,若是增加位的计数器从而达到能够对数据进行数据的方法确实可以在某种程度上达到一定效果;

而布隆过滤器中所设置的计数器是按照二进制展开从而达到计数的目的,当数据量大过其本身能够计数的大小时,其将会进行 “计数器回绕”;

  • 例:

    101 -> 110 -> 111 -> 000

这将完全影响对应的秩序;

故对于一般的布隆过滤器而言,其不能进行数据的删除操作;


🪅 布隆过滤器整体代码(供参考)

请添加图片描述

#pragma once

#include "BitSet.h"

struct _BKDR__Hash {
  size_t operator()(const std::string& key) {
    size_t hash = 0;
    for (auto e : key) {
      hash *= 31;
      hash += e;
    }
    return hash;
  }
};
struct _AP__Hash {
  size_t operator()(const std::string& key) {
    size_t hash = 0;
    for (size_t i = 0; i < key.size(); i++) {
      char ch = key[i];
      if ((i & 1) == 0) {
        hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
      } else {
        hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
      }
    }
    return hash;
  }
};
struct _DJB__Hash {
  size_t operator()(const std::string& key) {
    size_t hash = 5381;
    for (auto ch : key) {
      hash += (hash << 5) + ch;
    }
    return hash;
  }
};

template <size_t N, class K = std::string, class Hash1 = _BKDR__Hash , class Hash2 = _AP__Hash,
          class Hash3 = _DJB__Hash>
class BloomFilter {
 public:
 void set(const K& key){
   size_t len = N * _X;
   size_t hash1 = Hash1()(key) % len;
   _bits.set(hash1);
   size_t hash2 = Hash2()(key) % len;
   _bits.set(hash2);
   size_t hash3 = Hash3()(key) % len;
   _bits.set(hash3);
 }

bool test(const K& key){
  size_t len = N * _X;
  size_t hash1 = Hash1()(key) % len;
  if(!_bits.test(hash1)){
    return false;
  }
  size_t hash2 = Hash2()(key) % len;
  if (!_bits.test(hash2)) {
    return false;
  }
  size_t hash3 = Hash3()(key) % len;
  if (!_bits.test(hash3)) {
    return false;
  }
  else{
    return true;
  }
}
 private:
  const static size_t _X = 5;
  MyBitset::bitset<N*_X> _bits;  // N为数据量最多
};

其中"BitSet.h"文件参考上文当中的位图;


🪅 布隆过滤器的应用

请添加图片描述

布隆过滤器(Bloom Filter),其功能的应用范围可以以字面意思进行理解;

其为 “过滤器”,即实际上其本身的功能并不是将数据进行存储,而是对数据进行一个过滤的作用,例如使用布隆过滤器减少磁盘IO或者网络请求;

当一个数据在布隆过滤器当中被判断不存在,由于不存在在布隆过滤器中是一个肯定的,可以信任的返回;

所以当一个数据被判断不存在时,其就可以不需要再进行后续的查询请求;

除了上述应用以外,由于其存在误判性,其也可以在一些可以对误判进行容忍的场景下进行应用;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第7章 Page449 7.8.9智能指针 std::unique_ptr课堂作业,使用智能指针改写foo()函数

源代码&#xff1a; /** \brief 使用std::unique_ptr改写智能指针章节开始的foo()函数** \param* \param* \return**/ #include <iostream> #include <memory>using namespace std;struct O {~O(){cout << "我是被管的对象。我要被释放啦......" …

【Java多线程】对进程与线程的理解

目录 1、进程/任务&#xff08;Process/Task&#xff09; 2、进程控制块抽象(PCB Process Control Block) 2.1、PCB重要属性 2.2、PCB中支持进程调度的一些属性 3、 内存分配 —— 内存管理&#xff08;Memory Manage&#xff09; 4、线程&#xff08;Thread&#xff09;…

关于DVWA靶场Could not connect to the database service的几种解决办法

总的来说这个问题都是 config 配置文件没有修改正确 一般修改数据库的用户名和密码与 phpstudy 一致并且添加了 key 就能初始化成功的 但是我还遇到过另一种情况&#xff0c;修改了上面的东西依旧无法连接到数据库 Could not connect to the database service. Please check …

猫头虎分享已解决Bug || ValueError: Found array with dim 3. Estimator expected <= 2.

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

记录一下,我使用stm32实现pwm波输入,以及对频率和占空比的计算,同时通过串口输出(stm32-timer capture)(实现-重要)

1&#xff0c;首先看下半物理仿真 看下我的配置&#xff1a; 看下计算方法以及matlab的仿真输出的数据&#xff1a; timer3的ch2是选择高电平&#xff0c;计算频率 timer3的ch1是选择的是低电平&#xff0c;用来计算周期 其中TemPIpre表示的是CH2输出的值&#xff0c; TemPI…

飞天使-k8s知识点18-kubernetes实操3-pod的生命周期

文章目录 探针的生命周期流程图prestop 探针的生命周期 docker 创建&#xff1a;在创建阶段&#xff0c;你需要选择一个镜像来运行你的应用。这个镜像可以是公开的&#xff0c;如 Docker Hub 上的镜像&#xff0c;也可以是你自己创建的自定义镜像。创建自己的镜像通常需要编写一…

H5大气的互联网建站服务公司静态HTML网站模板源码

H5大气的互联网建站服务公司静态HTML网站模板源码 源码介绍&#xff1a;一款大气的互联网建站服务公司/工作室静态HTML网站模板&#xff0c;带有多个单页&#xff0c;可自行二开作为工作室或公司官网。 下载地址&#xff1a; https://www.changyouzuhao.cn/13456.html

【初始C++】引用的概念及使用场景、引用与指针的区别、内联函数、类型推导关键字auto、范围for循环、指针空值nullptr

目录 1.引用 1.1引用的概念 1.2引用的特性 1.3使用场景 1.4引用与指针的区别 2.内联函数 2.1内联函数的概念 2.2内联函数的特征 3.auto关键字&#xff08;C11&#xff09; 4.基于范围的for循环&#xff08;C11&#xff09; 5.指针空值nullptr&#xff08;C11&#x…

复习基础知识1

局部变量 写程序时&#xff0c;程序员经常会用到局部变量 汇编中寄存器、栈&#xff0c;可写区段、堆&#xff0c;函数的局部变量该存在哪里呢&#xff1f; 注意&#xff1a;局部变量有易失性 一旦函数返回&#xff0c;则所有局部变量会失效。 考虑到这种特性&#xff0c;人们…

【Python---内置函数】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;Python &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; Python---六大数据结构 往期内容内置函数1.all()2. any()3.bin()4.complex()5.divmod()6.enumerate()7.…

用HTML5 Canvas创造视觉盛宴——动态彩色线条效果

目录 一、程序代码 二、代码原理 三、运行效果 一、程序代码 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!-- 声明文档类型为XHTML 1.0 Transitional -…

windows@命令行映射磁盘驱动器若干方法@开机自动映射网络磁盘

文章目录 windows映射网络磁盘驱动器资源管理器中GUI方式创建命令行方式创建命令行列出驱动器列表删除取消映射持久化配置映射&#x1f47a;记住凭证 FAQ开机自启登录系统后自动挂载&#x1f47a;[以alist webdav 挂载为例]分析对策延迟挂载&#x1f47a;Note 访问已经挂载网络…

【C语言】简单贪吃蛇实现保姆级教学!!!

关注小庄 顿顿解馋૮(˶ᵔ ᵕ ᵔ˶)ა 新年快乐呀小伙伴 引言&#xff1a; 小伙伴们应该都有一个做游戏的梦吧&#xff1f;今天让小庄来用C语言简单实现一下我们的童年邪典贪吃蛇&#xff0c;顺便巩固我们的C语言知识&#xff0c;请安心食用~ 文章目录 贪吃蛇效果一.游戏前工作…

uniapp 开发一个密码管理app

密码管理app 介绍 最近发现自己的账号密码真的是太多了&#xff0c;各种网站&#xff0c;系统&#xff0c;公司内网的&#xff0c;很多站点在登陆的时候都要重新设置密码或者通过短信或者邮箱重新设置密码&#xff0c;真的很麻烦 所以准备开发一个app用来记录这些站好和密码…

使用TinyXML-2解析XML文件

一、XML介绍 当我们想要在不同的程序、系统或平台之间共享信息时&#xff0c;就需要一种统一的方式来组织和表示数据。XML&#xff08;EXtensible Markup Language&#xff0c;即可扩展标记语言&#xff09;是一种用于描述数据的标记语言&#xff0c;它让数据以一种结构化的方…

《区块链公链数据分析简易速速上手小册》第1章:区块链基础(2024 最新版)

文章目录 1.1 区块链技术概览&#xff1a;深入探究与实用案例1.1.1 区块链的核心概念1.1.2 重点案例&#xff1a;供应链管理1.1.3 拓展案例 1&#xff1a;数字身份验证1.1.4 拓展案例 2&#xff1a;智能合约在房地产交易中的应用 1.2 主流公链介绍1.2.1 公链的核心概念1.2.2 重…

深入理解lambda表达式

深入理解ASP.NET Core中的中间件和Lambda表达式 var builder WebApplication.CreateBuilder(args); var app builder.Build(); app.Use(async (context, next) > { // Add code before request. await next(context);// Add code after request.}); 这段C#代码是用于设…

杨中科 .netcore 依赖注入

1.概念 概念 生活中的“控制反转”:自己发电和用电网的电。 依赖注入(Dependency Injection&#xff0c;Dl)是控制反转:(Inversion of Control&#xff0c;l0c)思想的实现方式。 依赖注入简化模块的组装过程&#xff0c;降低模块之间的耦合度 自己发电的代码 var connSetti…

Peter算法小课堂—哈希与哈希表

额……字符串我们是第一次学&#xff0c;给大家铺一些基础的不能再基础的基础&#xff0c; 字符串比较大小 字符串大小的比较&#xff0c;不是以字符串的长度直接决定&#xff0c;而是从最左边第一个字符开始比较&#xff0c;大者为大&#xff0c;小者为小&#xff0c;若相等…

HTTP缓存技术

大家好我是苏麟 , 今天说说HTTP缓存技术 . 资料来源 : 小林coding 小林官方网站 : 小林coding (xiaolincoding.com) HTTP缓存技术 HTTP 缓存有哪些实现方式? 对于一些具有重复性的 HTTP 请求&#xff0c;比如每次请求得到的数据都一样的&#xff0c;我们可以把这对「请求-响…