IOインスタンスの読み書きモード

Kernel.#openIO.openはだいたい同じ。IOインスタンスを返す。

第二引数で指定する読み書きモードについてメモ。

各種モードの違い

読み込み可能 書き込み可能 開いた時点で
内容を白紙にする
書き込み
開始位置
r 1 0 0 -
r+ 1 1 0 先頭
w 0 1 1 先頭
w+ 1 1 1 先頭
a 0 1 0 末尾
a+ 1 1 0 末尾

r

デフォルト値。

読み込み可能 書き込み可能 開いた時点で
内容を白紙にする
書き込み
開始位置
r 1 0 0 -

_

# 読み込みできる(内容が残っている)
> open('alphabets.txt', 'r') {|f| f.read }
=> "abc\n"

# 書き込みできない
> open('alphabets.txt', 'r') {|f| f.write('def') }
IOError: not opened for writing

r+

読み込み可能 書き込み可能 開いた時点で
内容を白紙にする
書き込み
開始位置
r+ 1 1 0 先頭

_

# 読み込み可能(内容が残っている)
> open('alphabets.txt', 'r+') {|f| f.read }
=> "abc\n"

# 書き込み可能(書き込み位置=先頭から上書きされる)
> open('alphabets.txt', 'r+') {|f| f.write('de') }
=> 2
> open('alphabets.txt', 'r+') {|f| f.read }
=> "dec\n"

w

読み込み可能 書き込み可能 開いた時点で
内容を白紙にする
書き込み
開始位置
w 0 1 1 先頭

_

# 読み込みできない
> open('alphabets.txt', 'w') {|f| f.read }
IOError: not opened for reading

# 書き込みできる(既存の内容は開いた時点で削除されているため、白紙状態に書き込み)
> open('alphabets.txt', 'w') {|f| f.write('de') }
=> 2
> exit
% cat alphabets.txt
=> de%

w+

読み込み可能 書き込み可能 開いた時点で
内容を白紙にする
書き込み
開始位置
w+ 1 1 1 先頭

_

# 読み込みも可能(ただし既存の内容は開いた時点で削除されている)
> open('alphabets.txt', 'w+') {|f| f.read }
=> ""

# 書き込みできる(既存の内容は開いた時点で削除されているため、白紙状態に書き込み)
> open('alphabets.txt', 'w+') {|f| f.write('de') }
=> 2
> exit
% cat alphabets.txt
de%

a

読み込み可能 書き込み可能 開いた時点で
内容を白紙にする
書き込み
開始位置
a 0 1 0 末尾

_

# 読み込みできない
> open('alphabets.txt', 'a') {|f| f.read }
IOError: not opened for reading

# 書き込みできる(末尾に追記)
> open('alphabets.txt', 'a') {|f| f.write('fgh') }
=> 3
> exit
% cat alphabets.txt
defgh%

a+

読み込み可能 書き込み可能 開いた時点で
内容を白紙にする
書き込み
開始位置
a+ 1 1 0 末尾

_

# 読み込みできる
> open('alphabets.txt', 'a+') {|f| f.read }
=> "defgh"

# 書き込みできる(末尾に追記)
> open('alphabets.txt', 'a+') {|f| f.write('ijk') }
=> 3
> exit
% cat alphabets.txt
defghijk%

ファイルポインタに関する考察

IO#posは「ファイルポインタの現在の位置」を返す。*1

確かめてみる。

open('alphabets.txt', 'r') {|f| f.read }  # => "abcde\n"

open('alphabets.txt', 'r+') do |f|
  p f.pos        # => 0
  p f.gets(2)    # => "ab"
  p f.pos        # => 2
  f.write('x')
  p f.pos        # => 3
end

open('alphabets.txt', 'r') {|f| f.read }  # => "abxde\n"

open('alphabets.txt', 'a+') do |f|
  p f.pos        # => 0
  p f.gets(2)
  p f.pos        # => 2
  f.write('x')
  p f.pos        # => 7
  p f.gets       # => nil
end

open('alphabets.txt', 'r') {|f| f.read }  # => "abxde\nx"

意外だったのは、a+でもr+と同様にファイルポインタは先頭から始まり、読み込むにつれて進んでいくことだ。

writeしたときにr+だと素直にその時点でのファイルポインタの位置から書き込むが、a+だと突然posが末尾に移動したうえで書き込みをしている模様。

そして書き込みを終えたら元の位置に戻る訳ではなく、末尾のままとなる。そのため読み込むことはできなくなる。

リファレンスなどでもこの辺りの説明は見当たらなかった。 Rubyの実装をみればわかるのだろうが、まだちと難しいので宿題にしておこう。