海量数据处理----哈希分治

在开始之前,因为以下代码都是使用的C++以及其中的容器来实现,所以要先对容器进行简单的理解

vector:属于C++的顺序容器之一,底层类似“动态数组”。也就是大小可以动态改变大的数组。因为其里面提供了resize扩容成员方法。并且也提供了[]运算符重载,可以让我们像使用数组一样去访问其元素。它还提供了迭代器,我们也可以使用迭代器遍历和访问其元素。顺序容器在删除(erase)和增加(insert)元素时,会出现迭代器失效问题。

priority_queue:也是C++顺序容器之一,优先级队列。底层是一个“特殊的队列”,为何特殊?其插入元素并不是像队列一样先进后出,依次进行。而是根据权值的高低,进行自动的排序放置,底层默认是一个小根堆,可以根据传入greater函数对象来把它变成大根堆。

map:属于C++关联容器之一,底层是红黑树,会根据键值进行自动的排序,所以其对于数据查找效率会很快,它底层pair第一个元素是键值,第二个元素就是数据。Map不允许键值重复。其次增删元素都不会造成迭代器失效。与其相似的有multimap(允许键值重复)。

TOP K与查重问题

1.在海量数据中查找前K个最大或最小的数:

    srand(time(NULL));                                    //海量数据处理
    vector<int> vec1;
    for (int i = 0; i <100000; ++i)
    {
        vec1.push_back(rand() % 100000);                   //放入10万个数据
    }

    priority_queue<int, vector<int>> v1;  //如果要是找前K个最大数就改成
                                          //priority_queue,greater> v1;  再将下面的判断条件改成v1.top()
    for (int i = 0; i < 10; ++i)                   
    {
        v1.push(vec1[i]);
    }                                     //先放K个数到优先级队列建立初始根堆

    for (int i = 10; i < 100000; ++i)
    {
        if (v1.top()>vec1[i])          //遍历后面的数和堆顶元素比较,然后根据关系再调整根堆
        {
            v1.pop();
            v1.push(vec1[i]);
        }
    }   

    for (int i = 0; i < 10; ++i)                 //前十个最小的数
    {
        cout << v1.top()<

2.在海量数据里查看重复次数大于K的数:

map<int, int> intmap;                                       //利用hash_map 解决海量数据中重复数据
    vector<int>::iterator it = vec1.begin();         //使用迭代器遍历
    for (; it != vec1.end(); ++it)
    {
        //intmap[*it]++;                         //map的[]运算符重载自带插入

        map<int, int>::iterator it2 = intmap.find(*it);               //利用Map自带的find去寻找,因为泛型的find只会去遍历查找,效率太低
        if (it2 == intmap.end())                          //第一次出现则标记次数为1
        {
            intmap.insert(make_pair(*it, 1));                  //make_pair打包数据
        }
        else                                       //后面再出现则++
            it2->second++;
    }

    map<int, int>::iterator it1 = intmap.begin();
    for (; it1 != intmap.end(); ++it1)
    {
        if (it1->second > 10)
            cout << "key:" << it1->second << "val:" << it1->first << endl;
    }

3.查看重复次数的TOP K:

priority_queue<int,vector<int>, greater<int>> v2;                   

    for (int i = 0; i < 10; ++i)                          
    {
        v2.push(intmap[i]);
    }

    for (int i = 10; i < 100000; ++i)
    {
        if (v2.top()for (int i = 0; i < 10; ++i)
    {
        cout << v2.top() << " ";
        v2.pop();
    }

4.查看重复次数前K的元素:


struct _Data                                  //封装数据
{
    _Data() :_data(-1), _count(0){}
    _Data(const int&data, const int &count) :_data(data), _count(count){}
    bool operator>(const _Data &src)const{ return _count > src._count; }
    bool operator<(const _Data &src)const{ return _count < src._count; }
    int _data;                  //数据
    int _count;                 //重复次数
};


    priority_queue<_Data, vector<_Data>, greater<_Data>> v3;   
    map<int, int>::iterator it3 = intmap.begin();
    for (; it3 != intmap.end(); ++it3)
    {
        if (v3.size() < 10)             
        {
            v3.push(_Data(it3->first, it3->second));
        }
        else
        {
            if (it3->second>v3.top()._count)
            {
                v3.pop();
                v3.push(_Data(it3->first, it3->second));
            }
        }
    }

    while (!v3.empty())
    {
        cout << "data:" << v3.top()._data << "count:" << v3.top()._count << endl;
        v3.pop();
    }

5.数据过于庞大,从而无法一次装载到内存中,因此通过装载到磁盘文件中进行处理:


    srand(time(NULL));

    FILE * p = fopen("data.txt", "w");              //先把大量数据放入文件中
    if (NULL == p)
    {
        return 0;
    }
    for (int i = 0; i < 1000000; ++i)
    {
        fprintf(p, "%d ", rand() % 32767);
    }

    fclose(p);

    const int FILE_SIZE = 200;                     //海量数据分文件处理  当数据量大到无法一次装到内存中,我们可以分批放入文件操作
    FILE *_pf[FILE_SIZE] = { NULL };
    int data = 0;

    for (int i = 0; i//打开FILE_SIZE个小文件
    {
        char fileName[20] = "data";
        char buf[4] = { 0 };
        _itoa(i, buf, 10);
        strcat(fileName, buf);                       //给小文件以data1----data199形式命名
        _pf[i] = fopen(fileName, "w");
    }

    FILE *pf = fopen("data.txt", "r");
    while (!feof(pf))                          //feof当文件没有结束  文件结束以EOF结束
    {
        fscanf(pf, "%d", &data);                  //从data.txt中读出数据
        int fileIndex = data%FILE_SIZE;           //将数据%FILE_SIZE的映射形式分配到每个小文件中。
        fprintf(_pf[fileIndex], "%d ", data);    //把数据放入对应的小文件
    }
    for (int i = 0; imap<int, int> intmap;                   //利用map表建立映射关系
    priority_queue<_Data, vector<_Data>, greater<_Data>> v1; 
    int num;
    for (int i = 0; i < FILE_SIZE; ++i)
    {
        char fileName[20] = "data";
        char buf[4] = { 0 };
        _itoa(i, buf, 10);
        strcat(fileName, buf);
        _pf[i] = fopen(fileName, "r");
        while (!feof(_pf[i]))
        {
            fscanf(_pf[i], "%d", &num);         //从对应文件读出数据并计数
            intmap[num]++;                      //map的[]运算符重载自动带有插入操作。默认是0.
        }
        map<int, int>::iterator it3 = intmap.begin();    
        for (; it3 != intmap.end(); ++it3)
        {
            if (v1.size() < 10)
            {
                v1.push(_Data(it3->first, it3->second));
            }
            else
            {
                if (it3->second>v1.top()._count)
                {
                    v1.pop();
                    v1.push(_Data(it3->first, it3->second));
                }
            }
        }
        intmap.clear();                  //使用一次则清除一次map表数据
        fclose(_pf[i]);                  
        _pf[i] = NULL;
    }
    while (!v1.empty())
    {
        cout << "data:" << v1.top()._data << "count:" << v1.top()._count << endl;
        v1.pop();
    }

综合上面的问题我们来总结一下处理海量数据的技巧:
分而治之 + hash统计 + 堆/快速排序

1.分而治之/hash映射:针对数据太大,内存受限,只能是:把大文件化成(对数据取模映射)小文件,即16字方针:大而化小,各个击破,缩小规模,逐个解决。

2.hash统计:当大文件转化了小文件,那么我们便可以采用常规的hashmap(ip,value)(代码中我使用了C++的map来进行映射底层是红黑树)来进行频率统计。

3.堆/快速排序:统计完了之后,便进行排序(可采取堆排序,在上述代码中我采用的是C++的priority_queue优先级队列,意思相同),得到次数最多的数据。

当我们的数据能够一次的装入内存的时候,我们就可以不需要第一步,直接进行hash统计然后利用堆排序即可。如果不能则三步都需要做到,尤其是对内存大小有限制的问题。

你可能感兴趣的:(大数据处理)