为何Encoding.UTF8.GetMaxByteCount(1)返回6?如何单字符编码出6字节?
UTF8编码单个char输出6字节的场景解析
核心疑问
为什么Encoding.UTF8.GetMaxByteCount(1)返回6,但实际测试中编码单个char最多只得到4字节?是否存在能让单个char编码出6字节的场景?
背景与测试结果
根据微软官方文档描述,GetMaxByteCount的返回值应考虑最坏情况,包括遗留代理项或EncoderFallback的处理结果,但常规测试中:
Encoding.UTF8编码单个char最多返回3字节(多为替换字符U+FFFD的UTF-8编码)Encoder处理代理对时,单个有效代理项会被缓存,第二个代理项输入时输出4字节的完整UTF-8序列- 但
GetMaxByteCount(1)始终返回6
测试代码如下:
string smilelyface = "😄"; // <--- 2 chars, encodes to 4 UTF8 bytes Encoding enc = Encoding.UTF8; int mbc = enc.GetMaxByteCount(1); Console.WriteLine("mbc: {0}", mbc); // <---- 6 byte[] sixbytes = new byte[mbc]; int gb = enc.GetBytes(smilelyface.AsSpan(0, 1), sixbytes); // Encode 1st char Console.WriteLine("retval: {0}", gb); // <----- 3 gb = enc.GetBytes(smilelyface.AsSpan(1, 1), sixbytes); // Encode 2nd char Console.WriteLine("retval: {0}", gb); // <----- 3 bool b = enc.TryGetBytes(smilelyface.AsSpan(0, 1), sixbytes, out int outbyteswritten); Console.WriteLine("outbyteswritten: {0}", outbyteswritten); // <----- 3 b = enc.TryGetBytes(smilelyface.AsSpan(1, 1), sixbytes, out outbyteswritten); Console.WriteLine("outbyteswritten: {0}", outbyteswritten); // <----- 3 Encoder encr = enc.GetEncoder(); encr.Convert(smilelyface.AsSpan(0, 1), sixbytes, false, out int charsused, out int bytesused, out bool completed); Console.WriteLine("BytesUsed: {0}", bytesused); // <----- 0 encr.Convert(smilelyface.AsSpan(1, 1), sixbytes, false, out charsused, out bytesused, out completed); Console.WriteLine("BytesUsed: {0}", bytesused); // <----- 4
原因:EncoderFallback的最坏情况
GetMaxByteCount(1)返回6的本质是考虑了自定义EncoderFallback的极端场景:当Fallback策略为单个无效char生成两个U+FFFD替换字符时,每个U+FFFD编码为3字节UTF-8,总长度就是3*2=6字节。
默认的ReplacementFallback只会生成一个U+FFFD(3字节),所以常规测试看不到6字节的结果,但通过自定义Fallback可以实现这个场景。
实现单个char编码出6字节的示例
以下代码通过自定义EncoderFallback,让单个无效代理项char被替换为两个U+FFFD,从而编码出6字节:
class DoubleReplacementFallback : EncoderFallback { public override int MaxCharCount => 2; // 每个无效字符返回2个替换字符 public override EncoderFallbackBuffer CreateFallbackBuffer() { return new DoubleReplacementFallbackBuffer(); } } class DoubleReplacementFallbackBuffer : EncoderFallbackBuffer { private readonly char[] _replacement = new[] { '\ufffd', '\ufffd' }; private int _index; public override bool Fallback(char charUnknown, int index) { _index = 0; return true; } public override bool Fallback(char charUnknownHigh, char charUnknownLow, int index) { _index = 0; return true; } public override char GetNextChar() { if (_index < _replacement.Length) return _replacement[_index++]; return '\0'; } public override bool MovePrevious() { if (_index > 0) { _index--; return true; } return false; } public override int Remaining => _replacement.Length - _index; } // 使用自定义Fallback创建UTF8编码实例 var utf8WithDoubleFallback = new UTF8Encoding(encoderFallback: new DoubleReplacementFallback()); // 单个无效代理项char(比如高代理项D800) char invalidChar = '\ud800'; byte[] buffer = new byte[utf8WithDoubleFallback.GetMaxByteCount(1)]; int bytesWritten = utf8WithDoubleFallback.GetBytes(new[] { invalidChar }, 0, 1, buffer, 0); Console.WriteLine($"编码单个char得到的字节数:{bytesWritten}"); // 输出6 Console.WriteLine($"字节内容:{BitConverter.ToString(buffer.Take(bytesWritten).ToArray())}"); // 输出 EF BF BD-EF BF BD
结论
- 默认UTF8编码下,单个
char最多输出3字节(替换字符)或通过Encoder配合代理对输出4字节; GetMaxByteCount(1)返回6是考虑了自定义Fallback的最坏情况,即单个无效字符被替换为两个3字节的U+FFFD;- 通过实现自定义
EncoderFallback,可以让单个无效char编码出6字节的UTF8序列。
内容的提问来源于stack exchange,提问作者David Wohlferd
相关产品推荐
相关产品推荐

