【Python】バイナリファイルのランダムアクセスの基本・前編【入門第72回】
目次
- バイナリファイルへのランダムアクセス
- バイナリファイルを読み書きモードで開く
- ファイルオブジェクトのオフセット位置とは?
- seekメソッドの詳細
- ファイルの先頭にシークする
- ファイルの先頭からデータを読み込む
- ファイルの末尾にシークする
- ファイルの末尾にデータを追記する
- おわりに
- 関連動画
バイナリファイルへのランダムアクセス
ランダムアクセスとはファイルのデータに自由自在にアクセスする技術です。
これが使えるようになると簡単なデータベースなどを作れるようになります。
ちなみにランダムアクセスと対になるのがシーケンシャルアクセスで、これはファイルの先頭から順々にアクセスしていく方法です。
ファイルの入出力において、バイナリファイルのランダムアクセスは初心者の方にとっては1つの難関となるでしょう。
一部では、ファイルのランダムアクセスができるようになったら、その人のプログラミングの腕は中級レベルだと言われています。
ですので初心者の方はできなくても気にせずに何度もトライしてみてください。
ランダムアクセスでは主にseek
メソッドを多用します。
このメソッドでファイルオブジェクト内のオフセット位置を変更し、読み込みや書き込みを行います。
seek
メソッドはプログラマーの好きな位置にファイルのオフセット位置を変更することが出来ますが、これをランダムアクセスと呼びます。
今回は具体的には↓を見ていきます。
バイナリファイルを読み書きモードで開く
ファイルオブジェクトのオフセット位置とは?
seekメソッドの詳細
ファイルの先頭にシークする
ファイルの先頭からデータを読み込む
ファイルの末尾にシークする
ファイルの末尾にデータを追記する
ほかのファイル入出力についての入門記事は↓をご覧ください。
- 【Python】ファイルを開いて閉じる。open関数の使い方【入門第54回】
- 【Python】テキストファイルの読み込み(モードr, rt)【入門第55回】
- 【Python】テキストファイルへの書き込み(モードw, wt)【入門第56回】
- 【Python】テキストファイルへの追記(モードa, at)【入門第57回】
- 【Python】テキストファイルを排他的に開いて書き込む(モードx, xt)【入門第58回】
- 【Python】バイナリファイルの読み込み(モードrb)【入門第59回】
- 【Python】バイナリファイルへの書き込み(モードwb)【入門第60回】
- 【Python】バイナリファイルへの追記(モードab)【入門第61回】
- 【Python】バイナリファイルを排他的に開いて書き込む(モードxb)【入門第62回】
- 【Python】テキストファイルを読み書きモードで開く(モードr+, r+t)【入門第63回】
- 【Python】テキストファイルを読み書きモードで新規作成して開く(モードw+, w+t)【入門第64回】
- 【Python】テキストファイルを読み書きモードで、新規作成して、排他的に開く(モードx+, x+t)【入門第65回】
- 【Python】バイナリファイルを読み書きモードで開く(モードr+b)【入門第66回】
- 【Python】バイナリファイルを読み書きモードで新規作成して開く(モードw+b)【入門第67回】
- 【Python】バイナリファイルを読み書きモードで排他的に開く(モードx+b)【入門第68回】
- 【Python】テキストファイルを読み書きモードで追記する(モードa+)【入門第69回】
- 【Python】バイナリファイルを読み書きモードで追記する(モードa+b)【入門第70回】
- 【Python】バイナリファイルのランダムアクセスの基本・前編【入門第71回】
- 【Python】バイナリファイルのランダムアクセスの基本・後編【入門第72回】
- 【Python】ランダムアクセスによる固定長レコードの読み書き・前編【入門第73回】
バイナリファイルを読み書きモードで開く
ランダムアクセスは基本的にseek
メソッドが使えれば可能です。
そのため、特に「このモードで開かなくてはいけない」というのはありません。
今回は例として、r+b
モードを使いたいと思います。
このモードは読み書きモードでバイナリファイルを開くモードです。
特徴として、ファイルが存在しない場合はエラーを送出し、ファイルが存在する場合はオフセット位置をファイル先頭にしてファイルを開きます。
たとえば↓のような内容のバイナリファイルalphas.dat
があるとします。
:::text
abc
このファイルを開くには↓のようにします。
:::python
with open('alphas.dat', mode='r+b') as fp:
pass
r+b
モードについて詳しく知りたい方は↓の記事をご覧ください。
ファイルオブジェクトのオフセット位置とは?
開いたファイルのファイルオブジェクトはすべて内部に「ファイルのオフセット位置」を持っています。
ファイルオブジェクトはファイルを読み書きするとき、このオフセット位置を基準にしてデータを読み書きします。
たとえばオフセット位置が0
の位置だったら、0
の位置からread
やwrite
を行い、オフセット位置が32
だったら、32
の位置からread
やwrite
を行います。
このオフセット位置はtell
メソッドで確認できます。
たとえば先ほどのalphas.dat
をモードr+b
で開いた直後のファイルオブジェクト内のオフセット位置を↓のようにして出力してみましょう。
:::python
with open('alphas.dat', mode='r+b') as fp:
print(fp.tell())
↑のコードの実行結果は↓のようになります。
:::text
0
バイナリファイルの場合、tell
メソッドの返す値はファイル先頭から現在のオフセット位置までのバイト数です。
↑の結果は0
なのでファイル先頭から0
バイト、つまりオフセット位置はファイル先頭になっています。
オフセット位置はread
やwrite
を呼び出すと、その分オフセット位置が進んで更新されます。
read
やwrite
はデータを読み書きするとき、オフセット位置を進めながらデータを読み書きするためです。
そのためデータの読み書き前のオフセット位置と、データの読み書き後のオフセット位置は異なっている場合があります。
seekメソッドの詳細
このオフセット位置を手動で変更するのがランダムアクセスです。
オフセット位置を手動で変更するにはseek
メソッドを使います。
seek
メソッドはたとえば↓のようにして使います。
:::python
fp.seek(0, os.SEEK_SET)
seek
メソッドの第1引数には第2引数で指定した基点からの相対位置、第2引数には基点となる定数を指定します。
os.SEEK_SET
はファイル先頭を表す定数です。
この定数を使うには↓のようにしてos
モジュールをインポートする必要があります。
:::python
import os
seek(0, os.SEEK_SET)
の場合、基点はSEEK_SET
なのでファイル先頭です。
その基点からの相対位置は0
になっているので、この命令の意味は「ファイル先頭にシーク(移動)する」という意味になります。
↓が基点となる定数の一覧です。
- SEEK_SET または 0 -- ストリームの先頭 (デフォルト)。 相対位置は 0 もしくは正の値でなければなりません。
- SEEK_CUR または 1 -- 現在のストリーム位置。 相対位置は負の値も可能です。
- SEEK_END または 2 -- ストリームの末尾。 相対位置は通常負の値です。
ちなみにストリームとはデータの流れのことで、この場合はファイル内のデータ全体を指しています。
つまり、データが流れている通り道のことを言います。
この概念はLinux
プログラミングなどに出てきますが、紛らわしい場合はファイルに置き換えても大丈夫です。
↓の公式ドキュメントではこのストリームという用語を使っていますが、本記事では「ファイル」と言います。
ファイルよりストリームのほうがなんかかっちょいい?
そう?
ファイルの先頭にシークする
ファイルの先頭にシークするにはseek
メソッドを↓のようにして使います。
:::python
import os
with open('alphas.dat', mode='r+b') as fp:
# ファイル先頭にシーク
fp.seek(0, os.SEEK_SET)
# 現在のオフセット位置を確認
print(fp.tell())
↑のコードの実行結果は↓のようになります。
:::text
0
seek
メソッドの引数の意味は先ほど書いたとおりです。
ファイルの先頭からデータを読み込む
alphas.dat
ファイルの先頭にオフセット位置をシークして、データを読み込むには↓のようにします。
:::python
import os
with open('alphas.dat', mode='r+b') as fp:
# ファイル先頭にシーク
fp.seek(0, os.SEEK_SET)
# ファイル先頭からデータを読み込む
data = fp.read()
print(data)
↑のコードの実行結果は↓のようになります。
:::text
b'abc'
read
メソッドを使うとファイルのオフセット位置がファイル末尾まで自動で移動します。
そのため、ふたたびデータを読み込みたい場合は↓のようにして再びシークする必要があります。
:::python
import os
with open('alphas.dat', mode='r+b') as fp:
# ファイル先頭にシーク
fp.seek(0, os.SEEK_SET)
# ファイル先頭からデータを読み込む(オフセット位置がファイル末尾に)
data = fp.read()
print(data)
# ファイル先頭にシーク
fp.seek(0, os.SEEK_SET)
# ファイル先頭からデータを読み込む(オフセット位置がファイル末尾に)
data = fp.read()
print(data)
↑のコードの実行結果は↓のようになります。
:::text
b'abc'
b'abc'
ファイルの末尾にシークする
alphas.dat
ファイルの末尾にシークするには↓のようにします。
:::python
import os
with open('alphas.dat', mode='r+b') as fp:
# ファイル末尾にシーク
fp.seek(0, os.SEEK_END)
# 現在のオフセット位置を確認
print(fp.tell())
↑のコードの実行結果は↓のようになります。
:::text
3
seek(0, os.SEEK_END)
の第2引数のos.SEEK_END
はファイル末尾を表す定数です。
その位置から0
バイト目ということなので、これは「ファイル末尾へシークする」という意味になります。
alphas.dat
ファイルの内容は↓のようになっています。
:::text
abc
そのため、tell
メソッドが返した3
バイトという値が、abc
のc
の後を指していることがわかると思います。
abc
はa
, b
, c
それぞれが1
バイトです。
ファイルの末尾にデータを追記する
alphas.dat
ファイルの末尾へデータを追記するには↓のようにします。
:::python
import os
with open('alphas.dat', mode='r+b') as fp:
# ファイル末尾にシーク
fp.seek(0, os.SEEK_END)
# ファイル末尾へデータを追記
fp.write(b'def')
↑のコードを実行するとalphas.dat
ファイルの中身は↓のようになります。
:::text
abcdef
write
メソッドを呼び出すと、ファイルのオフセット位置は書き込みに合わせて進みます。
そのため、↑の例ではwrite
メソッドを呼び出した直後のオフセット位置はファイル末尾になっています。
ですので↓のようにして続けて追記することが可能です。
:::python
import os
with open('alphas.dat', mode='r+b') as fp:
# ファイル末尾にシーク
fp.seek(0, os.SEEK_END)
# ファイル末尾へデータを追記
fp.write(b'def')
fp.write(b'ghi')
fp.write(b'jkl')
おわりに
ランダムアクセスの基本・前編をお送りしました。
ここで紹介しているseek
メソッドの使い方は基礎の基礎ですが、後編からちょっと難しくなる予定です。
後編へと続きます。
また見てね