Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2020年12月16日] [更新 2022年12月28日] Delphi 一般・その他
テキストをバイナリに変換して保存
動作確認等 Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
[整理番号 - 0820]







テキストをバイナリに変換して保存


意味不明なタイトルになっていますが、これには理由があります。

「テキストをバイナリに変換して保存したいのですが、どのようにしたらいいのか ?」という質問を頂きました。
「何も処理する必要はありません。コンピュータのデータは全てバイナリです」
と回答しました。

文字列を書き込むとか、文字列 (テキスト) を保存するという言い方は一般的に使われています。しかし、コンピュータでは、実際に文字を記憶したり保存することはできません。この記事は、そのことを理解して頂くために作成しました。
現実の、実際のアプリの開発で、本ページで説明している内容は、おそらく、ほとんど意識する必要はないと思われます。しかし、表題のような質問をされる方には是非、確認して頂きたい内容であると思っています。



[備考 1]
文字列の文字には、改行文字のように、画面に表示されない制御文字があります。ディスクへの保存では、これらの文字も保存します。
コンピュータにおけるテキストは、視覚的に見える文字列のことですが、一般的に言うテキストの保存では、制御文字も保存するので、プログラムとしては文字列の保存てす。


[備考 2]
テキストは、今読んでいる文字の集まりのことてす。単語や文章として認識できる文字列を意味します。バイナリは、2 つの異なる状態のことです。




上の Youtube 動画では、現在の CPU には数 10 億個から数 100 億個のトランジスタがあると言っています。1971年、世界で初めて発売された CPU, Intel 4004 のトランジスタは 2,300 個でした。





01_コンピュータのデータは全てバイナリ


現在普及してるコンピュータのデータは全てバイナリです。
バイナリ (binary) は、 2 つの状態、対になっている状態のことです。この状態は、スイッチに例えることがあります。実際、機械的なスイッチであるリレーを使用したリレー式計算機の時代がありました。現在のコンピュータで使用しているデータ記憶用の電子回路は、このスイッチに相当する機能で構成されています。コンピュータのメモリはその集合体です。ハードディスクでは、磁性を持つ微小素子 (磁石) の磁極の向きがスイッチの状態に相当します。

このスイッチの ON, OFF は、0 と 1 で表わします。これを 2 進数と言います。
10 進数の 1 桁は 9 までです。次のように、9 に 1 を足すと 1 つ桁が増えて 10 になります。


    9 + 1 = 10
   10 + 1 = 11
   99 + 1 = 100
  999 + 1 = 1000 

2 進数の 1 桁を「ビット (bit)」と言います。bit には「わずかな」とか「微小な」という意味がありますが、コンピュータにおける bit は、binary digit から作られた用語です。1 ビットは 1 までです。次のように、1 に 1 を足すと 1 つ桁が増えて 2 ビットの 10 になります。10 進数の 10 と区別するために、通常は「イチゼロ」と読みます。


    1 + 1 = 10
   10 + 1 = 11
   11 + 1 = 100
  111 + 1 = 1000 

2 進数の 1 を 1 ビット左にシフトすると 10 進数で 2 倍の 2 になります。更に左にシフトするとその 2 倍の 4 になります。更に左にシフトするとその 2 倍の 8 になります。
右にシフトすると、逆に 1 / 2 の値になります。


        1    // 10 進数の 1
       10    // 10 進数の 2
      100    // 10 進数の 4
     1000    // 10 進数の 8
    10000    // 10 進数の 16
   100000    // 10 進数の 32

       11    // 10 進数の 3
      110    // 10 進数の 6
     1100    // 10 進数の 12



[備考 1]
メモリ等のスイッチ機能の電子回路には、主に、フリップフロップと呼ばれている方式と、充電状態を使用するコンデンサ方式があります。
メモリには、電源の供給がなくなると記憶状態が失われる揮発性メモリと、USB メモリのように、電源が供給されなくなっても記憶状態を保持する不揮発性のメモリがあります。不揮発性のメモリには、ROM (Read Only Memory・ロム) と呼ばれる読出し専用のメモリがあります。

