ログイン
ユーザ名:

パスワード:


パスワード紛失

新規登録
メインメニュー
フォーラム一覧   -   トピック一覧
   AIR 2.0
     FileStreamによる行単位ロードについて
投稿するにはまず登録を

スレッド表示 | 新しいものから 前のトピック | 次のトピック | 下へ
投稿者 トピック
nuda
投稿日時: 2012-3-8 13:02
一見さん
登録日: 2012-3-8
居住地:
投稿: 3
FileStreamによる行単位ロードについて
初めて投稿させていただきますnudaと申します。
よろしくお願いいたします。

さて

現在、数百MB〜数GBのサイズのテキストファイル(CSV)を行単位でロードする機能を作成中です。

FileStreamクラスを用いて、1バイトずつ読み込み改行コード判定(0x0D0A)を行なって、ラインブレイク+行ごとの処理

という風に実装しているのですが、1バイト単位で判定が行なわれるため、思ったように速度が出ません。

生のバイトストリームを行単位(0x0D0A)でかつ高速にロードするテクニックをご存知の方いらっしゃいましたらご教示願います。

※ちなみに、FileStreamはopenAsyncを使って非同期で開いています。
Pepe
投稿日時: 2012-3-8 22:22
モデレータ
登録日: 2006-1-10
居住地:
投稿: 1274
Re: FileStreamによる行単位ロードについて
1点気になるのですが、ロードしたデータをどうなさるのでしょうか?
数ギガなので変数(=メモリ)に展開は無いと思うのですが…

それと別に1バイトずつ読み込まなくても
readUTFBytes で1度に1メガぐらいで読み込んで
改行コードで string.split(",") して、配列化すれば
良いのではないでしょうか?


----------------

nuda
投稿日時: 2012-3-8 22:41
一見さん
登録日: 2012-3-8
居住地:
投稿: 3
Re: FileStreamによる行単位ロードについて
Pepeさん、投稿ありがとうございます。

>1点気になるのですが、ロードしたデータをどうなさるのでしょうか?

ロードしたデータはSQLiteに格納して後続処理で使います。
CSVを1レコードずつ読んで、SQLiteにINSERTするようなイメージです。

>改行コードで string.split(",") して、配列化すれば
良いのではないでしょうか?

当初はFile#loadメソッドで一括で読み込んでCRLFでスプリットしていましたが、それだとあまりにメモリを圧迫するため、1バイトずつ読むような処理に変えました。

また、ある程度のチャンクサイズでロードしてCRLFでスプリットする方法も考えたのですが、スプリットしてしまうとレコード区切りがわからなくなるので断念しました。(「ロードしたデータの末尾がCRLFでないなら断片データ」のような判定条件を使えば、できないことは無いかもしれませんが…)

また、ロード対象のCSVは列方向が可変長であり、1行のデータをSQLite上のテーブルに正規化するようなイメージですので、あくまで「1行単位」の処理をする必要があるのです。

加えて、問題のロジックは上位のクラスから「HogeHoge#next」のような形でコールされ、nextメソッドを呼ぶたびにCSV1レコード分のデータを返すようなイメージです。

制約上この構造を返るわけにはいかないので(インタフェースの実装として定義されるため、他との兼ね合いで変更するわけにはいきません)、なんとかうまい方法は無いものかと考えております。
Pepe
投稿日時: 2012-3-9 9:40
モデレータ
登録日: 2006-1-10
居住地:
投稿: 1274
Re: FileStreamによる行単位ロードについて
1メガ読み込んで最初の改行コードまでのデータを処理して、
ファイル・ハンドルのオフセットをその位置に戻すなどできますが、
基本的に1バイト単位、又は1行単位にI/Oが発生するようでは
速度は決して上がりません。

また、処理するデータ量が少ない場合は
非同期では無く同期的にデータを読み込む方が早くなるはずです。

「先読み」というのは一般的な方法です。
先読みしたデータをキャッシュしておき
返すのは1レコードにすれば良いだけだと思います。

それすらできないロジック構造ならば
それはI/O処理において適切では無い構造と言うことになります。


