10文字=10バイトではない ― PowerShellでUTF-8とShift_JISのバイト数を比べる

プログラミング・Web開発

この記事について
この記事は.NETのEncoding APIとUTF-8仕様を確認し、PowerShellで文字数とバイト数の違いを観察する形で整理しています。

検証ステータス:📘 公式仕様確認済み・PowerShell実機未確認
Shift_JIS系code pageの利用可否はPowerShell/.NET環境で差があるため、取得できない場合の処理も含めています。

「10文字だから10バイト」とは限りません。文字列は保存や通信の前にEncodingによってbyte列へ変換され、そのbyte数は文字とEncodingの組み合わせで決まります。

この違いは、固定長データ、CSV、API、古い業務システムとの連携で重要になります。

文字列とbyte列は別物

flowchart LR
    A[文字列] --> B{Encoding}
    B -->|UTF-8| C[UTF-8 byte列]
    B -->|code page 932| D[Shift_JIS系 byte列]
    C --> E[byte数]
    D --> F[byte数]

PowerShell/.NETではGetByteCountで、そのEncodingが生成するbyte数を数えられます。

UTF-8で確認する

$texts = @(
    'ABC',
    '日本',
    'A日本',
    '😀'
)

$utf8 = [System.Text.Encoding]::UTF8

foreach ($text in $texts) {
    [pscustomobject]@{
        Text       = $text
        StringSize = $text.Length
        UTF8Bytes  = $utf8.GetByteCount($text)
    }
}

ASCII範囲の文字と日本語、絵文字では、同じ「見た目の1文字」でもUTF-8のbyte数が異なります。

String.Lengthも「人間が見る文字数」と同じとは限らない

.NETのStringはUTF-16 code unitの列として扱われます。そのため、補助平面の文字などでは、見た目の1文字とString.Lengthが一致しないことがあります。

flowchart TB
    A[見た目の文字] --> B[.NET String]
    B --> C[UTF-16 code units]
    B --> D[Encodingでbyte列へ]
    C --> E[String.Length]
    D --> F[GetByteCount]
    E -. 同じ概念ではない .- F

ここを「文字数」と「byte数」の2種類だけで単純化しすぎないのが大切です。

Shift_JIS系code page 932と比較する

PowerShell 7 / .NETでは、legacy code pageを使うためにCodePagesEncodingProviderの登録が必要な環境があります。

try {
    $sjis = [System.Text.Encoding]::GetEncoding(932)
}
catch {
    [System.Text.Encoding]::RegisterProvider(
        [System.Text.CodePagesEncodingProvider]::Instance
    )
    $sjis = [System.Text.Encoding]::GetEncoding(932)
}

$utf8 = [System.Text.Encoding]::UTF8

foreach ($text in @('ABC', '日本', 'A日本')) {
    [pscustomobject]@{
        Text       = $text
        UTF8Bytes  = $utf8.GetByteCount($text)
        SJISBytes  = $sjis.GetByteCount($text)
    }
}

同じ文字列でも、Encodingが変わればbyte列も変わります。

「Shift_JISなら日本語は2バイト」と決めつけない

code pageに含まれない文字や、single-byteで表現される文字もあります。さらに、Windowsのcode page 932と「Shift_JIS」という名称を完全に同一の仕様として雑に扱うと、細部で誤解が生まれます。 、PowerShell/.NETでcode page 932を取得して比較する実験として扱います。

固定長データでなぜ問題になるのか

古いシステム連携では、項目幅が「文字」ではなくbyte単位で定義されることがあります。

項目A: 10 bytes
項目B: 20 bytes
項目C: 8 bytes

文字列を単純にSubstringで切ってから保存すると、想定byte数を超える場合があります。

flowchart LR
    A[入力文字列] --> B[文字数で切る]
    B --> C[Encoding]
    C --> D{指定byte幅以内?}
    D -->|No| E[固定長レコードがずれる]
    D -->|Yes| F[次の項目へ]

逆に、byte列を途中で乱暴に切ると、multi-byte文字の途中で分断する可能性があります。固定長データでは「どのEncodingで何byteか」を仕様として確認する必要があります。

BOMと改行は別に考える

Encoding.GetByteCount(string)は、その文字列をEncodingしたときのbyte数を計算します。

一方、実際のファイルサイズには次が加わる場合があります。

  • BOM

  • CRLF / LFなどの改行

  • 区切り文字

  • ファイル全体のheader

「文字列本体のbyte数」と「保存したファイルのサイズ」を混同しません。

実ファイルでも確認する

文字列を保存した後、実際のファイルサイズも比較すると理解しやすくなります。

$text = '日本'

$path = '.\utf8-sample.txt'
[System.IO.File]::WriteAllText($path, $text, [System.Text.UTF8Encoding]::new($false))

$bytes = [System.IO.File]::ReadAllBytes($path)

[pscustomobject]@{
    TextByteCount = [System.Text.Encoding]::UTF8.GetByteCount($text)
    FileByteCount = $bytes.Length
}

ここではBOMなしのUTF-8を明示しているので、余計な条件を減らして比較できます。

まとめ

  • 文字列はEncodingによってbyte列へ変換される

  • String.Lengthと見た目の文字数とbyte数は同じ概念ではない

  • UTF-8とcode page 932では同じ文字列でもbyte数が変わる

  • 固定長データは文字数ではなく仕様上のEncodingとbyte幅を見る

  • BOMや改行を含むファイルサイズは、文字列本体のGetByteCountとは別に考える

公式情報・一次情報

Microsoft Learn — Encoding.GetByteCount
https://learn.microsoft.com/en-us/dotnet/api/system.text.encoding.getbytecount

Microsoft Learn — Encoding.GetEncoding
https://learn.microsoft.com/en-us/dotnet/api/system.text.encoding.getencoding

Microsoft Learn — CodePagesEncodingProvider
https://learn.microsoft.com/en-us/dotnet/api/system.text.codepagesencodingprovider

RFC 3629 — UTF-8
https://www.rfc-editor.org/rfc/rfc3629.html

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました