12

Pythonに文字列があるとしましょう:

>>> s = 'python'
>>> len(s)
6

今私はencodeこのような文字列:

>>> b = s.encode('utf-8')
>>> b16 = s.encode('utf-16')
>>> b32 = s.encode('utf-32')

上記の操作から得られるのはバイト配列です。つまりb、、は単なるバイト配列です(各バイトはもちろん8ビット長です)。b16b32

しかし、文字列をエンコードしました。それで、これはどういう意味ですか?「エンコーディング」の概念を生のバイト配列にどのように結び付けるのでしょうか。

答えは、これらのバイト配列のそれぞれが特定の方法で生成されるという事実にあります。これらの配列を見てみましょう。

>>> [hex(x) for x in b]
['0x70', '0x79', '0x74', '0x68', '0x6f', '0x6e']

>>> len(b)
6

この配列は、各文字に対して1バイトがあることを示しています(すべての文字が127を下回るため)。したがって、文字列を「utf-8」に「エンコード」すると、各文字の対応するコードポイントが収集され、配列に配置されます。コードポイントが1バイトに収まらない場合、utf-8は2バイトを消費します。したがって、utf-8は可能な限り最小のバイト数を消費します。

>>> [hex(x) for x in b16]
['0xff', '0xfe', '0x70', '0x0', '0x79', '0x0', '0x74', '0x0', '0x68', '0x0', '0x6f', '0x0', '0x6e',  '0x0']

>>> len(b16)
14     # (2 + 6*2)

ここで、「utf-16へのエンコード」は最初に2バイトのBOM(FF FE)をバイト配列に入れ、その後、各文字について2バイトを配列に入れることがわかります。(この場合、2番目のバイトは常にゼロです)

>>> [hex(x) for x in b32]
['0xff', '0xfe', '0x0', '0x0', '0x70', '0x0', '0x0', '0x0', '0x79', '0x0', '0x0', '0x0', '0x74', '0x0', '0x0', '0x0', '0x68', '0x0', '0x0', '0x0', '0x6f', '0x0', '0x0', '0x0', '0x6e', '0x0', '0x0', '0x0']

>>> len(b32)
28     # (2+ 6*4 + 2)

「utf-32でのエンコード」の場合、最初にBOMを配置し、次に各文字に対して4バイトを配置し、最後に2つのゼロバイトを配列に配置します。

したがって、「エンコーディングプロセス」は、文字列内の文字ごとに1〜2バイトまたは4バイト(エンコーディング名に応じて)を収集し、それらにさらにバイトを追加および追加して、バイトの最終結果配列を作成すると言えます。

さて、私の質問:

  • エンコードプロセスについての私の理解は正しいですか、それとも何かが足りませんか?
  • b変数のメモリ表現でb16あり、b32実際にはバイトのリストであることがわかります。文字列のメモリ表現は何ですか?文字列のメモリには正確に何が格納されていますか?
  • encode()を実行すると、各文字の対応するコードポイント(エンコーディング名に対応するコードポイント)が収集され、配列またはバイトに入れられることがわかっています。私たちがするとき、正確には何が起こりdecode()ますか?
  • utf-16とutf-32では、BOMが付加されていることがわかりますが、utf-32エンコーディングで2つのゼロバイトが付加されているのはなぜですか?
4

3 に答える 3

19

まず、UTF-32は 4 バイトのエンコーディングであるため、その BOM も 4 バイト シーケンスです。

>>> import codecs
>>> codecs.BOM_UTF32
b'\xff\xfe\x00\x00'

また、コンピュータ アーキテクチャが異なればバイト オーダーの扱いも異なるため (エンディアンと呼ばれます)、BOM にはリトル エンディアンとビッグ エンディアンの 2 つのバリエーションがあります。

>>> codecs.BOM_UTF32_LE
b'\xff\xfe\x00\x00'
>>> codecs.BOM_UTF32_BE
b'\x00\x00\xfe\xff'

BOM の目的は、その順序をデコーダーに伝えることです。BOM を読むと、それがビッグ エンディアンかリトル エンディアンかがわかります。したがって、UTF-32 文字列の最後の 2 つの null バイトは、最後にエンコードされた文字の一部です。

したがって、UTF-16 BOM は類似しており、次の 2 つのバリアントがあります。

>>> codecs.BOM_UTF16
b'\xff\xfe'
>>> codecs.BOM_UTF16_LE
b'\xff\xfe'
>>> codecs.BOM_UTF16_BE
b'\xfe\xff'

デフォルトでどちらが使用されるかは、コンピューターのアーキテクチャによって異なります。

UTF-8は BOM をまったく必要としません。UTF-8 は 1 文字あたり 1 バイト以上を使用しますが (より複雑な値をエンコードするために必要に応じてバイトを追加します)、これらのバイトの順序は標準で定義されています。Microsoft はとにかく UTF-8 BOM を導入する必要があると判断しましたが (メモ帳アプリケーションが UTF-8 を検出できるようにするため)、BOM の順序は決して変わらないため、その使用はお勧めできません。

