这是一个很老的的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
<
io.h
>
#include
<
string
.h
>
#include
<
fcntl.h
>
#include
<
stdio.h
>
#include
<
stdlib.h
>
#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
<
stdio.h
>
#include
<
stdlib.h
>
#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
;
}
//
---------------------------------------------------------------------------
如有错误,或者你有什么好的建议请来信:[email protected]
发现代码贴上去总是走样,文件路径‘//’也成了‘/’,‘/n’也没了,MakeFile的2句写记录语句应该分别是,不然,测试会出问题:
fprintf(f, "%s/n", TOPSTRING);
和
fprintf(f, "%12ld %12ld %12ld %12ld/n", v[0], v[1], v[2], v[3]);