【C++】哈希思想

news2024/11/25 21:49:31

目录

哈希介绍:

一,位图

1-1,位图的认识

1-2,位图的简单实现

1-3,位图的应用

二,布隆过滤器

2-1,布隆过滤器的认识

2-2,布隆过滤器的简单实现

2-3,布隆过滤器的应用

三,哈希应用海量数据总结


哈希介绍:

        哈希/散列是一种思想和技术,而不是一个具体的容器。哈希思想是一种将元素与其存储位置建立关联的方法。具体来说,哈希思想是通过一个特定的函数(哈希函数)将元素(键值)映射到一个确定的存储位置(哈希值)。这样,当我们需要查找某个元素时,就可以直接根据这个元素对应的哈希值进行查找,而无需遍历整个数据结构。哈希表(或称为散列表)只是根据哈希思想实现的一种具体的数据结构或容器,哈希应用还在其它方面上运用,比如位图,布隆过滤器等。  


一,位图

1-1,位图的认识

        首先我们来观察一道面试题:给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在 这40亿个数中。【腾讯】

        解决以上问题有许多不同的方法,大致分为:1,遍历,时间复杂度O(N)    2,排序,时间复杂度O(NlogN),然后利用二分查找: logN。这两种方法在时间效率上虽可行,但是空间上却存在致命的问题。我们来做详细分析问题。

        我们先来观看空间大小之间的转换。

                1G(吉字节)等于2^32MB,1024^3字节,即1G = 1,073,741,824B。

                1MB(兆字节)等于2^32KB,即1024KB(千字节),即1MB = 1024KB。

                1KB(千字节)等于2^32B,即1024B(字节),即1KB = 1024B。

        数据空间计算时,空间换算可大致约计算为1G = 10^3MB = 10^6KB = 10^9B(10亿B)。1个无符号整数占4字节空间,40亿个不重复的无符号整数共占160亿字节空间,1G约等于10亿字节,160亿数据约等于16G空间大小。当使用以上方法时,普遍计算机上空间根本不够存储,因此需要考虑其它方法。

位图解决

        位图是一种使用二进制位(0或1)来表示某种状态的数据结构,适用于海量数据、数据无重复的场景。位图运用的思想是哈希思想,采取的是直接定值法存储。通常,位图是用来在海量数据中判断某个数据存不存在的,处理海量数据时基本都要运用位图思想。

        数据是否在给定的整形数据中,结果强调的是在或者不在,刚好是两种状态,那么可以使用计算机中最小单位来进行存储此状态,即使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。         

        注意:存储时这里是以位单位来存储数据是否存在的状态,存储状态时采用的又是直接定值法,而无符号整数的范围是[0,2^32 - 1],因此,开辟位空间大小的个数与数据大小无关,只跟数据范围有关,即开辟最大位空间是2^32 - 1 + 1 = 2^32,对应空间大小是0.5G。(0.5G=512MB=2^30*8/2b=2^32)。一旦存在超出此范围的数据量必然有大量的重复数据,如:若是将40亿个数据改为100亿个数据其中必然有大量重复的数据。总的来说,位图开辟空间时只根据数据范围开辟,数据的个数与开辟空间的数量无关,与数据大小范围有关,因为这里使用的是直接定址法,数据的大小对应数据的存储位置。如下图。

        C++标准库中的bitset容器可以被视为一种位图的实现。它提供了一种方便且高效的工具来处理位级别的数据,在bitset中,每一位都代表一个布尔值(true或false),可以用来表示各种信息或状态。bitset容器使用链接文档如下,这里不在说明。

        bitset使用链接:bitset位图使用大全

1-2,位图的简单实现

        通过以上分析,位图是以位单位开辟空间的,数据类型中若不使用位段是没有以位为单位进行空间存储的,因此,这里可使用常用类型int,char,dounle等为存储单元进行位的划分而存储状态。其次这里最好不要直接一次开辟无符号类型最大数据的空间比特位,即2^32次方(42亿多)。因为有时存储的数据可能并没有太大,直接开辟最大空间太浪费数据,除非数据量太大或有预感出现数值太大的数据才采用开辟大空间比特位。这里最好使用非类型模板参数来控制开辟空间的大小。

