[C#] 43. ストリーム(Stream)とバイナリ(byte[])、エンコード(Encoding)、そしてusingを使い方とIDisposableインターフェース


Study / C#    作成日付 : 2021/10/04 18:33:04   修正日付 : 2021/10/04 18:34:18

こんにちは。明月です。


この投稿はC#でストリーム(Stream)とバイナリ(byte[])、エンコード(Encoding)、そしてusingを使い方とIDisposableインターフェースに関する説明です。


以前の投稿でファイルを扱い(IO)に関して説明しました。

link - [C#] 42. ファイルを扱い(IO)とファイルメタデータ(FileInfo)を使い方


ここでStringデータをファイルに作成する時、Encodingを使ってbyte[]配列に変換してファイルを作成するし、ファイルの内容を読み込む時にはbyte[]配列で読み込んでEncodingを使ってStringに変換してコンソールに出力することまで例で説明しました。

我々がファイルを読み込んで作成する時、FileStreamというクラスを使ってファイルを作成するし読み込みました。


まず、Streamに関する説明ですが、ストリームとは一連のデータ配列という意味です。つまり、一つのデータ値では意味がなく、データの集合あるいは配列が一つのデータとして意味になるという意味です。

我々がStringのデータをbyteに変換したので、UTF-8の変換により英語文字の一つが一つの値で配列に格納されていますが、もし英語ではなく日本語ならどのぐらいのバイトになるでしょう?


using System;
using System.IO;

namespace Example
{
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // ファイルメタデータ取得
      var file = new FileInfo("d:\\work\\test.txt");
      // FileStream生成	
      // 生成するファイル名、オプション(ファイル開く)、ファイルアクセス権限(読み取り)
      var stream = new FileStream("d:\\work\\test.txt", FileMode.Open, FileAccess.Read);
      try
      {
        // ファイルメタデータでファイルサイズを取得してbyte配列を生成
        var binary = new byte[file.Length];
        // streamでファイルを読み取り
        stream.Read(binary, 0, binary.Length);
        // データを16進数で表現する。
        foreach (var b in binary)
        {
          // コンソールに出力
          Console.Write("{0:X2} ", b);
        }
        // 改行
        Console.WriteLine();
        // データサイズをコンソールに出力
        Console.WriteLine("Size = " + file.Length);
      }
      finally
      {
        // ストリームを閉める。
        stream.Close();
      }

      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


上の例で最後の0x2E(46)はasciiコードのピリオドです。その以外の15バイトは日本語です。つまり、ひらがなの一文字で3バイトのサイズを持っています。

この「こ」になるデータは「E3 81 93」ですが、ここで一つのbyteは値は意味がありません。データの意味を持つためには3byteが合わせて、Stringに「こ」というデータに認識することです。

これがストリームということです。一つのデータを表現するためには一連の値を配列で表すことです。


ここで説明しやすくするためにStringのデータで説明しましたが、プログラムでは使ってるリソースはイメージや動画などのデータのファイルなどがあります。そのデータはすべてバイトになっています。

つまり、このデータは一つのbyteの値では意味がありませんが、byteの値が一連の配列になるとイメージや動画になることです。これをストリームと言います。

例えば、我々がイメージプログラムでイメージをモニターに出力するため、約束したbyteの集合(規約)、データストリームが必要ということです。動画も同じ意味です。画面に連続なイメージを出力するし、合わせてスピーカーに音を出すデータ、約束したbyteの集合を動画データというし、データストリームと言います。


このデータを扱うデータはほぼStreamを扱うクラスを持っています。

IOもそうだし、通信を通ってデータ送受信するソケットにもStreamクラスがあります。その以外に、ハードウェアの間にデータを転送することだけではなく、メモリにbyte[]のタイプではなく、ストリームタイプのままで割り当てするMemoryStreamもあります。


このストリームはコネクション(Connection)が存在しますが、データのロックと思えば良いです。データストリームは途中でデータが規約に合わせて修正できないと読めないデータになるからです。(Check in, Check out機能だと思えば?)

それで常にこのストリームは使用が終わったらClose関数でコネクション(Connection)を閉めなければならにです。


もちろん、プログラムが終了するとプログラムで使ったすべてのコネクションは自動に終了されますが、サーバみたいに24時間に実行しているプログラムならこのリソースコネクションもよく管理しなければならないです。

それで以前にはtry ~ finallyでよく使いましたが、C#にはそれよりもっとシンプルなusingキーワードでコネクションを管理することができます。

using System;
using System.IO;

namespace Example
{
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // ファイルメタデータ取得
      var file = new FileInfo("d:\\work\\test.txt");
      // FileStream生成	
      // 生成するファイル名、オプション(ファイル開く)、ファイルアクセス権限(読み取り)
      // usingはIDisposableインターフェースを継承したクラスで、usingのスタック領域が終了すると自動にCloseを呼び出します。
      using (var stream = new FileStream("d:\\work\\test.txt", FileMode.Open, FileAccess.Read))
      {
        // ファイルメタデータでファイルサイズを取得してbyte配列を生成
        var binary = new byte[file.Length];
        // streamでファイルを読み取り
        stream.Read(binary, 0, binary.Length);
        // データを16進数で表現する。
        foreach (var b in binary)
        {
          // コンソールに出力
          Console.Write("{0:X2} ", b);
        }
        // 改行
        Console.WriteLine();
        // データサイズをコンソールに出力
        Console.WriteLine("Size = " + file.Length);
      }

      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


結果は同じ結果ですが、try ~ finallyよりコードが綺麗です。

usingキーワードはIDisposableインターフェースを継承したクラスでスタックが終了すると自動にDispose関数が呼び出します。

using System;

namespace Example
{
  // IDisposableインターフェースを継承
  class Test : IDisposable
  {
    // Close関数
    public void Close()
    {
      // コンソールに出力
      Console.WriteLine("Close!!!");
    }
    // IDisposableインターフェースのDispose関数の再定義
    public void Dispose()
    {
      // Close関数を呼び出す。
      Close();
    }
  }
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // usingキーワード、Testインスタンス生成
      using(var test = new Test())
      {
        // コンソールに出力
        Console.WriteLine("Hello world");
      }

      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


上の例はusingキーワードを使うためにTestクラスを作成しました。

TestクラスはIDisposableインスタンスを継承してDispose関数を再定義します。FileStreamクラスは上の流れで動くと思えば良いです。


また、始めに戻ってエンコードに関して説明します。

上でストリームとは一連のデータの値の配列だと説明しました。そのため、Stringのデータをbyte[]に変換しなければならないですが、ToCharArray関数で変換とEncoding.UTF8.GetBytesで変換する方法があります。

using System;
using System.Text;

namespace Example
{
  class Program
  {
    // 出力関数
    static void Print(dynamic val)
    {
      // 配列を繰り返しで
      foreach (var b in val)
      {
        // コンソールに出力
        Console.Write("{0:X2} ", (byte)b);
      }
      // 改行
      Console.WriteLine();
    }
    // 実行関数
    static void Main(string[] args)
    {
      // 出力 ToCharArray
      Print("Hello world".ToCharArray());
      // 出力 Encoding.UTF8.GetBytes
      Print(Encoding.UTF8.GetBytes("Hello world"));
      // 改行
      Console.WriteLine();
      // 出力 ToCharArray
      Print("こんにちは".ToCharArray());
      // 出力 Encoding.UTF8.GetBytes
      Print(Encoding.UTF8.GetBytes("こんにちは"));

      // 任意のキーを押してください
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}


参考でcharとbyteの関係はunsigned char、符号ないcharタイプがbyteタイプです。なので、charとbyteは同じデータを扱うデータタイプです。

それでStringでToCharArray関数を使ってcharに変換することができますが、英語やasciiコードで表現する値はEncodingで変換することと同じ結果で出力されます。

でも、日本語はasciiコードで表現することができません。つまり、C#だけではなく、メモ帳などの他のプログラムでも読み込まれるためにはエンコードが必要です。上にはUTF-8のEncodingタイプで変換しました。


メモ帳で文字タイプを確認すればUTF-8で作成されていることを確認できます。

なので、一般的に文字列(String)をbyte[]に変換する時にはEncodingクラスを使ってbyte[]配列に変換してFileStreamでファイルを作成、読み込みします。


ここまでC#でストリーム(Stream)とバイナリ(byte[])、エンコード(Encoding)、そしてusingを使い方とIDisposableインターフェースに関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

#C#
最新投稿