C#,文本文件与文本流编码问题的终极解决方案之源程序

 

一、文件后缀那点事

一般的文件都有个后缀,比如:demo.cs ,cs 代表了 csharp ,也就是 c#。这个文件一般是一个  文本格式 C# 的源代码(源程序)文件。

那么 demo.cs 还可以是什么文件呢?实际上也可以是一张图片的文件,也可以是一个视频mp4文件或者是一段音乐的MP3文件。

一言以蔽之,文件后缀只是文件的衣服,里面是什么东东?不一定哈!

下面文字都是从百科抄来的。

C#,文本文件与文本流编码问题的终极解决方案之源程序_第1张图片

 

二、文本文件(流)编码那点事

对于国际化开发团队而言,文本文件或者文本流(比如数据交换的 xml, json)等等的编码问题,一直并且永远是程序员的梦魇

文本文件的编码一般是 ASCII,GB2312(GBK),UTF(一级UTF-BOM)等等。不同编码的文件有其特别的标志性字节数据

因而,判断一个文件的编码的算法,如果需要保证100%的正确,只有从头到尾逐个字节去判断。这么简单?

对于 1KB 的文件是简单可行的,对于 1GB 的文件呢?16TB 的文件呢?为了判断一个文件的编码甚至需要 10 分钟,你觉得可以接受吗?

本文给出了比较理想的解决方案。慢慢看吧。

先了解一下主流编码的基本概念。

2.1 ASCII

ASCII (American Standard Code for Information Interchange):美国信息交换标准代码是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准,并等同于国际标准 ISO/IEC 646。ASCII第一次以规范标准的类型发表是在1967年,最后一次更新则是在1986年,到目前为止共定义了128个字符。

ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。标准ASCII 码也叫基础ASCII码,使用7 位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字0 到9、标点符号,以及在美式英语中使用的特殊控制字符。其中:
0~31及127(共33个)是控制字符或通信专用字符(其余为可显示字符)
如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(响铃)等;
通信专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等;
ASCII值为8、9、10 和13 分别转换为退格、制表、换行和回车字符。它们并没有特定的图形显示,但会依不同的应用程序,而对文本显示有不同的影响。
32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字。
65~90为26个大写英文字母,97~122号为26个小写英文字母,其余为一些标点符号、运算符号等。

同时还要注意,在标准ASCII中,其最高位(b7)用作奇偶校验位。所谓奇偶校验,是指在代码传送过程中用来检验是否出现错误的一种方法,一般分奇校验和偶校验两种。奇校验规定:正确的代码一个字节中1的个数必须是奇数,若非奇数,则在最高位b7添1;偶校验规定:正确的代码一个字节中1的个数必须是偶数,若非偶数,则在最高位b7添1。

后128个称为扩展ASCII码。许多基于x86的系统都支持使用扩展(或“高”)ASCII。扩展ASCII 码允许将每个字符的第8 位用于确定附加的128 个特殊符号字符、外来语字母和图形符号。
 

2.2 GB2312、GBK、GB18030

2.2.1 起源

《信息交换用汉字编码字符集》是由中国国家标准总局1980年发布,1981年5月1日开始实施的一套国家标准,标准号是GB 2312—1980。
GB2312编码适用于汉字处理、汉字通信等系统之间的信息交换,通行于中国大陆;新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB 2312。
基本集共收入汉字6763个和非汉字图形字符682个。整个字符集分成94个区,每区有94个位。每个区位上只有一个字符,因此可用所在的区和位来对汉字进行编码,称为区位码。
把换算成十六进制的区位码加上2020H,就得到国标码。国标码加上8080H,就得到常用的计算机机内码。1995年又颁布了《汉字编码扩展规范》(GBK)。GBK与GB 2312—1980国家标准所对应的内码标准兼容,同时在字汇一级支持ISO/IEC10646—1和GB 13000—1的全部中、日、韩(CJK)汉字,共计20902字。

2.2.2 收录汉字

GB 2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时,GB 2312收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。
GB 2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。
对于人名、古汉语等方面出现的罕用字,GB 2312不能处理,这导致了后来GBK及GB 18030汉字字符集的出现。
GB 2312中对所收汉字进行了“分区”处理,每区含有94个汉字/符号。这种表示方式也称为区位码。
01-09区为特殊符号。
16-55区为一级汉字,按拼音排序。
56-87区为二级汉字,按部首/笔画排序。
10-15区及88-94区则未有编码。
举例来说,“啊”字是GB2312之中的第一个汉字,它的区位码就是1601。

2.2.3 存储字节

