スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

[PHP] fopen() "r+" "w+" どちらも「読み込み/書き込み共用です」 どない違うねんという話

テキストファイルに書かれた数値を読み込んで1プラスして再書き込み。
カウンタとか、なにかのユニークな数値を管理するためによくやる手法かと思います。

で、同時アクセスされたときに読み書きの間で処理が前後すると同じ値が採番されてしまったりするので、だいたいは下記のような手順で進みますよね。(詳細なソースは省きます)

fopen() でファイルオープン

flock() で排他ロック

fread() で読み込み

加算した値を計算

rewind() とか fseek() とかでファイルポインタを先頭に移動

fwrite() とかで書き込み

flock() でロック解除

fclose() でファイルを閉じる


 ありふれた処理ではあるのですが、そういった処理はたいていDB処理することが最近多かったので、はて fopen() では読み書き両方したいときにはどんな感じで読み込めばよかったかいなとリファレンスを読み直したりすると、

"r+" ... 読み込み/書き出し用にオープンします。
"w+" ... 読み込み/書き出し用にオープンします。



 そんなのはわかっとるわ! となるわけです。
 もうちょっと親切ならこう書いてあります。

"r+" ... 読み込み/書き出し用にオープンします。ファイルポインタをファイルの先頭に置きます。
"w+" ... 読み込み/書き出し用にオープンします。ファイルポインタをファイルの先頭に置き、 ファイルサイズをゼロにします。ファイルが存在しない場合には、 作成を試みます。



 "r" と "w" の違いで、「ファイルが存在しない場合には、 作成を試みます。」はわかります。
 "r" はファイルが存在することが前提で動くので、ファイルがないことが想定されるときには"w"を使おうと。
 じゃあ、「ファイルサイズをゼロにします。」とは何なのか。

 わからんので、まあ "w+" で組んでみようと。そうして動かしてみると奇怪な現象になります。
 ファイルから書き込まれているはずの値が読めなかった場合は異常な処理として「1」を採番して、それを書き込むといった処理をした場合、常に1が書き込まれカウントアップされないといった動作になります。

 はてこれは奇怪な、あなたは「読み書き両方」できるんだろう? と思ったものの、これが「ファイルサイズをゼロにします。」の落とし穴。読んでそのままなのですが、"w+" で fopen() されたときは、その時点でファイルサイズがゼロになり、中身が空の、のっぺらぼうになります。
 すなわち、その前にファイルに何か書き込まれていても意味がないということ。確かに読み書き両方できるけれど、いったんファイルの内容は失われるということです。

 というわけで、「fopen() する前のファイルの内容を読んで書き戻す」という今回のような用途には "r+" を使わなければなりません。そういえば C言語を学び始めたときに同じような話を聞いたような気もするがもう10年以上も前の話だものなあ。

 で、こういった処理のときにもうひとつ落とし穴というか見事に困ったのでこうやってメモ書きをしているわけですが、カウンターのように常に値が増えていくものなら問題ありません。
 しかし、これが fopen() する前のファイルサイズより短いものを書き込む場合にちょっと気をつけなければいけない点があります。

 たとえば、先に "1234" と書かれていて、ここに "1" を書き込んだとします。
 ファイルには "1" と書かれていることを期待しますが、処理後にファイルを確認すると "1234" になっています。書き込みに失敗したのか、というとそうでもありません。では、と "2" を書き込むと、 "2234"となります。

 すなわち、「ファイルポインタを先頭に戻して1文字だけ書き込んだ」ので、それ以降の文字は残っているわけです。"w+" では「ファイルサイズをゼロに」されるのでこういうことはありませんが、逆に "r+" で内容を残すことにするとこういうことになるわけです。

 なら、"r" で読んで、"w" で書けば理屈としてはいいわけですが、ロックが外れる瞬間が発生してうまくありません。やはり "r+" を使わねばなりません。なら、「ファイルサイズをゼロに」できればいいのではないか。そのとおり、 ftruncate() というものがございます。

ftruncate -- ファイルを指定した長さに丸める
http://manual.xwd.jp/function.ftruncate.html

ftruncate( $fp, 0 );

 として、書き込む前にファイルの内容をクリアしてしまいましょう。これで "w" / "w+" でオープンしたようにファイルサイズがゼロになるので、いままでの内容がどうであったかを意識せずに、書き込んだもののみがファイルに残ります。

 こんなことで半日悩んだりするので、経験がどれだけあってもメモ書きは大事だなあと思うしだい。



拍手コメントお返事:
ftruncate() 、あんまり聞きなれないですよね・・・。
こういうことがなければ、マニュアル見ても「これいつ使うの」と感じたと思います。
拍手ありがとうございます。(2011/03/25)

拍手コメントお返事:
チャットや掲示板のログも同じファイルを使いまわす以上、類似した落とし穴はありそうですね。
拍手有り難うございます。(2011/01/05)

拍手コメントお返事:
けっこう同じことで悩まれる方が多いようですね。
拍手有り難うございます。(2010/08/31)

拍手コメントお返事:
お役に立ったのであれば、こうやって書き記している意味もあろうというものです。
拍手有り難うございます。(2009/08/25)

拍手コメントお返事:
お悩みは解決されましたでしょうか。少しでもお役に立ったのであれば幸いです。
拍手有り難うございます。(2009/12/01)


関連記事
スポンサーサイト

comment

管理者にだけメッセージを送る

No title

初めまして。

現在、C言語を頑張って勉強しています。
fopen()の挙動がどうも自分の思い通りにいかず悶絶していたのですが、
こちらの記事を参考にさせて頂きましたら、なんとか自分の思ってる通りの動作をさせる事が出来ました!
とても分かり易いご説明、本当に有難う御座います!!

No title

わかんないですよね。

マニュアルとかももうちょっと丁寧に書いておくれ、と思います。
もともとそんななのか、訳文がへっぽこなのかどっちかわかりませんけど。

コメントありがとうございました。

No title

昨日、ちょうどこれで半日苦しめられました;

なかなか解決策が分からずfopenの引数の理解ができてないことが原因かと思い、このサイトに立ち寄りましたがビンゴでした。

わかりやすい解説をありがとうございます!
ここで解説してもらわなければまだ悩んでました。
「 r+ 」も「 w+ 」も大きな落とし穴すぎますね笑

こういう手間がいらないデータベースのお手軽な魅力もわかった気がします。

No title

DB だと SQL文で +1 で UPDATE すれば済む話ですからねえ。
SQL文を使うとはいうもののいくらか直観的です。

最近はコーディングも型とかそんなに気にしなくなって
楽にはなりましたけど、ファイルの扱いとかはたまにこういう
低水準的なことを気にしないといけないのは鬱陶しいですね。

コメント有難うございました。
検索フォーム
リンク
最新記事
最新コメント
カテゴリ
RSSリンクの表示
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。