Python讀寫文件你真的了解嗎?


內容概述

Python文件操作

針對大文件如何操作

為什么不能修改文件?

你需要知道的基本知識

1. Python文件操作

這一部分內容不是重點,因為很簡單網上很多,主要看看文件操作的步驟就可以了。

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Author: rex.cheny
# E-mail: rex.cheny@outlook.com

import os
import time
import sys

FILE_PATH = "/Volumes/Work/ProgrammingStudy/AutoOperationStudy/Day03/file.txt"
if os.path.exists(FILE_PATH):
    # 把文件對象給一個變量,這樣后續才能操作這個文件對象,這里默認是以只讀方式打開
    FILE_HANDLER = open(FILE_PATH, encoding="utf-8")
    # 讀取
    """
    read(size) 不加SIZE可以是整個文件讀取,加上SIZE可以一部分一部分的讀取,對於大文件要使用SIZE分段讀取,對於小文件可以整體讀取
    readline() 一次讀取一行
    readlines() 一次讀取全部文件到一個列表,適合讀取配置文件,因為配置文件通常都是一行一行的,對於配置文件這種非常適合因為它比較小而且
    配置文件都是一行一行的
    """
    data = FILE_HANDLER.read()
    print(FILE_HANDLER.fileno())
    time.sleep(600)
    print(data)
    # 關閉
    FILE_HANDLER.close()
else:
    print("文件:", FILE_PATH, "不存在。")

# 一行一行讀取
if os.path.exists(FILE_PATH):
    FILE_HANDLER = open(FILE_PATH, encoding="utf-8")
    dataList = FILE_HANDLER.readlines()
    for line in dataList:
        print(line)
    FILE_HANDLER.close()

# 寫入一行文件
if os.path.exists(FILE_PATH):
    """
    mode  r 是只讀模式  
          w 是只寫模式打開文件,其實是創建,所以如果原來的文件有內容它就給它清空了,所以在原有的內容寫就不能用這種模式
          a 追加模式,只能追加
          r+ 讀寫模式,可以讀取也能追加,只能追加
          rb 讀取二進制文件
          wb 寫入二進制文件
    """
    FILE_HANDLER = open(FILE_PATH, encoding="utf-8", mode="a")
    FILE_HANDLER.write("你好嗎\n")
    # 這個語句是立即落盤, 因為延遲寫入,上面的write只是寫入緩存並沒有真正寫入磁盤,如果對於寫入內容比較多,你不去調用flush的話可能如果執行完write語句后剛好計算機
    # 斷電,那么就容易出現數據丟失的情況。
    FILE_HANDLER.flush()
    # 關閉已打開的文件流
    FILE_HANDLER.close()

更加簡單的創建文件對象的方式,上面的方式需要手動關閉文件流,下面的方式它會自動關閉文件流

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Author: rex.cheny
# E-mail: rex.cheny@outlook.com

import os
import sys

FILE_PATH = "/Volumes/Work/ProgrammingStudy/AutoOperationStudy/Day03/file.txt"

if os.path.exists(FILE_PATH):
    with open(FILE_PATH, encoding="utf-8", mode="a") as FILE_HANDLER:
        FILE_HANDLER.write("你好嗎\n")

 通過with open還可以打開多個文件

with open (FILE1, "r") as f1, open(FILE2, "w") as f2:
    pass

2. 針對大文件如何操作

如何操作大文件比如10G一個文件,顯然不能使用read()和readlines(),就算你的內存夠大,這樣一個文件都讀取到內存里顯然不是一個好辦法,太浪費了內存了,另外從磁盤加載到內存頁需要時間。是不是可以使用readline()?可以一行行讀取,不過讀的時候雖然是一行,可是不斷的讀取到內存最終還是會占用很大內存,所以一個好的方法是,讀取一行到內存,處理完后把內存的那一行內容刪除,然后再讀取一行,也就是內存只保留一行。

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Author: rex.cheny
# E-mail: rex.cheny@outlook.com

import os
import sys

FILE_PATH = "/Volumes/Work/ProgrammingStudy/AutoOperationStudy/Day03/file.txt"

if os.path.exists(FILE_PATH):
    with open(FILE_PATH, encoding="utf-8", mode="r") as FILE_HANDLER:
# 這里並沒有調用文件對象的read方法,此時這里這個文件對象是一個迭代器 for line in FILE_HANDLER: print(line)

 

說明:由於上面使用的是迭代器而不是一個列表,所以你想控制輸出N行或者第N行你就需要自己來做一個計數器,也就自己記錄當前是第幾行。

3. 為什么不能修改文件

你通過r+打開文件發現即便你使用當前讀取到第N行(假設有M行)你調用write()還是無法插入,這個新增的內容只會顯示在最后一行

你通過w打開文件發現源文件被清空了,感覺好危險的樣子。

感覺vi編輯器、word這些都能直接修改啊?

這都是為什么呢?

數據存儲簡要原理

磁盤分區的最小單位是柱面,但是數據存儲的最小單位是block也就是塊,這個是文件系統相關內容,不是磁盤的物理特性。一個文件通常會占用1個或多個塊。下面簡要畫圖來看:

為了性能通常都會尋找連續的塊來存放數據(讀寫效率最高,減少尋址時間),但是隨着時間的推移(刪除、新建、更新文件等操作)導致很多文件存放的塊並不是連續的,就想文檔2。假設我們現在修改文檔1

而且需要修改的數據正好在文檔1的塊2中。如果這時候允許你直接修改,那就意味着原來的數據存放位置要想后挪動,可是你看如果挪動,block03的內容就容易覆蓋了block04也就是文檔2的內容,顯然這是不允許的,所以肯定不允許你從數據物理存放的角度來插入數據,所以就算你通過r+模式打開也無法插入。如果你以w模式打開系統會當做新文件來處理所以清空原來的數據。

