如何对大文件进行排序

Last updated on 8 months ago

排序问题虽然有了解,很多只是基于理论上,实践比较少,刚好最近遇到个问题:

侵删

image-20220420153810671

这个以前遇到过类似的问题, 假如 你有一个20GB的文件,对文件的内容排序,内存限制在 1G,你要怎么排序

以前有做过总结,就是分割文件,逐个文件先排好序,最后然后再合并,说起来很简单,但做起来就。。。。

这次题目有 分割文件,我下载了这个压缩包,解压 看有1000个 20M的文件

image-20220420153113356

思路: 将这个一 千个文件,文件名读取出来,按照条件逐个抽出来,并且排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void read_lryic(char *path)
{
DIR *dir = opendir(path); //打开目录文件
cout << path << endl;
struct dirent *entry;
int i = 0;
while ((entry = readdir(dir)) != 0)
{
if (strstr(entry->d_name, ".txt"))
{
filename.push_back(entry->d_name);
printf(" %d %s\n", i++, entry->d_name);
}
}
}

现在文件名有了,进行筛选排序,这里用map来存放临时的数据,key 为时间戳,value 为对应的数据,为什么用map,map是会自动自动排序,根据key的值,而这里的没条数据都是 json,用了一个jsoncpp 库(如果用 go 估计已经写完了),原本想直接解析字符串,发现有点麻烦,还是用库吧, 提取出 时间戳,作为key,提取完一个文件后,重新写入到新的文件里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void sortfile(){
Json::Reader reader;
Json::Value obj;
//读文件名字
read_lryic("/home/hrp/Cpp/recore/");
string file;
for (auto str : filename){
file = "/home/hrp/Cpp/recore/" + str;
std::ifstream fIn(file);
int i = 0;
if (fIn)
{
std::string str;
while (std::getline(fIn, str))
{
if(str.find("张三") != string::npos){

//std::cout << i++ << endl;
reader.parse(str, obj);
long timestamp = obj["timestamp"].asInt64();
ret[timestamp] = str;
}
}
cout << file << endl;
}
else
{
std::cout << "Open file faild." << std::endl;
}
ofstream out1(file);
for(auto i=ret.begin();i!=ret.end();i++){
//cout << i->second;
out1 << i->second ;
out1 << "\n" ;
}
ret.clear();
out1.close();
}
}

现在完成 按照条件 完成每个文件的排序了

接下来就是 归并,如何归并呢,现在有1000个文件,逐个遍历这1000个文件,每次都提取一行来,一次遍历 提取了1000行数据,此时可以用大顶堆的特性,就是1000个数据放进这个大顶堆,堆顶肯定是最大数,按照这个特性,先读取一千条,放在大顶堆了,每次 pop出堆顶的(肯定最大),然后要 补上一个,读哪个文件的呢,肯定是pop出去哪条数据所在的文件,所以现在就是要去那个文件里再读一条数据,以此类推,说是这样说 ,但是这题还有其他附加条件(根据时间分类写)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
clock_t start,end;//定义clock_t变量
start = clock(); //开始时间

Json::Reader reader;
Json::Value obj;

map<string,ifstream*> filesmap;
map<string,ofstream*> outfile;

sortfile();
time_t tt;
struct tm *ttime;
//用 map 保存文件的读写流,这里是读
for(auto file: filename){
auto temp = file;
file = "/home/hrp/Cpp/recore/" + temp;
filesmap.emplace(file, new ifstream(file));
}
//这里用c++ 的优先队列 来模拟堆,插入的数据 为 时间戳和文件的字符名字
priority_queue<pair<long, string>> topTime;
map<long,string> Temp;
string data;
//先读取 全部文件的 第一条数据,push到 优先队列中
for(auto iter = filesmap.begin(); iter != filesmap.end(); iter++) {
std::getline(*iter->second, data);

reader.parse(data, obj);
long timestamp = obj["timestamp"].asInt64();
pair<long,string> b(timestamp,iter->first);

topTime.push(b);
Temp[timestamp] = data;
}
// 现在 对队列操作,此时队列是 出一个,进一个,维持平衡,没有进,就一直出,知道 队列为空位止
// Temp 维护 这1000个数据,动态增减
while (!topTime.empty()){
// cout << Temp[topTime.top()] << endl;
//时间戳转 日期
struct tm *ttime;
tt = topTime.top().first/1000;
ttime = localtime(&tt);
char formatime[64];
strftime(formatime, 64, "%Y-%m-%d", ttime);
string strtime(formatime);
//这里是写文件,根据时间戳 转换的日期来 写文件
// outfile key 为文件名 value 对应的读写流
// 为 0 ,说明第一次,创建文件
if(outfile.count(strtime) == 0){

outfile.emplace(strtime,new ofstream(strtime));
if (!outfile[strtime]->is_open())
cout << strtime << "error" << endl;
*outfile[strtime] << Temp[topTime.top().first]+ "\n";
Temp.erase(topTime.top().first);

}else{
//根据 日期写入 数据
*outfile[strtime] << Temp[topTime.top().first] + "\n";
Temp.erase(topTime.top().first);

}
// top是最大的,此时pop出去,记录下 pop去的是哪个文件
auto TopTemp = topTime.top();
topTime.pop();

//如果 读取到了,更行 优先队列的数据
if(std::getline(*filesmap[TopTemp.second], data)){
reader.parse(data, obj);
long timestamp = obj["timestamp"].asInt64();
pair<long,string> b(timestamp,TopTemp.second);
Temp[timestamp] = data;
topTime.push(b);
}


}
//最后 要关闭文件,才会保存
for(auto iter = outfile.begin(); iter != outfile.end(); iter++) {
iter->second->close();
}
end = clock(); //结束时间
cout<<"time = "<<double(end-start)/CLOCKS_PER_SEC<<"s"<<endl; //输出时间(单位:s)


运行的机器是: 腾讯云服务器 4核 4g

耗时 117 .s

内存使用不大,500M 以内解决,就是 cpu 占用率高

排序好的

总结:

  • 认清了 知道 和 做到的差距!!!!!
  • 有些api 日常少使用, 很不收悉,都是在查查查
  • 要先有整体思路 然后细化问题,逐个解决

代码整体设计 不是很好,后续在改进, 用多线程 多路归并?