2010/07/07

[翻譯]玩轉你的記憶體 -- 使用TMemoryStream

雖然是1998年的文章,但2010年看到這篇文章的我,還是覺得有很大的收獲,在這邊分享出來
原文: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裡是它主要的功能。

Table A: Important TMemoryStream properties and methods
PropertyDescription

Position
The current value of the stream position indicator.
目前Stream位置指標所指向的值

Position
is a read/write property.
可寫可讀的屬性
SizeThe number of bytes of data currently in the stream.
目前Stream的Bytes資料容量
MethodDescription

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類別的結果比較。

Listing A: STREAMU.H
 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結合,新的程式設計前景將會呈現在你的眼前。

沒有留言:

張貼留言