//开辟n位空间(此空间的大小是根据数据的最大值开辟的,单位是位)
template <size_t n>
class bitset
{
public:
    bitset()
    {
        _bitset.resize(n / 8 + 1, 0); //开辟空间要向上开辟,这里直接暴力加一,即向上开辟8位空间
    }

    //把x映射的位标记成1
    void set(const size_t x)
    {
        size_t i = x / 8;
        size_t j = x % 8;
        _bitset[i] |= (1 << j);
    }

    //把x映射的位标记成0
    void reset(const size_t x)
    {
        size_t i = x / 8;
        size_t j = x % 8;
        _bitset[i] &= ~(1 << j);
    }

    //测试数据x是否存在
    bool test(const size_t x)
    {
        size_t i = x / 8;
        size_t j = x % 8;
        return _bitset[i] & (1 << j);
    }
private:
    vector<char> _bitset;  //这里采取char型,一单元可存储8位(采用int型一单元存储32位)
};

测试样例:

void test_bitset()
{
    bitset<100> bs1;

    //无符号整数最大数据2^32 - 1,测试数据时应开辟2^32位空间,防止数据大小过大,如下
    /*bitset<-1> bs2;
    bitset<UINT_MAX> bs3;
    bitset<0xffffffff> bs4;*/

    bs1.set(50);
    bs1.set(30);
    bs1.set(90);

    for (size_t i = 0; i < 100; i++)
    {
        if (bs1.test(i))
        {
            cout << i << "->" << "在" << endl;
        }
        else
        {
            cout << i << "->" << "不在" << endl;
        }
    }
    bs1.reset(90);
    bs1.set(91);
    cout << endl << endl;

    for (size_t i = 0; i < 100; i++)
    {
        if (bs1.test(i))
        {
            cout << i << "->" << "在" << endl;
        }
        else
        {
            cout << i << "->" << "不在" << endl;
        }
    }
}

1-3,位图的应用

        位图存储的只是不重复数据的状态,使用于海量数据,且位图的局限性很强,只能解决整型数据,无法解决非整型数据,因此位图一般运用于海量数据中的判断问题,如以下:

        1,快速查找某个数据是否在一个集合中
        2,排序 + 去重
        3,求两个集合的交集、并集等
        4,操作系统中磁盘块标记

        下面我们来运用位图解决以下问题:

        1,给定100亿个整数,设计算法找到只出现一次的整数?

        思路:设计两个位图存储100亿个数据,两个位图中标记共有00、01、10、11四种状态。00代表一次都没有出现,01代表只出现了一次,10代表出现了2次,11代表出现了3次及三次以上。在以上封装位图的基础上进行以下算法封装,如下:

template <size_t n>
class two_bitset
{
public:
    void set(const size_t x)
    {
        if (_bitset1.test(x) == false)
        {
            _bitset1.set(x);
        }
        else
        {
            _bitset2.set(x);
        }
    }
    void reset(const size_t x)
    {
        if (_bitset1.test(x) == true)
        {
            _bitset1.reset(x);
        }
        if (_bitset1.test(x) == false && _bitset2.test(x) == true)
        {
            _bitset1.set(x);
            _bitset2.reset(x);
        }
    }
    //以下算法是统计具体出现的次数
    /*int test(const size_t x)
    {
        if (!_bitset1.test(x) && !_bitset2.test(x))
        {
            return 0;
        }
        else if (_bitset1.test(x) == true && _bitset2.test(x) == false)
        {
            return 1;
        }
        else if (_bitset1.test(x) == false && _bitset2.test(x) == true)
        {
            return 2;
        }
        else
        {
            return 3;
        }
    }*/

    bool test(const size_t x)
    {
        if (_bitset1.test(x) == true && _bitset2.test(x) == false)
        {
            return true;
        }
        return false;
    }
private:
    bitset<n> _bitset1; //位图1
    bitset<n> _bitset2; //位图2
};

测试样例:

void test_bitset2()
{
    int a[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6 };
    two_bitset<100> bs;
    for (auto e : a)
    {
        bs.set(e);
    }

    for (size_t i = 0; i < 100; i++)
    {
        //cout << i << "->" << bs.test(i) << endl;
        //输出只出现一次的数据

        if (bs.test(i))
        {
            cout << i << endl;
        }
    }
}

扩展延伸:        

        以上空间需求最大是1G,若是这里条件限制空间只有512MB甚至更小该如何呢?若空间严格限制,可以分次数开辟空间进行操作,比如空间只有512MB,数据大小范围又是[0,2^32 - 1],我们可分两次统计。第一次只统计前一半的数据,即[0,2^31)。第二次统计后一半大小的数据,即[2^31,2^32 - 1],不过由于使用直接定址法时空间不够存储,所以第二次统计时读取数据x - 2^31进行眏射统计。分次数操作虽然可以满足空间内存上的限制,但是需注意每次运行完后的数据以及相关占用内存的都会被清除,下一次的调用不能与上一次数据相关联。运用时一定要思考此种方法是否可行。

        2,给两个文件,分别有100亿个无符号整数,我们只有1G内存,如何找到两个文件交集?

        思路:代码还是问题一中的代码,1G内存刚好够开辟最大的两个位图。分别将两个文件中数据存储在两个位图中,然后直接统计两个位图状态为11的数据。

测试代码:

void test_bitset3()
{
    int a1[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6 };
    int a2[] = { 5,3,5,99,6,99,33,66 };

    bitset<100> bs1;
    bitset<100> bs2;

    for (auto e : a1)
    {
        bs1.set(e);
    }

    for (auto e : a2)
    {
        bs2.set(e);
    }

    //查找两堆存储在位图中的数据的交集
    for (size_t i = 0; i < 100; i++)
    {
        if (bs1.test(i) && bs2.test(i))
        {
            cout << i << endl;
        }
    }
}

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

        思路:与问题二一样,只不过这里只需统计状态为00、01、11的数据。

测试样例:

void test_bitset3()
{
    int a1[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6 };
    int a2[] = { 5,3,5,99,6,99,33,66 };

    bitset<100> bs1;
    bitset<100> bs2;

    for (auto e : a1)
    {
        bs1.set(e);
    }

    for (auto e : a2)
    {
        bs2.set(e);
    }

    //查找出现次数不超过2次的所有整数
    for (size_t i = 0; i < 100; i++)
    {
        if (!bs1.test(i) || !bs2.test(i))
        {
            cout << i << " ";
        }
    }
    cout << endl;
}


二,布隆过滤器

2-1,布隆过滤器的认识

        布隆过滤器和位图具有相似的功能,但是位图运用的是哈希思想中的直接定值法,也就是说只能处理正整数,而布隆过滤器是由一个很长的二进制向量(或称为位数组,即位图)和一系列哈希函数组成的概率型数据结构,可理解为在位图的基础上使用了多个哈希函数进行眏射。这样一来布隆过滤器就可以处理所有类型的数据。

        布隆过滤器的工作原理是,当添加元素时,通过多个哈希函数将元素映射到位数组中的不同位置,并将这些位置设为1。当查询一个元素是否存在于集合中时,布隆过滤器会检查该元素通过哈希函数映射到的所有位置是否都被设置为1。如果所有位置都是1,布隆过滤器就认为该元素可能存在于集合中;如果有任何一个位置是0,那么就可以确定该元素不在集合中。

        注意:布隆过滤器可能出现错误的判断现象。哈希函数由于存在碰撞的可能性,即不同的输入可能会产生相同的哈希结果,因此布隆过滤器可能会错误地认为某个元素存在于集合中。这种错误判断就是误报。这种误报的发生概率与布隆过滤器的大小、哈希函数的数量以及集合中元素的数量有关。在布隆过滤器的大小固定的情况下,如果集合中的元素数量增加,或者哈希函数的数量不足,误报的概率就会增加。比如当我们试图查询一个并不在集合中的元素时,由于哈希碰撞的可能性,该元素通过哈希函数映射到的某些位置可能已经被集合中的其他元素设置为1。这样,布隆过滤器就可能错误地认为该元素存在于集合中,从而产生误报。

        布隆过滤器特点是高效地插入和查询,可以用来告诉你“某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。布隆过滤器虽然存在误判,但是综合效率非常高。

        C++标准库中没有提供布隆过滤器的实现。布隆过滤器不是一种通用的数据结构,而是一种用于解决特定问题(即快速判断元素是否存在于某个集合中)的概率型数据结构。C++标准库的设计原则之一是提供通用、可移植且高效的基础组件。由于布隆过滤器具有特定的应用场景和特性,它可能并不适合作为C++标准库的一部分。相反,对于需要布隆过滤器的开发者,他们可以根据自己的需求实现布隆过滤器或者使用第三方库。因此,C++标准库并没有直接提供布隆过滤器的实现,若有需求可使用第三方库自己实现。

