がりらぼ

WindowsRuntimeの応援ブログ

WindowsRutimeのStream入出力について

Streamとは

テキストファイルのデータを読み込むとき、WindowsRuntimeではこのようなコードでデータを読み込むことができます。

StorageFolder folder= ApplicationData.Current.LocalFolder;
StorageFile file = await folder.GetFileAsync("file.txt");
string text=await FileIO.ReadTextAsync(file);

FileIOのReadTextAsyncの1つのメソッドでファイルのすべてを読み込むことができるので非常に便利です。

しかし、このテキストファイルが非常に巨大だった場合どうなるでしょうか。

ReadTextAsyncは非同期メソッドですがいつまでも非同期読み込み処理が終わらず、巨大なデータのすべてが必要でない場合余計なデータまで読み込むためアプリのレスポンスが悪くなります。

ファイルの読み込みもそうですが、動画のネットワークデータ転送などの場合、すべてのデータを転送しないと動画を再生できないようなアプリケーションは非常に非効率的です。動画を現在転送できているデータ分だけ再生できるようにすべきです。

そのような問題を解決するために、WindowsRuntimeではStreamという概念を用意しています。(.Netでもあります)

Streamとはデータが流れてくる川のようなもので、データを徐々に読み取ったりデータの一部だけ切り取って読み取ることができたりします。

f:id:garicchi:20150104102340p:plain

先ほども書いたように、ファイルの読み取りやネットワークデータ転送などのI/Oですべてのデータを転送しきってからじゃないと読み取れないのは非効率なのでStreamのインターフェースはさまざまな入出力で用意されています。

WindowsRuntimeのStream

WindowsRuntimeで用意されているStreamは以下の4つがあります。

  • IInputStream
  • IOutputStream
  • IRandomAccessStream
  • InMemoryRandomAccessStream

IInputStreamはストリームからメモリへの読み込みStreamを提供し、

IOutputStreamはメモリからストリームへの書き込みStreamを提供します。

IRandomAccessStreamは以下の継承関係の図のように、IInputStreamとIOutputStreamを継承、つまり読み込みと書き込みを提供します。

InMemoryRandomAccessStreamはメモリ内からメモリ内へのストリームに限定したStreamを提供します。

f:id:garicchi:20150104102411p:plain

IInputStreamの実装を見てみます。

f:id:garicchi:20150104102423p:plain

ReadAsyncメソッドのみを提供しています。

つまり、「何バイトを読み込む」ことしかできません。ReadAsyncを呼び出すたびにその数のバイトずつStreamから読み込むことができるので逐次的な読み込みを提供します。

IOutputStreamの実装を見てみます。

f:id:garicchi:20150104102432p:plain

WriteAsyncとFlushAsyncの2つのメソッドを提供します。

WriteAsyncはそのバッファのデータ分だけStreamに逐次的に書き込みを行います。

FlushAsyncメソッドは少し特殊なメソッドでほとんど使用することがありません。

Windowsはストリームからデータを書き込むときに内部バッファにデータをキャッシュします。内部バッファがいっぱいになったときか、一定時間内部バッファにアクセスがなかった場合、内部バッファのデータを書き込みます。

つまり内部バッファがデータを保持しているときにシステムがダウンするとファイルにデータが書き込まれないわけです。

そこで重要なデータの場合、FlushAsyncメソッドを使用することで内部バッファのデータをファイルに即座に書き込みます。

しかし本当に重要なデータ以外でFlushAsyncメソッドを頻繁に使用するとアプリのパフォーマンスが低下してしまうためほとんどのアプリでFlushAsyncを使用する必要はありません。

IRandomAccessStreamの実装を見てみます。

IInputStreamとIOutputStreamはデータの逐次的な書き込みか読み込みを提供します。しかし「データのここからここまでを書き込みたい」とか、「データの最後何バイトだけ読み込みたい」みたいなことを実現することができません。

その機能を提供するのがIRandomAcccessStreamです。

IRandomAccessStreamはSeekメソッドを提供し、ストリームの好きな位置まで移動することができ、好きな位置からデータを読み込み、書き込みすることができます。まさにランダムアクセスが可能なわけです。

f:id:garicchi:20150104102446p:plain

ストリームのサンプル

ではストリームによるファイル読み込みと書き込みの簡単なサンプルを見てみましょう。

//7文字(7バイト)のデータ
string data = "ABCDEFG";
//ASCIIでエンコード
byte[] bytes = Encoding.GetEncoding("ASCII").GetBytes(data);
//sampleというファイルを作成
StorageFile file = await KnownFolders.PicturesLibrary.CreateFileAsync("sample",CreationCollisionOption.ReplaceExisting);
//ファイルのストリームを開く
IRandomAccessStream outputStream = await file.OpenAsync(FileAccessMode.ReadWrite);
//IBuffer型にデータを変換
IBuffer buffer = bytes.AsBuffer();
//データを書き込み
await outputStream.WriteAsync(buffer);
outputStream.Dispose();