在使用GB2312的程序中,通常采用EUC储存方法,以便兼容于ASCII。浏览器编码表上的“GB2312”,通常都是指“EUC-CN”表示法。
每个汉字及符号以两个字节来表示。第一个字节称为“高位字节”(也称“区字节)”,第二个字节称为“低位字节”(也称“位字节”)。
“高位字节”使用了0xA1-0xF7(把01-87区的区号加上0xA0),“低位字节”使用了0xA1-0xFE(把01-94加上 0xA0)。 由于一级汉字从16区起始,汉字区的“高位字节”的范围是0xB0-0xF7,“低位字节”的范围是0xA1-0xFE,占用的码位是 72*94=6768。其中有5个空位是D7FA-D7FE。
例如“啊”字在大多数程序中,会以两个字节,0xB0(第一个字节) 0xA1(第二个字节)储存。区位码=区字节+位字节(与区位码对比:0xB0=0xA0+16,0xA1=0xA0+1)。
 

2.3 UNICODE、UTF-8

2.3.1 UNICODE

统一码(Unicode),也叫万国码、单一码,是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。

如果把各种文字编码形容为各地的方言,那么Unicode就是世界各国合作开发的一种语言。
在这种语言环境下,不会再有语言的编码冲突,在同屏下,可以显示任何语言的内容,这就是Unicode的最大好处。就是将世界上所有的文字用2个字节统一进行编码。那样,像这样统一编码,2个字节就已经足够容纳世界上所有的语言的大部分文字了。
Universal Multiple-Octet Coded Character Set,简称为UCS。
现在用的是UCS-2,即2个字节编码,而UCS-4是为了防止将来2个字节不够用才开发的。
Unicode(统一码、万国码、单一码)是一种在计算机上使用的字符编码。它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。1990年开始研发,1994年正式公布。随着计算机工作能力的增强,Unicode也在面世以来的十多年里得到普及。
Unicode是基于通用字符集(Universal Character Set)的标准来发展,并且同时也以书本的形式(The Unicode Standard,目前第五版由Addison-Wesley Professional出版,ISBN-10: 0321480910)对外发表。
2005年3月31日推出的Unicode 4.1.0。
2022年9月13日推出的15.0版本。

2.3.2 编码方法

在Unicode中:汉字“字”对应的数字是23383。在Unicode中,我们有很多方式将数字23383表示成程序中的数据,包括:UTF-8、UTF-16、UTF-32。UTF是“UCS Transformation Format”的缩写,可以翻译成Unicode字符集转换格式,即怎样将Unicode定义的数字转换成程序数据。例如,“汉字”对应的数字是0x6c49和0x5b57,而编码的程序数据是:
BYTE data_utf8[] = {0xE6, 0xB1, 0x89, 0xE5, 0xAD, 0x97}; // UTF-8编码
WORD data_utf16[] = {0x6c49, 0x5b57}; // UTF-16编码
DWORD data_utf32[] = {0x6c49, 0x5b57}; // UTF-32编码
这里用BYTE、WORD、DWORD分别表示无符号8位整数,无符号16位整数和无符号32位整数。UTF-8、UTF-16、UTF-32分别以BYTE、WORD、DWORD作为编码单位。“汉字”的UTF-8编码需要6个字节。“汉字”的UTF-16编码需要两个WORD,大小是4个字节。“汉字”的UTF-32编码需要两个DWORD,大小是8个字节。根据字节序的不同,UTF-16可以被实现为UTF-16LE或UTF-16BE,UTF-32可以被实现为UTF-32LE或UTF-32BE。下面介绍UTF-8、UTF-16、UTF-32、字节序和BOM。


2.3.3 UTF-8

UTF-8以字节为单位对Unicode进行编码。从Unicode到UTF-8的编码方式如下:
Unicode编码(十六进制)║UTF-8字节流(二进制)
F   ║0xxxxxxxx║110xxxxx 10xxxxxx║1110xxxx 10xxxxxx 10xxx10xxxx║11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是4个字节。从上表可以看出,4字节模板有21个x,即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。
例1:“汉”字的Unicode编码是0x6C49。0x6C49在0x0800-0xFFFF之间,使用用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将0x6C49写成二进制是:0110 1100 0100 1001,用这个比特流依次代替模板中的x,得到:1110 0110 1011 0001 1000 1001,即E6 B1 89。
例2:Unicode编码0x20C30在0x010000-0x10FFFF之间,使用用4字节模板了:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。将0x20C30写成21位二进制数字(不足21位就在前面补0):0 0010 0000 1100 0011 0000,用这个比特流依次代替模板中的x,得到:,即F0 A0 B0 B0。

2.3.4 BOM

如果需要跨平台编程,本小节文字很重要!Windows一般有BOM,Linux一般没有BOM!

如何判断字节流的字节序?Unicode标准建议用BOM(Byte Order Mark)来区分字节序,即在传输字节流前,先传输被作为BOM的字符"零宽无中断空格"。这个字符的编码是FEFF,而反过来的FFFE(UTF-16)和FFFE0000(UTF-32)在Unicode中都是未定义的码位,不应出现在实际传输中。下表是各种UTF编码的BOM:
UTF编码 ║ Byte Order Mark
UTF-8 ║ EF BB BF
UTF-16LE ║ FF FE
UTF-16BE ║ FE FF
UTF-32LE ║ FF FE 00 00
UTF-32BE ║ 00 00 FE FF
基本上,计算机只是处理数字。它们指定一个数字,来储存字母或其他字符。在创造Unicode之前,有数百种指定这些数字的编码系统。
 

 

C#,文本文件与文本流编码问题的终极解决方案之源程序_第2张图片

三、自动判断文本编码并加载的静态类

作者文章的全都直接奉献C#源代码,不会让大家花一分钱。

using System;
using System.IO;
using System.Text;

namespace DeepConfuser
{
    /// 
    /// 自动识别字符集的文件读取类
    /// 北京联高软件开发有限公司
    /// www.315soft.com
    /// 
    public static class FileLoader
    {
        /// 
        /// 自动识别文件的编码,并读取(文本文件);
        /// 
        /// 
        /// 
        public static string Load(string filename)
        {
            if (!File.Exists(filename))
            {
                throw new Exception("FileLoader.Load ERROR:Can't found file " + filename);
            }
            Encoding en = GetFileEncoding(filename);
            try
            {
                return File.ReadAllText(filename, en);
            }
            catch (Exception ex)
            {
                throw new Exception("FileLoader.Load ERROR:" + ex.Message);
            }
        }

        ///  
        /// 判断文件的编码类型 
        ///  
        /// 文件路径 
        /// 文件的编码类型 
        private static Encoding GetFileEncoding(string filename)
        {
            try
            {
                FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read);
                Encoding r = GetStreamEncoding(fs);
                fs.Close();
                return r;
            }
            catch (Exception ex)
            {
                throw new Exception("FileLoader.GetFileEncoding ERROR:" + ex.Message);
            }
        }

        ///  
        /// 判断文件流的编码类型 
        ///  
        /// 文件流 
        /// 流的编码类型 
        private static Encoding GetStreamEncoding(FileStream filestream)
        {
            try
            {
                byte[] Unicode = new byte[] { 0xFF, 0xFE, 0x41 };
                byte[] UnicodeBIG = new byte[] { 0xFE, 0xFF, 0x00 };
                //带BOM 
                byte[] UTF8 = new byte[] { 0xEF, 0xBB, 0xBF };
                Encoding reVal = Encoding.Default;

                BinaryReader r = new BinaryReader(filestream, System.Text.Encoding.Default);
                int i;
                int.TryParse(filestream.Length.ToString(), out i);
                byte[] ss = r.ReadBytes(i);
                if (IsUTF8Bytes(ss) || (ss[0] == 0xEF && ss[1] == 0xBB && ss[2] == 0xBF))
                {
                    reVal = Encoding.UTF8;
                }
                else if (ss[0] == 0xFE && ss[1] == 0xFF && ss[2] == 0x00)
                {
                    reVal = Encoding.BigEndianUnicode;
                }
                else if (ss[0] == 0xFF && ss[1] == 0xFE && ss[2] == 0x41)
                {
                    reVal = Encoding.Unicode;
                }
                r.Close();
                return reVal;
            }
            catch (Exception ex)
            {
                throw new Exception("FileLoader.GetStreamEncoding ERROR:" + ex.Message);
            }
        }

        ///  
        /// 判断是否带BOM的UTF8格式(估算方法)
        /// BOM:Byte Order Mark,定义字节顺序。
        /// UTF-8不需要BOM表明字节顺序,但用BOM来表示编码方式。
        /// Windows就是采用BOM来标记文本文件的编码方式的,
        /// 可以把UTF-8和ASCII等编码区分开来,
        /// 但在Windows之外(如,Linux ),会带来问题。
        ///  
        ///  
        ///  
        private static bool IsUTF8Bytes(byte[] data)
        {
            // 字节数
            int charByteCounter = 1;
            // 当前字节
            byte curByte;
            for (int i = 0; i < data.Length; i++)
            {
                curByte = data[i];
                if (charByteCounter == 1)
                {
                    if (curByte >= 0x80)
                    {
                        // 判断当前 
                        while (((curByte <<= 1) & 0x80) != 0)
                        {
                            charByteCounter++;
                        }
                        // 标记位首位若为非0 则至少以2个1开始
                        // 如:110XXXXX...........1111110X 
                        if (charByteCounter == 1 || charByteCounter > 6)
                        {
                            return false;
                        }
                    }
                }
                else
                {
                    // 若是UTF-8 此时第一位必须为1 
                    if ((curByte & 0xC0) != 0x80)
                    {
                        return false;
                    }
                    charByteCounter--;
                }
            }
            if (charByteCounter > 1)
            {
                throw new Exception("FileLoader.IsUTF8Bytes ERROR: UNEXPECTED byte FORMAT!");
            }
            return true;
        }
    }
}

使用方法:

using DeepConfuser;


public class LoaderDemo
{
    public static string[] LoadtoLines(string filename = @"c:\demo.an_unknow_codeing_file")
    {
        string buf = FileLoader.Load(filename);
        string[] lines = buf.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
        return lines;
    }
}

简单,实用,免费,无限!源自 DeepConfuser & Truffer.

你可能感兴趣的:(C#实用代码,Coding,Recipes,c#,开发语言,文本文件,编码,json)