2-2,布隆过滤器的简单实现

        布隆过滤器主要用于判断,即插入和判断,但一般不直接支持删除工作,因为在删除一个元素时,可能会影响其他元素,比如:两个元素在多个哈希函数计算出的比特位上刚好有重叠,一旦删除其中一个,另一个也将受到影响。若非要实现删除功能,我们可将布隆过滤器中的每一比特位扩展成一个小的计数器,插入元素时给对应的哈希地址加一,删除时减1,但计数器也是占用内存空间的,可以说将位存储单元扩大了好几倍,因此,删除操作一般不采用。如下三个哈希函数对应的情况图。

        一旦删除上图中"tencent"元素,如果直接将该元素所对应的二进制比特位置0,“baidu”元素也 被删除了。

        我们模拟实现三个哈希函数,专门处理串类型的布隆过滤器。这里主要操作基本都通过位图实现。如下:

//三大处理字符串的哈希函数
//下面处理串的操作都是大佬研究出出现冲突率很小的算法

struct HashFuncBKDR
{
    size_t operator()(const string& s)
    {
        size_t hash = 0;
        for (auto ch : s)
        {
            hash *= 131;
            hash += ch;
        }
        return hash;
    }
};
struct HashFuncAP
{
    size_t operator()(const string& s)
    {
        size_t hash = 0;
        for (size_t i = 0; i < s.size(); i++)
        {
            if ((i & 1) == 0)
            {
                hash ^= ((hash << 7) ^ (s[i]) ^ (hash >> 3));
            }
            else
            {
                hash ^= (~((hash << 11) ^ (s[i]) ^ (hash >> 5)));
            }
        }
        return hash;
    }
};
struct HashFuncDJB
{
    size_t operator()(const string& s)
    {
        size_t hash = 5381;
        for (auto ch : s)
        {
            hash = hash * 33 ^ ch;
        }
        return hash;
    }
};

template <size_t N,
    class K = string,
    class HashFunc1 = HashFuncBKDR,
    class HashFunc2 = HashFuncAP,
    class HashFunc3 = HashFuncDJB>
    class BloomFilter
{
public:
    BloomFilter()
    {
        _bitset = new bitset<2 * N>;
    }

    void Set(const K& data)
    {
        size_t pos1 = HashFunc1()(data) % N;
        size_t pos2 = HashFunc2()(data) % N;
        size_t pos3 = HashFunc3()(data) % N;

        _bitset->set(pos1);
        _bitset->set(pos2);
        _bitset->set(pos3);
    }
    //判断是只能百分百确定不存在,由于误判无法百分百确定存在
    bool Test(const K& data)
    {
        size_t pos1 = HashFunc1()(data) % N;
        size_t pos2 = HashFunc2()(data) % N;
        size_t pos3 = HashFunc3()(data) % N;

        //判断时只要有一个为零,代表该元素一定不在哈希表中,否则可能在哈希表中
        if (!_bitset->test(pos1))
            return false;
        if (!_bitset->test(pos2))
            return false;
        if (!_bitset->test(pos3))
            return false;

        return true;   //存在误判,不一定准确
    }
private:
    /*注意:不能直接创建对象,库中bitset里面定义的数组是静态数组,如 : int a[N]。因为静态数组开辟的空间存储在栈区上,栈的大小限制取决于多种因素,而在大多数现代操作系统中,默认的栈大小通常不足以容纳数百万个整数的数组。如果你需要分配大量的内存,更好的做法是使用动态内存分配。*/

    /*堆的大小通常比栈大得多,并且可以通过操作系统的内存管理机制进行扩展。C++STL大多数容器的底层通常是在堆上分配空间的,运行完毕之后自动释放*/
    //bitset<2 * N> _bitset;

    //动态开辟内存,直接在堆上存储
    bitset<2 * N>* _bitset;
};

样例测试1:

void Test1()
{
    string strs[] = { "百度","字节","腾讯" };
    BloomFilter<10> bf;
    for (auto& s : strs) bf.Set(s);

    for (auto& s : strs) cout << bf.Test(s) << endl;
    cout << endl;

    for (auto& s : strs) cout << bf.Test(s + 'a') << endl;
    cout << endl;

    cout << bf.Test("摆渡") << endl;
    cout << bf.Test("百渡") << endl;
}

