前言

在 Windows 系统下,文本编码存在有无 BOM 的编码。BOM(Byte Order Mark),字节顺序标记,出现在文本文件头部,Unicode 编码标准中用于标识文件是采用哪种格式的编码。有文件 BOM 头的 Unicode 编码容易识别,无 BOM 文件头的要在文件中查找字节顺序来判断 Unicode 编码。

Unicode 称为 Unicode 16 LE
BigEndianUnicode 称为 Unicode 16 BE
Unicode32 称为 Unicode 32 LE
Unicode32BE 称为 Unicode 32 BE

一、有 BOM 文件头

文件编码文件头 0-3 字节顺序 BOM 标记汉字字节组成以“中文”为例的编码(尾含回车)
Unicode0xFF,0xFE,必非0,视中英文定一个汉字 2 字节,“中文”二字共 4 字节2D 4E 87 65 0D 00 0A 00
BigEndianUnicode0xFE,0xFF,视中英文定,必非0一个汉字 2 字节,“中文”二字共 4 字节4E 2D 65 87 00 0D 00 0A
UTF320xFF,0xFE,0,0一个汉字 4 字节,“中文”二字共 8 字节2D 4E 00 00 87 65 00 00 0D 00 00 00 0A 00 00 00
UTF32BE0,0,0xFE,0xFF一个汉字 4 字节,“中文”二字共 8 字节00 00 4E 2D 00 00 65 87 00 00 00 0D 00 00 00 0A
UTF80xEF,0xBB,0xBF一个汉字3字节,“中文”二字共6字节E4 B8 AD E6 96 87 0D 0A

汉字混搭对比示例说明:

文件编码字符串十六进制
Unicode 16 LELOVE C#\r\n中文FF FE 4C 00 4F 00 56 00 45 00 20 00 43 00 23 00 0D 00 0A 00 2D 4E 87 65
Unicode 16 BELOVE C#\r\n中文FE FF 00 4C 00 4F 00 56 00 45 00 20 00 43 00 23 00 0D 00 0A 4E 2D 65 87
Unicode 32 LELOVE C#\r\n中文FF FE 00 00 4C 00 00 00 4F 00 00 00 56 00 00 00 45 00 00 00 20 00 00 00
43 00 00 00 23 00 00 00 0D 00 00 00 0A 00 00 00 2D 4E 00 00 87 65 00 00
Unicode 32 BELOVE C#\r\n中文00 00 FE FF 00 00 00 4C 00 00 00 4F 00 00 00 56 00 00 00 45 00 00 00 20
00 00 00 43 00 00 00 23 00 00 00 0D 00 00 00 0A 00 00 4E 2D 00 00 65 87
UTF8LOVE C#\r\n中文EF BB BF 4C 4F 56 45 20 43 23 0D 0A E4 B8 AD E6 96 87

从上面有 BOM 头的编码可以看出:
Unicode 16 LE 与 Unicode 16 BE 的区别是每 2 个字节反序。
Unicode 32 LE 与 Unicode 32 BE 的区别是每 4 个字节反序。
Unicode 16 英语字符区间内,一个英文字母为单字节,占用一个 00 字节;Unicode 32 占用空间更大,一个英文字母占用 4 个 00 字节;UTF8文件越大占用空间越小。几乎没有 00 字节。

二、无 BOM 文件头

文件编码文件头 0-4 字节顺序 BOM 标记汉字字节组成以“中文”为例的编码(尾含回车)
Unicode一个汉字 2 字节,“中文”二字共 4 字节2D 4E 87 65 0D 00 0A 00
BigEndianUnicode一个汉字 2 字节,“中文”二字共 4 字节4E 2D 65 87 00 0D 00 0A
UTF32一个汉字 4 字节,“中文”二字共 8 字节2D 4E 00 00 87 65 00 00 0D 00 00 00 0A 00 00 00
UTF32BE一个汉字 4 字节,“中文”二字共 8 字节00 00 4E 2D 00 00 65 87 00 00 00 0D 00 00 00 0A
UTF8一个汉字3字节,“中文”二字共6字节E4 B8 AD E6 96 87 0D 0A
ANSI一个汉字 2 字节,“中文”二字共 4 字节D6 D0 CE C4 0D 0A

汉字混搭对比示例说明:

文件编码字符串十六进制(无BOM头部字节识别码,可从字节排序识别)
Unicode 16 LELOVE C#\r\n中文4C 00 4F 00 56 00 45 00 20 00 43 00 23 00 0D 00 0A 00 2D 4E 87 65
Unicode 16 BELOVE C#\r\n中文00 4C 00 4F 00 56 00 45 00 20 00 43 00 23 00 0D 00 0A 4E 2D 65 87
Unicode 32 LELOVE C#\r\n中文4C 00 00 00 4F 00 00 00 56 00 00 00 45 00 00 00 20 00 00 00 43 00 00 00
23 00 00 00 0D 00 00 00 0A 00 00 00 2D 4E 00 00 87 65 00 00
Unicode 32 BELOVE C#\r\n中文00 00 00 4C 00 00 00 4F 00 00 00 56 00 00 00 45 00 00 00 20 00 00 00 43
00 00 00 23 00 00 00 0D 00 00 00 0A 00 00 4E 2D 00 00 65 87
UTF8LOVE C#\r\n中文4C 4F 56 45 20 43 23 0D 0A E4 B8 AD E6 96 87

从上面没有有 BOM 头的编码可以看出,与有 BOM 头的编码相比,只是少了文件头的 BOM 标识。

三、简体中文汉字编码

中文编码(按新旧顺序)代码页汉字字符串以“中文”为例的编码
Unicode 321200中文2D 4E 87 65
Unicode 32 BE1201中文4E 2D 65 87
Unicode1200中文2D 4E 00 00 87 65 00 00
Unicode BE1201中文00 00 4E 2D 00 00 65 87
UTF865001中文E4 B8 AD E6 96 87
GB18030CP54936中文D6 D0 CE C4
GBKCP936中文D6 D0 CE C4
GB2312CP20936中文D6 D0 CE C4
HZ-GB2312CP52936中文D6 D0 CE C4
BIG5CP950中文A4 A4 A4 E5

四、C# 程序对编码的识别

//声明引用指令名称空间
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

1、文件选择按钮代码:

    private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog Openfiledialog = new OpenFileDialog();
            Openfiledialog.InitialDirectory = Application.StartupPath;
            Openfiledialog.Filter = "文本文件(*.TXT)|*.TXT|所有文件(*.*)|*.*";
            Openfiledialog.Title = "打开文件";
            Openfiledialog.Multiselect = true;
            Openfiledialog.FilterIndex = 0;

            dataGridView1.Columns.Clear();
            DataGridViewColumn col;
            DataGridViewRow row;
            DataGridViewCell cell = new DataGridViewTextBoxCell();
            string[] HeaderText = { "文件", "编码" };
            for (int i = 0; i < 2; i++)
            {
                col = new DataGridViewColumn();
                col.HeaderText = HeaderText[i];
                col.CellTemplate = cell;
                col.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter;

                dataGridView1.Columns.Add(col);
                dataGridView1.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
            }

            if (Openfiledialog.ShowDialog() == DialogResult.OK)
            {
                string[] FilePath = Openfiledialog.FileNames;

                foreach (string file in FilePath)
                {
                    row = new DataGridViewRow();
                    cell = new DataGridViewTextBoxCell();
                    cell.Value = file;
                    row.Cells.Add(cell);

                    cell = new DataGridViewTextBoxCell();
                    cell.Value = GetEncoding(file).WebName;
                    row.Cells.Add(cell);
                    dataGridView1.Rows.Add(row);
                }
            }
            dataGridView1.AutoResizeColumns();
        }

