はじめに
「なぜ日本語は2バイト?」「半角カナってSJISとUTF-8で違うの?」「ファイルを開いたら文字化けした」──こうした疑問の根本は 文字コードとバイト数の関係 にあります。
本記事では、主要な文字コード規格ごとのバイト数を体系的に整理し、ファイルエンコーディングにまつわる実践的な知識もまとめます。
1. 文字コードの基本
「文字集合」と「符号化方式」の違い
| 用語 | 意味 | 例 |
|---|---|---|
| 文字集合(Character Set) | どの文字を収録するかの規格 | Unicode、JIS X 0208 |
| 符号化方式(Encoding) | 文字をどのバイト列に変換するかの規格 | UTF-8、Shift-JIS |
Unicode という文字集合を、UTF-8・UTF-16・UTF-32 という異なる符号化方式でバイト列に変換できます。
2. 主要エンコーディングとバイト数
2-1. ASCII(US-ASCII)
- バイト長:固定 1 バイト
- 収録文字:英数字・記号・制御文字(128文字)
- コードポイント範囲:U+0000〜U+007F(0x00〜0x7F)
'A' → 0x41 (1バイト)
'0' → 0x30 (1バイト)
' ' → 0x20 (1バイト)
2-2. UTF-8
Unicode文字を可変長(1〜4バイト)で表現します。ASCIIと後方互換性があり、現在のデファクトスタンダードです。
| コードポイント範囲 | バイト数 | 代表的な文字 |
|---|---|---|
| U+0000〜U+007F | 1バイト | 半角英数字・記号(ASCII互換) |
| U+0080〜U+07FF | 2バイト | ラテン文字拡張、アラビア文字など |
| U+0800〜U+FFFF | 3バイト | ひらがな・カタカナ・漢字・半角カナ |
| U+10000〜U+10FFFF | 4バイト | 絵文字・一部の漢字(CJK拡張)など |
# Python で確認
text = "A あ 亜 😀 ア"
for c in text.split():
b = c.encode("utf-8")
print(f"{c!r} → {b.hex()} ({len(b)}バイト)")
実行結果:
'A' → 41 (1バイト)
'あ' → e3818 (3バイト) # U+3042
'亜' → e4ba9c (3バイト) # U+4E9C
'😀' → f09f9880 (4バイト) # U+1F600
'ア' → efbdb1 (3バイト) # U+FF71(半角カタカナ)
ポイント:UTF-8 では半角カナも 3バイト です。「半角だから軽い」とはなりません。
2-3. Shift-JIS(SJIS / CP932)
Windowsや古い日本語システムで広く使われてきたエンコーディングです。
| 文字の種類 | バイト数 | 例 |
|---|---|---|
| 半角英数字・記号(ASCII範囲) | 1バイト |
A、0、!
|
| 半角カタカナ | 1バイト |
ア、イ、ウ(0xA1〜0xDF) |
| ひらがな・カタカナ(全角) | 2バイト |
あ、ア
|
| 漢字(JIS第1〜2水準) | 2バイト |
亜、漢
|
text = "A あ 亜 ア"
for c in text.split():
try:
b = c.encode("shift_jis")
print(f"{c!r} → {b.hex()} ({len(b)}バイト)")
except UnicodeEncodeError:
print(f"{c!r} → エンコード不可")
実行結果:
'A' → 41 (1バイト)
'あ' → 82a0 (2バイト)
'亜' → 88ad (2バイト)
'ア' → b1 (1バイト) ← 半角カナは1バイト!
ポイント:Shift-JIS では半角カナが 1バイト。UTF-8(3バイト)と大きく異なります。
2-4. EUC-JP
主にUnix/Linux系で使われた日本語エンコーディング。
| 文字の種類 | バイト数 |
|---|---|
| 半角英数字(ASCII範囲) | 1バイト |
| ひらがな・カタカナ・漢字 | 2バイト |
| 半角カタカナ | 2バイト(0x8E + 1バイト) |
| JIS第3・4水準漢字 | 3バイト |
2-5. UTF-16
| 文字の種類 | バイト数 |
|---|---|
| BMP内の文字(U+0000〜U+FFFF) | 2バイト |
| サロゲートペア(U+10000〜) | 4バイト |
JavaやC#の内部文字列表現はUTF-16です。String.length() が返す値は「文字数」ではなく「UTF-16コードユニット数」のため、絵文字を含む文字列では注意が必要です。
String s = "😀";
System.out.println(s.length()); // 2(サロゲートペア)
System.out.println(s.codePointCount(0, s.length())); // 1(文字数)
3. 文字種別・エンコード別バイト数まとめ
| 文字 | 文字種 | UTF-8 | Shift-JIS | EUC-JP | UTF-16 |
|---|---|---|---|---|---|
A |
半角英字 | 1 | 1 | 1 | 2 |
1 |
半角数字 | 1 | 1 | 1 | 2 |
!(全角) |
全角記号 | 3 | 2 | 2 | 2 |
あ |
ひらがな | 3 | 2 | 2 | 2 |
ア |
全角カタカナ | 3 | 2 | 2 | 2 |
ア |
半角カタカナ | 3 | 1 | 2 | 2 |
亜 |
漢字(基本) | 3 | 2 | 2 | 2 |
𠮷(吉の異体字) |
漢字(拡張) | 4 | エラー | エラー | 4 |
😀 |
絵文字 | 4 | エラー | エラー | 4 |
UTF-8 で表現できない文字は Shift-JIS / EUC-JP では「エンコード不可」となります。
4. 半角カナの落とし穴
なぜ半角カナは問題になるか
| 観点 | 問題 |
|---|---|
| UTF-8では3バイト | 「半角だから容量節約」にならない |
| 文字化けリスク | SJISとUTF-8が混在するシステムで化ける |
| 検索・照合の不一致 | DBの照合順序によって全角と区別される |
| NFKC正規化で全角変換 | Unicodeの正規化で意図せず全角になることがある |
実務での推奨
- 新規システム:半角カナを使わない(全角カタカナで統一)
-
既存データの変換:
unicodedata.normalize('NFKC', text)で全角変換
import unicodedata
text = "アイウエオ"
normalized = unicodedata.normalize("NFKC", text)
print(normalized) # アイウエオ(全角カタカナ)
5. ファイルエンコーディングの実践知識
5-1. BOM(Byte Order Mark)
UTF-8、UTF-16 などのファイル先頭に付与されるマーカーバイト。
| エンコーディング | BOMバイト列 |
|---|---|
| UTF-8 with BOM | EF BB BF |
| UTF-16 LE | FF FE |
| UTF-16 BE | FE FF |
UTF-8のBOMは不要(UTF-8にバイト順はない)ですが、Excelなどが自動付与することがあります。BOM付きUTF-8をそのまま読み込むとプログラムで先頭に \ufeff が混入するトラブルがあります。
# BOM付きファイルを安全に読む
with open("file.csv", encoding="utf-8-sig") as f: # utf-8-sig でBOMを自動除去
content = f.read()
5-2. 改行コード
| 改行コード | バイト | 使用OS |
|---|---|---|
LF(\n) |
0x0A |
Unix / Linux / macOS |
CRLF(\r\n) |
0x0D 0x0A |
Windows |
CR(\r) |
0x0D |
旧macOS(〜OS9) |
テキストをバイナリモードで読む場合は改行コードが変換されないため注意が必要です。Gitリポジトリでは .gitattributes で text=auto を設定することで自動変換できます。
* text=auto
*.sh text eol=lf
*.bat text eol=crlf
5-3. 文字化けの原因と対処
| 原因 | 症状 | 対処 |
|---|---|---|
| 書き込みと読み込みのエンコードが違う | ひらがなが ??? や 繝「繝? になる |
両端のエンコードを揃える |
| BOM付きUTF-8を通常UTF-8として読む | 先頭に  が混入 |
utf-8-sig で読む |
| Shift-JISファイルをUTF-8として読む |
縺ゅk のような文字列 |
正しいエンコードを指定 |
| サロゲートペア非対応のシステム | 絵文字や一部漢字が ? になる |
UTF-8対応に移行 |
文字化けしたファイルのエンコード推測:
import chardet
with open("unknown.txt", "rb") as f:
raw = f.read()
result = chardet.detect(raw)
print(result) # {'encoding': 'SHIFT_JIS', 'confidence': 0.99, 'language': 'Japanese'}
5-4. データベースの文字コード設定
MySQLの utf8 は実は3バイト制限(4バイト文字=絵文字・一部漢字を格納できない)です。絵文字を扱うなら utf8mb4 を使います。
-- テーブル作成時
CREATE TABLE messages (
id INT PRIMARY KEY,
body TEXT
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 既存テーブルの変更
ALTER TABLE messages CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
5-5. プログラミング言語での文字列長
「文字数」の定義はエンコーディングや言語によって異なります。
# Python 3(str はUnicodeコードポイント単位)
s = "😀"
print(len(s)) # 1(コードポイント数)
print(len(s.encode("utf-8"))) # 4(バイト数)
// Java(String はUTF-16コードユニット単位)
String s = "😀";
System.out.println(s.length()); // 2(UTF-16コードユニット)
System.out.println(s.codePointCount(0, s.length())); // 1(コードポイント数)
System.out.println(s.getBytes("UTF-8").length); // 4(バイト数)
// JavaScript(String はUTF-16コードユニット単位)
const s = "😀";
console.log(s.length); // 2
console.log([...s].length); // 1(スプレッド構文でコードポイント単位)
console.log(new TextEncoder().encode(s).length); // 4(バイト数)
6. 演習問題
⭐ 問題1:バイト数を計算せよ
次の文字列を UTF-8 でエンコードした場合のバイト数を答えてください。
"Hello世界"
模範解答
-
H,e,l,l,o→ 各1バイト × 5 = 5バイト -
世→ U+4E16(BMP内) → 3バイト -
界→ U+754C(BMP内) → 3バイト - 合計:5 + 3 + 3 = 11バイト
print(len("Hello世界".encode("utf-8"))) # 11
⭐⭐ 問題2:半角カナの差を確認せよ
次のコードの出力を答えてください。
s = "アイウ"
print(len(s.encode("utf-8")))
print(len(s.encode("shift_jis")))
模範解答
9 # UTF-8:半角カナは3バイト × 3文字
3 # Shift-JIS:半角カナは1バイト × 3文字
半角カタカナ(ア〜ン)は UTF-8 では U+FF65〜U+FF9F 範囲のBMP文字として3バイトで表現されます。Shift-JIS では 0xA1〜0xDF の1バイト領域に割り当てられています。
⭐⭐⭐ 問題3:エンコード推測と変換
Shift-JIS で書かれた次のバイト列を UTF-8 の文字列に変換してください。
raw = bytes([0x82, 0xA0, 0x82, 0xA2, 0x82, 0xA4]) # SJISの「あいう」
模範解答
raw = bytes([0x82, 0xA0, 0x82, 0xA2, 0x82, 0xA4])
text = raw.decode("shift_jis") # SJISとして復号 → 'あいう'
utf8_bytes = text.encode("utf-8") # UTF-8に再エンコード
print(text) # あいう
print(utf8_bytes) # b'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86'
print(len(utf8_bytes)) # 9(3バイト × 3文字)
まとめ
| エンコーディング | 半角英数 | ひらがな・漢字 | 半角カナ | 絵文字 |
|---|---|---|---|---|
| UTF-8 | 1B | 3B | 3B | 4B |
| Shift-JIS | 1B | 2B | 1B | 非対応 |
| EUC-JP | 1B | 2B | 2B | 非対応 |
| UTF-16 | 2B | 2B | 2B | 4B |
- 新規開発は UTF-8(BOMなし)一択
- 半角カナは使わない(文字化けリスク+UTF-8では軽くない)
-
MySQLは
utf8でなくutf8mb4を使う -
絵文字や拡張漢字は4バイト。
length()系APIは「バイト数」「UTF-16コードユニット数」「コードポイント数」のどれを返すか意識する
参考
- Unicode 公式サイト
- RFC 3629 - UTF-8
- JIS X 0208 - 7ビット及び8ビットの2バイト情報交換用符号化漢字集合
- Python 公式ドキュメント - codecs
- MySQL 公式ドキュメント - The utf8mb4 Character Set
- Unicode正規化 (MDN Web Docs)
@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!