[備考 2]
CD や DVD 等の光ディスクは、円盤表面のピットと呼ばれる微細な凹凸等による光の反射を使用します。読出しの時は、レーザー光などの反射光の強度によって 0, 1 の情報とします。書き込みは、表面に塗布した物質をレーザー光照射で化学変化させる方式や、結晶化させる等の方式があります。
写真のように、文字や画像を光ディスクに焼き付けるわけではありません。

[備考 3]
バイナリデータの表示に 2 進数の 0, 1 を使用しますが、スイッチの ON, OFF に例えることがあるように、これは、実際には論理値です。現在のコンピュータでは、論理演算を使用して、数学における 2 進法の加減乗除等の計算を行っています。論理演算には、論理回路と呼ばれている電子回路が使われています。




02_バイナリデータの表示には 16 進数を使用する


2 進数は 0 と 1 の羅列です。0 と 1 の羅列では冗長すぎて扱うには不便です。そこで、プログラムで扱う時は、4 ビットを 1 桁とする表記が使われています。4 ビットは 0 から 15 までの 16 通りの状態を表現できます。これを 16 進数と言います。
16 進数を 1 桁で表記するために、10 進数の 10 以上の値には、以下のように、アルファベットの A から F までを割り当てます。

2 進数は、4 ビット目だけが 1 の時は 8, 3 ビット目だけが 1 の時は 4, 2 ビット目だけが 1 の時は 2, 1 ビット目だけが 1 の時は 1 です。このことから、4 ビットの値を 16 進の値にするのは比較的容易です。例えば、1011 は 8 + 2 + 1 = 11 ですから B となります。


  2 進数  16 進数
 -----------------
  0000     0
  0001     1
  0010     2
  0011     3
  0100     4
  0101     5
  0110     6
  0111     7
  1000     8
  1001     9
  1010     A
  1011     B
  1100     C
  1101     D
  1110     E
  1111     F


16 進数の F に 1 を足すと 1 つ桁が増えて 16 進数では 10 になります。同じ様に FF に 1 を加算すると 16 進数では 100 になります。


    F + 1 = 10
   F0 + 1 = F1
   FF + 1 = 100
  FFF + 1 = 1000 


A や B は文字です。Delphi では 16 進数の文字列の先頭に $ (ドル記号) を付けると、数値として扱えるようになっています。C / C++ では 0x を追加します。
下図は、16 進数 E を 10 進数の数値として表示した結果です。C / C++ では 0xE とすれば数値として扱えます。
10 進数と同じように、先行する 0 は 省略できます。例えば $0E は $E と同じです。 桁数を明確にする時は #0E と表記します。



図1
16 進数の E を 10 進数で表示
  • 16 進数表示の文字列の先頭に $ を付けるだけ
  • 16 進数の E は 2 進数では 1110

リスト1
procedure TForm1.Button1Click(Sender: TObject);
var
  LValue : Integer;
begin
  Memo1.Lines.Clear;

  LValue := $E;
  Memo1.Lines.Add('$E');
  Memo1.Lines.Add(IntToStr(LValue));
end;


IntToHex 関数を使用すると、数値を 16 進数表示の文字列にできます。



図2
数値を 16 進数表示の文字列にする
  • IntToHex 関数を使用する

リスト2
procedure TForm1.Button1Click(Sender: TObject);
var
  LValue : Integer;
begin
  Memo1.Lines.Clear;

  LValue := 14;

  Memo1.Lines.Add(IntToStr(LValue));

  // IntToHex の第 2 引数は 16 進数の桁数
  Memo1.Lines.Add('$' + IntToHex(LValue, 2));
end;


10 進数の 123 は 2 進数で 0111 1011 です、これは 16 進数で 7B です。



[備考]
データの処理単位が 6 ビット、12 ビット、24 ビット、36 ビット等、3 の倍数ビットのコンピュータがありました。それらのコンピュータでは 8 進数が使われていました。
C 言語では、数値の先頭に 0 (ゼロ) を付けると 8 進数を意味します。例えば 0123 は 8 進数の 123 を意味します (2 進数では 001 010 011、16 進数では 53,10 進数では 83)。0123 は、Delphi では 10 進数の 123 と同じとなります。




03_コンピュータのデータ処理の最小単位はバイト


コンピュータでデータを扱う場合、16 進数の 1 桁である 4 ビットでは少なすぎます。7 ビットは 10 進数で 127 ですから、英語圏であれば英数字と記号や制御文字の全てを表現できます。12 ビット単位で処理するコンピュータもありました。
このように、コンピュータで扱う処理単位のビット数を Word (ワード) と言っていましたが、現在、Word という用語はこの意味では使われていません。現在では、コンピュータにおけるデータ処理単位として Byte (バイト) という用語が使われています。

コンピュータのプログラムやデータを、パンチカードと呼ばれるカードに複数の穴をあけて、それを読み取り機械を使用してコンピュータに入力していた時代がありました。この穴の有無がビットに相当します。この穴をあける穿孔機の、穴をあける部品の動作が、鳥がくちばしでつつく動作に似ていることからバイト (Byte) とう用語が生まれました。つつくは英語で bite (バイト) です。bite では bit と間違えやすいので、2 文字目を y にしたのが現在の Byte (バイト) という用語です。

パソコンと呼ばれるコンピュータの普及に伴い、8 ビットを単位として処理する CPU が普及してきました。この 8 ビットを 1 バイトとするのが定着してきました。この 8 ビット、つまり 1 バイトが、コンピュータでデータを処理する時の最小単位です。8 ビットは 16 進数で 2 桁ですから $FF です。その意味でも扱いやすいと言えます。
ISO(国際標準化機構) / IEC(国際電気標準会議)も 2008年に、8 ビットを 1 バイトとする定義を、単位系のリストに追加しました。
バイトの定義は ISO / IEC の規格 IEC 80000-13 (情報学・情報工学) にあります。この文書 (PDF) は以下のリンクから入手できます (有料)。

バイナリデータを 16 進数のバイト単位で表示した場合、それをバイトデータと言うこともあります。



[備考]
ISO / IEC の規格文書は国立国会図書館 (東京本館) で閲覧できます。複写サービスはしていますが、著作権法の制約で、各規格の半分までとなります。貸し出しはしていません。




04_メモリにもディスクにも文字は存在しない


コンピュータやハードディスク等の記憶媒体を「箱」に例えて、その箱に文字や画像を格納しておく、という説明があります。しかし、それはエンドユーザ向けの説明です。文字列 (テキスト) をディスクに保存しても、ディスク内に文字が入るわけではありません。ディスクへの保存とは、ディクス内の微小な磁性素子 (磁性体・磁石) の磁極の向きを変更する操作です。

コンピュータでは、文字を扱うために、文字ごとに数値を割り当てています。この値をコードポイントと言いますが、「文字コード」あるいは「文字のコード」とか「文字の値」という用語も使われています。
例えば「東」という文字のコードポイントは $6771 です。下図はそのことを確認した結果です。数値の先頭に # を付加するとコードポイントを意味します。#$6771#$4EAC にすると「東京」という文字列になります。

String 型の変数に「東京」という文字列を代入すると、16 進数の 67714EAC というデータがメモリに書き込まれます 。メモリはスイッチ回路の巨大な集まりです。データをメモリに書き込むということは、メモリの特定の位置の、(この例では 4 バイトなので) 32 個のスイッチの状態を変えることです。東京という文字列そのものがメモリに書き込まれるわけではありません。と言うより、書き込みようがありません。

変数に文字列や数値を代入するのも、メモリストリームに書き込むのも、TStringList に文字列を追加するのも、メモ帳に文字列を書き込むのも全て同じです。それらは全てメモリのどこかの位置にバイナリデータとして書き込まれます。



図3
文字もバイトデータ
  • 文字または文字列型の変数にコードポイントの値を代入して表示

リスト3
procedure TForm1.Button1Click(Sender: TObject);
var
  LText : string;
begin
  Memo1.Lines.Clear;

  LText := #$6771;
  Memo1.Lines.Add(Ltext);

  // 複数の文字 (文字列) の場合
  LText := #$6771#$4EAC;
  Memo1.Lines.Add(Ltext);
end;


データをディスクに保存する時も同じです。数値や文字列は、バイナリデータとしてディスク内のどこかの場所に書き込まれます。通常、それらのデータを、2 進数の 0, 1 てばなく、16 進数 2 桁であるバイト単位で表します。
テキストファイルとして保存すると、ディスクにファイル名のバイナリデータが書き込まれ、テキストファイルのテキストのバイナリデータがそのファイル名に関連付けされます。ディスクには、それらのファイル名とその関連付けを管理する目次等の管理領域があります。メモリも同じです。変数などを管理する領域があるわけです。
当然ですが、それらの管理領域のデータもバイナリデータでず。コンピュータのデータは全てバイナリデータです。



