Python之檔案讀寫補充——R+模式下修改中文內容

格瑞姆瑞坡發表於2019-02-16

很高興在我寫的文章下看到有人回覆,然後我在測試回覆中的程式碼時居然發現個有趣的現象,並且得出下面的結論,請大家討論

先說結論

r+模式(讀寫)下,如果檔案內容已經存在了中文,當你試圖插入新內容時,必須使新內容的總體位元組數是當前編碼下單個漢字佔位位元組的整數倍。否則讀取時會報錯。
補充:如果你把指標調到末尾則沒這個問題,也就是說可以在後面寫,但是在前面插入內容的話就會有上面的問題。

底層原理說明:

為了搞清楚最底層的工作原理,我專門看了幾篇相關文章,得出如下結論,我覺得可以了,不用再深究了,再往深了說就有點兒捨本逐末了。
首先,計算機儲存的都是二進位制數字,也就是說是每8位一組的0和1的各種組合,當寫入磁碟後,在物理層面這種0和1的組合就固定下來,再往底層說就是磁碟表面會出現凹凸不平的代表0和1的儲存元。(儲存元是儲存器中最小儲存單元,它的作用是用來存放一位二進位制程式碼0或1。)
所以在檔案寫入後這種0和1的組合就固定下來,如果這種組合是用UTF-8編碼寫入的,那麼當你寫入一段漢字後,這種組合就表現為每24個儲存元代表一個漢字。
此時如果你想修改這段漢字的任何一個字,你必須修改相應的24個儲存元,如果你只修改8個儲存元(一個位元組)或者16個儲存元(兩個位元組),那麼剩下的儲存元無法被當前編碼識別,也許碰巧會有某種編碼能夠識別這剩下的16個或者8個儲存元代表的組合,但這已經沒有意義。我還不知道有什麼辦法能讓計算機在指定位置使用一種編碼,然後在另一個位置使用另外一種編碼。也許有……暫時還沒學到?

PS:總之,你可以在R+模式下實現對檔案本身的修改,但是往往不會得到你期望的結果,沒有實際應用意義,即使是修改英文內容,除非你事先知道在確切位置修改確切的內容(比如你想修改19個英文字母,那麼你必須相應地寫入19個新英文字母,如果超過19個的話,它會覆蓋後面的內容)。

上面的話比較繞口,下面我來根據實際例子說明一下。
首先 我們用下面的程式碼建立一個檔案,編碼採用utf-8

with open(`job`, mode=`w+`,encoding=`utf-8`) as f:
    f.write(`十步殺一人,千里不留行`)
    f.seek(0)
    print(f.read())
輸出結果為:
    十步殺一人,千里不留行

然後我們在r+模式下寫入一個位元組的內容:

with open(`job`, mode=`r+`,encoding=`utf-8`) as f:
    f.write(`1`)
    f.seek(0)
    print(f.read())
    結果報錯:大意是utf-8編碼無法解碼。
    UnicodeDecodeError: `utf-8` codec can`t decode byte 0x8d in position 1: invalid start byte

最開始我是有些懵逼的,寫入和讀取我都是用的utf-8編碼,怎麼會存在無法解碼呢?後來我分別測試了2個位元組、3個位元組、4個位元組、5個位元組、6個位元組的內容後,我恍然大悟,原來必須寫入3的倍數的位元組數內容才可以。

測試過程如下:

寫入兩個位元組的內容:

with open(`job`, mode=`r+`,encoding=`utf-8`) as f:
    f.write(`aa`)
    f.seek(0)
    print(f.read())
    結果報錯:
    UnicodeDecodeError: `utf-8` codec can`t decode byte 0x81 in position 2: invalid start byte

寫入三個位元組的內容:

with open(`job`, mode=`r+`,encoding=`utf-8`) as f:
    f.write(`aaa`)
    f.seek(0)
    print(f.read())
    結果不報錯:
    aaa步殺一人,千里不留行

寫入四個位元組的內容:

with open(`job`, mode=`r+`,encoding=`utf-8`) as f:
    f.write(`aaaa`)
    f.seek(0)
    print(f.read())
    結果報錯:
    UnicodeDecodeError: `utf-8` codec can`t decode byte 0xad in position 4: invalid start byte

寫入五個位元組的內容:

with open(`job`, mode=`r+`,encoding=`utf-8`) as f:
    f.write(`aaaaa`)
    f.seek(0)
    print(f.read())
    結果報錯:
    UnicodeDecodeError: `utf-8` codec can`t decode byte 0xa5 in position 5: invalid start byte

寫入六個位元組的內容:

with open(`job`, mode=`r+`,encoding=`utf-8`) as f:
    f.write(`aaaaaa`)
    f.seek(0)
    print(f.read())
    結果不報錯:
    aaaaaa殺一人,千里不留行

從報錯的結果上來看,如果不是3的倍數,那麼在讀取時你寫入幾個位元組就會在第幾個位元組處報錯。

相關文章