1. 程式人生 > 實用技巧 >【STM32H7教程】第82章 STM32H7的QSPI匯流排應用之QSPI Flash執行程式(BOOT+APP方式)

【STM32H7教程】第82章 STM32H7的QSPI匯流排應用之QSPI Flash執行程式(BOOT+APP方式)

檔案操作

1. 學前三問

1.1 什麼是檔案 ?

檔案是作業系統提供給使用者/應用程式操作硬碟的一種虛擬的概念/介面。

使用者/應用程式
作業系統(檔案)  作業系統會把對使用者/應用程式檔案功能的呼叫轉成具體的硬碟操作
計算機硬體(硬碟)

1.2 為何要用檔案

使用者/應用程式可以通過檔案將資料永久儲存的硬碟中,即操作檔案就是操作硬碟。

使用者/應用程式直接操作的是檔案,對檔案進行的所有操作,都是在向作業系統傳送系統呼叫,然後再由操作將其

轉換成具體的硬碟操作。

1.3 如何用檔案:open( )

1.3.1 控制檔案讀寫內容的模式:t和b

強調: t和b不能單獨使用,必須跟r/w/a連用,預設就是t, t代表text,通常不寫

t文字(預設的模式)

  1. 讀寫都以str(unicode)為單位
  2. 文字檔案
  3. 必須指定encoding='utf-8'

b二進位制/bytes

  • 一般是一些音訊檔案或者視訊檔案

1.3.2 控制檔案讀寫操作的模式

  • r 只讀模式
  • w 只寫模式
  • a 只追加寫模式
  • +: r+, w+, a+ 在原來模式的基礎上只讀的變可讀可寫,可寫的變可寫可讀

2. 檔案操作的基本流程

2.1 開啟檔案

open("檔案路徑")
open("C:\a\b\c\d.txt")  # 但是\(右斜槓)在字串中轉義的作用
                        # 如果資料夾叫nb,那寫成路徑\nb在字串中就是換行符的意思了

2.1.1 如何取消轉義?

  • 字串前+r,eg:r"C:\a\b\nb\d.txt"
  • 用左斜槓代替右斜槓,open功能自動會把左斜槓替換成右斜槓

2.1.2 路徑分割符

  • win下預設的路徑分隔符是右斜槓\
  • linux下預設的路徑分隔符是左斜槓/

2.1.3 絕對路徑和相對路徑

絕對路徑就是檔案的全部路徑,從開始的碟符一直寫到該檔案。

open(r'E:\project\python\s29code\day11\1.開啟檔案.py')

相對路徑就是以當前檔案所在的資料夾為基礎,然後找檔案,所以也只能找到同文件夾下的檔案

2.2 操作檔案

# 1. 開啟檔案,由應用程式向作業系統發起系統呼叫open(...),
#    作業系統開啟該檔案,對應一塊硬碟空間,並返回一個檔案物件賦值給一個變數f
f=open('a.txt',moder='r',encoding='utf-8') #預設開啟模式就為r

# 2. 呼叫檔案物件的讀/寫方法,會被作業系統轉換為讀/寫硬碟的操作
data=f.read()  # 讀取檔案內容,將硬碟中的二進位制資料讀取到記憶體中 --> t控制將二進位制轉換成字元

2.3 關閉檔案

開啟一個檔案包含兩部分資源:應用程式的變數f和作業系統開啟的檔案。在操作完畢一個檔案時,必須把與該文

件的這兩部分資源全部回收,回收方法為:

1、f.close() #回收作業系統開啟的檔案資源,一旦關閉就不能操作了,但是f變數還在
2、del f #回收應用程式級的變數,一般我們不用寫。

其中del f一定要發生在f.close( ) 之後,否則就會導致作業系統開啟的檔案無法關閉,白白佔用資源, 而python

自動的垃圾回收機制決定了我們無需考慮del f,這就要求我們,在操作完畢檔案後,一定要記住f.close( )。

2.4 with上下文管理器

