想要高效读写文件吗?Python的Mmap()函数或许可以解决你的问题

开发 后端
本篇博客将详细介绍Python中文本文件的追加和截断、with语句原理、seek和tell方法、内存映射文件(mmap)以及大文件分块读取等方面的知识点,帮助读者深入了解Python文件IO操作的实现原理和高级应用。

文本文件的追加和截断

文件追加的概念和应用

在某些场景下,我们需要对已有的文件进行修改,常见的方式是打开文件后将新的内容写入,这会覆盖掉原来的内容。但如果想保留原文件的内容并在其末尾添加新的内容,就需要用到文件追加操作。文件追加是指向一个已存在的文件末尾添加新的内容,并且不影响原先的内容。

使用 a 模式打开文本文件进行追加操作

Python中使用 open() 函数打开文件时,可以通过设置文件模式参数来指定文件的操作方式。其中,a 模式表示以追加的方式打开文件,即将新的内容添加到文件末尾。

with open('file.txt', 'a') as f:
    f.write('Hello, world!\n')

在上述代码中,我们打开了一个名为 file.txt 的文件,并使用 a 模式将字符串 'Hello, world!\n' 写入文件末尾。需要注意的是,在使用 a 模式时,如果文件不存在,则会自动创建新文件。

truncate 方法实现文本文件截断

除了在文件末尾追加新内容之外,我们有时还需要对文件进行截断操作,即只保留文件前几行或前几个字符,而舍弃文件中后面的内容。Python中提供了 truncate() 方法来实现这一功能。truncate() 方法可以指定文件的长度(以字节为单位),使文件中多余的部分被删除掉,从而实现文件截断。

with open('file.txt', 'r+') as f:
    f.seek(0)           # 将文件指针移到文件开头位置
    f.truncate(10)      # 截断文件,保留前10个字符

在上述代码中,我们首先使用 r+ 模式打开文件,并将文件指针移到文件开头位置,然后使用 truncate() 方法截断文件,并指定保留文件前10个字符。需要注意的是,在使用 truncate() 方法时,必须以读写模式打开文件,否则会抛出异常。

示例代码

下面是一个完整的示例代码,演示了如何使用 a 模式打开文件追加内容,并使用 truncate() 方法截断文件。

def append_and_truncate():
    with open('file.txt', 'a') as f:
        f.write('Hello, world!\n')

    with open('file.txt', 'r+') as f:
        f.seek(0)
        f.truncate(10)

    with open('file.txt', 'r') as f:
        print(f.read())

在上述代码中,我们首先使用 a 模式打开文件 file.txt 并写入一行文本,然后再以 r+ 模式打开同一文件进行截断操作,保留前10个字符。最后,我们以只读模式打开文件并打印其内容,结果应该如下所示:

Hello, worl

with 语句原理

with 语句的作用和优势

with语句是Python提供的一种简化文件操作的语法结构,其作用是在文件使用完后自动关闭文件,避免了手动关闭文件时可能出现的错误。除了文件操作之外,with语句还可以用于其他资源的管理,例如网络连接、数据库连接等。

with open('file.txt', 'r') as f:
    data = f.read()

在上述代码中,我们使用 with 语句打开文件 file.txt 并读取其中的内容,这样即使在处理文件过程中出现异常,Python也会自动关闭文件,避免了文件资源泄露的问题。可以看到,使用 with 语句可以让代码更加简洁和优雅。

with 语句原理及其底层实现

with语句的实现原理是基于上下文管理器(context manager)的概念。上下文管理器是一个对象,它定义了进入和退出某个上下文时要执行的操作。使用 with 语句时,必须将一个支持上下文管理器协议的对象传递给它,然后 with 语句会在进入和退出上下文时自动调用该对象的 enter() 和 exit() 方法。

