假设我有一个ASCII文件(称为'test.txt'),如下所示:
A B C D
X Y Z
^ EOF, no CR after the 'Z'...
在Python中,我可以读取这样的最后一个字节(最后一个字符):
with open('test.txt', 'r') as f:
f.seek(-1, os.SEEK_END)
ch=f.read(1)
我可以像这样截断最后3个字符:
with open('test.txt', 'r') as f:
f.seek(-3, os.SEEK_END)
f.truncate()
现在假设我有一个以UTF-8编码的第二个文件(名为'test.utf'),其中包含以下单字节和多字节字符:
A B C D
Ⓐ Ⓑ Ⓒ Ⓓ
Z Ⓩ
我知道如何阅读整个文件(使用编解码器):
>>> f=codecs.open('/tmp/test.utf', 'r', 'utf-8')
>>> L=f.readlines()
>>> L
[u'A B C D\n', u'\u24b6 \u24b7 \u24b8 \u24b9\n', u'Z \u24cf']
我想我可以使用collections模块中的deque来获取最后N个字符:
>>> from collections import deque
>>> with codecs.open(fn,'r+', encoding) as f:
... last_3=deque(f.read(),3)
>>> last_3
deque([u'Z', u' ', u'\u24cf'], maxlen=3)
所以问题:无论如何(我错过了)我可以逻辑地通过逻辑字符向后退一个UTF-8文件字符而不将整个文件读入内存?使用ASCII很容易;只需要在文件开头附近寻找一个字节。但在UTF-8中,Ⓩ
是3个字节(E2 93 8F
),Z
只是一个字节。
回想一下,UTF-8是可变宽度 - 每个字符在1到4个字节之间。除非你从头开始,否则我认为没有办法知道角色边界是什么......
答案 0 :(得分:8)
您可以这样做,但不能作为个别角色。将文件视为字节。
每个UTF-8字符由1到4个字节组成。要读取文件末尾,请读取最后4 * n个字节并开始查找字符边界。 UTF-8字符的第一个字节具有最高位模式0
或11
,其间的所有其他字节将具有模式10
。只需向后搜索,直到计算出与模式匹配的正确数字。
with open('test.txt', 'rb') as f:
f.seek(-4, os.SEEK_END)
ch=f.read(4)
for i in range(3, -1, -1):
pattern = ord(ch[i]) & 0xc0
if pattern in (0x00, 0x40, 0xc0):
ch = ch[i:]
break
答案 1 :(得分:5)
除非你从头开始,否则我认为没有办法知道角色界限是什么......
那不是真的。你可以在任何一块UTF-8中找到起点:
因此,序列中的第一个字节以“0”(单字节字符)或“11”(两个或更多字节中的第一个)开头。后续字节都以'10'开头。
所以你只需要从文件末尾读取几个字节来确定字符的开始和结束位置。