C# Unicode编码 自动处理BOM头

软件安装

  1. 需要安装Notepad++(或SublineText)等相关软件查看或修改编码格式,方便学习。
  2. 本文使用的是Notepad++

GB2312的局限性

  1. GB是"国标"二字的拼音简称,GB2312是最早的国标码。
  2. GB2312里面收录了6763个汉字,其中一级汉字3755个,二级汉字3008个,同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个字符。
  3. GB2312收录的6763个汉字是简体中文,不是繁体中文,能够表示日常用语,而某些古诗文则不能用GB2312来表示。
  4. 从上面第二条对GB2312的定义可以看出,GB2312的编码表里面没有阿拉伯文,没有希腊文,没有北欧系文字,连中国的繁体文字都是没有的。
  5. 后来诞生了GBK,GB18030等编码。和GB2312的主要区别就是收录的汉字更多了,例如GBK总共收录了21003个汉字、883个符号,并将简体和繁体融于一库。
  6. 结论:GB2312不能用于游戏项目多国语言配置表工作。

Unicode简介

  1. Unicode又称统一码、万国码、单一码。是一种国际标准
  2. Unicode每个字符占4个字节
  3. 由于Unicode每个字符占4个字节,太浪费存储空间了,因此诞生了4种编码方式,分别是UTF-7, UTF-8, UTF-16, UTF-32
  4. 其中UTF-32是完整的Unicode编码,但由于太浪费存储空间了,因此互网联领域基本上没人用,仅用于学术领域或某些特殊领域。

此Unicode非彼Unicode

  1. 通常意义上,我们所指的Unicode需要结合语义的上下文来进行判断,基本上有2种情况。
    1. 情况1:是指"统一码",即说明的是"一种标准"。 例如:“请问你在Coding的过程中是否使用Unicode?”
    2. 情况2:专指UTF-16。 例如:“C#的CLR虚拟机中,所有的string都是Unicode编码”
  2. 在C#编程中,查看Encoding中的编码格式,发现没有UTF-16,只有Unicode。即 System.Text.Encoding.Unicode
  3. 在C#编程中,内存、硬编码固定采用UTF-16,这在《CLR Via C#》这本书中也有说到,即CLR虚拟机采用UTF-16
  4. 面对一个中国程序员,“请问你在Coding的过程中是否使用Unicode?” 的意思是 “请问你是否使用UTF-8?”。
  5. 因此,此Unicode非彼Unicode,一定要注意区别。

Unicode到底是几个字节

  1. Unicode是一种标准,Unicode的编码目前只有4种规范,即UTF-7, UTF-8, UTF-16, UTF-32
  2. 其中UTF-8是1到4个字节,绝大部分中文占用3个字节,极少数中文占用4个字节
  3. UTF-16是2到4个字节,绝大部分中文占用2个字节,极少数中文占3个字节,有些外国文字占用4个字节
  4. 可以认为UTF-32是完整格式,UTF-8和UTF-16都是压缩格式,不能压缩成1个字节的,就是2个字节。不能压缩成2个字节的就是3个字节,以此类推

Unicode和ANSI的关系

  1. 这个世界上一共有3种字符代码规则(世界级规范的),一种是ASCII,一种是ANSI,最后一种是Unicode,他们是不同的编码规则
  2. ASCII是单字节的,而ANSI是当单字节不够用时,延伸成2个字节的编码,当2个字节依然不够时延伸成3个字节的编码。因此可以认为ASCII是ANSI的子集。
  3. 不同国家和地区定义了不同ANSI标准,由此产生了GB2312、BIG5、JIS等标准。
  4. 在简体中文系统中,ANSI编码代表GB2312编码,反之亦然(即GB2312就代表ANSI)。
  5. ANSI和Unicode都是兼容ASCII码的,简单的说就是编码的值完全相等。只是Unicode会在前面补0,对齐编码格式,仅此而已。
  6. ANSI和Unicode之间的值则是不兼容的,需要一张转换表,才能使得ANSI中的编码对应Unicode中的编码。

利用BOM头来固化Excel和WPS对配置文件的读写编码格式

  1. Unicode标准认为0xFF,0xFE是不可见字符,在任何一国的文字中都没有意义,因此可以利用它来给文件打一个标记,表示这个文件是Unicode文件
  2. 不带BOM,会让Excel或WPS执行编码识别流程(这个流程可能与各种规则相关,包括但不限于Windows注册表规则,用户配置规则,软件的扫描识别编码规则,总之在不同的电脑上识别起来不太稳定),带BOM会直接命令Excel使用对应的格式打开文件(免费版WPF依然不听话,付费版的我没有测试过)。
  3. Excel可根据下列表中的内容判断BOM头和具体的Unicode编码格式
字符编码BOM(十六进制)
UTF-8EF BB BF
UTF-16(BE)大端FE FF
UTF-16(LE)小端FF FE
UTF-32(BE)大端00 00 FE FF
UTF-32(LE)小端FF FE 00 00
GB-1803084 31 95 33

生成带BOM的文件

  1. 使用Notepad++等软件进行转换即可
  2. 转换之后可以发现文件会多出BOM头,用C#的读取所有字节可以看到BOM头

