字符串里包含很多空格"aa bb ee dd ff 2332 898f 2e 23",有什摸好办法快速删除? ( 积分: 10 )

  • 主题发起人 主题发起人 xydrj
  • 开始时间 开始时间
dirk兄,试试这段的效率,多了2个申请和释放内存、String和PChar的2个转换,看看要多花多少时间

function Kill32(s: string): string;
var
p,pRes : pchar;
i,iLen : integer;
Addr_p,Addr_pRes : Cardinal;
begin
iLen := Length(s);
p := AllocMem(iLen + 1); //申请内存 ,用于保存源串
pRes := AllocMem(iLen + 1); //申请内存,用于保存返回子串
Addr_p := Cardinal(p); //保存地址
Addr_pRes := Cardinal(pRes); //保存地址
StrPCopy(p,s);

For i := 0 to iLen - 1 do begin
if p^ <> ' ' then begin
pRes^ := p^; //从源串中复制一个字符
inc(Cardinal(pRes));//移动临时字符串指针
end;

inc(Cardinal(p));//移动源字符串指针
end; //For i

Result := StrPas(PChar(Addr_pRes));
FreeMem(Pchar(Addr_pRes),iLen + 1); //释放内存
FreeMem(Pchar(Addr_p),iLen + 1); //释放内存
end;
 
正则表达式替换
 
因为delphi有编译开关,String有时候代表AnsiString,有时候又代表ShortString,所以得到长度还是用的Length函数,并且特地用了StrPCopy转换为PChar,用StrPas转换为String,为了更安全。
 
来自:dirk, 时间:2005-7-1 22:03:13, ID:3121358
>>s := s + 'a'; //重要!

呵呵,“重要”处就是为了把s变成pchar后让i从0开始循环吗?既然知道copy on write,那s := s + 'a';似乎有违初衷,如果s足够大,就这一句就能让人等的不耐烦了,而且,只要有这句就不可能是“原地址操作”,s已经重新分配地址啦!


dirk
>>s := s + 'a'; //重要!的原因是不加这句,s编译后在只读段中,不允许写呵,演示程序的关键是后面的循环,此处为建立环境。建议dirk做测试
 
to dirk:
老大,我承认POS的效率不高,但你也不用这么样和我的比啊。
我的是一个完整的字符串。
你的是已经写在了一个连表里面。

你说这样对我公平吗?(并且我的不用写读入的啊,你的读入还的写结构呢!)
 
呵呵,不好意思,无泪,比较只是想说明一下delete效率低,当然pos的效率也很低,没别的意思。

另外,我也没有写在链表里,只是直接写string结构中的字符数组部分,仍然是完整的字符串,不信你看看我的代码运行后integer(s)的值,而你的已经变了。

to renshouren,jshyhzj
我用那个38M的文件对你们的代码做了测试,

测试字符串长度:39177434字节,包含空格数:921287
测试结果:
renshouren:消耗时间在 12xxx~22xxx 毫秒之间,比较集中在 13xxx~15xxx 毫秒。
jshyhzj:消耗时间在 96x~104x 毫秒之间,比较集中在 98x~101x 毫秒。
我的代码:消耗时间在 130x~135x 毫秒之间,比较集中在 132x~134x 毫秒。
然后修改了我的代码,定义了个pchar,先把s转成pchar,其它代码不变:消耗时间在 96x~103x 毫秒之间,比较集中在 98x~102x 毫秒。

对于jshyhzj所说的“重要”之处做了测试,去掉,完全正常运行,另外,如果加了‘a’,在最后setlength应该是j-1,不加,则不用-1。

看来用“字符串[下标]”的方式要比直接使用pchar来的慢。
 
to: dirk:
误解我的意思了

procedure TForm1.Button1Click(Sender: TObject);
var
s: string;
i, j: integer;
p: pchar;
begin
s := 'jkddjdjjd j dj j d j j jj j j j jj jj j j jj j j j jj j j jjjjjjjjjjj';
//编译后串'jkddjdjjd j dj j d j j jj j j j jj jj j j jj j j j jj j j jjjjjjjjjjj'位于一个只读段,S指向此只读段,所以后面的移动字符必然出错
s := s + 'a'; //重要!让S在堆中复制串
//我的目的是用'jkddjdjjd j dj j d j j jj j j j jj jj j j jj j j j jj j j jjjjjjjjjjja'进行测试,如果你写成函数当然没有此问题,你可以把我的代码原封不动地测试
//速度还可以更快点:先找到第一个空格,进行循环,我在第一个空格前的字符移动都是无意义的呵
 