class File:
    def __init__(self, filename):
        self.filename = filename
    
    def __enter__(self):
        print('Enter')
        self.file = open(self.filename, 'r')
        return self.file
    
    def __exit__(self, exc_type, exc_value, traceback):
        print('Exit')
        self.file.close()

with File('file.txt') as f:
    data = f.read()

在上述代码中,我们定义了一个名为 File 的上下文管理器,并实现了其 enter() 和 exit() 方法。在 with 语句中使用 File 对象时,Python会自动调用其 enter() 方法打开文件,并在代码块执行完毕后调用 exit() 方法关闭文件。

示例代码

下面是一个完整的示例代码,演示了如何使用 with 语句打开文件并读取其中的内容。

class File:
    def __init__(self, filename):
        self.filename = filename
    
    def __enter__(self):
        self.file = open(self.filename, 'r')
        return self.file
    
    def __exit__(self, exc_type, exc_value, traceback):
        self.file.close()

def read_file():
    with File('file.txt') as f:
        data = f.read()
        print(data)

if __name__ == '__main__':
    read_file()

在上述代码中,我们定义了一个名为 File 的上下文管理器,并在 read_file() 函数中使用 with 语句打开文件 file.txt 并读取其中的内容,然后自动关闭文件。需要注意的是,在 with 语句中打开文件时,必须指定文件模式参数,并且不能使用 a 模式进行追加操作。

seek 和 tell 方法

seek 和 tell 方法的作用和区别

在Python中,文件对象提供了两个基本方法来控制文件指针的位置:seek() 和 tell()。其中,seek() 方法用于将文件指针移到文件的任意位置,而 tell() 方法则返回当前文件指针的位置。

with open('file.txt', 'r') as f:
    data = f.read(10)   # 读取前10个字符
    pos = f.tell()      # 获取当前文件指针位置
    f.seek(0)           # 将文件指针移到文件开头位置
    data2 = f.read(10)  # 重新读取前10个字符

print(pos)
print(data2)

在上述代码中,我们使用 with 语句打开文件 file.txt 并读取其中的前10个字符,然后获取当前文件指针的位置,并将文件指针移到文件开头位置,最后重新读取前10个字符。需要注意的是,在使用 seek() 方法时,必须以二进制模式打开文件。

文件指针和偏移量的概念和使用方法

文件指针是一个表示当前读写位置的指针,它指向文件中下一个要读取或写入的字节的位置。在Python中,文件指针的位置可以通过 tell() 方法获取,并且可以使用 seek() 方法将其设置为任意位置。seek() 方法接受一个整数参数,代表相对于文件开头的偏移量(以字节为单位),并可指定偏移量的起始位置(0表示文件开头,1表示当前位置,2表示文件末尾)。

with open('file.txt', 'r') as f:
    f.seek(5)       # 将文件指针移到第6个字符处
    data = f.read() # 从第6个字符开始读取文件内容

在上述代码中,我们使用 seek() 方法将文件指针移到第6个字符处,然后读取从该位置开始的文件内容。

实现随机访问和修改文件内容

由于可以通过 seek() 方法将文件指针移到文件的任意位置,因此可以实现随机访问文件内容。例如,我们可以通过 seek() 方法将文件指针移到某一行的开头位置,然后读取该行的内容。类似地,我们也可以使用 seek() 和 write() 方法来修改文件的特定位置。

with open('file.txt', 'r+') as f:
    f.seek(5)           # 将文件指针移到第6个字符处
    f.write('WORLD')    # 将字符 WORLD 插入到文件中
    f.seek(0)           # 将文件指针移到文件开头位置
    data = f.read()     # 重新读取文件内容

print(data)

在上述代码中,我们使用 r+ 模式打开文件 file.txt,并将文件指针移到第6个字符处,然后使用 write() 方法向文件中插入字符串 'WORLD'。最后,我们再次将文件指针移到文件开头位置并读取文件的全部内容,输出结果应该为:

HelloWORLD, how are you?

