一覧CS-02-1 · 文字・バイト・データ量

文字コードとバイト数の基礎

文字が数値として保存される仕組みと、文字数・データ量の関係を学びます。

説明

コンピューターが扱うデータは、0と1の組合せで表されます。この0または1の一桁がビットで、8ビットをまとめたものが1バイトです。文字も、決められた規則に従ってバイト列に変換してから保存します。この規則を文字コードと呼びます。

このモジュールでは、「文字がバイト列になるとどう見えるのか」「同じ文字数でもデータ量が変わるのはなぜか」を学びます。まず1バイトの値を2進数と16進数で表示し、次に英字と日本語をASCII・UTF-8という文字コードで変換します。最後に、そのバイト数を使って容量の単位と転送時間の計算を説明します。

11バイトの16進表記

8桁の2進数をそのまま書くと長いため、バイトは16進数で表すことがよくあります。16進数は0〜9とA〜Fを使い、Aは10、Fは15を表します。1桁が4ビットに相当するので、1バイトは16進数2桁で表せます。

例として、16進数の「A5」をバイト列に変換し、その内容を表示します。

raw =変数rawに保存 bytes.fromhex('a5')16進表記からバイト列を作る; raw.hex(' ')16進数で表示

'a5'と表示されました。大文字のA5と小文字のa5は同じ値です。rawに入っているのは、文字のAと5を保存した2バイトではなく、16進数A5で表す1バイトです。

2バイト数と、その中に入っている値

同じ1バイトについて、長さ・値・2進表記を並べます。

len(raw)バイト数, raw[0]先頭バイトの値, format(raw[0], '08b')8桁の2進数で表示

結果の1はバイト数、165は10進数の値、10100101は同じ値の2進表記です。2進数の各桁は、右から1、2、4、8、16、32、64、128を表します。1になっている桁を足すと128+32+4+1=165です。

31バイトで表せる値の範囲

00とFFでもバイト数を調べます。値の大きさによって、必要な長さが変わるかを確認してください。

[(x.hex()16進表記, len(x)バイト数, x[0]先頭バイトの値) for x in (bytes.fromhex('00'), bytes.fromhex('ff'))00とFFを順に処理]

00の値は0、FFの値は255ですが、どちらも1バイトです。8ビットの組合せは2⁸=256通りあります。負の数を扱わない符号なし整数なら、1バイトで0〜255を表せます。

4ASCIIでの英字の表し方

ASCIIでは、英字や数字などに0〜127の番号が割り当てられています。例として文字「A」をASCIIへ変換します。

text = 'A'文字列を保存; raw = text.encode('ascii')ASCIIへ符号化; raw.hex(' ')16進表記, raw[0]10進数の値

文字「A」に対応する値は、16進数で41、10進数で65です。最初に使った16進数A5とは異なります。文字と番号の対応は、説明欄の末尾にある「対応表・資料」で全128値を確認できます。

5ASCIIとUTF-8に共通する文字

UTF-8は、日本語を含む多くの文字を表せる文字コードです。まず「ABC」をUTF-8へ変換します。Unicodeで文字に割り当てられた番号を、コードポイントと呼びます。

text = 'ABC'3文字の文字列; raw = text.encode('utf-8')UTF-8へ符号化; [ord(c) for c in text]各文字のコードポイント, raw.hex(' ')バイト列の16進表記

「A」「B」「C」のコードポイントは10進数で65・66・67、保存するバイトは16進数で41・42・43です。ASCIIに含まれる文字は、UTF-8でも同じ1バイトで表されます。

6UTF-8では「あ」が3バイトになる

次は「Aあ」です。各文字のコードポイントと、UTF-8で保存するバイト列を並べます。

text = 'A\u3042'「Aあ」を保存; raw = text.encode('utf-8')UTF-8へ符号化; [f'U+{ord(c):04X}' for c in text]各文字のコードポイントを16進表示, raw.hex(' ')バイト列の16進表記

「A」はU+0041、「あ」はU+3042です。一方、UTF-8で保存するバイト列は41 E3 81 82で、計4バイトになります。「あ」のコードポイント3042を、そのまま2バイトにして保存するわけではありません。

7文字数とバイト数が異なる理由

「Aあ」の文字数と、UTF-8に変換したバイト列の長さを調べます。

len(text)コードポイント数, len(raw)バイト数, len(raw) * 8ビット数

「Aあ」は2コードポイントですが、UTF-8では4バイト、つまり32ビットです。「A」は1バイト、「あ」は3バイトなので、文字数だけではデータ量を求められません。

なお、結合文字などを含む文字列では、見た目の文字数とコードポイント数も一致しないことがあります。

8UTF-8のビットの並び

UTF-8は、先頭の数ビットで、そのバイトが文字の始まりなのか、続きなのかを表します。各バイトを2進数にして、その並びを確認します。