with上文幫你開啟檔案,下文就是幫你自動關閉檔案.

with open('a.txt',mode='rt') as f1:
    res = f1.read()   # 讀取檔案內容,硬碟的搬運工
    print(res)

# f1稱之為檔案物件/檔案控制代碼,就是用來控制檔案的
# 當with語句的程式碼塊執行完畢後會自動執行f.close()
# 開啟多個檔案
with open('a.txt',mode='rt') as f1,open('b.txt',mode='rt') as f2:
#with open('a.txt',mode='rt') as f1,\
#   open('b.txt',mode='rt') as f2:   通過右斜槓將一句程式碼換行寫
    res1 = f1.read()   # 讀取檔案內容
    res2 = f2.read()   
    print(res1)
    print(res2)

2.5 指定操作檔案的字元編碼

open功能還有一個引數就是encoding='解碼格式'

with open(r'a.txt', mode='rt',encoding='utf-8') as f1:
    res = f1.read()   # t模式會將f.read()讀出來的結果解碼成unicode
    print(res, type(res))
    
    
# 記憶體: utf-8格式的二進位制 ----> 解碼 ---->  unicode
# 硬碟(a.txt內容: utf-8格式的二進位制)

如果你不指定encoding引數的話,作業系統會使用自己的預設編碼進行解碼。

  • mac/linux utf-8
  • windows gbk

3. 檔案的操作模式

3.1 控制檔案操作的三種模式

r(預設的):只讀
w:只寫
a:只追加寫

3.1.1 r 模式的使用

r (預設的操作模式):只讀模式,當檔案不存在時,會報錯。

with open(r'd.txt', mode='rt',encoding='utf-8') as f1:
    res = f1.read()
    print(res, type(res))
    
# 執行結果:
Traceback (most recent call last):
  File "E:/project/python/s29code/day11/2.r模式.py", line 1, in <module>
    with open(r'd.txt', mode='rt',encoding='utf-8') as f1:
FileNotFoundError: [Errno 2] No such file or directory: 'd.txt'

當檔案存在時,檔案指標跳到開始位置,所以當我們f.read()時會把檔案指標從頭讀到尾,即一次性

把檔案內容全部從硬碟載入到記憶體中。注意:大檔案不能這樣讀,如果內容過大,會撐爆記憶體

# a.txt
你好啊

with open(r'a.txt', mode='rt',encoding='utf-8') as f1:
    res = f1.read() # 把所有內容從硬碟讀入記憶體
    print(res) # 你好啊  

3.1.2 w模式的使用

w模式,是隻寫模式,當檔案不存在的時候,用w模式開啟的話,會自動在該路徑建立一個新的檔案。

當檔案存在時,w模式會先清空原來的內容,指標位於開始位置,然後再寫內容。

with open(r'e.txt', mode='w',encoding='utf-8') as f1:
    f1.write("你好")

注意:w模式不是覆蓋,可以自己嘗試一下,假設原檔案的內容很多,你用w模式開啟,然後只寫一個字元

試試。

但是如果我們用w模式沒有關閉,連續寫入內容的話,不會清空之前的內容,並緊跟在舊的內容後面。

with open('b.txt',mode='w',encoding='utf-8') as f:
    f.write('大笨蛋\n')
    f.write("大傻子\n")
    f.write("二桿子")
    
# b.txt
大笨蛋
大傻子
二桿子

3.1.3 a模式的使用

a模式是隻追加寫模式,當檔案不存在時,和w模式一樣,會建立新檔案。

當檔案存在的時候,a模式開啟檔案,不會清空原檔案的內容,指標會自動移到檔案末尾。

with open('c.txt',mode="a",encoding="utf-8") as f:
    f.write('我是第一行\n')
    
# c.txt
我是第一行

當檔案沒有關閉的情況,用a模式持續寫入的話,是和w模式一樣的,新內容會緊跟舊內容的後面

with open('c.txt',mode="a",encoding="utf-8") as f:
    f.write('我是第一行\n')
    f.write('我是第二行\n')
    f.write('我是第三行\n')
    f.write('我是第四行\n')
    