C#的Encoding.Unicode.GetString(byte[] bytes)不会自动处理BOM头的问题

  1. Encoding.Unicode.GetString不会自动处理BOM头,但在断点调试的时候会显示为正确的字符串(非常容易引起Crash还怎么都找不出来),是因为前面的BOM头被隐藏了,特此说明。因此如果需要通过GetString函数获取到正确的字符串,请使用GetString的另一个重载函数。
  2. Encoding.Unicode.GetBytes不会自动添加BOM头,道理很简单,我们有可能会不停的写文件,每次写一点点,微软不可能每次都给我们带一个BOM头
  3. Encoding.Unicode.GetPreamble可以得到BOM头字节数组。因此,我们可以自行拼接BOM头和Encoding.Unicode.GetBytes的内容。
  4. File.ReadAllText明确传入编码格式时会处理BOM头,返回的字符串不会有问题
  5. File.WriteAllText明确传入编码格式时会处理BOM头,写入的文件不会有问题(带BOM头)
  6. File.ReadAllText和File.WriteAllText明确传入编码格式时会处理BOM头,是因为Encoding.Unicode.GetPreamble是虚函数,即不同编码的GetPreamble得到的字节数组是不一样的。当没有传入明确编码时,即Encoding等于null,则不会带BOM头。
  7. File.ReadAllBytes、File.WriteAllBytes不会处理BOM头。这很好理解,代码并不知道你读写的是什么数据,是结构体还是自定义的二进制数据,因此API不会去乱读写。

示例程序

  1. "卡ID Hi"字符串使用UTF-8来表示共8个字节。其中"ID Hi"共5个字节,"卡"占用3个字节。
  2. 因此,使用File.WriteAllText应该是11个字节,即多了3个字节的BOM头。
public class Program
{
    static void Main(string[] args)
    {
            //WriteAllText(自动处理了BOM头的问题,应该等于11个字节)
            File.WriteAllText("./a.txt", "卡ID Hi", Encoding.UTF8);

            //Read (自动处理了BOM头的问题)
            string content = File.ReadAllText("./a.txt", Encoding.UTF8);
            if (content == "卡ID Hi") { Console.WriteLine("Yes"); }

            //Read Bytes
            byte[] preArr = Encoding.UTF8.GetPreamble();
            byte[] bytes = File.ReadAllBytes("./a.txt");
            content = Encoding.UTF8.GetString(bytes, preArr.Length,  bytes.Length - preArr.Length);
            if (content == "卡ID Hi") { Console.WriteLine("Yes"); }
    }
}

自动处理BOM头

  1. 编写一个Utility.File类,采用UTF-8格式。
  2. 读写都自动处理BOM头问题。
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

public static partial class Utility
{
	/// <summary>
	/// 文件操作相关实用函数
	/// </summary>
	public static class File
	{
		/// <summary>
		/// 读取文件所有文本,支持共享读取(只支持UTF8格式)
		/// </summary>
		/// <param name="path">要读取文件的文件路径</param>
		/// <returns>读取到的文件内容</returns>
		public static string ReadFileAllText(string path)
		{
			if (!System.IO.File.Exists(path))
			{
				return string.Empty;
			}

			//开辟缓存
			FileInfo info = new FileInfo(path);
			byte[] byArr = new byte[info.Length];

			//读取全部文件内容(共享读取)
			FileStream stream = System.IO.File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
			stream.Read(byArr, 0, byArr.Length);
			stream.Close();

			//去BOM头,转为String
			string str = ReadTextRegular(byArr);

			return str;
		}

		/// <summary>
		/// 读取文件所有内容,支持共享读取
		/// </summary>
		/// <param name="path">要读取文件的文件路径</param>
		/// <returns>读取到的文件内容</returns>
		public static byte[] ReadFileAllBytes(string path)
		{
			if (!System.IO.File.Exists(path))
			{
				throw new Exception("File Not Found");
			}

			//开辟缓存
			FileInfo info = new FileInfo(path);
			byte[] byArr = new byte[info.Length];

			//读取全部文件内容(共享读取)
			FileStream stream = System.IO.File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
			stream.Read(byArr, 0, byArr.Length);
			stream.Close();

			return byArr;
		}

		/// <summary>
		/// 将内容写入文件,默认采用UTF8-BOM模式
		/// </summary>
		/// <param name="path">文件路径</param>
		/// <param name="content">文件内容</param>
		public static void WriteAllText(string path, string content)
		{
			System.IO.File.WriteAllText(path, content, Encoding.UTF8);
		}

		/// <summary>
		/// 从bytes字节数组中读取文本,会自动处理BOM头的问题
		/// </summary>
		/// <param name="bytes">字符数组</param>
		/// <returns>读取到的文本</returns>
		public static string ReadTextRegular(byte[] bytes)
		{
			string text = null;

			//去UTF8-BOM头
			//注意:Encoding.UTF8.GetString不会自动去掉BOM头
			byte[] preArr = Encoding.UTF8.GetPreamble();
			if (preArr[0] == bytes[0] && preArr[1] == bytes[1] && preArr[2] == bytes[2])
			{
				text = Encoding.UTF8.GetString(bytes, preArr.Length, bytes.Length - preArr.Length);
			}
			else
			{
				text = Encoding.UTF8.GetString(bytes);
			}
			return text;
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值