function ss(var s:string):integer;
var
bgn, i,j:integer;
p: pchar;
begin
Result := length(s);
p:= pchar(s);

j := 0; //似乎可以改为:j := Result - 1,处理串中根本没空格
for i := 0 to Result - 1 do
if p =' ' then
begin
j := i;
bgn := i + 1;
BREAK;
end;

for i := bgn to Result - 1 do
begin
if p<>' ' then
begin
// if j <> i then //循环体内做此判断,代价太大呵
p[j] := p;
inc(j);
end;
end;
setlength(s,j);
Result := Result - j;
end;

//没测试
//估计很快了
 
呵呵,原来说的是这个(不满中……),应该不会有什么代码要这样处理编译好的字符串吧?这个重要在这个案例中没有什么通用性。

汗,没注意i<>j,多出的30ms左右的时间就是消耗在比较上了。
 
dirk做哪块呀?
MSN: jsnjjnhzj@hotmail.com
 
如果S是ShortString,
p : Pchar;
p := PChar(s);
p[0] 好象是保存的字符串长度吧。
 
//最快是我上面贴的汇编代码,而下面也是本贴到目前为止唯一符合楼主要求的答案,
//这10分是我的,请楼主查收答案,尽快放分,我要赚分问超高难度问题,谢谢

//下面重温楼主的真实要求:
//字符间相隔就一个空格。其实我是想把空格去掉,然后把16进制转成10进制数。

//放一个 memo控件,一个按钮
//按钮事件
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
s:string;
t:tstringlist;
begin
memo1.Clear;
t:=tstringlist.Create;
s:='aa bb ee dd ff 2332 898f 2e 23';

t.Delimiter:=' ';
t.DelimitedText:=s;

for I := 0 to t.Count - 1 do
begin
memo1.Lines.Add(inttostr(strToInt('$'+t.Strings)));
end;

{memo1列出转换后的结果
170
187
238
221
255
9010
35215
46
35
}

end;
 
强 我收集了
 
分数无所谓啦,kinneng,只是测试结果可能不能令你满意,还是用那个38M的文本测试,经过30次测试,你的代码耗时:831ms~882ms,主要集中在861ms~872ms,jshyhzj的代码耗时:580ms~621ms,主要集中在601ms~611ms。

这样的代码,用几百k长度的字符串测试是无法测出比较正确的结果的。

测试代码见下:
ss1最快,kill32其次,ss居然是最慢的,基本稳定在971ms以上。

ps:测试结果比前面的快,是因为大环境的因素。

代码:
function Kill32(StrPoint:Pointer; StrLong:Integer):Integer;
asm
PUSH EDI //保留这些寄存器进栈
PUSH ESI
PUSH EBX
MOV EDI,EAX //设置目标和源指针,指向字串的第一个字节
MOV ESI,EAX
MOV ECX,StrLong //设置循环长度,即字串长度
XOR EBX,EBX //初始化目标长度计数器,记录字串处理后的长度
@@J2: //循环体开始
MOV AL,[ESI] //取一个字节
CMP AL, 32 //32是空格的编码
JZ @@J1 //是空格则跳过,不处理
MOV [EDI],AL //非空格,则保存到目标指针的位置
INC EDI //目标指针加一
INC EBX //目标长度计数器加一
@@J1:
INC ESI //源指针加一
LOOP @@J2 //执行循环体
MOV RESULT,EBX //返回处理后字串的长度
POP EBX //从栈中恢复寄存器
POP ESI
POP EDI
end;

procedure ss1(var s:string);
var
i,j:integer;
p:pchar;
begin
j := 0;
p := pchar(s);
for i := 0 to pinteger(integer(s)-4)^ do
begin
if s<>' ' then
begin
s[j] := s;
inc(j);
end;
end;
setlength(s,j);
end;

function ss(var s:string):integer;
var
bgn, i,j:integer;
p: pchar;
begin
Result := length(s);
p:= pchar(s);

j := 0; //似乎可以改为:j := Result - 1,处理串中根本没空格
for i := 0 to Result - 1 do
if p =' ' then
begin
j := i;
bgn := i + 1;
BREAK;
end;

for i := bgn to Result - 1 do
begin
if p<>' ' then
begin
// if j <> i then //循环体内做此判断,代价太大呵
p[j] := p;
inc(j);
end;
end;
setlength(s,j);
Result := Result - j;
end;
 
for i := 0 to pinteger(integer(s)-4)^ do //传一个空串你在这儿就死翘翘拉!!!
 