# c.txt
我是第一行
我是第二行
我是第三行
我是第四行

a模式和w模式的異同點:

  • 相同點:只能寫,不能讀,當檔案沒有關閉時,連續寫入,新內容緊跟在就內容之後
  • 不同點:a模式開啟已存在的檔案,不會清空原檔案內容,指標自動移動到檔案末尾

w模式和a模式的應用場景:

  • w模式一般用於新檔案的儲存,比如copy檔案底層就是把一個檔案讀出來,然後用w寫入到新檔案

  • a模式則是適用於在原檔案的基礎上寫新內容,比如記錄日誌,使用者註冊記錄檔案

3.1.4 瞭解+模式

+模式,不能單獨使用,只能和r,w或a模式一起使用

  • r+ 讀寫模式,基準是r模式,當檔案不存在時,用r+開啟會報錯,指標在開頭。
  • w+ 寫讀模式,基準是w模式,檔案不存在會建立檔案,檔案存在同樣會清空原檔案
  • a+ 追加讀模式,基準是a模式,檔案不存在會建立檔案,指標在末尾

3.1.5 提一嘴x模式

x模式,只寫模式,當檔案不存在的時候會建立新檔案,指標在檔案開頭,但是當檔案存在的時候會直接報錯

# d.txt 已經存在的檔案
with open('d.txt',moder="x",encoding="utf-8") as f:
    f.write("你好")

Traceback (most recent call last):
  File "E:/project/python/s29code/day11/5.x模式.py", line 1, in <module>
    with open('d.txt',mode='x',encoding='utf-8') as f:
FileExistsError: [Errno 17] File exists: 'd.txt'    

3.2 控制檔案內容的兩種模式

大前提: tb模式均不能單獨使用,必須與r/w/a之一結合使用
t(預設的):文字模式

  1. 讀寫檔案都是以字串( unicode )為單位的
  2. 只能針對文字檔案
  3. 必須指定encoding引數

b:二進位制模式:

  1. 讀寫檔案都是以bytes/二進位制為單位的
  2. 可以針對所有檔案
  3. 一定不能指定encoding引數

3.2.1 t 模式

# t 模式:如果我們指定的檔案開啟模式為r/w/a,其實預設就是rt/wt/at
 with open('a.txt',mode='rt',encoding='utf-8') as f:
     res=f.read() 
     print(type(res)) # 輸出結果為:<class 'str'>

 with open('a.txt',mode='wt',encoding='utf-8') as f:
     s='abc'
     f.write(s) # 寫入的也必須是字串型別

#強調:t 模式只能用於操作文字檔案,無論讀寫,都應該以字串為單位,而存取硬碟本質都是二進位制的形式,當指定 t 模式時,內部幫我們做了編碼與解碼

# 如果我們用t模式開啟視訊檔案的話
with open('a.mp4',mode='rt',encoding='utf-8') as f:
    ...   # ...  == pass 這樣並不會報錯,因為我們只是呼叫了作業系統的介面,告訴他用rt開啟檔案
          # 以後用utf-8對讀入記憶體的檔案進行解碼,但是我們還沒有讀/寫檔案,只是開啟檔案,所以不報錯

3.2.2 b模式

同樣b模式也是不能單獨使用的,必須和r、w、a配合使用,t模式只針對字元檔案,而b模式是針對所有檔案,包

包括文字檔案,但是一定要注意不能指定encoding引數,否則報錯。

with open('a.jpg',mode='rb') as f:
    res = f.read()   # 硬碟的二進位制讀入記憶體->b模式下,不做任何轉換,直接讀入記憶體
    print(res,type(res)) # bytes型別 -> 當成二進位制
    # b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x0C
    # <class 'bytes'>
    