[format(b, '08b')各バイトを8桁の2進数にする for b in rawrawの全バイトを順に処理]

「A」のバイトは0で始まります。これはASCII範囲の文字を1バイトで表す形式です。「あ」は1110で始まるバイトと、10で始まる二つのバイトになっています。

1110と10を除いた4+6+6ビットをつなげると0011 0000 0100 0010です。16進数にすると3042となり、「あ」のコードポイントが得られます。

9バイト列から文字列に戻す処理

文字列をバイト列に変換する処理を符号化、その逆を復号と呼びます。UTF-8で復号すると元の文字列に戻るかを確認します。

raw.decode('utf-8')UTF-8で文字列に戻す, raw.decode('utf-8') == text元の文字列との一致を判定

「Aあ」に戻り、比較結果はTrue、つまり等しいという結果になりました。保存されたバイト列を文字として扱うには、符号化したときと同じ文字コードで復号します。

10kBとKiBの換算

kBは1000バイト、KiBは1024バイトを1単位とする表記です。同じバイト列を1000個つなげ、二つの単位で容量を求めます。

payload = raw * 1000バイト列を1000個つなぐ; len(payload)B単位, len(payload) / 1000kB単位, len(payload) / 1024KiB単位

4000 Bを1000で割ると4 kB、1024で割ると3.90625 KiBです。どちらも同じ4000バイトを表しています。単位が違うと数値も変わるため、容量を確認するときは単位まで読む必要があります。

11GBとGiBで容量表示が変わる理由

GBは10⁹バイト、GiBは2³⁰バイトを1単位とします。1 GBに相当するバイト数を、それぞれの単位で表示します。

size = 10**910⁹バイト; print(f'{size / 10**9:.3f} GB / {size / 2**30:.3f} GiB')GBとGiBへ換算し小数点以下3桁で表示

1.000 GBが、GiBでは約0.931と表示されました。数値の違いは単位の換算によるものです。保存されているデータ量が減ったわけではありません。

12通信速度から転送時間を求める式

通信速度のMbit/sは、1秒当たりに転送するメガビット数です。ファイル容量のMBはメガバイトなので、そのまま割り算はできません。100 MBのデータを80 Mbit/sで送る例で、時間を計算します。

size = 100 * 10**6100 MBのバイト数; bits_per_second = 80 * 10**680 Mbit/s; size / (bits_per_second / 8)速度をB/sに直して転送秒数を計算

80 Mbit/sを8で割ると10 MB/sです。100 MB÷10 MB/sで、転送時間は10秒になります。この計算はデータ部分を一定の速度で送れる場合の値で、通信ヘッダーや再送による追加時間は含みません。

まとめ

文字「Aあ」は2コードポイントですが、UTF-8では41 E3 81 82の4バイトになります。文字に割り当てた番号と、保存するバイト列は、文字コードの規則に従って対応しています。

容量や転送時間の計算では、bitとB、kBとKiBなどの単位を確認します。同じ量でも、使う単位によって表示される数値は変わります。

操作履歴と解説を振り返れます。

Python 3

1
raw = bytes.fromhex('a5'); raw.hex(' ')
'a5'
2
len(raw), raw[0], format(raw[0], '08b')
(1, 165, '10100101')
A5の各ビット
重み1286432168421
ビット10100101
3
[(x.hex(), len(x), x[0]) for x in (bytes.fromhex('00'), bytes.fromhex('ff'))]
[('00', 1, 0), ('ff', 1, 255)]
4
text = 'A'; raw = text.encode('ascii'); raw.hex(' '), raw[0]
('41', 65)
5
text = 'ABC'; raw = text.encode('utf-8'); [ord(c) for c in text], raw.hex(' ')
([65, 66, 67], '41 42 43')
6
text = 'A\u3042'; raw = text.encode('utf-8'); [f'U+{ord(c):04X}' for c in text], raw.hex(' ')
(['U+0041', 'U+3042'], '41 e3 81 82')
文字とUTF-8の対応
文字コードポイントUTF-8バイト数
AU+0041411
あU+3042E3 81 823
7
len(text), len(raw), len(raw) * 8
(2, 4, 32)
8
[format(b, '08b') for b in raw]
['01000001', '11100011', '10000001', '10000010']
「あ」を表すUTF-8の3バイト
位置バイト形式を示すビットコードポイントのビット
1E311100011
28110000001
38210000010
9
raw.decode('utf-8'), raw.decode('utf-8') == text
('Aあ', True)
10
payload = raw * 1000; len(payload), len(payload) / 1000, len(payload) / 1024
(4000, 4.0, 3.90625)
11
size = 10**9; print(f'{size / 10**9:.3f} GB / {size / 2**30:.3f} GiB')
1.000 GB / 0.931 GiB
12
size = 100 * 10**6; bits_per_second = 80 * 10**6; size / (bits_per_second / 8)
10.0

演習完了