Unicode 文字列用に Python によって保存されるものについては、これは実際には Python 3.3 で変更されました。3.3 より前は、内部的に C レベルで、Python がワイド文字サポートを使用してコンパイルされているかどうかに応じて、Python は UTF16 または UTF32 のバイトの組み合わせを保存していました ( Python が UCS-2 または UCS-4 でコンパイルされているかどうかを確認する方法を参照してください)。 UCS-2 は本質的にUTF-16 であり、UCS-4 は UTF-32 です)。したがって、各文字には 2 バイトまたは 4 バイトのメモリが必要です。

Python 3.3 以降、内部表現は、文字列内のすべての文字を表すために必要な最小バイト数を使用します。プレーン ASCII および Latin1 でエンコード可能なテキストには 1 バイトが使用され、残りのBMPには2 バイトが使用され、その 4 バイトを超える文字を含むテキストが使用されます。Python は、必要に応じてフォーマットを切り替えます。したがって、ほとんどの場合、ストレージははるかに効率的になりました。詳細については、What's New in Python 3.3を参照してください。

Unicode と Python について読むことを強くお勧めします。

于 2012-11-20T09:17:07.933 に答える
5
  1. あなたの理解は、実際には「1、2、または 4 バイト」ではありませんが、本質的には正しいです。UTF-32 の場合は 4 バイトになります。UTF-16 および UTF-8 の場合、バイト数はエンコードされる文字によって異なります。UTF-16 の場合、2 バイトまたは 4 バイトになります。UTF-8 の場合、1、2、3、または 4 バイトになります。しかし、はい、基本的にエンコーディングは Unicode コード ポイントを取得し、それを一連のバイトにマップします。このマッピングがどのように行われるかは、エンコーディングによって異なります。UTF-32 の場合、これはコード ポイント番号の単純な 16 進数表現です。UTF-16 の場合、通常はそうですが、特殊な文字 (基本多言語面の外側) の場合は少し異なります。UTF-8 の場合、エンコーディングはより複雑です (ウィキペディアを参照).) 先頭の余分なバイトに関しては、これらはコード ポイントの断片が UTF-16 または UTF-32 で来る順序を決定するバイト オーダー マーカーです。
  2. 内部構造を見ることができると思いますが、文字列型 (または Python 2 の Unicode 型) のポイントは、その情報からユーザーを保護することです。ちょうど Python のリストのポイントが、生のデータを操作する必要から保護することです。そのリストのメモリ構造。文字列データ型が存在するため、メモリ表現を気にせずに Unicode コード ポイントを操作できます。生のバイトを使用する場合は、文字列をエンコードします。
  3. デコードを行うときは、基本的に文字列をスキャンして、バイトのチャンクを探します。エンコーディング スキームは基本的に、ある文字がいつ終了し、別の文字が開始するかをデコーダが認識できるようにする「手がかり」を提供します。したがって、デコーダーはスキャンし、これらの手がかりを使用して文字間の境界を見つけ、次に各部分を調べて、そのエンコーディングでどの文字を表しているかを確認します。各エンコーディングがコードポイントをバイトで前後にマッピングする方法の詳細を確認したい場合は、ウィキペディアなどで個々のエンコーディングを調べることができます。
  4. 2 つのゼロ バイトは、UTF-32 のバイト順マーカーの一部です。UTF-32 はコード ポイントごとに常に 4 バイトを使用するため、BOM も 4 バイトです。基本的に、UTF-16 で表示される FFFE マーカーは、2 つの余分なゼロ バイトでゼロが埋め込まれています。これらのバイト オーダー マーカーは、コード ポイントを構成する数値が最大から最小の順か、最小から最大の順かを示します。基本的には、「千二百三十四」という数字を 1234 と書くか 4321 と書くかの選択のようなものです。異なるコンピューター アーキテクチャでは、この問題に関して異なる選択が行われます。
于 2012-11-20T09:14:48.337 に答える
2

Python 3 を使用していると仮定します (Python 2 では、「文字列」は実際にはバイト配列であるため、Unicode の問題が発生します)。

(Unicode) 文字列は、概念的には一連の Unicode コード ポイントであり、「文字」に対応する抽象的なエンティティです。実際の C++ 実装はPython リポジトリで確認できます。コンピュータにはコード ポイントの固有の概念がないため、「エンコーディング」は、コード ポイントとバイト シーケンスの間の部分全単射を指定します。

エンコーディングは、可変幅エンコーディングにあいまいさがないように設定されています。バイトが表示された場合、現在のコード ポイントを完了するかどうか、または別のコード ポイントを読み取る必要があるかどうかが常にわかります。技術的には、これはプレフィックスフリーと呼ばれます。したがって、 を実行する.decode()と、Python はバイト配列を調べて、エンコードされた文字を一度に 1 つずつ作成して出力します。

2 つのゼロ バイトは、utf32 BOM の一部です。ビッグ エンディアンの UTF32 には0x0 0x0 0xff 0xfe.

于 2012-11-20T09:11:47.650 に答える