//兄弟再辛苦下,不可能比ss1慢呀

procedure ss(var s:string);
var
i,j:integer;
p: pchar;
begin
p:= pchar(s);
j := length(s) - 1;
for i := 0 to j do
if p =' ' then
begin
j := i;
BREAK;
end;

for i := j + 1 to length(s) - 1 do
begin
if p<>' ' then
begin
p[j] := p;
inc(j);
end;
end;
setlength(s,j);
end;
 
科学地判断一段程序的快慢,应该查阅这段程序编译后的执行代码,然后根据这些汇编指令运行周期,累计算出运行时间,方可说明问题,而且只有这样,才可以排除测试环境差异的影响,排除外界干扰的影响,特别是Windows这种复杂环境下产生的各种影响,而且只有用这种方法才有说服力,有没有道理请各位自己参详,不说了。

我关心的是分,上大富翁就是要分,这里除了分,可以说什么都没有,我问的问题没有答案,买书也没优惠,楼主的要求是将空格分开的16进制数据转成10进制,所有人都是问非所答的,而我是唯一肯去回答这个问题的人,我要分一点不过份,呵呵,不是吗?
 
顶一下,收集
 
这个问题好多年前就有了呀

世界上最快的替换函数 来自:Another_eYes
Type
TFastPosProc = function (TagStr, SrcStr: PChar;
TagCount: Integer; var SrcCount: Integer): Integer;

function FastPos(TagStr, SrcStr: PChar; TagCount: Integer;
var SrcCount: Integer): PChar; assembler;
asm
// 进入程序时, TagStr在EAX, SrcStr在EBX, TagCount在ECX
PUSH ESI
PUSH EDI
PUSH EBX
PUSH EBP

MOV ESI, EAX // EAX = TagStr
MOV EDI, EDX // EDX = SrcStr
MOV EDX, SrcCount // EDX= SrcCount
AND EDX, EDX // SrcCount <= 0?
JLE @NotFound // Yes, not found
DEC ECX // 如果第一个字符匹配时 需要比较的字节数
JL @NotFound // TagCount < 0? Yes-> not found
SUB EDX, ECX // else 计算需要搜索的SrcStr长度
// 此长度=SrcCount - TagCount + 1
JLE @NotFound // SrcCount < TagCount ? Yes-> not found
LODSB // AL = TagStr[1] ESI指向下一个字节
MOV AH, CL //保存当前TagStr长度mod 4的尾数
AND AH, 3 // 到AH中
SHR ECX, 1
SHR ECX, 1
MOV EBP, ECX // 按4字节算的长度, 保存在EBP
MOV ECX, EDX // ECX=SrcCount
@StartFind:
REPNZ SCASB // 搜索第一个字符
JNZ @NotFound // 找不到
MOV EDX, ECX // 保存剩余的SrcCount
MOV ECX, EBP // 取得TagStr长度
MOV EBX, EDI //保存当前SrcStr地址
REPZ CMPSD // 按4字节为单位比较比较Src和Tag
JNZ @FindNext // 不相等
XOR ECX, ECX // ECX清0
MOV CL, AH
REPZ CMPSB // 剩下的字节进行比较
JNZ @FindNext // 不相等
// 找到了, 返回找到的地址, 并修改SrcCount变量(已经去掉了匹配字符串的长度)
DEC EDX // 程序开始时多算一个字节, 现在去掉
MOV SrcCount, EDX
MOV EAX, EBX // 匹配起始地址, 未包含第一个字符
DEC EAX // 包含进第一个匹配字符
JMP @Found
@FindNext:
MOV ESI, SrcStr
INC ESI // 恢复参数准备查找下一轮匹配的字符
MOV ECX, EDX
MOV EDI, EBX
JMP @StartFind
@NotFound:
XOR EAX, EAX // 未找到, 返回 0, 并且SrcCount不变
@Found:
POP EBP
POP EBX
POP EDI
POP ESI
end;

function FastPosNoCase(Tag, Src: PChar; TagCount: Integer;
var SrcCount: Integer): PChar; assembler;
asm
PUSH ESI
PUSH EDI
PUSH EBX
MOV EDI, EAX // EAX = Tag
MOV ESI, EDX // EDX = Src

