在开始之前,因为以下代码都是使用的C++以及其中的容器来实现,所以要先对容器进行简单的理解
vector:属于C++的顺序容器之一,底层类似“动态数组”。也就是大小可以动态改变大的数组。因为其里面提供了resize扩容成员方法。并且也提供了[]运算符重载,可以让我们像使用数组一样去访问其元素。它还提供了迭代器,我们也可以使用迭代器遍历和访问其元素。顺序容器在删除(erase)和增加(insert)元素时,会出现迭代器失效问题。
priority_queue:也是C++顺序容器之一,优先级队列。底层是一个“特殊的队列”,为何特殊?其插入元素并不是像队列一样先进后出,依次进行。而是根据权值的高低,进行自动的排序放置,底层默认是一个小根堆,可以根据传入greater函数对象来把它变成大根堆。
map:属于C++关联容器之一,底层是红黑树,会根据键值进行自动的排序,所以其对于数据查找效率会很快,它底层pair第一个元素是键值,第二个元素就是数据。Map不允许键值重复。其次增删元素都不会造成迭代器失效。与其相似的有multimap(允许键值重复)。
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统计然后利用堆排序即可。如果不能则三步都需要做到,尤其是对内存大小有限制的问题。