样例测试2:

//此样例主要用来判断误判率
void Test2()
{
    srand(time(0));
    const size_t N = 10000000;
    BloomFilter<N> bf;

    std::vector<std::string> v1;
    //std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";
    //std::string url = "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=65081411_1_oem_dg&wd=ln2&fenlei=256&rsv_pq=0x8d9962630072789f&rsv_t=ceda1rulSdBxDLjBdX4484KaopD%2BzBFgV1uZn4271RV0PonRFJm0i5xAJ%2FDo&rqlang=en&rsv_enter=1&rsv_dl=ib&rsv_sug3=3&rsv_sug1=2&rsv_sug7=100&rsv_sug2=0&rsv_btype=i&inputT=330&rsv_sug4=2535";

    std::string url = "猪八戒";

    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 urlstr = url;
        urlstr += std::to_string(9999999 + i);
        v2.push_back(urlstr);
    }

    //相似字符串集的误判
    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 = "孙悟空";
        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;
}

        再次强调,布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因为有些哈希函数存在一定的误判。

2-3,布隆过滤器的应用

        1,假设用户在使用网站注册昵称,而已存在的昵称不被允许注册,这时系统该如何判断昵称是否存在?

        思路:已注册的昵称肯定是存放在数据库中,但是系统若直接从数据库中查找会导致效率太慢——中间还要进行网络的传播。这时我们可以考虑使用布隆过滤器,当发现已存在的昵称时直接返回拒绝,即使发生了误判——昵称没有注册过误判成了已存在,这里也影响不大。若是不忍受误判,当布隆过滤器判断出已存在时再去数据库中查找。有人可能会说这里判断了两次不是更加浪费吗?布隆过滤器的效率非常高,它在这里主要用于判断“不在”的情况,即使判断“在”,多跑了一次布隆过滤器,性能方面也是非常高的,相反,若两种情况都从数据库中查询,效率方面将非常低。布隆过滤器只是充当过滤价值。

        2,给两个文件A和B,分别有100亿个query(一系列字符串),但我们只有1G内存,如何找到两个文件交集?假设一个query平均占据50字节,分别给出精确算法和近似算法。

        近似算法:属于字符串的查询,位图已排除,直接使用布隆过滤器即可。

        精确算法:根据已知条件可计算出,100亿个query共约占500G(1G大约10亿字节),内存空间明显存储不下。有人可能会想将两文件数据都平均切割成几份存储到小文件中,然后每一小组分别使用指定的结构(比如红黑树、vector等)在内存中存储查找,但是这样一来效率非常低,切分成的小文件Ai需跟所有切分成小文件Bi比对一遍,效率是O(N^2)。

        下面我们来认识下哈希切分。首先,我们先编号1000个小文件Ai和1000个小文件Bi(i = 0,1,2......998,999)。然后从A,B文件中依次读取每一个query,通过哈希函数i = HashFunc(query) % 1000(因为有1000个小文件,所以余1000,对应的哈希值是文件的编号,为什么要开辟1000个小文件下面会说明)分别将query进入Ai、Bi小文件,因为A、B使用的都是同一个哈希函数,若存在相同的query一定会进入编号相同的Ai和Bi小文件(哈希冲突也会进入编号相同的文件),所以,最后分别将编号相同的Ai和Bi放到内存中使用数据结构(最好使用set<string>,因为set会过滤Ai和Bi中相同的数据)比对交集即可。

        以上之所以开辟1000个小文件是因为这样计算下来平均每个小文件共500G / 1000 = 0.5G,即Ai和Bi放入内存中刚好是1G。但是这里也存在问题,以上的计算只是平均理想的情况,实际中每个小文件占据的内存都会有幅度,若相同数据太多或哈希冲突太多,小文件会占据太大空间,可能会使内存存储不下。解决相同数据我们只需要使用set<string>结构过滤相同的数据即可,若是set还是抛异常,说明哈希冲突的太多,需要二次处理,使用不一样的哈希函数二次使用哈希切分。

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

        思路:以上一样,使用哈希切分开辟100个小文件,相同IP会进入相同编号的文件。下面可以使用map<string, int>统计次数,若map抛异常说明冲突很多,然后换个哈希函数进行二次哈希切分。


