很高兴在我写的文章下看到有人回复,然后我在测试回复中的代码时居然发现个有趣的现象,并且得出下面的结论,请大家讨论
先说结论
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的倍数,那么在读取时你写入几个字节就会在第几个字节处报错。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。