用delphi搞了一个小服务,来实现多线程,可断点续传地上下载文件。所有的小块单独跑的时候,都非常正常,但是组装在一起的时候,突然内存暴长一下子占用了1G,引起了缓存的大量开销,频繁的IO读写引起了low CPU的情况下面的系统无法响应。开始的时候以为是里面的一些内存流没有释放引起的,review了一下代码确认没有问题。所以只好让系统再卡一次,把调试器挂到正在跑的服务上。突然发现以下的代码在运行时候,有异常情况出现:
var
packagesStr: string;
I: Integer;
begin
if Length(FPackages) = 0 then
packagesStr := ''
else begin
SetLength(packagesStr, Length(FPackages));
for I := 0 to Length(FPackages) -1 do
packagesStr[I ] := chr(byte(FPackages[I]) + 65);
end;
FPackages 是一个记录每个文件包的状态的枚举值,想要把它系列化后保存起来,为了效率起见,所以用了这么一个小技巧(具体的,让我留到后面再讲)。本来才80几个包,结果在这里的字串长度长得惊人。然后很快地发现了问题:
packagesStr[I ] := chr(byte(FPackages[I]) + 65);
应该是
packagesStr[I + 1 ] := chr(byte(FPackages[I]) + 65);
因为当I=0的时候,引入第一个值:65,把这个字串的指针搞乱了,所以引起了海量内存的使用。
好了,让我们先看看delphi里面的string的结构吧:
string是一个比较有意思的类型,它不仅仅是一个字串那么简单。它的结构的开始是一个指针,指向了直正字串开始的地址,所以sting有一个copy on write的特性,也就是说当字串赋值的时候,两个串都是指向同一个内存块(也就是string[1]这个地址),但是如果串被改的时候,就会复制一份出来改。
再来说说我的这个小技巧:因为我这里可能有几十个包,所以保存成字串的时候,如果是一段一段地加起来,到时候会产生大量的临时变量,引起性能的问题。所以我预算了一下它需要多长,就先把这个长度开出来,然后按位地去赋值。
string还有一个妙用是可以当成stream来使用。以前找了一个base64加密的类,它的参数只能传string,而我想要把一副图片转成base64好让它能在IE里面方便地传到服务器上。于是我就把这个图片读到了一个string里面:
var
stream: TMemoryStream;
imagestr: string;
begin
with TJPEGImage.Create do
try
Assign(FCurrDrawingPad);
CompressionQuality := 80;
stream := TMemoryStream.Create;
try
SaveToStream(stream);
SetLength(imagestr, stream.Size);
stream.Position := 0;
stream.Read(imagestr[1], stream.Size);
Result := StrTobase64(imagestr);
finally
stream.Free;
end;
finally
Free;
end;