那么為什么r+可以追加呢?追加是在文件末尾,假設block03還有空間那么就繼續使用這個塊,如果發現這個塊空間不夠,那么除了使用這個塊剩余的空間它在最后保存的時候還會去申請新的塊來保存內容,總之原有數據順序不能變。

VI或者word為什么可以修改?

我們用VI打開文件尤其是一個大文件你會感覺慢,其實是因為它要把內容加載內存,然后你在內存中修改,也就是可以在文件任意位置修改,當修改完成保存磁盤的時候它會覆蓋原有內容,同時如果本次修改增加了更多內容則會申請新的磁盤塊用於存放。對於word來說也是一樣的,你發現打開一個10M以上的word文件也並不快它也是加載到內存里,有人說如果文件很大,我的內存不是很大,能不能打開呢?可以,你是否還記得一個叫做虛擬內存的東西么,當然如果物理內存+虛擬內存都不夠了,你也就打不開了。我記得之前用Windows電腦的時候出現過打開文件提示說內存不夠。

4. 如何實現修改文件呢

在python中其實也一樣,因為它底層調用的就是庫函數。如果你想實現修改的效果怎么辦?你要么讀取都讀取到內存中然后修改,最后再保存其實也就是覆蓋源文件

我這里有一個之前替換線上配置文件某些信息的例子,main()方法不是重點,可以忽略,后面的replacepath才是文件修改部分。

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import sys
import os
import re


def main(argv=None):
    if argv is None:
        argv = sys.argv

    # 規范化路徑
    path = os.path.normpath(r"/Users/rex.chen/Downloads/apps/")
    config_path = os.path.normpath(r"product/conf")

    # 獲取目錄列表
    appnamelist = os.listdir(path)
    modifyappname = []
    # 遍歷目錄
    for appname in appnamelist:
        print appname
        # 拼接路徑
        app_config_full_path = os.path.join(path, appname, config_path)
        filename = r"log4j.properties"
        filefullpath = os.path.join(app_config_full_path, filename)
        # 判斷路徑是否存在
        if os.path.exists(filefullpath):
            print filefullpath
            key_word = r"${CATALINA_OUT}"
            new_word = r"/work/logs/{{appname}}-{{port}}"
            # 調用自定義函數並獲取返回值
            result = replacepath(appname, filefullpath, key_word, new_word)
            if result:
                modifyappname.append(appname)
            else:
                pass
        else:
            print "文件路徑:%s 不存在" % filefullpath

    print "需要修改日志文件的程序個數為:%s 名稱如下:" % len(modifyappname)
    for tempname in modifyappname:
        print tempname


def replacepath(temp_appname, temp_filepath, temp_keyword, temp_newword, ismodify=False):

    # 打開文件
    file1 = open(temp_filepath, 'r')

    # 這里不能直接使用傳遞過來的keyword進行搜索是因為這里需要一個正則表達式。字符串雖然是一個特殊的正則表達式,但是因為有特殊符號需要轉義
    # 這里的含義是讀取文件整體內容,並對內容進行搜索查找符合表達式的內容,把找到的放到變量中,這是一個列表
    allchars = re.findall("\$\{CATALINA_OUT\}", file1.read())
    # 統計列表長度,這樣就獲取了匹配的個數
    count = len(allchars)
    print "關鍵字 %s 在程序 %s 出現次數: %s" % (temp_keyword, temp_appname, count)

    # 如果count大於0,表示這個文件里面有我們要替換的字符,如果不大於0則退出該函數
    if count > 0:
        ismodify = True
    else:
        file1.close()
        return ismodify

    # 移動指針到文件首部
    file1.seek(0)
    # 讀取所有內容
    alllines = file1.readlines()
    # 關閉文件流
    file1.close()

    # 設置正則
    p = re.compile("\$\{CATALINA_OUT\}")
    # 以寫模式打開文件,因為這里是同一個文件,所以w參數會清空該文件,這樣就實現了在同一個文件中查找和替換
    file2 = open(temp_filepath, 'w')
    # 遍歷之前的所有內容
    for line in alllines:
        try:
            # 使用sub進行替換,p對象包含正則也就是需要匹配的內容,temp_newword是新內容,line是在哪里進行查找匹配
            c = p.sub(temp_newword, line)
            # c 對象為替換后的新內容,然后寫入文件
            file2.write(c)
        except IOError as err:
            print "文件寫入錯誤:"+str(err)
            file2.close()

    ismodify = True
    return ismodify


if __name__ == "__main__":
    sys.exit(main())

  

我上面采取的方式就是全部讀取到內存,然后進行處理最后寫入。如果文件很大怎么辦呢?

如果文件過大你可以一次讀取一部分然后進行修改或者用上面提到的文件迭代器,修改后保存到一個新文件,然后在讀取一部分修改后追加到那個新文件,所有修改和保存完成之后,刪除原文件把新文件重命名為原來的文件名。

5. 額外的基礎知識

延遲寫入:磁盤是低速設備,內存是高速設備,如果高速設備向低速設備寫入數據那根據木桶原理其速度不會高於短板,所以程序就會等待,那么系統中通常有很多服務如果是一個對外提供服務的服務器那么寫入數據的操作一定少不了,如果都實時寫入磁盤那性能將會非常地下。所以所有寫入都先寫入buffer也就是緩存,然后由系統來覺得什么時候把緩存中的數據同步到磁盤上。通常來說緩存寫滿了就會進行一次同步或者在到了一個同步周期也會進行同步。這就是為什么會有flush()的原因。你可以嘗試一下,當你調用完write(),然后讓程序睡眠一下,你去查看原來的文件一定沒有你寫入的數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM