Main Content

ファイル入出力用の新しい System object の作成

この例では、2 つの異なる System object を作成、使用して、MATLAB® に出入りするデータのストリーミングを簡単にする方法を説明します。その 2 つとは TextFileReaderTextFileWriter です。

この例で説明するオブジェクトは、多くの現実的な使用例を扱います。また、オブジェクトは、より高度で特殊なタスクを実現するためにカスタマイズできます。

はじめに

System object は、matlab.System から派生する MATLAB クラスです。その結果、System object はすべて、以下の標準メソッドを含む共通のパブリック インターフェイスを継承します。

  • setup — オブジェクトを初期化 (通常はシミュレーション開始時)

  • reset — オブジェクトの内部状態をクリアし、既定の初期化後の状態に戻す

  • release — オブジェクト内部で使用されるリソース (メモリ、ハードウェア、OS 固有のリソース) を解放

新しい種類の System object を作成するときは、上記のすべてのメソッドに対して特定の実装を提供し、動作を決定します。

この例では、内部構造、および次の 2 つの System object の使用について説明します。

  • TextFileReader

  • TextFileWriter

MATLAB に出入りするデータをストリーミングするこれらの System object を作成するために、この例では、MATLAB で使用可能な標準の低水準ファイル I/O 関数 (fscanffreadfprintfおよびfwriteなど) を使用します。こうした関数の使用に関する詳細の大半を抽象化することにより、ストリーム データの読み取りと書き込みをよりシンプルかつ効率的にすることを目的としています。

この例には、System object を作成する多数の高度なコンストラクターの使用が含まれています。System object の作成に関するより基本的な説明については、System object の作成を参照してください。

クラス TextFileReader の定義

TextFileReader クラスには、クラス定義、パブリック プロパティとプライベート プロパティ、コンストラクター、保護されたメソッド (matlab.System 基底クラスからオーバーライド)、およびプライベート メソッドが含まれます。TextFileWriter クラスも同様の構造です。

クラス定義

クラス定義では、TextFileReader クラスが matlab.Systemmatlab.system.mixin.FiniteSource の両方から派生することが記述されています。

   classdef (StrictDefaults)TextFileReader < matlab.System & matlab.system.mixin.FiniteSource
  • matlab.System は必須であり、すべての System object の基底クラスです。

  • matlab.system.mixin.FiniteSource は、このクラスが有限数のデータ サンプルをもつ信号ソースであることを示しています。このタイプのクラスでは、通常のインターフェイスに加えて、System object™ の関数 isDone も公開されます。isDone が true を返すのは、オブジェクトが使用可能なデータの終端に達した場合です。

パブリック プロパティ

パブリック プロパティは、特定のアプリケーションに合わせてオブジェクトの動作を調整するためにユーザーが変更できます。TextFileReader には調整不可のパブリック プロパティが 2 つ (オブジェクトを最初に呼び出す前にのみ変更可能) と、調整可能なパブリック プロパティが 4 つあります。すべてのパブリック プロパティに既定値があります。ユーザーによる指定が他に何もない場合は、既定値が対応するプロパティに割り当てられます。

   properties (Nontunable)
       Filename   = 'tempfile.txt' 
       HeaderLines = 4
   end
   properties
       DataFormat = '%g' 
       Delimiter = ',' 
       SamplesPerFrame = 1024
       PlayCount = 1
   end

プライベート プロパティ

プライベート プロパティはユーザーには表示されず、以下を含む複数の目的を果たすことができます。

  • 計算される頻度が低く、後続するアルゴリズムの呼び出しで使用される値を保持する。たとえば、初期化時、setup が呼び出されたとき、またはオブジェクトが初めて呼び出されたときに使用される値です。これにより、実行時の再計算が不要になり、コア機能のパフォーマンスが改善します。

  • オブジェクトの内部状態を定義する。たとえば、pNumEofReached は、ファイルの終端インジケーターに達した回数を保存します。

   properties(Access = private)
       pFID = -1 
       pNumChannels 
       pLineFormat 
       pNumEofReached = 0
   end

コンストラクター

コンストラクターは、名前と値のペアを使用して TextFileReader オブジェクトを構築できるように定義されています。コンストラクターは、TextDataReader の新しいインスタンスが作成されたときに呼び出されます。コンストラクター内で setProperties を呼び出すと、構築時に名前と値のペアでプロパティを設定できます。コンストラクター内では、その他の初期化タスクを指定してはなりません。代わりに、setupImpl メソッドを使用します。

   methods
       function obj = TextFileReader(varargin)
           setProperties(obj, nargin, varargin{:});
       end
   end

matlab.System 基底クラスの保護されたメソッドのオーバーライド

すべての System object に共通するパブリック メソッドにはそれぞれ対応する保護されたメソッドがあり、これらは内部で呼び出されます。これらの保護されたメソッドの名前には、すべて Impl という接尾辞が含まれます。これらは、System object の動作をプログラムするためのクラスを定義する際に実装できます。