三,哈希应用海量数据总结

海量数据的处理方式有三种:位图、布隆过滤器、哈希切分。

        位图:只能处理正整数且判断数据的状态,运用时要考虑正整数和数据状态两方面。

        布隆过滤器:判断任意类型的数据状态,无论是空间效率还是时间效率都非常高,但是本身却存在“误判”,即布隆过滤器判断出“不在”是百分百确定的,判断出“在”不一定确定。若情况不允许误判可利用其它结构来填补这一现象。如以上网站注册昵称。

        哈希切分:能够精确的解决布隆过滤器的误判缺点。此方法是将大事化小,运用哈希思想将大数据切成一小块(不是平均切分),切小之后放到内存中,利用相关的数据结构和算法解决问题。此方法最后才考虑,优先考虑具有特点的数据结构是否可以解决,如:位图、布隆过滤器等。

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

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

相关文章

【产品经理修炼之道】- 需求挖掘之手机话费充值

画原型图从来就不是面试考察的重点&#xff0c;分析、解决问题才是产品经理的核心能力。那么该如何积累产品经验呢&#xff1f;本文以以「手机话费充值」为案例&#xff0c;分享整个设计流程和思路&#xff0c;希望对你有所启发。 不少产品新人有个误区&#xff1a;产品经理的日…

ThingsBoard处理设备上报的属性并转换为可读属性

一、前言 二、案例 1、AI生成JSON数据体 2、将json数据体直接通过遥测topic发送查看效果 3、可查看目前整个数据都在一起 ​编辑 4、配置附规则链路 5、对msg的消息值&#xff0c;进行数据的转换&#xff0c;并从新进行赋值。 6、规则链路关联关系 7、再次通过MQTT发送遥…

顺通鞋厂在线订单系统:优化订单流程,降低运营成本

引入在线订单系统成为企业优化订单流程、降低运营成本的有效途径。在线订单系统能够将订单信息实时共享给企业内部的各个部门以及外部供应商和客户。在线订单系统能够通过自动化处理订单流程&#xff0c;减少人工干预和重复劳动。在线订单系统通过优化订单流程&#xff0c;实现…

1.C++入门(上)

目录 1.C关键字 2.命名空间 作用域方面的优化 a.命名空间定义 b.命名空间使用 3.C 输入&输出 1.C关键字 C有63个关键字&#xff0c;C语言有32个关键字&#xff0c;存在重叠如荧光笔标出 2.命名空间 作用域方面的优化 如果变量&#xff0c;函数和类的名称都存在于全…

【C++】C++中的构造函数和析构函数详解

欢迎来到CILMY23的博客 本篇主题为&#xff1a;C中的构造函数和析构函数详解 个人主页&#xff1a;CILMY23-CSDN博客 系列专栏&#xff1a;Python | C | C语言 | 数据结构与算法 感谢观看&#xff0c;支持的可以给个一键三连&#xff0c;点赞关注收藏。 写在前头&#xff1…

【算法一则】【动态规划】求二维数组可组成的最大正方形

题目 在一个由 ‘0’ 和 ‘1’ 组成的二维矩阵内&#xff0c;找到只包含 ‘1’ 的最大正方形&#xff0c;并返回其面积。 示例 1&#xff1a; 输入&#xff1a;matrix [["1","0","1","0","0"],["1","0&…

为何鸿蒙开发成为求职市场的香饽饽 ?

在当今社会&#xff0c;技术的快速发展与更新迭代使得职场竞争愈发激烈。对于求职者来说&#xff0c;掌握一门热门且前景广阔的技术&#xff0c;无疑是获得理想工作的关键。鸿蒙开发&#xff0c;作为华为自主研发的操作系统开发技术&#xff0c;正逐渐成为求职市场的热门选择。…

手搓单链表(无哨兵位)(C语言)

目录 SLT.h SLT.c SLTtest.c 测试示例 单链表优劣分析 SLT.h #pragma once#include <stdio.h> #include <assert.h> #include <stdlib.h>typedef int SLTDataType;typedef struct SListNode {SLTDataType data;struct SListNode* next; }SLTNode;//打印…

触发器的基本概念及分类

目录 触发器的基本概念 作用对象 触发事件 触发条件 触发时间 触发级别或者触发频率 触发器的分类 DML 触发器 INSTEAD OF 触发器 系统触发器 Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 触发器的基本概念 …

