Pythonでビッグデータファイルを読み取る
Python では、大きなファイルを読み取るシナリオがよく発生します。ファイルが小さい場合は、通常、次のメソッド readlines() を使用します。このメソッドは、ファイルのすべての行を一度に読み取り、リストに入れ、最後にメモリに保存します。ファイルが大きい場合、メモリ使用量が非常に多くなり、メモリ オーバーフローが発生し、プロセスがシステムによって強制終了される可能性もあります。
# 读取方式一
with open(file_path, 'r+', encoding='utf-8') as f:
count = 0
for line in f.readlines():
count += 1
print(count)
実行前:
総システム メモリ: 8056.3M
システム使用メモリ: 6264.3M
システム空きメモリ: 1791.3M
20465実行
後:
総システム メモリ: 8056.3M
システム使用メモリ: 6266.3M
システム空きメモリ: 1789.3M
実験で使用したファイルサイズは800kとそれほど大きくはありませんが、 2Mのメモリを占有していることがわかります。
推奨される方法: イテレータ反復走査
Pythonにはイテレータという概念があります。反復は、実際にはシーケンス内の要素を処理する方法です。
次に、反復可能オブジェクトを使用して反復処理を行い、そのメモリ使用量をテストします。f の行では、キャッシュ IO とメモリ管理が自動的に使用されます。
# 读取方式二
with open(file_path, 'r+', encoding='utf-8') as f:
count = 0
for line in f:
count += 1
print(count)
実行前:
総システム メモリ: 8056.3M
システム使用メモリ: 5883.3M
システム空きメモリ: 2172.3M
20465
実行後:
総システム メモリ: 8056.3M
システム使用メモリ: 5884.3M
システム空きメモリ: 2171.3M
実験で使用したファイルのサイズは 800k とそれほど大きくはありませんが、使用するメモリは 1M であり、最初の方法に比べてメモリが節約されていることがわかります。
推奨される方法: ジェネレーター
ジェネレーターは、イテレーターを作成するためのシンプルかつ強力なツールです。これは、データを返すときに yield ステートメントを使用する必要があることを除いて、通常の関数のように記述されます。
# 读取方式三
# 生成器函数,用于自定义创建迭代器read_fileline_generator
def read_fileline_generator(file, size=4096):
while 1:
data = file.read(size)
if not data:
break
yield data
with open(file_path, 'r+', encoding='utf-8') as f:
count = 0
for line in read_fileline_generator(f):
count += 1
実行前:
総システム メモリ: 8056.3M
システム使用メモリ: 5719.3M
システム空きメモリ: 2336.3M
実行後:
総システム メモリ: 8056.3M
システム使用メモリ: 5720.3M
システム空きメモリ: 2335.3M
実験で使用したファイルのサイズは 800k とそれほど大きくはありませんが、使用するメモリは 1M であり、最初の方法に比べてメモリが節約されていることがわかります。
ビルダー
上記では、ジェネレーターを使用してファイルを反復処理するイテレーターを作成しました。では、発電機とは何でしょうか?配列を返す関数のようなジェネレーターを詳しく見てみましょう。
反復プロトコルをサポートするために、yield ステートメントを含む関数はジェネレーターにコンパイルされます。このタイプの関数が呼び出されると、ジェネレーター オブジェクトが返されます。返されたオブジェクトは反復インターフェイス、つまりメンバー メソッド __next( )__ は中断された時点から実行を継続します。
ただし、通常の関数とは異なります。
一般関数は一度にすべての値の配列を返しますが、ジェネレーター関数は一度に配列内の値を 1 つだけ生成するため、消費されるメモリは大幅に削減されます。
一般的な関数は実行後、値を返して終了しますが、ジェネレーター関数は、yield キーワードを使用して呼び出し元に値を返し、現在の実行状態を保持し、自動的に一時停止するため、次の実行時に実行を継続できます。後で呼ばれました。
ジェネレーターに next() 関数を使用するのは面倒ですが、for ループによってジェネレーターの next 関数が自動的にトリガーされます。
Python には 2 種類のジェネレーターがあります。
- **ジェネレーター関数:** も def によって定義されており、キーワード yield を使用して一度に 1 つの結果を返し、ブロックして再開します。
- **ジェネレータ式:** 必要な場合にのみ結果を生成するオブジェクトを返します
上ではジェネレーター関数を適用しましたが、ジェネレーター式とは何でしょうか?
ジェネレータ式はリスト内包表記に似ていますが、リスト内包表記では角括弧の代わりに括弧を使用します。
# 列表解析式
list = [ x**2 for x in range(5)]
print(list)
[0, 1, 4, 9, 16]
# 生成器表达式
generator_list = (x**2 for x in range(5))
print(generator_list)
<generator object <genexpr> at 0x0EE83840>
print(next(generator_list))
0
print(next(generator_list))
1
print(next(generator_list))
4
print(next(generator_list))
9
print(next(generator_list))
16
print(next(generator_list))
Traceback (most recent call last):
File "<input>", line 1, in <module>
StopIteration
テストコード
# python 读取较大数据文件
import psutil
mem = psutil.virtual_memory()
print('运行前:')
print('系统总计内存:%d.3M' % (float(mem.total)/1024/1024))
print('系统已经使用内存:%d.3M' % (float(mem.used)/1024/1024))
print('系统空闲内存:%d.3M' % (float(mem.free)/1024/1024))
file_path = 'H:\\study\\python\\long.txt'
# 读取方式一
# with open(file_path, 'r+', encoding='utf-8') as f:
# count = 0
# for line in f.readlines():
# count += 1
# print(count)
# 读取方式二
# with open(file_path, 'r+', encoding='utf-8') as f:
# count = 0
# for line in f:
# count += 1
# print(count)
# 读取方式三
def read_fileline_generator(file, size=4096):
while 1:
data = file.read(size)
if not data:
break
yield data
with open(file_path, 'r+', encoding='utf-8') as f:
count = 0
for line in read_fileline_generator(f):
count += 1
mem2 = psutil.virtual_memory()
print('运行后:')
print('系统总计内存:%d.3M' % (float(mem2.total) / 1024 / 1024))
print('系统已经使用内存:%d.3M' % (float(mem2.used) / 1024 / 1024))
print('系统空闲内存:%d.3M' % (float(mem2.free) / 1024 / 1024))