基于 C# 的文本文件的编码识别
- 前言
-
- [一、有 BOM 文件头](#一、有 BOM 文件头)
- [二、无 BOM 文件头](#二、无 BOM 文件头)
- 三、简体中文汉字编码
- [四、C# 程序对编码的识别](# 程序对编码的识别)
-
- 1、文件选择按钮代码:
- [2、获取文件编码,有 BOM 的文件识别](#2、获取文件编码,有 BOM 的文件识别)
- [3、获取文件编码,UTF8 无 BOM 文件的识别](#3、获取文件编码,UTF8 无 BOM 文件的识别)
- [4、获取文件编码,UTF16 无 BOM 文件的识别](#4、获取文件编码,UTF16 无 BOM 文件的识别)
- [5、获取非 UTF8、UTF16、UTF32 文件的 ASCII 和中文编码](#5、获取非 UTF8、UTF16、UTF32 文件的 ASCII 和中文编码)
- 五、程序通过验证
- 六、源代码下载
前言
在 Windows 系统下,文本编码存在有无 BOM 的编码。BOM(Byte Order Mark),字节顺序标记,出现在文本文件头部,Unicode 编码标准中用于标识文件是采用哪种格式的编码。有文件 BOM 头的 Unicode 编码容易识别,无 BOM 文件头的要在文件中查找字节顺序来判断 Unicode 编码。
一、有 BOM 文件头
文件编码 | 文件头 0-3 字节顺序 BOM 标记 | 汉字字节组成 | 以"中文"为例的编码(后回车) |
---|---|---|---|
Unicode | 0xFF,0xFE,非0,非0 | 一个汉字 2 字节,"中文"二字共 4 字节 | 2D 4E 87 65 0D 00 0A 00 |
BigEndianUnicode | 0xFE,0xFF,非0,非0 | 一个汉字 2 字节,"中文"二字共 4 字节 | 4E 2D 65 87 00 0D 00 0A |
UTF32 | 0xFF,0xFE,0,0 | 一个汉字 4 字节,"中文"二字共 8 字节 | 2D 4E 00 00 87 65 00 00 0D 00 0A 00 |
UTF32BE | 0xFE,0xFF,0,0 | 一个汉字 4 字节,"中文"二字共 8 字节 | 4E 2D 00 00 65 87 00 00 00 0D 00 0A |
UTF8 | 0xEF,0xBB,0xBF | 一个汉字3字节,"中文"二字共6字节 | E4 B8 AD E6 96 87 0D 0A |
二、无 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 00 0D 00 0A 00 |
UTF32BE | 无 | 一个汉字 4 字节,"中文"二字共 8 字节 | 4E 2D 00 00 65 87 00 00 00 0D 00 0A |
UTF8 | 无 | 一个汉字3字节,"中文"二字共6字节 | E4 B8 AD E6 96 87 0D 0A |
ANSI | 无 | 一个汉字 2 字节,"中文"二字共 4 字节 | D6 D0 CE C4 0D 0A |
三、简体中文汉字编码
中文编码(按新旧顺序) | 代码页 | 以"中文"为例 |
---|---|---|
GB18030 | CP54936 | D6 D0 CE C4 |
GBK | CP936 | D6 D0 CE C4 |
GB2312 | CP20936 | D6 D0 CE C4 |
HZ-GB2312 | CP52936 | D6 D0 CE C4 |
BIG5 | CP950 | A4 A4 A4 E5 |
四、C# 程序对编码的识别
1、文件选择按钮代码:
csharp
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 的文件识别
csharp
/// <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 (IsUTF8Bytes(bytes))
{
if (encoding == null)
{
if (IsOnlyAscii(bytes))//只有ASCII字符
{
return Encoding.ASCII;//ASCII或UTF8下的ASCII
}
else
{
return Encoding.UTF8; //无BOM标记的UTF8
}
}
else
{
return encoding;//无BOM标记的UTF16
}
}
else
{
if (encoding == null)
{
string STR = Encoding.Default.GetString(bytes);
if (IsOnlyAscii(bytes))
{
return Encoding.ASCII;
}
else if (IsGBK(STR))
{
return Encoding.GetEncoding("GBK");
}
else if (IsGB2312(STR))
{
return Encoding.GetEncoding("GB2312");
}
else if (IsGB18030(STR))
{
return Encoding.GetEncoding("GB18030");
}
else
{
return Encoding.Default;//无法识别,默认为GB2312
}
}
else
{
return encoding;//无BOM标记的UTF16
}
}
}
}
3、获取文件编码,UTF8 无 BOM 文件的识别
csharp
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 文件的识别
csharp
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 和中文编码
csharp
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;
}
private static bool IsGBK(string input)
{
// 正则表达式匹配所有汉字字符
var regex = new Regex(@"[\u4E00-\u9FA5]"); // 检查输入字符串中的所有字符是否都匹配GBK编码区间
return regex.IsMatch(input);
}
private static bool IsGB2312(string input)
{
var regex = new Regex(@"[\uB0A1-\uF7FE\u8140-\uA4D0]"); // 检查输入字符串中的所有字符是否都匹配GB2312编码区间
return regex.IsMatch(input);
}
private static bool IsGB18030(string input)
{
var regex = new Regex(@"[\u4E00-\u9FA5\uE7C7-\uE7F3]");// 检查输入字符串中的所有字符是否都匹配GB18030编码区间
return regex.IsMatch(input);
}