標準的なパブリック メソッドとそれらの内部実装の対応関係の詳細については、呼び出しシーケンスの概要を参照してください。

たとえば、TextFileReader は、以下の Impl メソッドをオーバーライドします。

  • setupImpl

  • resetImpl

  • stepImpl

  • releaseImpl

  • isDoneImpl

  • processTunedPropertiesImpl

  • loadObjectImpl

  • saveObjectImpl

プライベート メソッド

プライベート メソッドは、同じクラスの他のメソッド内からのみアクセス可能です。残りのコードを読み取りやすくするために使用できます。クラスのさまざまな部分で複数回使用される個別のルーチン コード別にグループ分けすることにより、コードを再利用しやすくすることもできます。TextFileReader の場合、プライベート メソッドは以下のために作成されます。

  • getWorkingFID

  • goToStartOfData

  • peekCurrentLine

  • lockNumberOfChannelsUsingCurrentLine

  • readNDataRows

データの読み取りと書き込み

この例では、次を行うことによって TextFileReaderTextFileWriter の使用方法を説明します。

  • TextFileWriter を使用して、2 つの異なる正弦波信号のサンプルを含むテキスト ファイルを作成

  • TextFileReader を使用してテキスト ファイルから読み取り

単純なテキスト ファイルの作成

周波数が 50 Hz と 60 Hz の 2 つの正弦波信号を保存する新しいファイルを作成します。各信号について、保存されるデータはサンプリング レートが 8 kHz の 800 個のサンプルで構成されます。

データ サンプルを作成します。

fs = 8000;
tmax = 0.1;
t = (0:1/fs:tmax-1/fs)';
N = length(t);
f = [50,60];
data = sin(2*pi*t*f);

将来使用するために、データを分かりやすく説明するヘッダー文字列を形成します (オプションの手順)。

fileheader = sprintf(['The following contains %d samples of two ',...
    'sinusoids,\nwith frequencies %d Hz and %d Hz and a sample rate of',...
    ' %d kHz\n\n'], N, f(1),f(2),fs/1000);

信号をテキスト ファイルに保存するために、TextFileWriter オブジェクトを作成します。TextFileWriter のコンストラクターには、ターゲット ファイルの名前と、いくつかのオプションのパラメーターが必要です。これらは名前と値のペアとして渡すことができます。

TxtWriter = TextFileWriter('Filename','sinewaves.txt','Header',fileheader)
TxtWriter = 
  TextFileWriter with properties:

      Filename: 'sinewaves.txt'
        Header: 'The following contains 800 samples of two sinusoids,...'
    DataFormat: '%.18g'
     Delimiter: ','

TextFileWriter は、区切り記号のある ASCII ファイルにデータを書き込みます。そのパブリック プロパティには以下が含まれます。

  • Filename — 書き込まれるファイルの名前。この名前のファイルが既に存在する場合、既存のファイルは上書きされます。操作が開始されると、オブジェクトはファイル内のヘッダーの直後への書き込みを開始します。オブジェクトはその後、解放されるまでの間、後続の呼び出しがあるたびに新しいデータを追加します。reset を呼び出すと、ファイルの最初から書き込みが再開されます。

  • Header — 文字列。多くの場合、複数行で構成され、改行文字 (\n) で終了します。ユーザーによって指定され、実際のデータを説明する、人間が判読可能な情報を組み込み、変更することができます。

  • DataFormat — 各データ サンプルの保存形式。組み込みの MATLAB 関数fprintfで使用される formatSpec string 内で変換指定子として割り当て可能な任意の値を使用できます。DataFormat は、ファイルに書き込まれるすべてのチャネルに適用されます。このプロパティの既定値は '%.18g' です。この値を指定すると、倍精度の浮動小数点データを完全精度で保存できます。

  • Delimiter — 同じ時点のさまざまなチャネルからのサンプルを分離するために使用する文字。書き込まれたファイルの各行がそれぞれ 1 つの時点にマッピングされ、入力として指定されているチャネル数 (つまり、オブジェクトに渡される行列入力の列数) と同数のサンプルを含みます。

使用可能なすべてのデータをファイルに書き込むには、単一の呼び出しを使用できます。

TxtWriter(data)

関数 release を呼び出して、ファイルの制御を解除します。

release(TxtWriter)

これでデータは新しいファイルに保存されました。ファイルを視覚的に検査するには、次のように入力します。

edit('sinewaves.txt')

ヘッダーが 3 行を占めるため、データは 4 行目から始まります。

この単純なケースでは、信号全体の長さが小さく、システム メモリに十分に収まります。したがって、データはすべて一度に作成し、単一の手順でファイルに書き込むことができます。