# 理論上我們應該讀出來的是物理層間的010101這種,但是如果以這種顯示的話,不利於閱讀,以及操作資料
# 所以python對讀出來的內容做了轉換,是16進位制的顯示,平常我們就把當成二進位制來看就行了.
with open('b.txt',mode='rb') as f:
    res = f.read()       # 把躺在硬碟的二進位制直接讀出來,不做任何轉換
    print(res,type(res)) 
    print(res.decode('utf-8')) # 檔案是以utf-8編碼格式存的,可以進行字元解碼

4. 迴圈讀取檔案的兩種方式

4.1 for迴圈一行一行的讀

for迴圈不但可以迴圈那些基礎資料型別,也可以迴圈檔案控制代碼, 每次迴圈,以行為單位

但是for迴圈有個侷限

# t模式
with open('b.txt',mode='r',encoding='utf-8') as f:
    for line in f:
        print(line)
        

# 列印內容:        
# 是可撥打發紅包測

# 我付款金額日本

# 費勁兒家人
# b模式
with open('b.txt',mode='rb') as f:
    for line in f:
        print(line)
        

# 列印內容:
b'\xe6\x98\xaf\xe5\x8f\xaf\xe6\x8b\xa8\xe6\x89\x93\xe5\x8f\x91\xe7\xba\xa2\xe5\x8c\x85\xe6\xb5\x8b\r\n'  # \r\n --> 換行符
b'\xe6\x88\x91\xe4\xbb\x98\xe6\xac\xbe\xe9\x87\x91\xe9\xa2\x9d\xe6\x97\xa5\xe6\x9c\xac\r\n'
b'\xe8\xb4\xb9\xe5\x8a\xb2\xe5\x84\xbf\xe5\xae\xb6\xe4\xba\xba\r\n'
b'\xe4\xbb\x98\xe9\x87\x91\xe9\xa2\x9d\xe6\x97\xa5\xe5\x93\xa6\xe5\x9b\x9e\xe9\xa1\xbe'

4.2 while迴圈

之前就說過for迴圈能實現的,while迴圈也能實現,迴圈讀取檔案也是可以的

# t模式和b模式都適用
with open('b.txt',mode='rb') as f:
    while 1:
        content = f.read(1024) # 可以指定讀取檔案內容的大小,以位元組為單位
        if not content:
            break
        print(content)

4.3 小練習

檔案拷貝小程式,要求:字元檔案和文字檔案都適用。5.

file_path = input('請輸入要拷貝的檔案的絕對路徑:')
new_file_path = input('請輸入新檔案的絕對路徑:')
with open(file_path, mode='rb') as f, \
        open(new_file_path, mode='wb') as f1:
    for line in f:
        f1.write(line)

5. f 的常用方法

5.1 必須掌握

# 讀操作
f.read()  # 讀取所有內容,執行完該操作後,檔案指標會移動到檔案末尾
f.readline()  # 讀取一行內容,游標移動到第二行首部
f.readlines()  # 讀取每一行內容,作為一個元素存放於列表中

# 強調:
# f.read()與f.readlines()都是將內容一次性讀入內容,如果內容過大會導致記憶體溢位,若還想將內容全讀入記憶體,則必須分多次讀入,有兩種實現方式:
# 方式一
with open('a.txt',mode='rt',encoding='utf-8') as f:
    for line in f:
        print(line) # 同一時刻只讀入一行內容到記憶體中

# 方式二
with open('1.mp4',mode='rb') as f:
    while True:
        data=f.read(1024) # 同一時刻只讀入1024個Bytes到記憶體中
        if len(data) == 0:
            break
        print(data)

# 寫操作
f.write('1111\n222\n')  # 針對文字模式的寫,需要自己寫換行符
f.write('1111\n222\n'.encode('utf-8'))  # 針對b模式的寫,需要自己寫換行符
f.writelines(['333\n','444\n'])  # 迭代的將列表的元素寫入檔案
f.writelines([bytes('333\n',encoding='utf-8'),'444\n'.encode('utf-8')]) #b模式

5.2 瞭解操作

