一.题目及内容
大数据分析问题
[问题描述]
某搜索公司一天的用户搜索词汇是海量的(百亿数据量),请设计一种求出每天最热top 100 词汇的可行办法。
[基本要求]
(1) 随机生成海量数据,存入文件;从文件读入数据来处理。
(2) 显示数据文件的每一次处理结果。
二.思路过程
(1)生成随机大数据,并且要求大数据存在交集数据,以此保证有热词存在。
(2)顺序读大文件,对于每一行,也就是每个词x,取hash(x)%n,
然后按照该值存到n个小文件中(即为1,2,3,4…..n),这样将MB级别,或者GB级别大文件分块成kb级别,
如果有的文件超过了1M大小,还可以按照类似的方法继续往下分,直到分解得到的小文件的大小都不超过1M;
(3)读入每一个小文件,利用hash_map统计每个文件中出现的词以及相应的频率,
创建动态数据结构node组,存储data(键值)和num(频率),利用最大堆排列,并取出出现频率最大的100个词,
这里注意不一定只有100词,需要统计100之后是否存在和100相同的值,并把100词和相应的频率以结构[key,num]存入文件中,这样又得到n个文件。
(4)把n个文件读入归并成1个文件all.txt, 创建动态数据结构node组,存储data(键值)和num(频率),
利用最大堆排列,取出出现频率最大的100个词,思路到此。
三. 程序流程图和带有注释的程序
1.生成大数据文件
这里我使用的python生成的数据,之所以用python是当时我想直接在服务器上生成,减少本地电脑的负担。最后生成的数据是15003190条数据,也就是1500万左右,空间81MB,虽然这个数字达不到百亿级别,但是理论上百亿数据是一样的思路。之所以用英文不用中文是由于最开始使用的是随机汉字,但是汉字交集很小,很难有热词。
流程:
循环(1.5k)== 设定随机来源- > 选取长度随机词合并 -> 存储文件
Python代码:# which used for inserting new data
1. def create():
2. filepath = 'data.txt'
3. dataFrom = ['a','b','c','d','e','f','g','h','i','j'] # 随机来源
4. for i in range(10000000):
5. if(i%10000 == 0):
6. print(i)
7.
8. data = ''
9.
10. for j in range(random.randint(2,5)):
11. data += random.choice(dataFrom) # 产生2到5的长度数据
12.
13. with open(filepath , 'a' ) as f: # 存储文件
14. f.write(data + '\n')
15. f.close()
ps:事后写报告发现这里with open没必要每个循环都执行。。。可以修改为全局性!
2.顺序读大文件
这里用的是c++,对于每一行,也就是每个词x,取hash(x)%n,然后按照该值存到n个小文件中(即为1,2,3,4……n),这样将MB级别,或者GB级别大文件分块成kb级别,最后我得到的小文件最大的占用就是609kb。
流程:
设定分块大小num –> 读入大文件all.txt -->
循环(=真) ==对每一行哈希计算%num=分块位置- > 存储对应的分块区域
#include <iostream>
2. #include <fstream>
3. #include <string>
4. #include <typeinfo>
5. #include <iomanip>
6.
7. using namespace std;
8.
9.
10. int spit(int num = 500) {
11.
12. hash<string> str_hash; // 哈希函数
13.
14. ifstream file;// 大数据来源
15. file.open("C:\\Users\\lenovo\\Desktop\\大二上作业\\数据结构\\data.txt",
ios::in);
16.
17. if (!file.is_open())
18.
19. return 0;
20.
21.
22. std::string strLine;
23.
24. int i = 0;
25.
26. ofstream *ofile = new ofstream[num+1]; // 预处理存储num个小文件,num可以自定义,这里设置500
27.
28. for (i = 1; i <= num; i++) { // 目的是open只需要1次,而不是每一次写入都要open,浪费资源
29. string filename = to_string(i) + ".txt"; // 存储名
30. ofile[i].open("C:\\Users\\lenovo\\Desktop\\大二上作业\\数据结构\\ton\\" +
filename);
31. }
32.
33. i = 0;
34.
35. while (getline(file, strLine))// 读入大数据文件每一行
36. {
37. i++;
38.
39. if (strLine.empty()) // 是否为空
40. continue;
41.
42. int ton = str_hash(strLine) % num; // 哈希计算分块位置
43.
44. ofile[ton] << strLine << endl; // 写入一行
45.
46. if (i % 10000 == 0) {
47. cout << i << endl; // 每写入10000行输出一次,进度说明
48. }
49.
50. }
51.
52. file.close();
53. for (i = 1; i <= num; i++) {
54. ofile[i].close();
55. }
56. delete[] ofile;
57. }
3.读入每一个小文件
利用hash_map统计每个文件中出现的词以及相应的频率
创建动态数据结构node组,存储data(键值)和num(频率),利用最大堆排列,并取出出现频率最大的100个词,这里注意不一定只有100词,需要统计100之后是否存在和100相同的值,并把100词和相应的频率以结构[key,num]存入文件中,这样又得到n个文件。
这里是最核心的地方。
流程:
循环(500)==
读入小文件 - > 利用hash_map统计频率 ->
hash_map 遍历到node结构的tree组 ->
利用最大堆对tree结构排序 - >
输出前一百的数据到文件
1. int get100() {
2.
3. for (int q = 1; q <= 500; q++) {
4. hash_map<string, int> hm; //建立hash_map 结构 以数据作为key,频率作为value
5. hash_map<string, int> ::iterator it;
6.
7. ifstream file; // 建立小文件对象
8. ofstream ofile; // 建立小文件对象
9.
10. string filename = to_string(q) + ".txt"; // 文件来向和去向设定
11. file.open("C:\\Users\\lenovo\\Desktop\\大二上作业\\数据结构\\ton\\" + filename, ios::in);
12. ofile.open("C:\\Users\\lenovo\\Desktop\\大二上作业\\数据结构\\100\\" + filename);
13.
14. if (!file.is_open())
15. break;
16.
17. string strLine;
18. int allNum = 0; // 统计当前小文件的行数,便于创建动态结构node组
19. while (getline(file, strLine)) {
20. if (strLine.empty())
21. continue;
22. hm[strLine] += 1; // hash_map 统计频率
23. allNum++;
24. }
25. cout << "*****" << endl;
26.
27. it = hm.begin();
28.
29. node* tree = new node[allNum]; // 用来排序
30. int startNum = 0;
31.
32. while (it != hm.end()) // 遍历hash_map
33. {
34. tree[startNum].num = it->second; //存储数据
35. tree[startNum].data = it->first;
36. startNum++;
37. // cout << "pair it_map.key = " << it->first << " pair it_map.string = " << it->second << endl;
38. ++it;
39. }
40.
41. Heap_sort(tree, allNum);// 最大堆排列,传入的是node结构
42. cout << "***" << endl;
43.
44. int i = 0;
45. while (true) // 输出前一百的数据
46. {
47.
48. i++;
49. cout << i << " " << tree[i].num << " " << tree[i].data << endl;
50. ofile << tree[i].data <<","<< tree[i].num << endl;
51.
52. if (i >= 100 && tree[i].num != tree[i + 1].num) // 保证100之后是否存在和100相同的数据
53. break;
54.
55.
56.
57. }
58. delete[] tree; // 释放空间
59. file.close();
60. ofile.close();
61.
62. }
63.
64. return 0;
65. }
4.把n个文件读入归并成1个文件all.txt。
流程:
循环(500)== 写入同一个all.txt ->统计行数返回。
1. int getALl(int &num) {
2.
3. ofstream ofile;
4. ofile.open("C:\\Users\\lenovo\\Desktop\\大二上作业\\数据结构\\all.txt"); //来源
5.
6. for (int q = 1; q <= 500; q++) {
7. ifstream file;
8.
9.
10. string filename = to_string(q) + ".txt"; //去向
11. file.open("C:\\Users\\lenovo\\Desktop\\大二上作业\\数据结构\\100\\" + filename, ios::in);
12.
13. if (!file.is_open())
14. break;
15.
16. string strLine;
17.
18. while (getline(file, strLine)) {
19. if (strLine.empty())
20. continue;
21. ofile << strLine << endl;
22. num += 1;
23.
24. }
25.
26. file.close();
27.
28. }
29.
30. return 0;
31. }
5.创建动态数据结构node组,
存储data(键值)和num(频率),利用最大堆排列,取出出现频率最大的100个词,思路到此。
输出前一百的数据到文件
1. int main() {
2.
3. int dataLine;
4. getALl(dataLine);
5.
6. cout << dataLine;
7.
8. ifstream file;
9. file.open("C:\\Users\\lenovo\\Desktop\\大二上作业\\数据结构\\all.txt", ios::in);
10. ofstream ofile;
11. ofile.open("C:\\Users\\lenovo\\Desktop\\大二上作业\\数据结构\\last.txt");
12.
13. node* tree = new node[dataLine];// 动态结构node
14. int startNum = 0;
15.
16. string strLine;
17.
18. while (getline(file, strLine)) {
19. if (strLine.empty())
20. continue;
21.
22. vector<string> v = split(strLine , ","); //分割split;
23. tree[startNum].num = to_int(v[1]);
24. tree[startNum].data = v[0];
25. startNum++;
26.
27. }
28.
29.
30. Heap_sort(tree, dataLine);
31. cout << "***" << endl;
32.
33. int i = 0;
34. while (true)
35. {
36. i++;
37. cout << i << " " << tree[i].num << " " << tree[i].data << endl;
38. ofile << i << " " << tree[i].num << " " << tree[i].data << endl;
39.
40. if (i >= 100 && tree[i].num != tree[i + 1].num) // 输出判断前一百
41. break;
42. }
43. delete[] tree;
44. file.close();
45. ofile.close();
46.
47.
48. }
四.执行程序名,并打印程序运行时的初值和运算结果
1.生成大数据,在服务器用python生成
生成文件:
2.分块500文件
源文件85MB,分块内存占用4MB
3. 读入每一个小文件,利用hash_map统计每个文件中出现的词以及相应的频率,创建动态数据结构node组,存储data(键值)和num(频率),利用最大堆排列,并取出出现频率最大的100个词。
4.最后,前一百生成
五.实验结果分析,实验收获和体会
1.这道题大数据这一点其实体现的很关键,如果是10G的文件,要求1G内存读取,那么就对内存要求非常高了,而关键的思想就是分而治之,分块处理!!!
2.最开始我创建node组没有用delete处理导致内存占用飙升到1G左右,而delete后占用不到4MB,体现了代码对资源的把握要非常高,如何处理资源占用问题非常关键。
3.hash_map之所以使用它,也是由于它的查询非常快,时间复杂度非常低,并且它的思想也是分而治之,类似于桶分布原理。
六.实验的改进意见和建议。
这里的实验我采用的是分而治之思想,但是不一定要在本机运行,当数据达到一定程度,其实可以用分布式计算,把数据划分给不同的子机器,子机器处理后传达给中心机器,同步参数后继续划分给不同的子机器,依次循环即可实现对数据的统计。
并且这里因为数据量还是不大的问题,所以桶分布为500,如果数据更多可以划分更多的桶
再则,其实1500万处理,完成 整个过程也要大概290多秒的时间,本人设备 i5 均跑3.7Ghz,内存 20G,不同机器可能时间不同,主要时间还是花费在读取行数据上面。想过是否能采用多线程处理机制,或者说内存映射之类,后来想想题目根本点在于低内存处理大数据,如果多线程处理,就失去意义了,倒不如采用上面分布式处理!