[備考]
文字に割り当てる数値にはいろいろな方式があります。これを文字符号化方式と言います。
Delphi 2009 以降で使用されている文字符号化方式は、UTF-16 の名前で呼ばれている方式です。この方式は Unicode (ユニコード) と言われています。文字符号化方式によって、同じ文字でもコードポイントの値が異なります。
Unocode (UTF-16) のコードポイントであることを明確にするには、先頭に U+ を付けて表記します。例えば文字「東」は U+6771 とします。U+ の後ろに続く値は 16 進数です。




05_バイナリデータが文字として見える仕組み


Windows には、いろいろなデザインやスタイルの書体が実装されています。それらのデザインやスタイルごとにフォントファイルがあります。そのフォントファイルには、コードポイント別にフォントデータがあります。ただし、全てのフォントファイルに全ての文字のデータがあるわけではありません。使用可能な「文字」はフォントファイル、つまり、書体によって異なります。

フォント (書体、サイズ、色等) を指定して文字を表示する命令を実行します。例えば $6771 というバイトデータを指定すると、システムは、フォントファイルの中から $6771 に相当するフォントデータを取り出します。そのフォントデータに基づいて文字を描画して、画面に表示したり印刷します。
下図は「東」という文字のフォントデータの例です。複数の点のデータがあります。それらの点を直線あるいは曲線で結ぶと文字になります。フォントデータには、それらの点をどの順番で、点ど点を直線で結ぶのか曲線で結ぶのかという情報も含まれています。
これが、バイトデータ (バイナリデータ) である文字コードが「文字」として見える仕組みです。

このように、文字は、メモリやディスク内に存在するのではなく、画面に表示したり印刷する時に初めて「文字」になります。メモリやディスク内に「文字」は存在しません。メモリやディスク内に存在するのは単なるバイナリデータです。



図4
フォントデータの例
  • 文字を構成するための複数の点のデータがある
図5
点と点を直線あるいは曲線で結ぶと文字になる

メモリやディスク内に存在するのは単なるバイナリデータです。
上の例で使用した $6771 というバイナリ値は、文字コードとして処理すれば「東」という文字になりますが、Integer 型の値として処理すれば 26481 となります。
メモ帳等のテキストエディタは、バイナリデータをコードポイントと解釈して処理します。その結果、文字あるいは文字列 (テキスト) として見えることになります。



図6
メモリやディスク内に存在するのは単なるバイナリデータ
  • $6771 というバイナリデータは、
  • 整数の値として処理すれば 26481
  • 文字のコードとして処理すれば「東」という文字

リスト4
procedure TForm1.Button1Click(Sender: TObject);
var
  LInteger : Integer;
begin
  Memo1.Lines.Clear;

  // $6771 は東という文字の文字のコードの値
  LInteger := $6771;

  Memo1.Lines.Add(IntToStr(LInteger));
  Memo1.Lines.Add(Char(LInteger));
end;


[備考]
文字の描画の仕組みで分かるように、文字の描画には多くの処理が必要です。そのため、文字の描画を伴う処理は時間がかかります。それを回避するため、TMemo 等にテキストファイルを読み込む場合、一般的に、TMemo.Lines.BeginUpdate メソッド等を使用して文字の描画を抑制します。
文字の描画を伴わなければ、文字列 (テキスト) のバイナリデータに対する処理だけになり、処理が高速になります。




06_文字列の保存は文字列のバイナリデータの保存


文字列 (テキスト) を保存するということは、文字列の各々の文字のバイナリデータであるコードポイントの値を保存することです。これを再確認するサンプルです。
「日本語」という文字列の各文字のバイナリ値を Ord 関数で取得します。これは 2 バイトの整数値です。それらの値をそのままメモリストリームに順番に書き込んでディスクに保存します。保存したファイルを TMemo に読み込んで表示すると元の「日本語」という文字列が表示されます。
ディスクに保存したのはバイナリデータとしての数値です。文字列そのものではありません。