f.readable()  # 檔案是否可讀
f.writable()  # 檔案是否可讀
f.closed    # 檔案是否關閉
f.encoding  # 如果檔案開啟模式為b,則沒有該屬性
f.flush()   # 立刻將檔案內容從記憶體刷到硬碟,一般情況下當所有寫操作完畢後,才會寫入硬碟,但是我們             # 可以用flush強制寫到硬碟
f.name      # 返回檔名稱

6. 檔案指標的移動

f.seek(n,模式) n指的是移動的位元組個數

f.tell( ) 返回檔案指標當前位置

指標移動的單位都是以位元組(bytes)為單位

只有一種情況特殊:
t模式下的read(n) n代表字元的個數

強調: t模式下只能用0模式,b模式下0,1,2都可以用

6.1三種模式

模式:
0:參照物是開頭位置
1:參照物是當前位置
2:參照物是檔案末尾

6.1.1 0模式

with open(r'a.txt', mode='rt', encoding='utf-8') as f:
    f.seek(6, 0)
    f.seek(3,0)
    # f.seek(3, 1) # 報錯
    res = f.read()
    print(res)

6.1.2 1模式

with open(r'a.txt', mode='rb') as f:
	f.seek(6, 1)
	f.seek(3,1)
    #f.seek(300, 1)  # 向右可以無限移動
    # print(f.read())
    res = f.tell()
    print(res)

6.1.3 2模式

with open(r'a.txt', mode='rb') as f:
    f.seek(-6, 2)
    f.seek(-3, 2)
    # f.seek(-30, 2) # 超過了最大的移動距離

    # f.seek(0, 0)
    # f.seek(-1, 0) # 報錯,開頭無法再向左移動了
    
    res = f.tell() # 返回檔案指標的時候都是從左向右數
    print(res)

6.1.4 小作業

# 寫一個動態更新顯示最新日誌的一個小程式
import time

with open(r'a.txt', mode='rb') as f:
    # 將指標跳到檔案末尾
    f.seek(0, 2)
    while 1:
        res = f.readline()
        if len(res) == 0:
            time.sleep(0.5)
        else:
            print(res.decode(), end='')
            
# 追加日誌的程式
with open(r'a.txt',mode='a+',encoding='utf-8') as f:
    f.write('20201120 張三消費1000元\n')

6.2 修改檔案的兩種方式

首先補充一個知識點,就是硬碟的資料是不能修改的,記憶體的資料是可以修改的。比如有一個文字檔案,裡面有

資料:張三 北京大學 大一學生,你想修改成 張三 男 20 北京大學 大一學生。平常我們用的文字編輯器是可以

直接修改的。但是你用python程式碼試一下看一下結果。

with open(r'a.txt',mode='r+t',encoding='utf-8') as f:
    f.seek(10,0) # 指標移動到張三後面
    f.write('男 20')

# 當你再次開啟檔案你會發現 男 20會把北京大學覆蓋了

原因是硬碟不支援修改,或者說成是追加。如果一塊資料發生移動,那麼在它後面的所有資料都會移動,那麼每次

修改檔案都會產生這種問題。所以硬碟只能寫不能中間插入。而我們平常用的文字編輯器只是把記憶體中的所有修改

後的資料再儲存到硬碟上。

6.2.1 第一種方式:

with open( 'c.txt' ,mode='rt',encoding='utf-8' ) as f:
	res = f.read ( )
	data = res.replace( 'alex' , 'dsb')
    print(data)
with open( 'c.txt' ,mode='wt',encoding='utf-8' ) as f1:
	f1.write(data)

6.2.2 第二種方式:

import os

with open(r'c.txt', mode='r', encoding='utf-8') as f, \
        open(r'.c.txt.swap', mode='w', encoding='utf-8') as f1:
    # .原檔名.swap 是linux中的臨時交換的檔案命名
    for line in f:
        data = line.replace('alex', 'dsb')
        f1.write(data)
os.remove('c.txt')  # 刪除原檔案
os.rename('.c.txt.swap', 'c.txt')  # 對新檔案重新命名