示例代码

下面是一个完整的示例代码,演示了如何使用 seek() 和 tell() 方法实现随机访问和修改文件内容。

def random_access():
    with open('file.txt', 'r+') as f:
        f.seek(5)
        f.write('WORLD')
        f.seek(0)
        data = f.read()
        print(data)

if __name__ == '__main__':
    random_access()

在上述代码中,我们首先使用 r+ 模式打开文件 file.txt 并将文件指针移到第6个字符处,然后使用 write() 方法插入字符串 'WORLD'。最后,我们重新将文件指针移到文件开头位置并读取文件的全部内容,输出结果应该为:

HelloWORLD, how are you?

内存映射文件(mmap)

mmap 的作用和优势

Python中提供了一种特殊的文件操作方式,称为内存映射文件(mmap)。内存映射文件是一种将文件内容映射到内存中的技术,它允许我们通过内存来读写文件内容,从而避免了频繁访问磁盘的开销。同时,内存映射文件还可以让我们像处理数组一样高效地对文件进行随机访问和修改。在处理大型二进制文件时,内存映射文件非常有用。

mmap 原理及其底层实现

在Python中,使用 mmap() 函数可以将一个文件对象映射到内存中,从而生成一个内存映射文件对象。内存映射文件对象具有文件对象的所有方法,例如 read()、write()、seek() 等,并且也可以像操作数组一样进行随机访问和修改。

import mmap

with open('file.bin', 'r+b') as f:
    mm = mmap.mmap(f.fileno(), 0)
    
    # 读取前10个字节
    data1 = mm[:10]
    print(data1)
    
    # 修改前5个字节
    mm[:5] = b'Hello'
    
    # 查找字符串
    pos = mm.find(b'world')
    print(pos)
    
    # 替换字符串
    mm[pos:pos+5] = b'WORLD'
    
    # 关闭内存映射文件
    mm.close()

在上述代码中,我们使用 mmap() 函数将文件 file.bin 映射到内存中,并获取了一个内存映射文件对象 mm。然后,我们可以像处理数组一样对内存映射文件进行读写操作。例如,我们可以使用切片符号 [:] 来读取文件的前10个字节,使用 find() 方法查找特定字符串的位置,并使用切片符号来替换字符串中的部分内容。最后,我们调用 close() 方法关闭内存映射文件。

注意事项

在使用 mmap() 函数时,需要注意以下几点:

  1. 内存映射文件只能用于二进制文件的处理,不支持文本模式。
  2. 内存映射文件是通过共享内存实现的,在修改文件内容时需要注意并发访问问题,否则可能导致数据损坏或进程挂起。
  3. 在某些操作系统上,如果文件长度超过了可用的虚拟内存大小,则无法创建内存映射文件对象。

由于 Python 的 mmap() 函数依赖于底层操作系统的 mmap() 系统调用,因此其行为和性能可能在不同的操作系统上有所不同。在编写使用 mmap() 函数的代码时,通常需要对其进行测试和优化,以确保其在特定平台上的表现符合预期。

示例代码

下面是一个完整的示例代码,演示了如何使用 mmap() 函数创建内存映射文件对象,并对其进行读写操作。

import mmap

def memory_map():
    with open('file.bin', 'r+b') as f:
        # 将文件映射到内存中
        mm = mmap.mmap(f.fileno(), 0)
        
        # 读取前10个字节
        data1 = mm[:10]
        print(data1)
        
        # 修改前5个字节
        mm[:5] = b'Hello'
        
        # 查找字符串
        pos = mm.find(b'world')
        print(pos)
        
        # 替换字符串
        mm[pos:pos+5] = b'WORLD'
        
        # 关闭内存映射文件
        mm.close()

if __name__ == '__main__':
    memory_map()