MOV EDX, SrcCount
XCHG EDX, ECX // 结果: ECX = SrcCount EDX = TagCount
DEC EDX // 找到第一个字符后需要匹配的个数
JL @NotFound // TagCount <= 0 ?
OR ECX, ECX // SrcCount <= 0 ?
JLE @NotFound
SUB ECX, EDX
JLE @NotFound // SrcCount < TagCount ?
MOV AH, [EDI] // 取第一个字符进行查找
INC EDI
CMP AH, 97
JB @StartScan // < 'a' ?
CMP AH, 122 // > 'z' ?
JA @StartScan
SUB AH, 32 // 转大写
@StartScan:
OR ECX, ECX
JZ @NotFound
LODSB
CMP AL, 97
JB @@1
CMP AL, 122
JA @@1
SUB AL, 32 // 转大写
@@1:
CMP AH, AL
JZ @CmpStr
DEC CX
JNZ @StartScan
JMP @NotFound
@CmpStr:
DEC ECX
MOV EBX, EDX
SHL EAX, 16 // 保存AH
@ContinueCompare:
MOV AH, [ESI + EBX]
CMP AH, 97
JB @@2
CMP AH, 122
JA @@2
SUB AH, 32
@@2:
MOV AL, [EDI+EBX]
CMP AL, 97
JB @@3
CMP AL, 122
JA @@3
SUB AL, 32
@@3:
CMP AH, AL
JNZ @FindNext
DEC EBX
JG @ContinueCompare
@Found:
DEC ECX
MOV SrcCount, ECX
MOV EAX, ESI
DEC EAX
JMP @OutPoint
@NotFound:
XOR EAX, EAX
JMP @OutPoint
@FindNext:
SHR EAX, 16 // 恢复AH
JMP @StartScan
@OutPoint:
POP EBX
POP EDI
POP ESI
end;

function FastReplace(var Target: string; FindStr: string; ReplaceStr: string;
CaseSensitive: Boolean = True): Integer;
// Target: 需要进行替换操作的字符串
// FindStr: 要替换的字符串
// ReplaceStr: 替换成
// CaseSensitive: 是否大小写区分, 默认True (速度快)
// 返回值: 共替换了几个字符串
var
MaxCnt: Integer; // 保存匹配位置缓冲区的大小
MatchPoses: array of Integer; // 缓冲区, 保存所有匹配的位置
AdjustLen: Integer; // 最后需要调整Target的大小
n, // 当前匹配位置
l1, // Target的原始长度
l2, // FindStr的长度
l3, // ReplaceStr的长度
l: Integer; // 当前剩余需要匹配字符数
Proc: TFastPosProc; // 查找过程(大小写区分用FastPos, 不区分用FastPosNoCase)
begin
Result := 0;
l1 := Length(Target);
l2 := Length(FindStr);
l3 := Length(ReplaceStr);
if (l1 = 0) or (l2 = 0) then Exit;
AdjustLen := 0;
MaxCnt:=0;
l := l1;
if CaseSensitive then
Proc := @FastPos
else
Proc := @FastPosNoCase;
// 找出所有匹配字符串的位置, 并填到MatchPoses数组中
n := Integer(PChar(Target));
while (n <> 0) do
begin
n := Integer(Proc(PChar(FindStr), Ptr(n), l2, l));
if n <> 0 then
begin
if Result >= MaxCnt then // 超过缓冲区大小?
begin
MaxCnt := MaxCnt + 256; // 扩展缓冲区
SetLength(MatchPoses, MaxCnt);
end;
MatchPoses[Result] := n + AdjustLen; // 完成替换后在字符串中的位置
Inc(AdjustLen, l3 - l2);
Inc(Result);
Inc(n, l2);
end;
end;
if Result = 0 then Exit; // 未找到匹配
if AdjustLen > 0 then // 新字串比老字串长
begin
SetLength(Target, l1 + AdjustLen); // 分配内存
// 因为新字串比老字串长
// 因此复制字串时将从尾部开始
asm
PUSH ESI
PUSH EDI
PUSH EBX
PUSH EBP // 保存现场
MOV EAX, ReplaceStr
ADD EAX, L3
DEC EAX // ReplaceStr的结尾地址
MOV EBP, EAX // 保存
MOV EDX, MatchPoses // EDX将指向MatchPoses中最后一个替换
MOV EAX, Result
MOV EBX, EAX // EBX 保存需要替换的次数
DEC EAX
SHL EAX, 1
SHL EAX, 1
ADD EDX, EAX // EDX 定位到MatchPoses[Result-1]
MOV ESI, Target
ADD ESI, L1
DEC ESI // 原Target结尾处
MOV EDI, ESI
ADD EDI, AdjustLen // 新Target结尾处
STD // 设置方向标志: 从尾到头
@@1:
MOV ECX, EDI
LEA EAX, [EDX]
ADD EAX, L3
DEC EAX
SUB ECX, EAX // ECX为两个要替换字符串中间的字符数
MOV EAX, ECX
SHR ECX, 1
SHR ECX, 1
REP MOVSD
MOV ECX, EAX
AND ECX, 3
REP MOVSB // 复制原字符串
MOV EAX, ESI // 保存当前原串位置到EAX
SUB EAX, L2 // 调整位置跳过需要被替换的字符串
MOV ESI, EBP // ESI指向替换成的字符串
MOV ECX, L3 // 长度
SHR ECX, 1
SHR ECX, 1
REP MOVSD
MOV ECX, L3
AND ECX, 3
REP MOVSB // 复制新串到相应位置
SUB EDX, 4 // 下一个要替换的位置
MOV ESI, EAX // 恢复原串位置
DEC EBX
JNZ @@1 // 替换已完成?
// 因为是倒序, 所以第一个需要
// 替换的字符串前的原始串不会
// 改变, 没有必要再进行复制

