导读:
这是一个很老的的C函数,用来实现大的磁盘文件排序。在以前DOS操作系统下,对磁盘文件的排序一般有3种方法:1、将磁盘文件装入内存排序,将排序结果保存到新的文件,这适用于很小的(64K以内)、不需要经常索引的文件;2、对磁盘文件按关键字进行分块排序后,形成一个索引文件。块的大小一般为512K,常采用B+树或者B-数算法,这种方法适用于需要经常索引的磁盘文件,如DBF文件;3、把磁盘文件分片排序后,形成很多排序片文件,然后将这些排序片文件合并起来,输出为一个排序文件,这种方法适用于很大的、但又不需要经常索引的磁盘文件。
可见,在DOS有限的内存条件下,磁盘文件分片归并排序是使用比较广泛的一种外存储器排序算法。现在计算机的物理内存一般足够大(最小的也有256MB吧),Windows的虚拟内存更是多达4个GB(对每一个应用程序而言),这对于很多磁盘文件的内存排序应该是足够了,况且现在的记录文件都放在各种数据库中,所以磁盘文件分片归并排序算法可能没有市场了(不过内存多路归并排序还是有市场的)。作为怀旧,把代码贴在这里,以免“失传”!
/**************************************************************************
* 文 件 名 : MERGE.H *
* 编 制 人 : 湖北省公安县统计局 毛 泽 发 *
* 日 期 : 1991.8 *
**************************************************************************/
#define S_IREAD 0x0100
#define S_IWRITE 0x0080
#if defined(__TINY__) || defined(__SMALL__) || defined(__MENIUM__)
#define SSIZE 25600 /* 排序缓冲区字节 */
#define NULL 0
#else
#define SSIZE 65024 /* 排序缓冲区字节 */
#define NULL 0L
#endif
#define MAXMERGE 4 /* 排序合并每趟每次最大片 */
#define MAXMEREC (SSIZE / (MAXMERGE + 1)) /* 文件最大记录长 */typedef int cdecl mercmpf(const void *, const void *);/* 通用排序函数.
参 数:排序文件名;原文件名;原文件头字节数;文件记录长;用户提供的比较函数.
返回值:成功 > 0;内存不够.记录超长返回 0;文件操作出错 -1 */
int fmerge(char *foname, char *finame, int ftops, int lrd, mercmpf *cmpf);
/**************************************************************************
* 文 件 名 : MERGE.C *
* 编 制 人 : 湖北省公安县统计局 毛 泽 发 *
* 日 期 : 1991.8 *
**************************************************************************/#include #include #include #include #include #include "merge.h"
static mercmpf *mercmp = NULL; /* 比较函数 */
static char *merbuf = NULL; /* 排序动态缓冲区 */
static char *filetop = NULL; /* 原文件文件头存放动态缓冲区 */
static int filetopchs; /* 原文件文件头长 */
static int merlrd; /* 文件记录长 */
static int outfile(char *fname, unsigned size, int flag);static int formerge(char *foname, char *finame, char *tmp, unsigned m);static domerge(char *foname, char *tmp1, char *tmp2, int irun);static void smerge(int *md, int m, char *buf[], int outf, char *outbuf, int size);static int dopass(char *name1, char *name2, int irun);/* 通用排序函数.
参 数:排序文件名;原文件名;原文件头字节数;文件记录长;用户提供的比较函数.
返回值:成功 > 0;内存不够.记录超长返回 0;文件操作出错 -1 */
int fmerge(char *foname, char *finame, int ftops, int lrd, mercmpf *cmpf)
{
char tmp1[68], tmp2[68];
int irun;
unsigned size;
if(lrd > MAXMEREC) return 0 /* 记录超长 */ merlrd = lrd;
size = (SSIZE / lrd) * lrd; /* 排序缓冲区实际长 */ if((merbuf = (char *)malloc(size)) == NULL) return 0 /* 分配动态缓冲区 */ if(ftops && (filetop = (char *)malloc(ftops)) == NULL) return 0 filetopchs = ftops;
mercmp = cmpf;
strcpy(tmp1, "&&&1"); /* 临时文件名 */ strcpy(tmp2, "&&&2");
irun = formerge(foname, finame, tmp1, size); /* 分片排序 */ if(irun > 1) /* 如果排序片大于 1 */ irun = domerge(foname, tmp1, tmp2, irun); /* 合并排序片 */ free(merbuf);
if(filetopchs) free(filetop);
return irun;
}/* 写一排序片文件 */
static int outfile(char *fname, unsigned size, int flag)
{
int h, c;
if((h = open(fname, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IWRITE)) == -1)
return -1 if(flag && filetopchs) /* 如果是最终文件同时原文件有文件头 */ write(h, filetop, filetopchs); /* 写入文件头内容 */ c = write(h, merbuf, size); /* 写排序片到文件 */ close(h);
return c;
}/* 分片排序 */
static int formerge(char *foname, char *finame, char *tmp, unsigned m)
{
unsigned irun, ret;
int f, flag = 0 char tmpname[68];
if((f = open(finame, O_RDONLY | O_BINARY)) == -1) return -1/* 打开原文件 */ if(filetopchs) /* 如有文件头,保存其内容到缓冲区 */ read(f, filetop, filetopchs);
irun = 0 do{
ret = read(f, merbuf, m); /* 读一排序片到排序缓冲区 */ if(ret == 0 || ret == 0xffff) break /* 原文件结束或出错,退出 */ qsort(merbuf, ret / merlrd, merlrd, mercmp); /* 排序 */ if(ret == m || irun > 0) /* 如原文件长大于或等于一排序片长 */ sprintf(tmpname, "%s.%03d", tmp, irun); /* 采用临时文件名 */ else{ /* 否则,直接用排序文件名 */ strcpy(tmpname, foname);
flag = 1 /* 最终文件标记 */ }
ret = outfile(tmpname, ret, flag); /* 写排序片 */ irun ++ }while(ret == m);
close(f);
if(ret == 0xffff) return ret; /* 出错返回 -1 */ return irun; /* 返回排序片数 */}/* 分配每一合并趟不同临时文件名;控制合并趟数 */
static domerge(char *foname, char *tmp1, char *tmp2, int irun)
{
char *p;
while(irun > 1){
if(irun <= MAXMERGE) strcpy(tmp2, foname);
irun = dopass(tmp1, tmp2, irun);
p = tmp1;
tmp1 = tmp2;
tmp2 = p;
}
return irun;
}/* 执行合并趟,计算.分配每次合并所需文件数,缓冲区大小,控制每次合并的执行 */
static int dopass(char *name1, char *name2, int irun)
{
int fi, i, nrun, m, size;
char oname[68], inname[68], *p[MAXMERGE], *q;
int md[MAXMERGE], fo;
size = SSIZE / merlrd; /* 合并缓冲区容纳记录数 */ nrun = 0 for(fi = 0 fi < irun; fi += MAXMERGE){
m = irun - fi; /* 每次合并实际排序片数 */ if(m > MAXMERGE) m = MAXMERGE;
for(i = 0 i < m; i ++) p[i] = merbuf + (i * merlrd); /* 分配读缓冲区 */ if(irun <= MAXMERGE) strcpy(oname, name2); /* 最终合并形成排序文件 */ else sprintf(oname, "%s.%03d", name2, nrun);/* 中间合并采用临时文件 */ if((fo = open(oname, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IWRITE)) == -1)
break /* 打开写文件 */ i = 0 do{ /* 分别打开读文件 */ sprintf(inname, "%s.%03d", name1, fi + i);
md[i] = open(inname, O_RDONLY | O_BINARY);
}while(md[i ++] != -1 && i < m);
if(i != m){
close(fo);
for(fi = 0 fi < i; fi ++) close(md[fi]);
break }
if(irun <= MAXMERGE && filetopchs) /* 最终合并写文件头(如有) */ write(fo, filetop, filetopchs);
q = merbuf + (m * merlrd); /* 分配写缓冲区 */ smerge(md, m, p, fo, q, size - m); /* 合并 */ for(i = 0 i < m; i ++){ /* 删除各排序片文件 */ close(md[i]);
sprintf(inname, "%s.%03d", name1, fi + i);
unlink(inname);
}
close(fo);
nrun ++ }
if(nrun != (irun + MAXMERGE - 1) / MAXMERGE) return -1 return nrun;
}/* 执行实际排序片合并 */
static void smerge(int *md, int m, char *buf[], int outf, char *outbuf, int size)
{
int i, j, n = merlrd, w = merlrd * size;
char *s = buf[0], *p, *q = outbuf, *end = q + w;
for(i = 0 i < m; i ++) /* 从各片文件中读第一条记录 */ read(md[i], buf[i], n);
while(1){
if(n == merlrd){ /* 如各片文件均有记录,各片记录反向插入排序 */ for(i = 1 i < m; i ++){
for(p = buf[i], j = i - 1 j >= 0 && mercmp(p, buf[j]) > 0 j --)
buf[j + 1] = buf[j];
buf[j + 1] = p;
}
}
else m -- /* 一片文件内容结束 */ if(!m){ /* 如所有片文件结束,写缓冲区残余记录,退出 */ if(q != outbuf) write(outf, outbuf, q - outbuf);
break }
if(q == end){ /* 刷新一次写缓冲区到文件 */ if(write(outf, outbuf, end - outbuf) != w) break q = outbuf;
}
i = m - 1 j = (buf[i] - s) / merlrd;
memmove(q, buf[i], merlrd); /* 将各片记录中值最小(大)者移入写缓冲区 */ q += merlrd;
n = read(md[j], buf[i], merlrd); /* 从该片中读下一记录,继续 */ }
}
可以看到,上面2个文件时间是1991年的,真是老古董了,如MERGE.H文件开头就没有什么诸如#ifndef __MERGE_H......的代码,我记得那个时候好像没这个写法的。函数里面当初也作了很详细的注释,所以算法就不再讲了(要讲我还得先分析代码,早忘记了 ^_^ )。
为了示范该函数的使用方法,我还是用BCB6写了一个简单的演示程序,如果你想试一下老古董,不妨也写一个?可以将MERGE.H文件中的排序缓冲区加大一些,可提高排序速度。
//---------------------------------------------------------------------------
#include #include #include "merge.h"
#pragma hdrstop
#define TOPSTRING "湖北省公安县统计局 毛 泽 发"
#define TOP_SIZE 30
#define RECORD_SIZE 53
#define RECORD_COUNT 10000
//---------------------------------------------------------------------------/* 为了方便观察,随机生成了一个RECORD_COUNT行的文本文件 */
void MakeFile(char *filename)
{
int i, j;
long v[4];
FILE *f;
f = fopen(filename, "w");
fprintf(f, "%s ", TOPSTRING);
randomize();
for (i = 0 i < RECORD_COUNT; i ++)
{
for (j = 0 j < 4 j ++)
v[j] = random(0x7fffffff);
fprintf(f, "%12ld %12ld %12ld %12ld ", v[0], v[1], v[2], v[3]);
}
fclose(f);
}int cdecl CompRecord(const void *ra, const void *rb)
{
int a[4], b[4];
int i, n;
sscanf((char*)ra, "%ld%ld%ld%ld", &a[0], &a[1], &a[2], &a[3]);
sscanf((char*)rb, "%ld%ld%ld%ld", &b[0], &b[1], &b[2], &b[3]);
for (n = 0, i = 0 i < 4 && n == 0 i ++)
n = a[i] - b[i];
return n;
}#pragma argsused
int main(int argc, char* argv[])
{
printf("正在随机制造一个文本文件d:/test.txt... ");
MakeFile("d:/test.txt");
printf("正在进行磁盘文件排序,排序文件d:/sort.text... ");
fmerge("d:/sort.txt", "d:/test.txt", TOP_SIZE, RECORD_SIZE, CompRecord);
printf("磁盘文件排序完毕! ");
system("pause");
return 0}//---------------------------------------------------------------------------
如有错误,或者你有什么好的建议请来信:maozefa@hotmail.com
发现代码贴上去总是走样,文件路径‘//’也成了‘/’,‘/n’也没了,MakeFile的2句写记录语句应该分别是,不然,测试会出问题:
fprintf(f, "%s/n", TOPSTRING);
和
fprintf(f, "%12ld %12ld %12ld %12ld/n", v[0], v[1], v[2], v[3]);
这是一个很老的的C函数,用来实现大的磁盘文件排序。在以前DOS操作系统下,对磁盘文件的排序一般有3种方法:1、将磁盘文件装入内存排序,将排序结果保存到新的文件,这适用于很小的(64K以内)、不需要经常索引的文件;2、对磁盘文件按关键字进行分块排序后,形成一个索引文件。块的大小一般为512K,常采用B+树或者B-数算法,这种方法适用于需要经常索引的磁盘文件,如DBF文件;3、把磁盘文件分片排序后,形成很多排序片文件,然后将这些排序片文件合并起来,输出为一个排序文件,这种方法适用于很大的、但又不需要经常索引的磁盘文件。
可见,在DOS有限的内存条件下,磁盘文件分片归并排序是使用比较广泛的一种外存储器排序算法。现在计算机的物理内存一般足够大(最小的也有256MB吧),Windows的虚拟内存更是多达4个GB(对每一个应用程序而言),这对于很多磁盘文件的内存排序应该是足够了,况且现在的记录文件都放在各种数据库中,所以磁盘文件分片归并排序算法可能没有市场了(不过内存多路归并排序还是有市场的)。作为怀旧,把代码贴在这里,以免“失传”!
/**************************************************************************
* 文 件 名 : MERGE.H *
* 编 制 人 : 湖北省公安县统计局 毛 泽 发 *
* 日 期 : 1991.8 *
**************************************************************************/
#define S_IREAD 0x0100
#define S_IWRITE 0x0080
#if defined(__TINY__) || defined(__SMALL__) || defined(__MENIUM__)
#define SSIZE 25600 /* 排序缓冲区字节 */
#define NULL 0
#else
#define SSIZE 65024 /* 排序缓冲区字节 */
#define NULL 0L
#endif
#define MAXMERGE 4 /* 排序合并每趟每次最大片 */
#define MAXMEREC (SSIZE / (MAXMERGE + 1)) /* 文件最大记录长 */typedef int cdecl mercmpf(const void *, const void *);/* 通用排序函数.
参 数:排序文件名;原文件名;原文件头字节数;文件记录长;用户提供的比较函数.
返回值:成功 > 0;内存不够.记录超长返回 0;文件操作出错 -1 */
int fmerge(char *foname, char *finame, int ftops, int lrd, mercmpf *cmpf);
/**************************************************************************
* 文 件 名 : MERGE.C *
* 编 制 人 : 湖北省公安县统计局 毛 泽 发 *
* 日 期 : 1991.8 *
**************************************************************************/#include #include #include #include #include #include "merge.h"
static mercmpf *mercmp = NULL; /* 比较函数 */
static char *merbuf = NULL; /* 排序动态缓冲区 */
static char *filetop = NULL; /* 原文件文件头存放动态缓冲区 */
static int filetopchs; /* 原文件文件头长 */
static int merlrd; /* 文件记录长 */
static int outfile(char *fname, unsigned size, int flag);static int formerge(char *foname, char *finame, char *tmp, unsigned m);static domerge(char *foname, char *tmp1, char *tmp2, int irun);static void smerge(int *md, int m, char *buf[], int outf, char *outbuf, int size);static int dopass(char *name1, char *name2, int irun);/* 通用排序函数.
参 数:排序文件名;原文件名;原文件头字节数;文件记录长;用户提供的比较函数.
返回值:成功 > 0;内存不够.记录超长返回 0;文件操作出错 -1 */
int fmerge(char *foname, char *finame, int ftops, int lrd, mercmpf *cmpf)
{
char tmp1[68], tmp2[68];
int irun;
unsigned size;
if(lrd > MAXMEREC) return 0 /* 记录超长 */ merlrd = lrd;
size = (SSIZE / lrd) * lrd; /* 排序缓冲区实际长 */ if((merbuf = (char *)malloc(size)) == NULL) return 0 /* 分配动态缓冲区 */ if(ftops && (filetop = (char *)malloc(ftops)) == NULL) return 0 filetopchs = ftops;
mercmp = cmpf;
strcpy(tmp1, "&&&1"); /* 临时文件名 */ strcpy(tmp2, "&&&2");
irun = formerge(foname, finame, tmp1, size); /* 分片排序 */ if(irun > 1) /* 如果排序片大于 1 */ irun = domerge(foname, tmp1, tmp2, irun); /* 合并排序片 */ free(merbuf);
if(filetopchs) free(filetop);
return irun;
}/* 写一排序片文件 */
static int outfile(char *fname, unsigned size, int flag)
{
int h, c;
if((h = open(fname, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IWRITE)) == -1)
return -1 if(flag && filetopchs) /* 如果是最终文件同时原文件有文件头 */ write(h, filetop, filetopchs); /* 写入文件头内容 */ c = write(h, merbuf, size); /* 写排序片到文件 */ close(h);
return c;
}/* 分片排序 */
static int formerge(char *foname, char *finame, char *tmp, unsigned m)
{
unsigned irun, ret;
int f, flag = 0 char tmpname[68];
if((f = open(finame, O_RDONLY | O_BINARY)) == -1) return -1/* 打开原文件 */ if(filetopchs) /* 如有文件头,保存其内容到缓冲区 */ read(f, filetop, filetopchs);
irun = 0 do{
ret = read(f, merbuf, m); /* 读一排序片到排序缓冲区 */ if(ret == 0 || ret == 0xffff) break /* 原文件结束或出错,退出 */ qsort(merbuf, ret / merlrd, merlrd, mercmp); /* 排序 */ if(ret == m || irun > 0) /* 如原文件长大于或等于一排序片长 */ sprintf(tmpname, "%s.%03d", tmp, irun); /* 采用临时文件名 */ else{ /* 否则,直接用排序文件名 */ strcpy(tmpname, foname);
flag = 1 /* 最终文件标记 */ }
ret = outfile(tmpname, ret, flag); /* 写排序片 */ irun ++ }while(ret == m);
close(f);
if(ret == 0xffff) return ret; /* 出错返回 -1 */ return irun; /* 返回排序片数 */}/* 分配每一合并趟不同临时文件名;控制合并趟数 */
static domerge(char *foname, char *tmp1, char *tmp2, int irun)
{
char *p;
while(irun > 1){
if(irun <= MAXMERGE) strcpy(tmp2, foname);
irun = dopass(tmp1, tmp2, irun);
p = tmp1;
tmp1 = tmp2;
tmp2 = p;
}
return irun;
}/* 执行合并趟,计算.分配每次合并所需文件数,缓冲区大小,控制每次合并的执行 */
static int dopass(char *name1, char *name2, int irun)
{
int fi, i, nrun, m, size;
char oname[68], inname[68], *p[MAXMERGE], *q;
int md[MAXMERGE], fo;
size = SSIZE / merlrd; /* 合并缓冲区容纳记录数 */ nrun = 0 for(fi = 0 fi < irun; fi += MAXMERGE){
m = irun - fi; /* 每次合并实际排序片数 */ if(m > MAXMERGE) m = MAXMERGE;
for(i = 0 i < m; i ++) p[i] = merbuf + (i * merlrd); /* 分配读缓冲区 */ if(irun <= MAXMERGE) strcpy(oname, name2); /* 最终合并形成排序文件 */ else sprintf(oname, "%s.%03d", name2, nrun);/* 中间合并采用临时文件 */ if((fo = open(oname, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IWRITE)) == -1)
break /* 打开写文件 */ i = 0 do{ /* 分别打开读文件 */ sprintf(inname, "%s.%03d", name1, fi + i);
md[i] = open(inname, O_RDONLY | O_BINARY);
}while(md[i ++] != -1 && i < m);
if(i != m){
close(fo);
for(fi = 0 fi < i; fi ++) close(md[fi]);
break }
if(irun <= MAXMERGE && filetopchs) /* 最终合并写文件头(如有) */ write(fo, filetop, filetopchs);
q = merbuf + (m * merlrd); /* 分配写缓冲区 */ smerge(md, m, p, fo, q, size - m); /* 合并 */ for(i = 0 i < m; i ++){ /* 删除各排序片文件 */ close(md[i]);
sprintf(inname, "%s.%03d", name1, fi + i);
unlink(inname);
}
close(fo);
nrun ++ }
if(nrun != (irun + MAXMERGE - 1) / MAXMERGE) return -1 return nrun;
}/* 执行实际排序片合并 */
static void smerge(int *md, int m, char *buf[], int outf, char *outbuf, int size)
{
int i, j, n = merlrd, w = merlrd * size;
char *s = buf[0], *p, *q = outbuf, *end = q + w;
for(i = 0 i < m; i ++) /* 从各片文件中读第一条记录 */ read(md[i], buf[i], n);
while(1){
if(n == merlrd){ /* 如各片文件均有记录,各片记录反向插入排序 */ for(i = 1 i < m; i ++){
for(p = buf[i], j = i - 1 j >= 0 && mercmp(p, buf[j]) > 0 j --)
buf[j + 1] = buf[j];
buf[j + 1] = p;
}
}
else m -- /* 一片文件内容结束 */ if(!m){ /* 如所有片文件结束,写缓冲区残余记录,退出 */ if(q != outbuf) write(outf, outbuf, q - outbuf);
break }
if(q == end){ /* 刷新一次写缓冲区到文件 */ if(write(outf, outbuf, end - outbuf) != w) break q = outbuf;
}
i = m - 1 j = (buf[i] - s) / merlrd;
memmove(q, buf[i], merlrd); /* 将各片记录中值最小(大)者移入写缓冲区 */ q += merlrd;
n = read(md[j], buf[i], merlrd); /* 从该片中读下一记录,继续 */ }
}
可以看到,上面2个文件时间是1991年的,真是老古董了,如MERGE.H文件开头就没有什么诸如#ifndef __MERGE_H......的代码,我记得那个时候好像没这个写法的。函数里面当初也作了很详细的注释,所以算法就不再讲了(要讲我还得先分析代码,早忘记了 ^_^ )。
为了示范该函数的使用方法,我还是用BCB6写了一个简单的演示程序,如果你想试一下老古董,不妨也写一个?可以将MERGE.H文件中的排序缓冲区加大一些,可提高排序速度。
//---------------------------------------------------------------------------
#include #include #include "merge.h"
#pragma hdrstop
#define TOPSTRING "湖北省公安县统计局 毛 泽 发"
#define TOP_SIZE 30
#define RECORD_SIZE 53
#define RECORD_COUNT 10000
//---------------------------------------------------------------------------/* 为了方便观察,随机生成了一个RECORD_COUNT行的文本文件 */
void MakeFile(char *filename)
{
int i, j;
long v[4];
FILE *f;
f = fopen(filename, "w");
fprintf(f, "%s ", TOPSTRING);
randomize();
for (i = 0 i < RECORD_COUNT; i ++)
{
for (j = 0 j < 4 j ++)
v[j] = random(0x7fffffff);
fprintf(f, "%12ld %12ld %12ld %12ld ", v[0], v[1], v[2], v[3]);
}
fclose(f);
}int cdecl CompRecord(const void *ra, const void *rb)
{
int a[4], b[4];
int i, n;
sscanf((char*)ra, "%ld%ld%ld%ld", &a[0], &a[1], &a[2], &a[3]);
sscanf((char*)rb, "%ld%ld%ld%ld", &b[0], &b[1], &b[2], &b[3]);
for (n = 0, i = 0 i < 4 && n == 0 i ++)
n = a[i] - b[i];
return n;
}#pragma argsused
int main(int argc, char* argv[])
{
printf("正在随机制造一个文本文件d:/test.txt... ");
MakeFile("d:/test.txt");
printf("正在进行磁盘文件排序,排序文件d:/sort.text... ");
fmerge("d:/sort.txt", "d:/test.txt", TOP_SIZE, RECORD_SIZE, CompRecord);
printf("磁盘文件排序完毕! ");
system("pause");
return 0}//---------------------------------------------------------------------------
如有错误,或者你有什么好的建议请来信:maozefa@hotmail.com
发现代码贴上去总是走样,文件路径‘//’也成了‘/’,‘/n’也没了,MakeFile的2句写记录语句应该分别是,不然,测试会出问题:
fprintf(f, "%s/n", TOPSTRING);
和
fprintf(f, "%12ld %12ld %12ld %12ld/n", v[0], v[1], v[2], v[3]);