简单问题:关于比较流的最好方法.200分. (200分)

  • 主题发起人 主题发起人 jingtao
  • 开始时间 开始时间
J

jingtao

Unregistered / Unconfirmed
GUEST, unregistred user!
一段比较两个文件不同之处的代码.
该段代码很没有效率.
请问:
(1)如何做效率最快?
(2)如果文件很大(比如说2MB).怎么做最合理?
请给代码.谢谢.
 
可以使用指针操作来进行比较
具体代码等我试验一下再说
 
好的.
其实数组本身就是指针.
 
:) beta比我快了一步
不过不知道是不是一样的。 我的随便测了一下, 你看看结果是否正确,速度是不快了。
var
I: Integer;
StreamA, StreamB, StreamC: TMemoryStream;
P1, P2, P3: ^Char;
begin
StreamA := TMemoryStream.Create;
StreamB := TMemoryStream.Create;
StreamC := TMemoryStream.Create;
Memo1.Lines.SaveToStream(StreamA);
Memo2.Lines.SaveToStream(StreamB);

P1 := StreamA.Memory;
P2 := StreamB.Memory;
StreamC.SetSize(StreamA.Size);
P3 := StreamC.Memory;

for I := 0 to StreamA.Size - 1 do
begin
if P1^ = P2^ then
P3^ := ' '
else
P3^ := P1^;
Inc(P1);
Inc(P2);
Inc(P3);
end;

Memo3.Lines.LoadFromStream(StreamC);
StreamA.Free;
StreamB.Free;
StreamC.Free;
end;
 
老贝,PChar不能容下太大的数据啊.
比如说A.txt为1M就完了.我一边等待一边在作,呵呵
function CompareMem(P1, P2: Pointer; Length: Integer): Boolean; assembler;
asm
PUSH ESI
PUSH EDI
MOV ESI,P1
MOV EDI,P2
MOV EDX,ECX
XOR EAX,EAX
AND EDX,3
SHR ECX,1
SHR ECX,1
REPE CMPSD
JNE @@2
MOV ECX,EDX
REPE CMPSB
JNE @@2
@@1: INC EAX
@@2: POP EDI
POP ESI
end;
 
确实是这样
但你把它们复制了一遍, 这是很影响效率的,如果文件上了几十M的话,就更夸张了。
 
我这个可是速度一流哦
鹿鼎记.txt 2,545,680 bytes
我让两个流都装载一样的数据,然后比较计时, 比较部份只用50毫秒就搞定
 
Sorry, 匆忙之间,忘记了在循环里面加上 Inc(pa); Inc(pb); !!!!

//PChar不能容下太大的数据啊
谁说用 PChar 容了?它只是一个指针而已,数据还放在 MemoryStream 里面呢:)

//把它们复制了一遍, 这是很影响效率的
如果如 jingtao 所说,只有几M,那对于 MemoryStream 来说应该是没有问题的,我试过
但是如果上百M,那当然只有用 ReadFile 或其他办法,一次读一部分了。
 
procedure TForm1.Button2Click(Sender: TObject);
Var
//cMyChar1,cMyChar2,cMyChar3:array[1..800] of char;
aStream,bStream,cStream:TMemoryStream;
ap, bp: PChar;
Blank: Char;
i:integer;
begin
aStream:=TMemoryStream.Create;
bStream:=TMemoryStream.Create;
cStream:=TMemoryStream.Create;
aStream.LoadFromFile('a.txt');
bStream.LoadFromFile('b.txt');
aStream.SetSize(800);
bStream.SetSize(800);
cStream.SetSize(800);
//aStream.ReadBuffer(cMyChar1,800);
ap := aStream.Memory;
//bStream.ReadBuffer(cMyChar2,800);
bp := bStream.Memory;
//aStream.Free;
//bStream.Free;
Blank := '0';
for i:=1 to 800 do
//if cMyChar1=cMyChar2 then
// cMyChar3:='0'
//else
// cMyChar3:=cMyChar2;
begin
if ap^ = bp^ then
cStream.Write(ap^, 1)
else
cStream.Write(Blank, 1);
Inc(ap);
Inc(bp);
end;
aStream.Free;
bStream.Free;
cStream.Position := 0;
cStream.SaveToFile('c.txt');
cStream.Free;
end;

没有测试哦:)应该很快的(不超过 10M 的话)。
 
经过 射雕英雄传 与 鹿鼎记 的测试, 我上面的代码没有问题
呵呵
基本上来说,我跟beta的代码是一样的,但beta调用了Stream.Write,这个速度可比不上
直接赋值来得快,因为我看了MemoryStream.Write方法,里面还要执行好一段代码
而这段代码在我们这里是完全没必要的了。
 
没想到还有这么多夜猫子在。
 
对不起,我没有时间写代码。
但是我觉得你算法有些问题,拖慢了效率。
可否这样,加入两个TstringStream:ASsteam,BSsteam.
asteam和bsteam拣小的分两半,大小为Q
ASsteam.copyfrom(asteam,Q).....
BSsteam.copyfrom(bsteam,Q).....
if ASsteam.datastring<>BSsteam.datastring then
begin
清空ASsteam,BSsteam
ASsteam.copyfrom(asteam,Q/2).....
BSsteam.copyfrom(bsteam,Q/2).....

当ASsteam/ASsteam大小到一定范围时(比如800)二两者不同时,进行你程序中的具体比较。
相同的就不用比较了。

