原文:Manipulating memory with TMemoryStream
譯文:愛抱怨本鋪. eden的雜念溜
Manipulating memory with TMemoryStream
C++ Builder Developer's Journal 雜誌3月號裡有篇"File I/O",講述TFileStream類別和如何使用該類別讀寫在磁碟中的檔案。
但有些時候,與其使用磁碟,你會比較想在記憶體中處理較大的資料。
好消息是TFileStream有個親戚叫TMemoryStream,正好是為此目的而設計。
在本篇文章中,我們將展示如何使用TMemoryStream去處理記憶體,就和我們3月刊的檔案處理方法一樣。
我們將證明如何使用與檔案I/O相同的方法去讀寫記憶體。
聽起來不錯!但為什麼我要這樣做啊?
你可能會想將檔案分配到記憶體內而不會立即顯示出來。
有很多種很棒的理由讓你選擇它,但最令人在意的還是它的執行速度。
不論何時操作資料,關心的還是它的執行速度。
比方說…舉個例子,你想在程式中的特定時間點進行資料儲存。
但磁碟無法在寫入檔案的同時去處理不必要的工作。
相反的,你可以寫入資料到記憶體內,之後再從該記憶體複製資料到磁碟內,完成即時處理的需求。
假設:有時你會想從磁碟中讀取檔案,做一些處理(比方說像是加密/解密處理),然後再寫回檔案中。
每次寫入檔案時都會很快速的讀取檔案到記憶體中,並且修改及組合資料。
最後,假如你一直是以TMemo來操作字串資料和複製資料,你會知道這程序是如此之慢。
同樣的選擇,你可以讀取字串到記憶體內,並且在記憶體內進行工作,只把最終的結果複製到TMemo。
我們將展示這個小範例…嗯,我們還是先來看TMemoryStream所能提供的東西吧。
TMemoryStream basics
TMemoryStream提供可以在記憶體中工作的成員和方法。表A裡是它主要的功能。
Property | Description |
---|---|
Position | The current value of the stream position indicator. 目前Stream位置指標所指向的值 |
Position | is a read/write property. 可寫可讀的屬性 |
Size | The number of bytes of data currently in the stream. 目前Stream的Bytes資料容量 |
Method | Description |
CopyFrom() | Copies a specified number of bytes from a stream to this stream. 從某一個Stream指定特定容量複製到該Stream |
LoadFromFile() | Fills the memory stream with the contents of the specified file. 從檔案載入到Memory Stream |
LoadFromStream() | Fills the memory stream from another stream (either file or memory). 從另一個Memory Stream載入 |
Read() | Reads a specified number of bytes from the stream to the specified memory location (such as an array). 讀取序列資料,回傳Bytes |
Write() | Writes a specified number of bytes from a memory location to the stream. 寫入Bytes序列資料 |
SaveToFile() | Saves the contents of the memory stream to a disk file. |
SaveToStream() | Saves the contents of the memory stream to another stream. |
Seek() | Moves the file-position indicator by the specified amount from the start of the file, from the end of the file, or from the current position. |
SetSize() | Allocates the specified amount of memory for the stream. |
TMemoryStream的概念大致上類似TFileStream(詳細的資料可以參考3月刊的"File I/O")。* Eden 尚未翻譯
Loadxxx和Savexxx方法可以輕易地存取檔案和資料流到記憶體中。下面的範例是從磁碟讀出資料,並操作記憶體資料寫回到磁碟中:
1 2 3 4 5 | TMemoryStream* stream = new TMemoryStream; stream->LoadFromFile("myfile.dat"); // do some stuff to the stream stream->SaveToFile("myfile.dat"); delete stream; |
SetSize()方法雖然可以配置給資料流固定的記憶體總量。但不一定需要這樣做,因為TMemoryStream會自動分配和釋放所需的記憶體。但如果你需要精確掌握記憶體空間時,仍然可以去設定它,這樣可以在關鍵時刻節省很多Clock週期。
假設,我們試著在某個時間點寫入324kb大小的記憶體資料流。記憶體資料流以每8kb區塊作為一個單位。因此,寫入324kb資料給資料流時,會產生40個記憶體區塊。當我們對所分配好的324kb記憶體資料流空間寫入資料時,不以VCL自動處理記憶體操作下,平均時間會從950ms縮短為570ms,節省的時間可以說是相當地多。
那來個範例吧!
最好的學習方式還是來個範例。比方說你想加密一個文字檔。你可以透過改變每個字元的ACSII值去完成這項工作,讓這個文件無法被正常顯示。隨後你可以解譯它後再看看是否真的有還原。或許你也可以將加密/還原前後的結果個別存放到一個Memo元件。整個操作流程大致上如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | TMemoryStream* stream = new TMemoryStream; stream->LoadFromFile(OpenDialog1->FileName); stream->Position = 0; for (unsigned int i=0;i<stream->Size;i++) { char c; stream->Read(&c, 1); c += 32; stream->Position--; stream->Write(&c, 1); } // file is scrambled in memory // later... descramble and display stream->Position = 0; for (unsigned int i=0;i<stream->Size;i++) { char c; stream->Read(&c, 1); c -= 32; stream->Position--; stream->Write(&c, 1); } stream->Position = 0; Memo1->Lines->LoadFromStream(stream); |
這個範例程式大致上還蠻簡單明遼,但請注意這行:
stream->Position--;
在你從資料流當前的指標去讀取該字元之後,該指標會自動移動到下一個字元。你需要往回移動一個字元以利你將加密後的字元寫入。此外,請注意解密後的資料流可以輕易地透過Memo元件的LoadFromStream函式來讀取它。這個高效能的函式實際上是屬於TStringList的成員。你可以任意地使用字串列舉出的記憶體資料流結合使用它(而且很多的VCL元件也是使用TStringList儲存它們的資料)
列表A和B的程式段包含了加密和解密文字檔,並且將結果顯示在一個Memo元件裡。運作時間會顯示在Label元件中。這程式你可以看出使用Memo元件和使用TMemoryStream類別的結果比較。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #ifndef StreamUH #define StreamUH //--------------------------------------------- #include <vcl\Classes.hpp> #include <vcl\Controls.hpp> #include <vcl\StdCtrls.hpp> #include <vcl\Forms.hpp> #include <vcl\Dialogs.hpp> //--------------------------------------------- class TForm1 : public TForm { __published: // IDE-managed Components TMemo *Memo1; TLabel *Label1; TButton *Button3; TLabel *Label2; TOpenDialog *OpenDialog1; TGroupBox *GroupBox1; TButton *Button1; TButton *Button2; void __fastcall Button1Click(TObject *Sender); void __fastcall Button2Click(TObject *Sender); void __fastcall Button3Click(TObject *Sender); void __fastcall FormCreate(TObject *Sender); private: // User declarations public: // User declarations __fastcall TForm1(TComponent* Owner); }; //--------------------------------------------- extern TForm1 *Form1; //--------------------------------------------- #endif |
Listing B: STREAMU.CPP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | #include <vcl\vcl.h> #pragma hdrstop #include "StreamU.h" //--------------------------------------------- #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { int start = GetTickCount(); Memo1->Lines-> LoadFromFile(OpenDialog1->FileName); char* buff = Memo1->Lines->GetText(); Screen->Cursor = crHourGlass; for (unsigned int i=0;i<strlen(buff);i++) { char c = buff[i]; c += 32; buff[i] = c; } for (unsigned int i=0;i<strlen(buff);i++) { char c = buff[i]; c -= 32; buff[i] = c; } Memo1->Lines->Text = buff; Screen->Cursor = crDefault; char buff2[20]; sprintf(buff2, "%.02f seconds", (GetTickCount() - start)/1000.0); Label2->Caption = buff2; } //--------------------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { TMemoryStream* stream = new TMemoryStream; stream->LoadFromFile(OpenDialog1->FileName); Screen->Cursor = crHourGlass; stream->Position = 0; for (unsigned int i=0;i<stream->Size;i++) { char c; stream->Read(&c, 1); c += 32; stream->Position--; stream->Write(&c, 1); } stream->Position = 0; for (unsigned int i=0;i<stream->Size;i++) { char c; stream->Read(&c, 1); c -= 32; stream->Position--; stream->Write(&c, 1); } stream->Position = 0; Memo1->Lines->LoadFromStream(stream); Screen->Cursor = crDefault; delete stream; char buff[20]; sprintf(buff, "%.02f seconds", (GetTickCount() - start)/1000.0); Label2->Caption = buff; } //--------------------------------------------- void __fastcall TForm1::Button3Click(TObject *Sender) { if (OpenDialog1->Execute()) { Button1->Enabled = true; Button2->Enabled = true; Memo1->Text = "File Selected: " + OpenDialog1->FileName; } } //--------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender) { Button1->Enabled = false; Button2->Enabled = false; } |
使用這個程式時,一定要使用至少大於50kb的文字檔,這樣的結果會比較明顯。你會發現TMemoryStream的高效能,它比Memo元件處理快了將近100倍之多。
結論
TMemoryStream是很好很強大的。當你了解如何使用TMemoryStream時,特別是與TFileStream和TStringList結合,新的程式設計前景將會呈現在你的眼前。
沒有留言:
張貼留言