----------------

nuda
投稿日時: 2012-3-9 10:14
一見さん
登録日: 2012-3-8
居住地:
投稿: 3
Re: FileStreamによる行単位ロードについて
Pepeさん、返信ありがとうございます、nudaでございます。

>基本的に1バイト単位、又は1行単位にI/Oが発生するようでは
速度は決して上がりません。

確かにおっしゃるとおりですね。

今までバイトをいじくるような処理をあまり書いたことがなかったので「ストリームから1バイトずつロードし判定する処理」と「内部バッファから1バイトずつロードし判定する処理」のI/O負荷の違いを意識していませんでした。

アドバイスにしたがって、以下のようなコードで処理を実現しようと思います。

// nextメソッド内部で使用される変数
// 内部バッファ		
private var internalBuffer:Array = new Array();

// 内部バッファデータ断片化フラグ
private var isFlagment:Boolean = false;

// コールされるたびにCSV1レコード分のデータを返却する
public function next():String {
	var currentPosition:Number = stream.position;
	var defaultReadLength:uint = stream.bytesAvailable;
	
	// バッファのサイズ判定
	if(internalBuffer.length > 0) {
		// バッファにデータが存在する場合はバッファ内データを返却する
		
		if(isFlagment) {
			// 後続のバイトをCRLFが出現するまでロード(断片化解消処理)
			if(internalBuffer.length == 1) {
				var bytes2:ByteArray = new ByteArray();
				var readCount:int = 0;
				var content3:String = "";
				while(true) {
					stream.readBytes(bytes2, stream.position, 1);
					readCount++;
					if(bytes2[bytes2.length - 1] == 10) {
						break;
					}
				}
				bytes2.position = currentPosition;
				content3 = bytes2.readMultiByte(readCount, File.systemCharset);
				content3 = content3.replace(CR_LF, BLANK);

        // 文字化けする心配は無いか?
				content3 = (internalBuffer.shift() as String) + content3;
				
				trace("content:" + content3);
				isFlagment = false;
				return content3;
			} else {
				var content4:String = internalBuffer.shift() as String;
				
				trace("content:" + content4);
				isFlagment = false;
				
				return content4;
			}
			
			
			
		} else {
			// バッファからデータを取り出して返却
			var content5:String = internalBuffer.shift() as String;
			
			trace("content:" + content5);
			
			
			
			isFlagment = false;
			return content5;
		}
	} else {
		// bytesAvailable分データをロードし内部バッファに格納
		var bytes:ByteArray = new ByteArray();
		
		stream.readBytes(bytes, stream.position, stream.bytesAvailable);
		
		// ロードしたバイト列の最後がLFで終わっているか
		if(bytes[bytes.length - 1] == 10) {
			// ラインブレイク
			isFlagment = false;

			bytes.position = currentPosition;
			var content2:String = bytes.readMultiByte(defaultReadLength, File.systemCharset);
			
			
			var tmpAry2:Array = content2.split(CR_LF);
			while(tmpAry2.length != 0) {
				var str2:String = tmpAry2.shift();
				internalBuffer.push(str2);
			}
			
			var result2:String = internalBuffer.shift() as String;
			trace("content:" + result2);
			retrun result2;
		} else {
			// 非ラインブレイク
			isFlagment = true;
			
			bytes.position = currentPosition;
			var content:String = bytes.readMultiByte(defaultReadLength, File.systemCharset);
			
			var tmpAry:Array = content.split(CR_LF);
			while(tmpAry.length != 0) {
				var str:String = tmpAry.shift();
				internalBuffer.push(str);
			}
			
			var result:String = internalBuffer.shift() as String;
			
			trace("content:" + result);
			return result;
		}
	}
}


※1 変数の使い方など、汚い部分がありますがご了承ください。

※2 終了条件はnextメソッドをコールする側で判断していますので、nextメソッド内にEOFブレイクのコードはありません
スレッド表示 | 新しいものから 前のトピック | 次のトピック | トップ

投稿するにはまず登録を