在上述代码中,我们使用 mmap() 函数将文件 file.bin 映射到内存中,并获取了一个内存映射文件对象 mm。然后,我们可以像处理数组一样对内存映射文件进行读写操作。最后,我们调用 close() 方法关闭内存映射文件。

大文件分块读取

当需要处理大型文件时,可能会遇到内存不足的问题。为了解决这个问题,我们可以将文件分成多个块进行读取和处理。这样可以避免一次性将整个文件读入内存,从而降低内存的使用量。在 Python 中,我们可以使用生成器来实现大文件分块读取。

生成器函数实现大文件分块读取

def read_in_chunks(file_obj, chunk_size=1024):
    """生成器函数:分块读取文件"""
    while True:
        data = file_obj.read(chunk_size)
        if not data:
            break
        yield data

在上述代码中,我们定义了一个生成器函数 read_in_chunks(),该函数接受两个参数:文件对象和块大小。在函数体内,我们使用 while 循环从文件中读取指定大小的数据块,并将其作为生成器对象的返回值。如果读取完整个文件,则退出循环并返回最后一块数据。

使用生成器函数读取大文件

with open('large_file.txt', 'r') as f:
    for chunk in read_in_chunks(f, chunk_size=1024):
        process_data(chunk)

在上述代码中,我们使用 with 语句打开文件 large_file.txt,并循环读取文件的分块数据。每次循环迭代时,处理函数 process_data() 将会被调用,并将当前的数据块作为参数传递进去。这样,在整个文件读取完成后,我们可以在 process_data() 函数内部处理所有的数据。

注意事项

需要注意以下几点:

  1. 在使用生成器函数处理大型文件时,需要根据实际情况选择合适的块大小。如果块的大小太小,则会增加系统的调用次数;如果块的大小太大,则可能会导致内存溢出。
  2. 如果在处理文件结束后没有显式地关闭文件对象,则可能会导致资源泄漏或其他问题。
  3. 在某些操作系统上,如果文件长度超过了可用的虚拟内存大小,则可能无法完整读取文件。

示例代码

下面是一个完整的示例代码,演示了如何使用生成器函数实现大文件分块读取。

def read_in_chunks(file_obj, chunk_size=1024):
    """生成器函数:分块读取文件"""
    while True:
        data = file_obj.read(chunk_size)
        if not data:
            break
        yield data

def process_data(data):
    """处理函数:输出数据块的长度"""
    print(len(data))

if __name__ == '__main__':
    with open('large_file.txt', 'r') as f:
        for chunk in read_in_chunks(f, chunk_size=1024):
            process_data(chunk)

在上述代码中,我们定义了一个生成器函数 read_in_chunks(),用于分块读取文件;另外还定义了一个处理函数 process_data(),用于输出数据块的长度。最后,在主程序中,我们使用 with 语句打开文件 large_file.txt 并循环读取文件的分块数据,并将其作为参数传递给 process_data() 函数进行处理。

责任编辑:姜华 来源: 今日头条
相关推荐

2010-03-24 12:59:27

无线上网信号

2018-05-28 14:38:44

PHPPython应用

2019-07-02 13:55:50

苹果谷歌亚马逊

2018-11-15 19:00:12

人工智能帕金森病医学

2015-12-31 10:45:25

云计算风险

2020-08-16 10:58:20

Pandaspython开发

2023-09-04 07:54:06

2022-02-28 19:32:27

I/O磁盘

2019-10-15 14:14:26

Linuxshell运维

2022-08-18 09:51:50

Python代码循环

2011-10-09 11:08:03

EMCOpenWorld云计算

2023-12-04 07:09:53

函数递归python

2020-12-18 07:43:57

csv文件乱码Python

2016-10-14 09:01:34

2021-03-18 18:38:48

边缘计算云计算数字化

2023-02-28 07:39:18

2024-01-03 14:52:15

数字化转型

2023-11-30 16:05:17

2023-06-01 16:41:39

NumPyPython
点赞
收藏

51CTO技术栈公众号