Ord 関数で取得するコードポイントの値は Unicode (UTF-16) における値ですから、ファイルから読み込む時はエンコードの指定が必要です。



図7
文字のバイナリ値を保存

リスト5
procedure TForm1.Button1Click(Sender: TObject);
var
  LText   : string;
  LUInt16 : UInt16;
  LStream : TMemoryStream;
  LCount  : Integer;
begin
  Memo1.Lines.Clear;

  LText := '日本語';

  LStream := TMemoryStream.Create;
  try
    for LCount := 1 to Length(LText) do begin
      // 文字のバイナリ値 (コードポイント) を取得
    LUInt16 := Ord(LText[LCount]);
    // その数値をメモリストリームに書き込む
    LStream.Write(@LUInt16, SizeOf(LUInt16));
    end;
    // 保存
    LStream.SaveToFile('000.txt');

    // 保存した値を読み込んで表示
    // UniCode で保存されているのでエンコードの指定が必要
    Memo1.Lines.LoadFromFile('000.txt', TEncoding.Unicode);
  finally
    FreeAndNil(LStream);
  end;
end;


次のサンプルは、文字列を指定して保存する例です。
LText[1] は、正確には Addr(LText[1]) です。つまり、最初の文字のメモリアドレスです。このメモリアドレスから順番にその文字列のバイナリデータが書き込まれています。このバイナリデータは、文字列の各文字のコードポイントの値ですから、メモリストリームに書き込むバイト数は、バイナリデータのバイト数です。

メモリ上のデータはバイナリです。このサンプルは、LText という String 型のバイナリデータをメモリストリームに書き込んでディスクに保存します。つまり、バイナリデータの保存です。
文字列 (テキスト) は、メモリ上ではバイナリデータとして存在します。ディスクに保存するということは、そのバイナリデータを保存することです。
このことを「文字列の保存」や「テキストの保存」と言います。バイナリデータの保存と言う必要はありません。何故なら、コンピュータのデータは全てバイナリなのですから。



リスト6
procedure TForm1.Button1Click(Sender: TObject);
var
  LText   : string;
  LStream : TMemoryStream;
begin
  Memo1.Lines.Clear;

  LText := '日本語';

  LStream := TMemoryStream.Create;
  try
    // 文字列を書き込む
    // LText[1] は正確には Addr(LText[1])。Addr は省略可能
    LStream.Write(LText[1], Length(LText) * SizeOf(Char));
    // 保存
    LStream.SaveToFile('000.txt');

    // 保存した値を読み込んで表示
    // UniCode で保存されているのでエンコードの指定が必要
    Memo1.Lines.LoadFromFile('000.txt', TEncoding.Unicode);
  finally
    FreeAndNil(LStream);
  end;
end;


処理の対象としての文字列 (テキスト) は、バイナリデータとして存在しているのですから、「テキストをバイナリに変換」という処理は有り得ません。「テキストをバイナリに変換」は、出来るとか出来ないという問題ではありません。



[備考 1]
上の 2 つサンプルで保存したテキストファイルを、メモ帳で読み込んで表示する時は、Unicode を指定して読み込まないと文字化けします。

[備考 2]
バイナリデータは目では見えません。そのため、16 進数の文字列で表示します。バイナリデータを取得して表示することは「変換」とは言いません。それは「バイナリデータの表示」です。





07_本物のプログラマは Pascal を使わない


「テキストをバイナリに変換して保存する方法」という質問は、不思議で理解に苦しむ質問です。この質問にどう対応していいのか悩みます。質問者が言う、バイナリに変換とか、バイナリが具体的にどのような状態を意味するのかが理解できません。一体、何をどうしたらいいのか分かりません。

1983年、テクトロニクスの Ed Post 氏が書いたエッセイ、「Real Programmers Don't Use Pascal (本物のプログラマは Pascal を使わない)」が Datamation 誌 (Vol. 29, No. 7 (July 1983) に掲載されました。「テキストをバイナリに変換」という質問が現れる背景は、このエッセイが書かれた背景と似ているのかも知れません。

くしくも、Delphi は Object Pascal と呼ばれる言語です。Pascal は Delphi の祖先と言うことになります。Delphi のようなプログラミング言語を使用すれば、バイナリの意味を知らなくてもプリケーションの開発が可能です。それはそれで素晴らしいことです。だからと言って、バイナリの意味を理解しないで、「テキストをバイナリに変換する方法」のような質問をすることには疑問を感じます。切なく悲しくなります。