"ABCDEFG"7文字のデータ、つまりASCIIエンコードで7バイトのデータをファイルに書き込みます。

StorageFileのOpenAsyncメソッドでFileAccessModeをReadWriteにすると、読み込みと書き込みどちらもできるストリームを取得することができます。

WindowsRuntimeでストリームに書き込むためにはIBuffer型にしなければいけないので変換し、データを書き込みます。

データサイズをみてみると、7バイトすべて書き込めていることがわかります。

f:id:garicchi:20150104102504p:plain

では次にこのファイルをストリームで読み込んでみましょう。

//ファイルを取得する
StorageFile file = await KnownFolders.PicturesLibrary.GetFileAsync("sample");
//ストリームを取得する
IRandomAccessStream inputStream = await file.OpenAsync(FileAccessMode.Read);
uint byteSize = 7;
byte[] bytes=new byte[byteSize];
//バッファに読み込む
await inputStream.ReadAsync(bytes.AsBuffer(),byteSize,InputStreamOptions.None);

inputStream.Dispose();
//デコード
string str = Encoding.GetEncoding("ASCII").GetString(bytes,0,bytes.Count());
//出力
Debug.WriteLine(str);
IRandomAccessStreamのReadAsyncメソッドの第二引数に読み込むバイトサイズを指定します。

今回は7バイトすべてを読み込んで出力してみます。

"ABCDEFG"の7バイトすべてを読み込むことができました。

f:id:garicchi:20150104102522p:plain

ではストリームの機能を生かして3バイトから6バイトまでの4バイトを読み込んでみましょう。

StorageFile file = await KnownFolders.PicturesLibrary.GetFileAsync("sample");
IRandomAccessStream inputStream = await file.OpenAsync(FileAccessMode.Read);
uint byteSize = 7;
byte[] bytes=new byte[byteSize];

//2バイト目までシークする
inputStream.Seek(2);
//シーク位置から4バイト読み込む
await inputStream.ReadAsync(bytes.AsBuffer(),4,InputStreamOptions.None);

inputStream.Dispose();

string str = Encoding.GetEncoding("ASCII").GetString(bytes,0,bytes.Count());
Debug.WriteLine(str);

Seekメソッドを使用することで指定バイトまでカーソルをシークすることができます。

ReadAsyncでカーソルの位置から4バイト読み込みます。

つまり、3バイトから4バイトまで読み込むランダムアクセスによる読み込みです。

結果、CDEFの4バイトを読み込むことができました。

f:id:garicchi:20150104102542p:plain

DataReaderとDataWriter

ストリームを使って読み込んだり書き込んだりするにしても、double型のデータとかstring型のデータとか、いちいちIBufferに変換したりするのは面倒です。

そこでストリームにintやdouble、stringなどの標準データ型を書き込みやすくするためにWindowsRuntimeではDataReaderとDataWriterを提供しています。

DataWriterは内部バッファという内部にデータを保持する領域を持っています。

Write**から始まるさまざまなメソッドがありますが、すべて内部バッファに書き込むために存在しています。

f:id:garicchi:20150104102552p:plain

そしてStoreAsyncメソッドを実行することで初めてファイルに内部バッファからデータがストリームに書き込まれます。

f:id:garicchi:20150104102558p:plain

StorageFile file = await KnownFolders.PicturesLibrary.CreateFileAsync("sample",CreationCollisionOption.ReplaceExisting);
IRandomAccessStream outputStream = await file.OpenAsync(FileAccessMode.ReadWrite);

DataWriter writer = new DataWriter(outputStream.GetOutputStreamAt(0));
//内部バッファに文字列を書き込むときのエンコード方式
writer.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
//内部バッファに書き込む
writer.WriteString("ABC");
//内部バッファに書き込む
writer.WriteString("DEFG");
//内部バッファのデータをストリームに書き込む
await writer.StoreAsync();

outputStream.Dispose();
 

DataReaderも同様に内部バッファを保持しています。

LoadAsyncメソッドはStreamから指定したバイトだけ内部バッファに読み込みます。

内部バッファに読み込んだデータは、Read**から始まるさまざまなメソッドによって標準型に変換することができます。

f:id:garicchi:20150104102613p:plain

つまりLoadAsyncを実行したあと、Read**メソッドを実行することでデータを読み込むことができるわけです。

f:id:garicchi:20150104102730p:plain

StorageFile file = await KnownFolders.PicturesLibrary.GetFileAsync("sample");
IRandomAccessStream inputStream = await file.OpenAsync(FileAccessMode.Read);
uint byteSize = 7;
byte[] bytes=new byte[byteSize];

DataReader reader = new DataReader(inputStream.GetInputStreamAt(0));
reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
//7バイトをストリームから内部バッファにデータを読み込む
await reader.LoadAsync(byteSize);
//内部バッファのデータを7バイトstringに変換する
string str= reader.ReadString(byteSize);

inputStream.Dispose();
//出力
Debug.WriteLine(str);

http://garicchi.com/?p=17901