随便说说,水平有限,不要骂我:)


 
//这是我用文件影射写的,凭记忆写的,稍做修改就可以用了

procedure searchText(FRname,FSname:String);
var
FRFileHandle,FSFileHandle:THandle; //文件内核句柄
FRMapHandle,FSMapHandle:THandle; //文件影射句柄
FRFilesize,FSFileSize,SLen,Rlen:integer;
PRData,FSData:pchar; //文件视图的地址
Sbuf,Rbuf:array[0..255]of char;
begin


FRFileHandle:=FileOpen(FRName,fmOpenRead);

if FRFileHandle=INVALID_HANDLE_VALUE then
raise Exception.Create('打开文件错误!');

try
FRFileSize:=GetFileSize(FRFileHandle,Nil);
FMapHandle:=CreateFileMapping(FRFileHandle,
nil,
PAGE_READONLY,
0,
FRFileSize,
nil
);
if FRMapHandle=0 then
raise Exception.Create('创建文件内存影射对象错误!');
finally
CloseHandle(FRFileHandle);
end;

FSFileHandle:=FileOpen(FSName,fmOpenRead);

if FSFileHandle=INVALID_HANDLE_VALUE then
raise Exception.Create('打开文件错误!');

try
FSFileSize:=GetFileSize(FSFileHandle,Nil);
FSMapHandle:=CreateFileMapping(FSFileHandle,
nil,
PAGE_READONLY,
0,
FSFileSize,
nil
);
if FSMapHandle=0 then
raise Exception.Create('创建文件内存影射对象错误!');
finally
CloseHandle(FSFileHandle);
end;

try
PRData:=MapViewOfFile(FRMapHandle,FILE_MAP_READ,
0,0,FFileSize);
if PData=Nil then
Raise Exception.Create('创建影射视图出错!');
finally
CloseHandle(FRMapHandle);
end;
//最好用个进度条
i:=0;
j:=0;

while i<FRFileSize do
while j<FSFileSize do
begin

FillChar(Rbuf,sizeof(Rbuf),i);
FillChar(Sbuf,sizeof(Sbuf),0);
if i+256<FRfielsize then
RLen=256
else RLen=FRfilesize-i;

if j+256<FSfielsize then
Slen=256
else RLen=FRfilesize-i;
inc(i,Rlen);
inc(j,Slen);
CopyMemory(@Rbuf,PRData,i);
CopyMemory(@Sbuf,PSData,j);
//在这里,你进行比较


end;



UnmapViewOfFile(PRData);
UnmapViewOfFile(PSData);


end;
 
jingtao, 你的 CompareMem 虽然快,但是在这里用不上,虽然 CompareMem 可以一次比较
两个大的空间,但是其结论只有一个:是或否,而你需要的是记录下每一个字节的异同信息

无忌用的文件映射的方法,文件映射是非常快的,但是他用的不好:)
本来已经映射出来了,他还拷贝了一遍:)
到 MapViewOfFile 返回一个 PChar 的时候,就可以接着我刚才说过的方法了
而不需要在后面再 CopyMemory 再进行比较。
何况他是把整个文件都一次性映射进去了,对于很大的文件这样是不可取得,
应该是分块映射,再比较;而不是全部影射、分块比较。
(以上纯属个人见解)
 
憔悴 的方法有点问题,CopyFrom 效率很低:)
xianjun 说得对,我用了 Write 的确是失败:)

关键是看你操作的文件到底有多大了,如果 10M 以内,就全部读入 MemoryStream 即可
如果经常超过 10M,就应该考虑用分块的内存映射(见前面所说)
 
谢谢大家
但是有个问题:
for I := 0 to StreamA.Size - 1 do
begin
if P1^ = P2^ then
// P3^ := ' '
P3^ := '0 '
else
// P3^ := P1^;
P3^ := P2^;

Inc(P1);
Inc(P2);
Inc(P3);
end;
当字符为单数时,得出的结果会多一位.WHY?
 
呵呵,其实我只是想说应该逐步分块比较,不同才做更细微得比较这么一个想法而已。
实际上不可能比较两个完全不同的东西,比如 射雕英雄传 与 鹿鼎记 ,要是这样
的话好像比较旧没有什么意义了,尤其大家讨论的又是大文件的比较。
实际情况更应该是比较的东西大同而小异,这样的话逐步分块比较应该是很有效率的。
是与实现上嘛,各位大侠和我可不是一个级别的。。不可同日而语,呵呵,我只是
一个Beginner Of Delphi,兼带爱骗分:)
 
老贝说的很对.xianjun的代码非常快.张无忌的也不错,不过我要的是直接对内存操作.
憔悴说的方法之前也想过,但是觉得这样一来会把问题越来越复杂化.呵呵
没有问题了,派分,谢谢大家,
刚好四个,平分了,每人50个大饼~
 
//不可同日而语
憔悴 快别这么说。大家是以同等的身份参与讨论的,要不怎么叫讨论,不叫上课?:)
你这个看法的确是很有见解的,我是这样理解的:
假定要比较的两个文件很相向,那么可以一次比较100字节(CompareMem)若不同,再到这
100字节内部进行比较,否则跳过。这样做的确可以提高效率(在这个假定下)。
但是,如果这个比较是给用户选择的,则很可能出现大不一样的情况,或者由于中间一个
错位(也就相差了 1 位,也可以说是相差很小,但是)从而导致后面全部不一样,那么这
个办法反而会降低效率。
关键是看应用的场合了。
 
后退
顶部