08_ノイマン型コンピュータ


現在のコンピュータが 2 進数による演算を使用しているのは、フォン・ノイマンの提案によるものです。コンピュータで負の整数を扱う際、補数を使用する考え方もノイマンによる提案です。メモリにアドレスを割り当てる考え方もノイマンによるものです。

フォン・ノイマンは 1944年8月、米陸軍の電子計算機 EDVAC の開発に参加し、1945年6月に報告書を作成しました。この報告書で、コンピュータの構成を、入力部、出力部、中央制御部、中央演算部、メモリ、としています。現在は、中央制御部と中央演算部を含めて CPU (中央演算装置) となっています。メモリにはデータとプログラムのコードを入力装置から読み込み、CPU がプログラムやデータを逐次処理します。メモリにプログラムを格納する方式をプログラム内蔵方式 (ストアドプログラム方式) と言います。
ノイマンの、この報告書に基づいたアーキテクチャのコンピュータを「ノイマン型コンピュータ」と呼んでいます。現在普及しているほとんどのコンピュータは、ノイマン型コンピュータと言えます。

ノイマンが開発に参加した EDVAC の演算処理能力は 333 回 / 秒でした。現在パソコンと呼ばれるコンピュータの演算処理能力は、480 GFLOPS (480 x 10 の 9 乗回 / 秒) もあります。



記憶単位が 0 と 1 しかないコンピュータが数値を扱い、文字列を扱い、マルチメディアと呼ばれる画像や音を処理し、機械を操作し、人工知能と言われる機能まで持つ時代になっています。考えてみれば実に不思議です。将来は人と同じ様に意識を持ち、感情や知性を持つようになるのではないかと、SF の世界のような話もあります。
物質の最小要素はクォークだそうですが、仮に原子だとします。原子が集まり分子になり、分子が集まって有機物質が構成されて細胞になり、自己増殖機能を備えるようになりました。そして、何億年もの時を経て、意識を持ち、知性や感情を持つ、人という生物に進化しました。不思議です。

クォークの挙動を説明するために、新しい素粒子が発見され、更にその素粒子の挙動を説明するために新しい素粒子が、そして人類はついに、究極の素粒子である「バイナリ粒子」を発見。ということになると面白い気がします。ある条件では存在し、ある条件では存在しないか、確認できい粒子と言ったところでしょうか。
しかし、それでも、何故そのような粒子が存在するのか、という研究になってしまいそうです。





09_アラン・チューリング


コンピュータのデータとその処理に 2 進数を使用することを考え、デジタル式の計算の考え方を示したのはイギリスの数学者アラン・チューリングです。知能を持つコンピュータに関する論文もあります。

チューリングは、第二次世界大戦中、当時最も難解と言われたイツの暗号エニグマの解読に成功しました。歴史家は、エニグマの解読により、戦争終結が 2 年は早くなり、1,400 万人以上の人命が救われたとしています。エニグマ解読の事実は、機密事項とされていましたが、1974年に出版された「ウルトラ・シークレット」よって、チューリングの功績を世間が知ることになりました。もし、エニグマが解読されていなかったら、日本よりも先に、ドイツに原爆が投下されていたことになります。

チューリングは、数理生物学と呼ばれる分野で、シマウマ、キリン、ヒョウ等の体表面の模様を、1 つの数式で表せることを示したことでも知られています。
1954年6月7日、41 歳で亡くなりました。

2009年8月、第二次世界大戦後にアラン・チューリングが受けた不当な扱い対して、謝罪を求める署名運動が起きました。
2009年9月10日、当時のイギリスのゴードン・ブラウン首相は、謝罪を表明し、「アランのおかげで自由に生活しているすべての人々を代表して、『すまなかった。あなたは真の賞賛に値する人だ』と宣言できることを誇りに思う」と述べました。
2021年6月に流通し始めたイギリスの 50 ポンド紙幣の裏面には、「コンピュータ科学の父」として、アラン・チューリングの肖像画が描かれています。イギリス紙幣の表側の肖像画は、全てエリザベス女王です。