2、获取文件编码,有 BOM 的文件识别

       /// <summary>获取文件的编码格式</summary>
        /// <param name="file_name">文件</param>
        /// <returns>文件的编码类型</returns>
        private static Encoding GetEncoding(string file_name)
        {
            //文件的字符集在Windows下有两种,一种是ANSI,一种Unicode。
            //对于Unicode,Windows支持了它的三种编码方式,
            //一种是小尾编码(Unicode),一种是大尾编码(BigEndianUnicode),一种是UTF - 8编码。
            if (file_name == null)
            {
                throw new ArgumentNullException(nameof(file_name));
            }

            FileStream fs = new FileStream(file_name, FileMode.Open, FileAccess.Read);
            long FsLeng = fs.Length;

            if (FsLeng < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(FsLeng));
            }

            byte[] bytes = new byte[FsLeng];

            if (fs.Length < 3)//小于BOM文件头3字节
            {
                fs.Close();
                return Encoding.ASCII;
            }

            fs.Read(bytes, 0, bytes.Length);
            fs.Close();

            if (bytes[0] == 0xFF && bytes[1] == 0xFE && (bytes[2] != 0 || bytes[3] != 0))//Unicode BOM标记
            {
                return Encoding.Unicode;
            }
            else if (bytes[0] == 0xFE && bytes[1] == 0xFF && (bytes[2] != 0 || bytes[3] != 0))//BigEndianUnicode BOM标记
            {
                return Encoding.BigEndianUnicode;
            }
            else if (bytes[0] == 0xFF && bytes[1] == 0xFE && bytes[2] == 0 && bytes[3] == 0)//UTF32 BOM标记
            {
                return Encoding.UTF32;
            }
            else if (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0xFE && bytes[3] == 0xFF)//UTF32BE BOM标记
            {
                return Encoding.GetEncoding("utf-32BE");
            }
            else if (bytes[0] == 0xEF && bytes[1] == 0xBB && (bytes[2] == 0xBF))//UTF8 BOM标记
            {
                return Encoding.UTF8;
            }
            else//无BOM标记
            {
                Encoding encoding = CheckUtf16Ascii(bytes, bytes.Length);//识别无BOM标记的UTF16
               
                if (encoding == null)//不是无BOM标记的UTF16
                {
                   if (IsOnlyAscii(bytes))//只有ASCII字符
                   {
                       return Encoding.ASCII;//纯 ASCII 
                   }
                   if(IsUTF8Bytes(bytes))识别无BOM标记的UTF8
                   {
                       return Encoding.UTF8; //无BOM标记的UTF8
                   }
                   else //排除所有 UTF16、UTF8、纯ASCII,剩下的就是中文状态的文本文件
                   {
                         if (IsBIG5(bytes))
                        {
                            return Encoding.GetEncoding("BIG5");//一代繁体编码
                        }              
                         else if (IsGB2312(bytes))//一代简体中文编码
                        {
                            return Encoding.GetEncoding("GB2312");
                        }
                        else if (IsGBK(bytes))//二代中文编码(包含简体、繁体中文)
                        {
                            return Encoding.GetEncoding("GBK");
                        }

                        else if (IsGB18030(bytes))//三代中文编码,四代中文编码为全球通 UTF 码中文
                        {
                            return Encoding.GetEncoding("GB18030");
                        }
                        else
                        {
                            return Encoding.Default;//无法识别,设定默认为系统编码
                        }
                }
                else
                {
                   return encoding;//无BOM标记的UTF16
                }
           }
        }

3、获取文件编码,UTF8 无 BOM 文件的识别

 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("非预期的byte格式");
            }
            return true;
        }

4、获取文件编码,UTF16 无 BOM 文件的识别

 private static Encoding CheckUtf16Ascii(byte[] buffer, int size)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException(nameof(buffer));
            }

            if (size < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(size));
            }

            if (size < 2)
            {
                return null;
            }

            // 将大小减小1,这样我们就不必担心字节对的边界检查
            size--;

            const double threshold = 0.5; // 允许一些非英语ISO-8859-1子集的UTF-16字符,同时仍检测编码。
            const double limit = 0.1;

            var leAsciiChars = 0;
            var beAsciiChars = 0;

            var pos = 0;
            while (pos < size)
            {
                byte ch1 = buffer[pos++];
                byte ch2 = buffer[pos++];

                // 偶数计数为空
                if (ch1 == 0 && ch2 != 0)
                {
                    beAsciiChars++;
                }

                // 奇数计数空值
                if (ch1 != 0 && ch2 == 0)
                {
                    leAsciiChars++;
                }
            }

            // 恢复大小
            size++;

            double leAsciiCharsPct = leAsciiChars * 2.0 / size;
            double beAsciiCharsPct = beAsciiChars * 2.0 / size;

            if (leAsciiCharsPct > threshold && beAsciiCharsPct < limit)
            {
                return Encoding.Unicode;
            }

            if (beAsciiCharsPct > threshold && leAsciiCharsPct < limit)
            {
                return Encoding.BigEndianUnicode;
            }

            // 无法识别的编码
            return null;
        }

5、获取非 UTF8、UTF16、UTF32 文件的 ASCII 和中文编码

        private static bool IsOnlyAscii(byte[] bytes)
        {
            if (bytes == null)
            {
                throw new ArgumentNullException(nameof(bytes));
            }
            for (int i = 0; i < bytes.Length; i++)
            {
                if (bytes[i] > 127) return false;//小于127的只有ASCII字符
            }
            return true;
        }
        static bool IsBIG5(byte[] bytes)
        {
            string input = Encoding.GetEncoding("BIG5").GetString(bytes);
            var regex = new Regex("[\x01-\x7f]|[\x81-\xfe]([\x40-\x7e]|[\xa1-\xfe])");//检查是否都匹配BIG5编码区间
            return regex.IsMatch(input);
        }

        private static bool IsGBK(byte[] bytes)
        {
            string input = Encoding.GetEncoding("GBK").GetString(bytes);
            // 正则表达式匹配所有汉字字符
            var regex = new Regex(@"[\u4E00-\u9FA5]"); // 检查输入字符串中的所有字符是否都匹配GBK编码区间
            return regex.IsMatch(input);
        }

        private static bool IsGB2312(byte[] bytes)
        {
            string input = Encoding.GetEncoding("GB2312").GetString(bytes);
            var regex = new Regex(@"[\uB0A1-\uF7FE\u8140-\uA4D0]"); // 检查输入字符串中的所有字符是否都匹配GB2312编码区间
            return regex.IsMatch(input);
        }

        private static bool IsGB18030(byte[] bytes)
        {
            string input = Encoding.GetEncoding("GB18030").GetString(bytes);
            var regex = new Regex(@"[\u4E00-\u9FA5\uE7C7-\uE7F3]");// 检查输入字符串中的所有字符是否都匹配GB18030编码区间
            return regex.IsMatch(input);
        }

五、获得各种编码的汉字

            Console.WriteLine ("GB2312汉字区 B0-F7,A1-FE");
            for (int i = 176; i < 248; i++)//GB2312汉字区,首字节 B0-F7, 第二字节 A1-FE
            {
                for (int j = 161; j < 255; j++)
                {
                    Console.Write("{0},{1},{2},{3} ", i, j,i.ToString("X")+ j.ToString("X"), GB18030.GetString(new byte[] { (byte)i, (byte)j }).ToString());
                }
                Console.Write("\r\n");
            }
            Console.WriteLine("");
            
            Console.WriteLine("CJK3汉字区 81-A0,40-FE");
            for (int i = 129; i < 161; i++)//CJK3汉字区 81-A0,40-FE
            {
                for (int j = 64; j < 255; j++)
                {
                    Console.Write("{0},{1},{2},{3} ", i, j, i.ToString("X") + j.ToString("X"), GB18030.GetString(new byte[] { (byte)i, (byte)j }).ToString());
                }
                Console.Write("\r\n");
            }
            Console.WriteLine("");
            
            Console.WriteLine("CJK4汉字区 AA-FE,40-FE");
            for (int i = 170; i < 255; i++)//CJK4汉字区 AA-FE,40-FE
            {
                for (int j = 64; j < 161; j++)
                {
                    Console.Write("{0},{1},{2},{3} ", i, j, i.ToString("X") + j.ToString("X"), GB18030.GetString(new byte[] { (byte)i, (byte)j }).ToString());
                }
                Console.Write("\r\n");
            }
            Console.WriteLine("");
            
            Console.WriteLine("BIG5基本汉字区 A4-C6,40-7E");
            for (int i = 164; i < 199; i++)//BIG5汉字区 A4-C6,40-7E
            {
                for (int j = 64; j < 126; j++)
                {
                    Console.Write("{0},{1},{2},{3} ", i, j, i.ToString("X") + j.ToString("X"), BIG5.GetString(new byte[] { (byte)i, (byte)j }).ToString());
                }
                Console.Write("\r\n");
            }
            Console.WriteLine("");
            
            Console.WriteLine("BIG5补充汉字区 C9-F9,40-D5");
            for (int i = 201; i < 250; i++)//BIG5汉字区 C9-F9,40-D5
            {
                for (int j = 64; j < 214; j++)
                {
                    Console.Write("{0},{1},{2},{3} ", i, j, i.ToString("X") + j.ToString("X"), BIG5.GetString(new byte[] { (byte)i, (byte)j }).ToString());
                }
                Console.Write("\r\n");
            }

六、获得各种编码的代码页和名称

            using (StreamWriter writer = new StreamWriter(Application.StartupPath + "\\EncodingCodePage.TXT", false, Encoding.Default))
            {
                EncodingInfo[] encodings = Encoding.GetEncodings();
                writer.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}", "编码代码页", "编码名称", "编码头名称", "编码WEB名称", "Windows系统编码代码页");
                foreach (var encodingInfo in encodings)
                {
                    Encoding encoding = Encoding.GetEncoding(encodingInfo.CodePage);
                    
                    writer.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}", encodingInfo.CodePage, encodingInfo.Name, encoding.HeaderName, encoding.WebName, encoding.WindowsCodePage);
                }
            }

七、程序通过验证

在这里插入图片描述

八、源代码下载

https://download.csdn.net/download/zyyujq/89805828

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