この方法が不可能な場合や現実的でない場合もあります。たとえば、データが大きすぎて単一の MATLAB 変数に収まらない (大きすぎてシステム メモリに収まらない) 場合などです。あるいは、データがループで周期的に作成されたり、外部ソースから MATLAB にストリーミングされる場合もあります。これらすべてのケースで、ファイルへのデータのストリーミングは、次の例と同様の方法で行うことができます。

ストリーミングされた正弦波発生器を使用して、ループごとのデータのフレームを作成します。必要な回数繰り返し実行してデータを作成し、ファイルに保存します。

frameLength = 32;
tmax = 10; 
t = (0:1/fs:tmax-1/fs)';
N = length(t);
data = sin(2*pi*t*f);
numCycles = N/frameLength;

for k = 1:10 % Long running loop when you replace 10 with numCycles. 
    dataFrame = sin(2*pi*t*f);
    TxtWriter(dataFrame)
end

release(TxtWriter)

既存のテキスト ファイルからの読み取り

テキスト ファイルから読み取るには、TextFileReader のインスタンスを作成します。

TxtReader = TextFileReader('Filename','sinewaves.txt','HeaderLines',3,'SamplesPerFrame',frameLength)
TxtReader = 
  TextFileReader with properties:

           Filename: 'sinewaves.txt'
        HeaderLines: 3
         DataFormat: '%g'
          Delimiter: ','
    SamplesPerFrame: 32
          PlayCount: 1

TextFileReader は、区切り記号のある ASCII ファイルから数値データを読み取ります。そのプロパティは TextFileWriter のものと類似しています。相違点は次のとおりです。

  • HeaderLinesFilename で指定されているファイル内でヘッダーが使用する行数。オブジェクトに対する最初の呼び出しは、行番号 HeaderLines+1 から読み取りを開始します。オブジェクトに対する後続の呼び出しは、その前に読み取った行の直後の行から読み取りを継続します。reset を呼び出すと、HeaderLines+1 行目から読み取りを再開します。

  • Delimiter — 同じ時点のさまざまなチャネルからのサンプルを分離するために使用する文字。このケースでは、区切り記号は、ファイルに保存されているデータ チャネルの数を特定する目的でも使用されます。オブジェクトが最初に実行されると、オブジェクトは HeaderLines+1 行目の Delimiter 文字の数、たとえば numDel をカウントします。次に、時点ごとに、オブジェクトは numChan = numDel+1 の数値を DataFormat の形式で読み取ります。アルゴリズムによって返される行列のサイズは SamplesPerFramenumChan 列となります。

  • SamplesPerFrame — オブジェクトに対する各呼び出しで読み取られる行数。この値は、出力として返される行列の行数でもあります。使用可能な最後のデータ行に達したとき、必要な SamplesPerFrame に満たない場合があります。その場合は、使用可能なデータをゼロでパディングして、SamplesPerFramenumChan 列のサイズの行列を取得します。すべてのデータが読み取られると、アルゴリズムは、reset または release が呼び出されるまで、単純に zeros(SamplesPerFrame,numChan) を返します。

  • PlayCount — ファイル内のデータが周期的に読み取られる回数。オブジェクトがファイルの最後に達しても、ファイルの読み取り回数が PlayCount に等しい数に達していない場合は、データの最初 (HeaderLines+1 行目) から読み取りが再開されます。ファイルの最後の数行で、SamplesPerFramenumChan 列のサイズの完全な出力行列を十分に形成できるサンプルが提供されていない場合、フレームは初期データを使用して完了します。このファイルが PlayCount 回読み取られると、アルゴリズムによって返される出力行列はゼロで埋められ、reset または release が呼び出されない限り、isDone に対するすべての呼び出しは true を返します。使用可能なデータを無限にループするには、PlayCountInf に設定できます。

テキスト ファイルからデータを読み取るには、より一般的なストリーミングの方法を使用します。このデータの読み取り方法は、非常に大きいデータ ファイルを処理する場合にも適しています。frameLength 行 2 列のデータ フレームを事前に割り当てます。

dataFrame = zeros(frameLength,2,'single');

ソース テキスト ファイルにデータが存在する状態で、テキスト ファイルから読み取ってバイナリ ファイルに書き込みます。メソッド isDone を使用して while ループの実行を制御する方法に注目してください。

while(~isDone(TxtReader))
    dataFrame(:) = TxtReader();
end

release(TxtReader)

まとめ

この例では、System object を作成、使用して、数値データ ファイルの読み取りと書き込みを行う方法を説明しました。TextFileReaderTextFileWriter を編集して、特殊用途のファイルの読み取りおよび書き込み操作を実行できます。これらのカスタム System object を、dsp.BinaryFileWriterdsp.BinaryFileReader のような組み込みの System object と組み合わせることもできます。

カスタム アルゴリズム用の System object の作成の詳細については、System object の作成を参照してください。

関連するトピック