【C++】哈希思想

目录

哈希介绍:

一,位图

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抛异常说明冲突很多,然后换个哈希函数进行二次哈希切分。


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

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

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

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

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

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

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

相关文章

3d设计网站有哪些(1)?渲染100邀请码1a12

做3d设计需要各类资源&#xff0c;比如模型、材质、贴图等&#xff0c;如果这些都要自己做会非常麻烦&#xff0c;所幸现在有网站可以下载&#xff0c;有的免费有的收费&#xff0c;这篇文章我就先分享几个给有需要的朋友看看吧。 1、Pikbest Pikbest成立于2018年7月12日&…

万辰集团如何破局“增收不增利”的困境?

在波澜不惊的食用菌加工行业&#xff0c;万辰集团&#xff08;300972.SZ&#xff09;曾是一名平凡的参与者。2021年战略转型的号角吹响&#xff0c;万辰集团挥别了传统业务&#xff0c;转而投身于快速增长的量贩零食市场&#xff0c;并迅速扩张到成为这一领域的重要玩家。万辰的…

TDSQL手动调整备份节点或冷备节点

一、背景描述 近期TDSQL数据库备份不稳定&#xff0c;有些set实例的备份任务未自动发起。经排查分析&#xff0c;存在多个set实例容量已经超过TB级别&#xff0c;且冷备节点都是同一台。因此&#xff0c;需要手动将当前备份节点改到其他备节点&#xff0c;开启增量备份&#x…

C语言笔试题之重排链表

重排链表 实例要求 1、给定一个单链表 L 的头节点 head &#xff0c;单链表 L 表示为&#xff1a; L0 → L1 → … → Ln - 1 → Ln2、请将其重新排列后变为&#xff1a; L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …3、不能只是单纯的改变节点内部的值&#xff0c;而是…

高级STM32应用开发:使用HAL库和RTOS

引言 STM32系列微控制器以其高性能、丰富的外设支持和低功耗特性&#xff0c;在工业、汽车及消费电子市场中占有显著地位。 下面我们旨在探讨STM32的高级开发技术&#xff0c;包括硬件抽象层&#xff08;HAL&#xff09;库的使用和实时操作系统&#xff08;RTOS&#xff09;的…

python批量删除文件

python批量删除文件 1、查询与删除2、添加模块到地址中3、批量删除多个路径中不需要导出的文件 1、查询与删除 mport osdef get_files_in_folder(folder_path):files []for file in os.listdir(folder_path):if os.path.isfile(os.path.join(folder_path, file)):files.appen…

Kafka 生产者应用解析

目录 1、生产者消息发送流程 1.1、发送原理 2、异步发送 API 2.1、普通异步发送 2.2、带回调函数的异步发送 3、同步发送 API 4、生产者分区 4.1、分区的优势 4.2、生产者发送消息的分区策略 示例1&#xff1a;将数据发往指定 partition 示例2&#xff1a;有 key 的…

大数据—数仓分层概论

数据仓库&#xff08;Data Warehouse&#xff09;是一个用于集成和存储大量数据的系统&#xff0c;旨在支持企业的决策制定过程。它是一个面向主题的、集成的、非易失的数据集合&#xff0c;用于支持管理决策制定过程。数据仓库的发展源于企业对数据分析和决策支持的需求&#…

tcp服务器端与多个客户端连接

如果希望Tcp服务器端可以与多个客户端连接&#xff0c;可以这样写&#xff1a; tcpServernew QTcpServer(this);connect(tcpServer,SIGNAL(newConnection()),this,SLOT(onNewConnection())); void MainWindow::onNewConnection() {QTcpSocket *tcpSocket;//TCP通讯的Sockettcp…

Linux系统安全及应用(1)

目录 一.账号安全控制 系统账号清理 二.密码安全控制 密码安全控制 三.命令历史限制 命令历史限制 四.限制su切换用户 1&#xff09;将信任的用户加入到wheel组中 2&#xff09;修改su的PAM认证配置文件 ​编辑五.PAM认证的构成 六.使用sudo机制提升权限…

PT Application Inspector 4.5 (Linux) - 静态、动态和交互式应用程序安全测试

PT Application Inspector 4.5 (Linux) - 静态、动态和交互式应用程序安全测试 唯一一款提供高质量分析和便捷工具以自动确认漏洞的源代码分析器 请访问原文链接&#xff1a;PT Application Inspector 4.5 (Linux) - 静态、动态和交互式应用程序安全测试&#xff0c;查看最新…

自定义一个RedisTemplate

1.引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis&…

【09-选择合适的评估指标:Scikit-learn中的性能度量】

文章目录 前言1. 了解评估指标的重要性2. 分类问题的评估指标准确率精确率和召回率F1分数混淆矩阵ROC曲线和AUC3. 回归问题的评估指标均方误差(MSE)均方根误差(RMSE)平均绝对误差(MAE)R分数4. 如何选择评估指标5. 使用Scikit-learn选择评估指标结论前言 在机器学习中,评…

跳出框架:Facebook的创新策略与社交影响

1. 引言 在数字化时代&#xff0c;社交媒体如同一面镜子&#xff0c;反映出我们社会的多元性和变革。Facebook&#xff0c;作为这面镜子中最明亮的一个&#xff0c;不仅改变了人们的日常生活&#xff0c;更深刻地塑造了社交、文化和经济的面貌。本文将深入探讨Facebook的创新策…

React真的好难用

我发现React就像个宗教一样&#xff0c;网络上总有一群信徒。信徒&#xff1a;React天下第一&#xff0c;谁也不能说他不好。 网络上大佬对React的评价一般有几类&#xff1a; React跟Vue比就是手动档和自动档的区别&#xff0c;高手都开手动档。—— 就一个破打工的&#xf…

Swin Transformer—— 基于Transformer的图像识别模型

概述 Swin Transformer是微软研究院于2021年在ICCV上发表的一篇论文&#xff0c;因其在多个视觉任务中的出色表现而被评为当时的最佳论文。它引入了移动窗口的概念&#xff0c;提出了一种层级式的Vision Transformer&#xff0c;将Shifted Windows&#xff08;移动窗口&#x…

c++图论基础(1)

目录 无向图 无向图度 无向图性质 有向图 有向图度 有向图性质 图的分类&#xff1a; 稀疏图&#xff1a; 稠密图&#xff1a; 零图&#xff1a; 有向完全图&#xff1a; 无向完全图&#xff1a; 度序列&#xff1a; 图是由顶点集合(简称点集)和顶点间的边(简称边…

mac上安装Tomcat

1. 简介 Tomcat 是一个开源的 Java 服务器&#xff0c;它实现了 Java Servlet、JavaServer Pages&#xff08;JSP&#xff09;和Java WebSocket 技术。Tomcat 是 Apache 软件基金会的一个项目&#xff0c;是一个轻量级、高性能的 Web 容器。作为一个 Web 服务器&#xff0c;To…

【Java EE】CAS原理和实现以及JUC中常见的类的使用

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

11.JAVAEE之网络原理1

1.应用层(和程序员接触最密切) 应用程序 在应用层这里,很多时候, 都是程序员"自定义"应用层协议的,(当然,也是有一些现成的应用层协议)&#xff08;这里的自定义协议,其实是非常简单的~~协议 >约定,程序员在代码中规定好,数据如何进行传输) 1.根据需求, 明确要传…