UTF8文件中的字符

the*_*olf 2 python utf-8

假设我有一个ASCII文件(称为'test.txt'),如下所示:

A B C D
X Y Z
     ^   EOF, no CR after the 'Z'...
Run Code Online (Sandbox Code Playgroud)

在Python中,我可以读取最后一个字节(最后一个字符),如下所示:

with open('test.txt', 'r') as f:
    f.seek(-1, os.SEEK_END)
    ch=f.read(1)
Run Code Online (Sandbox Code Playgroud)

我可以像这样截断最后3个字符:

with open('test.txt', 'r') as f:
    f.seek(-3, os.SEEK_END)
    f.truncate()
Run Code Online (Sandbox Code Playgroud)

现在假设我有一个以UTF-8编码的第二个文件(称为'test.utf'),其中包含以下单字节和多字节字符:

A B C D
? ? ? ?
Z ?
Run Code Online (Sandbox Code Playgroud)

我知道如何读取整个文件(使用编解码器):

>>> 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']
Run Code Online (Sandbox Code Playgroud)

我想我可以使用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)
Run Code Online (Sandbox Code Playgroud)

所以问题:无论如何(我错过了)在哪里我可以逻辑地通过逻辑字符向后退步UTF-8文件字符而不将整个文件读入内存?使用ASCII很容易; 只需要在文件开头附近寻找一个字节.但是在UTF-8中,?是3个字节(E2 93 8F)并且Z只是一个字节.

回想一下,UTF-8是可变宽度 - 每个字符1到4个字节.除非你从一开始就开始,我认为没有办法知道角色界限是什么......

Mar*_*som 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
Run Code Online (Sandbox Code Playgroud)


Thi*_*ilo 5

除非你从一开始就开始,我认为没有办法知道角色界限是什么......

这不是真的.您可以在任何UTF-8块中找到开头:

  • 如果设置了给定字节中的第一位,则它是多字节序列的一部分.
  • 如果第二位也被设置,则它是这种序列的开始.

因此,序列中的第一个字节以"0"(单字节字符)或"11"(两个或更多字节中的第一个)开头.后续字节都以'10'开头.

在维基百科上查看此图表.

因此,您只需要从文件末尾读取几个字节,以确定字符的开始和结束位置.