Delphi中Packed Record数组跨实例加载失败问题求助
问题根源分析
你遇到的核心问题是保存的是指针地址而非实际的记录数据。在同一个程序实例中,rates_M1里的指针指向的内存还存在,所以加载后能正常访问;但程序重启后,这些旧的指针地址已经无效(内存被回收或重新分配),此时访问这些指针指向的内容自然会出错。
看一下你保存数据的关键代码:
Stream.WriteBuffer(Pointer(rates_M1)^, L * SizeOf(ARate));
这里SizeOf(ARate)是指针的大小(32位系统是4字节,64位是8字节),你写入的是700万个指针的地址值,而不是每个指针指向的TRate记录的实际数据。
解决方案
根据你的需求,有两种靠谱的解决方式:
方案1:改用记录数组(推荐,更简单)
直接使用array of TRate替代array of PRate,这样数组里存储的是实际的记录数据,而非指针。修改后代码更简洁,也避免了内存管理的麻烦:
修改初始化与保存代码
procedure TForm4.InitSaveAndReload(Sender: TObject); var _start : TDatetime; filename : string; Stream : TFileStream; i,L : integer; rates_M1 : array of TRate; // 替换指针数组为记录数组 begin filename := edFile.Text; MemoLogs.Lines.Add('Initialization of 7 million array of records... Please wait.'); Refresh; // 初始化数组 _start := Now; SetLength(rates_M1, 7000000); for i:= 0 to 6999999 do begin // 直接操作数组内的记录,无需分配指针 rates_M1[i].time := DateTimeToUnix(IncMinute(Now, i)); rates_M1[i].open := 1.25698; rates_M1[i].low := 1.2574; rates_M1[i].high := 1.2547; rates_M1[i].close := 1.65874; rates_M1[i].tick_volume := 154; rates_M1[i].spread := 5; rates_M1[i].real_volume := 15741; end; MemoLogs.Lines.Add(IntToStr(Length(rates_M1)) + ' array of records initialized ' + TimeElapsedToString(MilliSecondsBetween(Now, _start), true)); // 保存数组 _start:= Now; Stream:= TFileStream.Create(filename , fmCreate); try L:= Length(rates_M1); Stream.WriteBuffer(L, SizeOf(L)); // 写入实际记录数据,SizeOf(TRate)是单个记录的完整大小 Stream.WriteBuffer(rates_M1[0], L * SizeOf(TRate)); finally Stream.Free; MemoLogs.Lines.Add(IntToStr(Length(rates_M1)) + ' records saved to disk in ' + TimeElapsedToString(MilliSecondsBetween(Now, _start), true)); end; LoadFromFile(); end;
修改LoadFromFile代码
procedure TForm4.LoadFromFile; var _start : TDatetime; filename : string; Stream : TFileStream; i,L : integer; rates : array of TRate; // 替换指针数组为记录数组 begin // 重新加载数组 _start := Now; filename := edFile.Text; Stream:= TFileStream.Create(filename , fmOpenRead); try Stream.Read(L, SizeOf(L)); SetLength(rates, L); // 读取实际记录数据 Stream.Read(rates[0], L * SizeOf(TRate)); finally Stream.Free; MemoLogs.Lines.Add(IntToStr(Length(rates)) + ' records loaded from disk in ' + TimeElapsedToString(MilliSecondsBetween(Now, _start), true)); end; // 打印前20条记录 MemoLogs.Lines.Add('Print 20 first records just reloaded in another array of records!' + TimeElapsedToString(MilliSecondsBetween(Now, _start), true)); for i := 0 to 20 do MemoLogs.Lines.Add('i=' + IntToStr(i) + #9 + IntToStr(rates[i].time) + #9 + FloatToStr(rates[i].open) + #9 ); end;
方案2:如果必须使用指针数组(不推荐,除非有特殊内存需求)
如果你因为内存限制必须使用指针数组,那保存时需要逐个写入每个指针指向的TRate数据,读取时逐个分配内存并写入数据:
修改保存代码
// 保存时遍历指针数组,写入每个记录的实际内容 Stream.WriteBuffer(L, SizeOf(L)); for i := 0 to L-1 do begin Stream.WriteBuffer(rates_M1[i]^, SizeOf(TRate)); end;
修改读取代码
// 读取时先获取长度,再逐个分配内存并读取数据 Stream.Read(L, SizeOf(L)); SetLength(rates, L); for i := 0 to L-1 do begin New(rates[i]); Stream.Read(rates[i]^, SizeOf(TRate)); end;
⚠️ 注意:这种方式需要记得在使用完数组后,遍历指针数组逐个调用Dispose(rates[i]),避免内存泄漏。
额外注意事项
- 你的
TRate是packed record,这非常好,确保了记录在内存中的布局紧凑,没有字节对齐的问题,跨实例/平台读取不会出现数据错位。 - 如果后续需要扩展记录字段,建议在文件开头写入一个版本号,方便后续兼容旧版本的文件。
内容的提问来源于stack exchange,提问作者och
相关产品推荐
相关产品推荐

