2013-09-17 7 views
2

Предположим, у меня был ASCII-файл (так называемый «test.txt»), как это:посимвольно в UTF8 файле

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() 

Теперь предположим, что у меня есть второй файл (так называемый «тэ st.utf ') в кодировке UTF-8 со следующими одно- и многобайтовые символы:

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'] 

И я полагаю, Я мог бы использовать 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 логическим чара cter БЕЗ чтения всего файла в память? С ASCII это легко; просто найдите один байт ближе к началу файла. Но в UTF-8 составляет 3 байта (E2 93 8F) и Z - всего один байт.

Напомним, что UTF-8 имеет переменную ширину - от 1 до 4 байтов на символ. Если вы не начинаете с самого начала, я думаю, что нет способа узнать, какие границы символов ...

ответ

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

В самом деле, где-то в Интернете вы можете найти электронную почту Кена Томпсона, где он объяснил, что это половина мотивация UTF-8 в дизайне: вы можете синхронизировать с любой точки в потоке, вперед или назад, не читая не более чем один символ. (Другая половина состоит в том, что байт, который представляет собой печатный символ ASCII, не может быть частью представления любого другого персонажа.) – abarnert

+0

... или, может быть, вы не можете его найти. По крайней мере, я не могу. Лучшее, что я мог найти, это [документ Plan 9 на UTF-8] (http://plan9.bell-labs.com/sys/doc/utf.pdf), который он соавтор. – abarnert

+1

@abarnert: это то, что вы ищете? http://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt, примерно наполовину вниз, цитирует Кен Томпсон: «6) Должно быть возможно найти начало персонажа эффективно начиная с произвольного местоположения в потоке байтов ». – Neil

5

Если вы не начинаете с самого начала, я думаю, что нет способа узнать, какие границы символов являются ...

Это неправда. Вы можете найти начало в любом фрагменте UTF-8:

  • Если установлен первый бит в заданном байте, он является частью многобайтовой последовательности.
  • Если также установлен второй бит, это начало такой последовательности.

Итак, первые байты в последовательности начинаются с «0» (однобайтовый символ) или «11» (первый из двух или более байтов). Последующие байты начинаются с «10».

Посмотрите на это chart on Wikipedia.

Таким образом, вам нужно прочитать только несколько байтов с конца файла, чтобы выяснить, где начинаются и заканчиваются символы.

Смежные вопросы