表格的单元格合并和表头的合并——vxe-table

vxe-table的官网&#xff1a;https://vxetable.cn/#/table/advanced/mergeCell在你的项目中下载安装完成后&#xff0c;先在main.js文件中引入&#xff1a; import VXETable from vxe-table import vxe-table/lib/style.css Vue.use(VXETable)一、单元格合并 效果图&#xff…

2024年【安全生产监管人员】考试技巧及安全生产监管人员模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【安全生产监管人员】考试技巧及安全生产监管人员模拟考试&#xff0c;包含安全生产监管人员考试技巧答案和解析及安全生产监管人员模拟考试练习。安全生产模拟考试一点通结合国家安全生产监管人员考试最新大纲…

Java | Leetcode Java题解之第50题Pow(x,n)

题目&#xff1a; 题解&#xff1a; class Solution {public double myPow(double x, int n) {long N n;return N > 0 ? quickMul(x, N) : 1.0 / quickMul(x, -N);}public double quickMul(double x, long N) {if (N 0) {return 1.0;}double y quickMul(x, N / 2);retu…

大数据开发详解

点击下载《大数据开发详解》 1. 前言 随着信息化时代的快速发展&#xff0c;大数据已经成为了企业和组织不可或缺的重要资源。大数据开发则是指通过一系列技术手段&#xff0c;对海量数据进行收集、存储、处理、分析和挖掘&#xff0c;以实现数据的价值化利用。大数据开发涉及…

进口透明可视耐腐蚀PFA进样管特氟龙圆底试管适配MC-ICP-MS

PFA进样管可适配Neptune plus多接收器等离子质谱仪&#xff08;MC-ICP-MS&#xff09;&#xff0c;广泛应用于地球化学、核保障、环境科学、金属组学领域&#xff0c;在生物、物理、化学、材料等多个学科的交叉方向也有良好的应用前景。 外观半透明&#xff0c;便于观察管内情…

YOLOV5 TensorRT部署 BatchedNMS(engine模型推理)(下)

主要是在王新宇代码的基础上改进,引入对BatchedNMS的解码 文章目录 1. 修改yolov5.cpp2.修改yololayer.h1. 修改yolov5.cpp 首先增加全局变量,名字根据转onnx时修改的节点名字来,查看onnx文件可以看到,顺序不要弄错。 const char *INPUT_NAME = “images”; const char …

基于深度学习的实时人脸检测与情绪分类

情绪分类 实时人脸检测与情绪分类 Kaggle Competion 数据集 fer2013 中的测试准确率为 66%CK数据集的检验准确率为99.87%情绪分类器模型预测从网络摄像头捕获的实时视频中的平均成本时间为 4~ 10ms 关键技术要点&#xff1a; 实时人脸检测&#xff1a;系统采用了前沿的人脸检…

提交链码-编辑前后端,调用链码功能

一 . 链码介绍 1.什么链码&#xff1f; • 链码是一段用 Go、Node.js 或者 Java 实现了规定接口的程序。链码在安全的Docker容器中运行&#xff0c; 与背书节点的进程隔离。通过应用程序提交的交易&#xff0c;链码初始化和管理账本状态。• 链码通常处理网络成员协商达成的业…

vue下载文件时显示进度条

1.单个下载&#xff08;开始是导出按钮 下载显示进度条&#xff09; html <el-button click.stop"exportReport(scope.row, scope.index)" v-if"!scope.row.schedule" icon"el-icon-download"size"small" type"text"styl…

2023最新!MySQL8于win10环境下的安装配置保姆级教程

2023最新&#xff01;MySQL8于win10环境下的安装配置保姆级教程 MySQL官网&#xff1a;https://www.mysql.com/downloads/ 导航 文章目录 2023最新&#xff01;MySQL8于win10环境下的安装配置保姆级教程导航一、MySQL下载二、安装MySQLchoosing a Setup Typeselect products…

软件测试面试题(三)

Selenium 相关Selenium 基础什么是 Selenium&#xff1f;什么是 Selenium WebdriverS 什么是 elenium IDE&#xff1f;常用自动化测试工具机器运行原理&#xff0c;写出一段元素查找的代码&#xff1f;如何开展自动化测试框架的构建&#xff1f;如何设计自动化测试用例webdrive…