What is a stream? TStream?
A stream is what its name suggests: a flowing "river of data". A stream has a beginning, an end, and you're always somewhere in between of these two points.
Using Delphi's TStream objects you can read from or write to various kinds of storage media, such as disk files, dynamic memory, and so on.
What data can a stream contain?
A stream can contain anything you like, in the order you like. In the example project accompanying this article, fixed-size records are used for simplicity purposes, but you can write any mix of variable-sized data to a stream. Remember however, that _you_ are responsible for the householding. There is no way Delphi can "remember" what kind of data are in a stream, or in what order!
Streams versus arrays
Arrays have the disadvantage of having a fixed size that must be known at compile time. Ok, you can use dynamic arrays.
A stream on the other hand, can grow up to the size of available memory, which is considerably large size on today's systems, without any "householding" chores.
A stream cannot be indexed, as an array can. But as you'll see below, "walking" up and down a stream is very easy.
Streams can be saved/loaded to/from files in one simple operation.
Flavours of streams
TStream is the base (abstract) class type for stream objects. Being abstract means that TStream should never be used as such, but only in it's descendant forms. For streaming any kinds of information, choose a descendant class according to the specific data and storage needs. For example:
TFileStream (for working with files)
TMemoryStream (for working with a memory buffer)
TStringStream (for manipulating in-memory strings)
TBlobStream (for working with BLOB fields)
TWinSocketStream (for reading and writing over a socket connection)
TOleStream (for using a COM interface to read and write)
As you'll see in the sample project, TmemoryStream and TFileStream are remarkably interchangeable and compatible.
unit Unit1; { Article___________________________________________________________ Streams, streams, ... TStream <a href="http://delphi.about.com/library/weekly/aa110803a.htm</p> <p>" title="http://delphi.about.com/library/weekly/aa110803a.htm</p> <p>" rel="nofollow">http://delphi.about.com/library/weekly/aa110803a.htm</p> <p></p></a> A stream is what it's name suggests: a flowing "river of data". A stream has a beginning, an end, and you're always somewhere in between of these two points. Learn about using the TStream class in Delphi: how to use stream objects to read from, write to, or copy information stored in a particular medium. } interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Spin, ExtCtrls, IniFiles; const FmtRec = '%6.6d %1s %4.4d %20s'; // record formatter type // just some testrecord TTestRec = record IntField : integer; CharField : char; ByteField : byte; StrField : string[20]; end; PTTestRec = ^TTestRec; TForm1 = class(TForm) Button1: TButton; Memo1: TMemo; ProcessMs1: TButton; Ms1SaveToFile: TButton; Ms1LoadFromFile: TButton; btnLoadFs: TButton; Edit3: TEdit; StreamWalkBut: TSpinButton; StaticText2: TStaticText; Edit4: TEdit; StaticText3: TStaticText; Memo2: TMemo; StaticText4: TStaticText; Button2: TButton; Button3: TButton; Button4: TButton; procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure ProcessMs1Click(Sender: TObject); procedure Ms1SaveToFileClick(Sender: TObject); procedure Ms1LoadFromFileClick(Sender: TObject); procedure btnLoadFsClick(Sender: TObject); procedure StreamWalkButDownClick(Sender: TObject); procedure StreamWalkButUpClick(Sender: TObject); procedure Edit4KeyPress(Sender: TObject; var Key: Char); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); procedure Button4Click(Sender: TObject); private { Private declarations } Ms1, Ms2 : TmemoryStream; procedure FillStream( AStream: TStream; N: integer ); procedure ShowStreamData(AStream: TStream; ABuffer: TStrings; ID : string); public { Public declarations } end; var Form1: TForm1; implementation const IniFileName: string = 'example.ini'; {$R *.DFM} //========================================== procedure TForm1.FormCreate(Sender: TObject); begin Ms1 := TmemoryStream.Create; Ms2 := TMemoryStream.Create; end; //=========================================== procedure TForm1.FormDestroy(Sender: TObject); begin Ms1.Free; Ms2.Free; end; {-----------------------------------------} { general procedure to show contents of } { streams populated with TTestRec records } {-----------------------------------------} procedure TForm1.ShowStreamData(AStream: TStream; ABuffer: TStrings; ID: string); var TestRec : TTestRec; begin ABuffer.BeginUpdate; // this speeds up things dramatically! ABuffer.Clear; ABuffer.Add( 'Contents of : ' + ID ); ABuffer.Add( 'Class : ' + AStream.ClassName ); ABuffer.Add( 'Size is : ' + IntToStr(AStream.Size) + ' bytes' ); // AStream.Position := 0; while AStream.Position < AStream.Size do begin AStream.Read( TestRec, SizeOf(TestRec) ); with TestRec do ABuffer.Add( Format( FmtRec, [IntField, CharField,ByteField, StrField])); end; ABuffer.EndUpdate; end; {----------------------------------------} { Fill any stream with N records } {----------------------------------------} procedure TForm1.FillStream( AStream: TStream; N: integer); var i : integer; TestRec : TTestRec; begin { important note -------------- Streams are persistent, so if , say, 1000 records were previously written, and after that another 100 records are written to it starting from the _beginning_ of the stream, the stream still will contain 1000 records! TStream does not have a SetSize() method, as TMemory/TFileStream have. So in here, we can only write from the beginning by setting Position to zero. Clearing of the stream must be done outside from this procedure } AStream.Position := 0; for i := 1 to N do with TestRec do begin IntField := Random(10000); CharField := chr(65 + random(26)); ByteField := i; StrField := 'SomeString ' + IntToStr(i); //======================================= // Rule Nr. 1 : **ALWAYS** use SizeOf() ! //======================================= AStream.Write( TestRec, SizeOf(TTestRec) ); end; end; //============================================ procedure TForm1.Button1Click(Sender: TObject); begin Ms1.Clear; FillStream( Ms1, 1000 ); ShowStreamData( Ms1, Memo1.Lines, 'Ms1' ); end; //============================================== procedure TForm1.ProcessMs1Click(Sender: TObject); Var TestRec : TTestRec; Start, Duration: DWORD; RecCount: integer; begin Start := GetTickCount; // ms since last boot Ms1.Position := 0; RecCount := 0; while Ms1.Position < Ms1.Size do begin Ms1.Read( TestRec, SizeOf(TestRec) ); inc( RecCount ); end; Duration := GetTickCount - Start; ShowMessage( 'Reading Stream took ' + IntToStr(Duration)+' ms.'+ #13 + IntToStr(RecCount) + ' records were read'); end; //================================================== procedure TForm1.Ms1SaveToFileClick(Sender: TObject); begin try Ms1.SaveToFile('test.dat'); except Showmessage(' Oops.. couldn''t write!' + #13 + SysErrorMessage(GetLastError)); end; end; //=================================================== procedure TForm1.Ms1LoadFromFileClick(Sender: TObject); begin try Ms1.LoadFromFile('test.dat'); except Showmessage(' Oops.. couldn''t read file!' + #13 + SysErrorMessage(GetLastError)); end; ShowStreamData( Ms1, Memo1.Lines, 'Ms1' ); end; //============================================= procedure TForm1.btnLoadFsClick(Sender: TObject); var Fs : TFileStream; begin try Fs := TFileStream.Create('test.dat', fmOPENREAD ); ShowStreamData( Fs, Memo2.Lines, 'Fs1' ); except Showmessage(' Oops.. couldn''t read file!' + #13 + SysErrorMessage(GetLastError)); end; Fs.Free; end; //================================================== procedure TForm1.StreamWalkButDownClick(Sender: TObject); var TestRec : TTestRec; begin if Ms1.Position < Ms1.Size then begin Ms1.Read( TestRec, SizeOf(TestRec) ); with TestRec do Edit3.Text := Format( FmtRec, [IntField, CharField,ByteField, StrField]); end; end; //=============================================== procedure TForm1.StreamWalkButUpClick(Sender: TObject); var TestRec : TTestRec; begin if Ms1.Position > 0 then begin // we first need to go back *2* records, since // each read() advances the stream position! Ms1.Seek( -2 * SizeOf(TestRec), soFROMCURRENT ); // clamp ! Delphi does not do any limit checks, // and will return garbage data if Ms1.Position < 0 then Ms1.Position := 0; // Ms1.Read( TestRec, SizeOf(TestRec) ); with TestRec do Edit3.Text := Format( FmtRec, [IntField, CharField,ByteField, StrField]); end; end; //============================================================ procedure TForm1.Edit4KeyPress(Sender: TObject; var Key: Char); var RecIndex : integer; TestRec : TTestRec; begin if Key = #13 then begin Key := #0; // suppress annoying beep... try RecIndex := Pred(StrToInt(Edit4.Text)) * SizeOf( TestRec ); // 1-based except on EConvertError do begin ShowMessage(' Gotcha! Illegal Number...' ); EXIT; end; end; Ms1.Seek( RecIndex, soFROMBEGINNING ); // clamp! if Ms1.Position < 0 then Ms1.Position := 0; if Ms1.Position >= Ms1.Size then Ms1.Position := Ms1.Size - SizeOf(TTestRec); // Ms1.Read( TestRec, SizeOf(TestRec) ); with TestRec do Edit3.Text := Format( FmtRec, [IntField, CharField,ByteField, StrField]); end; end; //=========================================== procedure TForm1.Button2Click(Sender: TObject); begin Ms1.Position := 0; // go to begin Ms2.Position := 0; Ms2.CopyFrom( Ms1, Ms1.Size ); ShowStreamData( Ms2, Memo2.Lines, 'Ms2' ); end; //============================================ procedure TForm1.Button3Click(Sender: TObject); begin Ms1.Position := Ms1.Size div 2; // half of stream Ms2.Position := 0; Ms2.CopyFrom( Ms1, Ms1.Size div 2 ); ShowStreamData( Ms2, Memo2.Lines, 'Ms2' ); end; //============================================ procedure TForm1.Button4Click(Sender: TObject); var Fs : TFileStream; begin try Fs := TFileStream.Create('test.dat', fmOPENWRITE or fmCREATE ); FillStream( Fs, 10 ); ShowStreamData( Fs, Memo2.Lines, 'Fs1' ); except Showmessage(' Oops.. couldn''t write file!' + #13 + SysErrorMessage(GetLastError)); end; Fs.Free; // << this actually writes the data to disk end; end.
最新评论
35 周 22 小时之前
37 周 2 天之前
1 年 17 周之前