10个字符不等于10个字节 ― 在 PowerShell 中比较 UTF-8 与 Shift_JIS 的字节数

プログラミング・Web開発カテゴリを表すパンダのイラスト 编程・Web开发

关于本文
本文通过确认 .NET 的 Encoding API 与 UTF-8 规范,并在 PowerShell 中观察字符数与字节数的区别来进行整理。

验证状态:📘 已确认官方规范・PowerShell 实机未验证
由于 Shift_JIS 系列 code page 的可用性在不同的 PowerShell/.NET 环境中存在差异,文中也包含了无法获取时的处理方式。

“因为是 10 个字符,所以是 10 个字节”这并不总是成立。字符串在保存或通信之前会通过 Encoding 转换成字节序列,而字节数是由字符与 Encoding 的组合决定的。

这种差异在处理固定长数据、CSV、API 以及与旧业务系统进行对接时非常重要。

字符串与字节序列是两码事

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 所生成的字节数。

通过 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 字节数也不尽相同。

String.Length 也不一定等于“人眼看到的字符数”

.NET 的 String 被处理为 UTF-16 code unit 序列。因此,对于补充平面(Supplementary Planes)等字符,外观上的 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

重要的是不要将其过度简化为“字符数”和“字节数”这两种简单的对应关系。

与 Shift_JIS 系列 code page 932 进行比较

在 PowerShell 7 / .NET 中,某些环境需要注册 CodePagesEncodingProvider 才能使用 legacy code page。

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 发生变化,字节序列也会随之改变。

切勿断言“如果是 Shift_JIS,日语就是 2 个字节”

有些字符不包含在 code page 中,也有一些字符是用单字节(single-byte)表示的。此外,如果将 Windows 的 code page 932 与“Shift_JIS”这一名称当作完全相同的规范来草率对待,会在细节上产生误解。 本文将其作为在 PowerShell/.NET 中获取并比较 code page 932 的实验来进行探讨。

为什么在固定长数据中会出现问题

在旧系统集成中,字段宽度有时不是按“字符”定义的,而是按字节单位定义的。

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

如果简单地用 Substring 截取字符串后再保存,有时可能会超出预期的字节数。

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

反过来,如果在中途粗暴地截断字节序列,可能会将多字节字符(multi-byte character)从中间切断。在处理固定长数据时,必须将“使用哪种 Encoding 以及有多少字节”作为规范来进行确认。

需要将 BOM 和换行符分开考虑

Encoding.GetByteCount(string) 计算的是该字符串进行 Encoding 时的字节数。

另一方面,实际的文件大小可能还会包含以下内容:

  • BOM

  • CRLF / LF 等换行符

  • 分隔符

  • 整个文件的 header

请不要将“字符串本身的字节数”与“保存后的文件大小”混为一谈。

通过实际文件进行确认

保存字符串后,顺便比较一下实际的文件大小会更容易理解。

$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 被转换为字节序列

  • String.Length、外观上的字符数以及字节数并不是同一个概念

  • 即使是相同的字符串,UTF-8 和 code page 932 的字节数也可能不同

  • 固定长数据不应看字符数,而应看规范中的 Encoding 和字节宽度

  • 包含 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

文档信息

文章??
10个字符不等于10个字节 ― 在 PowerShell 中比较 UTF-8 与 Shift_JIS 的字节数
?布日期
更新日期
来源
https://papanda925.com/?p=15171&lang=zh

?可: ?于本站?有相??利的正文及原??表,除非?有?明,可依据 CC BY 4.0 使用。本文可能包含使用生成式AI?建或??的内容。若代??有?可声明,或?接的GitHub???定了?可,?代?以??可?准。引用内容、第三方?料、?片及商?不在本?可范?内。 使用政策

标题和URL已复制