VBからdelphiに移植しながら勉強中です。かなり初歩的な質問かと思いますが宜しくご指導ください。VBではあるバイナリファイルの中のあるタグ(2バイト)を探しながらその場所を取得してそこからの相対位置を指定していくつかのデータ(LongとSingle)をGet #F,p,Vlng などと読み込ませるものを作っていましたがこれで一度に指定した型のデータが得られて便利でした。
Delphiでも同じような方法が何かあるのではないかとread、blockreadなどを試しながらやってみましたが失敗ばかりで質問させていただいた次第です。
なおバイナリファイルは最初の部分は種々雑多なデータ(これは読み込み不要)が混在するもので途中からデータの頭を示すタグ+データの定型化されたものが書かれています。この定型化された部分のデータだけ取得したいと思います。
このような場合どのようにして読み込んだら良いでしょうか。
TStreamを使う方法です。
var
FS :TFileStream;
Str,Tgt:String;
i:Integer;
begin
Tgt := '12';
FS:=TFileStream.Create('ここは読み込むファイル名',fmOpenRead);
Try
SetLength(Str,FS.Size);
FS.ReadBuffer(Str,FS.Size);
for i:=1 to Length(Str) do
if Str[i] = Tgt[1] then
if Str[i + 1] = Tgt[2] then
{ここから後ろがデータ}
Finally
FS.Free;
End;
end;
などはどうですか?
動作検証はしていません。
HOta様、ご回答をありがとうございました。
Str[i]というスタイルで取り出せればあとは何とか工夫して、と思って朝からこれに取り組んでいましたがストリームというものは初めてなので上手くいきません。初歩的と思いますがサンプルコードに関して再度質問させていただきますので宜しくお願いします。
1.下記のコードで試すと「ストリームからの読み込みエラー」で止まります。
2.SetLengthでもStrの配列領域を確保してありStr[i]でデータを取り出していますので配列宣言が必要ではないかと試しましたがわかりませんでした。
3.FS.Sizeを見ようとステップ実行しましたが表示されませんでした。
ストリームというものが抽象的に見えて理解できていませんが宜しくご指導ください。
Procedure Gread(Gfile:string);
var
FS :TFileStream;
Str:String; // Array of string;
Tgt1,Tgt2:String;
i:Integer;
begin
Tgt1:= '12'; Tgt2:='10';
FS:=TFileStream.Create(gfile,fmOpenRead);
Try
SetLength(Str,FS.Size);
FS.ReadBuffer(Str,FS.Size); /// ここでエラー発生
//for i:=1 to Length(Str) do
// if Str[i] = Tgt1 then
// if Str[i + 1] = Tgt2 then
// {ここから後ろがデータ}
Finally
FS.Free;
End;
end;
1.
失礼しました。
ここはポインターですね。
> SetLength(Str,FS.Size);
> FS.ReadBuffer(Str,FS.Size); /// ここでエラー発生
FS.ReadBuffer(Pointer(Str)^,FS.Size);
でどうでしょう。
HOta様、ありがとうございました。
やっとReadBufferが通り、Strの配列があら不思議という感じで確認できました。またバイト配列の方法でも確認しました。ここまでくればあとは当方の少ない知識で力づくでLongInt,Singleなどの値に変換(例:べき乗計算で足していくなど)しようと思いますが、もしもStr配列、またはバイト配列からLongIntなどを取得するスマートな方法がありましたら教えてください。
recordで読み込めます。
予め宣言します。
type
THoge = Packed record //Packed を付けること
dataByte:array[0..5] of byte;
dataLng:LongInt;
end;
・・・・・・・・・・・・・・・
var
Hoge :THoge ;
・・・・・・・・・・・・・・・
//開始位置までポジションを移す。HelpのTFileStreamを参照
//タグの終わりに移動
FS.Seek(タグの終わり,soFromBeginning);
FS.ReadBuffer(Hoge , sizeof(Hoge ));
//Hoge.dataByte[0]・Hoge.dataLngなどでアクセスできます。
> もしもStr配列、またはバイト配列からLongIntなどを取得するスマートな方法がありましたら教えてください。
えっと、stringやバイト配列に一旦読み込むのではなく、
そのままLongIntで読み込んだらどうでしょうか?
var n: LongInt;
begin
:
stream.ReadBuffer(n, sizeof(n));
:
あと、HOta さんのいうように、record使う方法もありますね。
指摘されているように、packedにしないと、構造体の並び方が勝手に最適化されるので注意です。
あと、もし、既存のデータを読む場合、アライメントも考慮しないといけないです。
アライメントについては(alignment)、ヘルプに書いてあったはず……。
>えっと、stringやバイト配列に一旦読み込むのではなく、
>そのままLongIntで読み込んだらどうでしょうか?
データ(タグ)位置を見つけるには、stringやバイト配列に一旦読み込まねば...
最初の部分の不要なデータのサイズが固定じゃないからタグ位置を探すんだよね...
ただ、既にタグ位置を見つけるため一度読込んでいるんだから、二度読みはムダ。
type
PHoge = ^THoge;
THoge = Packed record
dataLNG : LongInt;
dataSGL : Single;
・・・・・・・・・
end;
・・・・・・・・・・・・・・・
var
Hoge: PHoge;
・・・・・・・・・・・・・・・
//タグの次(データ先頭位置)が i+2としたら
Hoge := @Str[i+2];
//Hoge.dataLNG・Hoge.dataSGLなどでアクセス。
ただし、これはデータがリトルエンディアンであればの話。
もしもビッグエンディアンなら、もうひと手間必要。
HOta様、TOBY様、エコ様 貴重なアドバイスをありがとうございました。理解度ほとんどゼロでスタートしましたが昨日から苦戦すること約16時間,お蔭様で読み込みに成功しました。あれやこれや試していたら一時はグチャグチャになってしまいましたが結果的にHOta様のサンプルを基本にエコ様のアイデアをミックスしたようなものとなりました。
下記コードでは2度読みのようですがVBでGET #による一度読みよりもかなり速くなった感じがします。とりあえず(無理やり?)動いていますが間違っている点などありましたらご指摘くださるよう宜しくお願いします。
procedure Gread(gfile:string);
var
FS:TFileStream;
b:array of Byte;
b1,b2:byte;
i,ii,i5,n,nmem:Integer;
dataReal:real;
//dataLng:LongInt;
//dataSgl:Single;
dataint:integer;
begin
b1:=1; b2:=0; b3:=0; //タグの値
//配列のメモリを確保
nmem:=100;
gmemset(nmem);
n:=0;
FS:=TFileStream.Create(gfile,fmOpenRead);
Try
SetLength(b,FS.Size);
FS.ReadBuffer(Pointer(b)^,FS.Size);
i5:=length(b)-30;
for i:=1 to i5 do begin
if b[i] = b1 then
if b[i+1] = b2 then
if b[i+2] = b3 then
if b[i+24] = b1 then
if b[i+25] = b2 then
if b[i+26] = b3 then
begin
n:=n+1;
// ここで必要な配列をリセット
//// DATA1 (INT)
ii:=i+6; //DATA1の位置
FS.Seek(ii,soFromBeginning);
FS.ReadBuffer(dataint , 4);
DATA1[n]:=dataint;
//// DATA2 (REAL)
ii:=i+14;
FS.Seek(ii,soFromBeginning);
FS.ReadBuffer(datareal , 8);
DATA2[n]:=datareal;
end;
end;
Finally
FS.Free;
End;
end;
おやおや何時の間にか、タグが3バイトに変ったり、必要なデータが不連続な並び
になっていたり、SingleがDouble(Real)に変っていたり...
ま、いっか...だけど二度読みはイヤだね。
//// DATA1 (LongInt)
DATA1[n]:=PLongInt(@b[i+6])^;
//// DATA2 (Double/REAL)
DATA2[n]:=PDouble(@b[i+14])^;
小出しじじぃ様、具体的なコードをありがとうございます。ポインター(?)が理解できていないのですがコピペですぐに動くようになり、これで1度読みによるものができました。VBの時(GET#で)は2秒くらい掛かっていたものがDelphiではほとんど一瞬で読み込めるようになり驚いています。
>おやおや何時の間にか、タグが3バイトに変ったり、必要なデータが不連続な>並びになっていたり、SingleがDouble(Real)に変っていたり...
フォーマット不明のファイルの中から規則性を探し出しながらの作業でしたがタグも3バイト目もどうやら毎回同じようである、などと模索しながらやっていましたので失礼しました。とりあえず大成功、皆様に感謝します。ありがとうございました。
ツイート | ![]() |