CLD // 清方向标志
POP EBP
POP EBX
POP EDI
POP ESI // 恢复现场
end;
end
else if AdjustLen < 0 then // 因为是缩短Target, 所以先进行
// 替换与复制, 然后再调整内存
begin
asm
PUSH ESI
PUSH EDI
PUSH EBX
PUSH EBP // 保存现场
MOV EAX, ReplaceStr
ADD EAX, L1
ADD EAX, AdjustLen
PUSH EAX // 保存字符串的结尾(为计算最后一次替换
// 后还需要复制多少字节做准备)
MOV EBX, Result
MOV EDX, MatchPoses // EDX指向MatchPoses[0]
LEA ESI, [EDX]
MOV EDI, ESI
JMP @@2 // 第一次替换发生前的部分不需要进行复制
@@1:
LEA ECX, [EDX]
SUB ECX, EDI // 两次替换之间需要复制的字符数
MOV EAX, ECX
SHR ECX, 1
SHR ECX, 1
REP MOVSD
MOV ECX, EAX
AND ECX, 3
REP MOVSB // 复制
@@2:
MOV EAX, ESI
ADD EAX, L2 // 跳过原串中被替换字符串后的位置保存在EAX
MOV ESI, ReplaceStr
MOV ECX, L3
MOV EBP, ECX
SHR ECX, 1
SHR ECX, 1
REP MOVSD
MOV ECX, EBP
AND ECX, 3
REP MOVSB // 替换
MOV ESI, EAX // 恢复ESI到下一次需要移动或替换的位置
ADD EDX, 4 // EDX指向MatchPoses[i+1]
DEC EBX // 所有替换都已经完成?
JNZ @@1

POP ECX
SUB ECX, EDI // 最后一次替换后需要复制的字节数
MOV EBP, ECX
SHR ECX, 1
SHR ECX, 1
REP MOVSD
MOV ECX, EBP
AND ECX, 3
REP MOVSB // 复制
POP EBP
POP EBX
POP EDI
POP ESI // 恢复现场
end;
SetLength(Target, l1 + AdjustLen);
end
else begin // 字符串长度不变时
// 只需要进行替换而不必复制了
asm
PUSH ESI
PUSH EDI
PUSH EBX
PUSH EBP
MOV EBX, ReplaceStr //EBX 保存ReplaceStr地址
MOV EDX, MatchPoses
MOV EAX, Result
MOV EBP, L3
@@1:
LEA EDI, [EDX]
MOV ESI, EBX // 比 MOV ESI, ReplaceStr快
MOV ECX, EBP
SHR ECX, 1
SHR ECX, 1
REP MOVSD
MOV ECX, EBP
AND ECX, 3
REP MOVSB
ADD EDX, 4
DEC EAX
JNZ @@1
POP EBP
POP EBX
POP EDI
POP ESI
end;
end;
end;
 
1、通用的东西,不会比专用的快
2、大家不要觉得类似s[j] := s的操作很简单,那是一个硕大的过程,其中会
申请释放内存,在循环体内执行,效率出不来,使用汇编就是回避这些操作
3、优化程序,一定要分析指令执行过程,而不是拿38兆文件执行30次就定案
 

Similar threads

S
回复
0
查看
3K
SUNSTONE的Delphi笔记
S
S
回复
0
查看
2K
SUNSTONE的Delphi笔记
S
I
回复
0
查看
582
import
I
I
回复
0
查看
763
import
I
I
回复
0
查看
881
import
I
后退
顶部