现有文本文件“test.txt”(使用vc保存的,具体格式编码未知),只有2行字符串,如下图所示:
图1
其行数据是按照一定格式排列的,前6个字节表示是人名(字符串),后2个字节表示年龄(整形值)。例如第一行“熊选文28”,表示熊选文的年龄是28岁。注意,第二行其中的”?”不是中文的问号,也不是英文的问号,只是Windows系统没有对应字符,才显示? 。如下图,该“?”对应的两个字节:C6 32。
图2
根据上图的16进制字节编码推测,该文本应该不是UNICODE编码,因为头部没有BOM。
现在要求用C#编写程序来读上面这个文本文件,并将每行的人名和年龄分别提取出来。
很简单,用StreamReader就可以,代码如下:
public void ReadFileData(string fileName)
{
//System.Text.Encoding encode=System.Text.Encoding.Default;
System.Text.Encoding encode=System.Text.Encoding.GetEncoding("gb2312");
//default表示使用操作系统的编码即可,一般中文操作系统都是Encoding.GetEncoding("gb2312"),但是其他系统就不一样了
//所以,此处其实用Encoding.GetEncoding("gb2312")更精确一些,免得有时候换成英文或其他操作系统,读取数据就有问题了
using(StreamReader sr = new StreamReader(fileName, encode))
{
if (sr == null)
{
return;
}
int nRow=0;//行号
string sLineBuf = null;//行数据缓存
string sName = "";//人名
int nAge = 0;// 年龄
while ((sLineBuf = sr.ReadLine()) != null)//
{
nRow++;
if (sLineBuf == "")
continue;//如果为空字符串,跳过当前行
sName =GetSubString(sLineBuf, 0, 6);//读取人名(前6个字节)
string sAge =GetSubString(sLineBuf, 6, 2);//后2个字节
nAge = Convert.ToInt16(sAge);//读取年龄
}
}
}
因为字符串自带的取子字符串的函数Substring()是按字符数来截取的,而不是按照字节数来截取。故我自己写了一个按照字节数来截取子字符串的函数:GetSubString。
/// <summary>
/// 从一个字符串中某个位置(该位置以字节数而不是字符数计算)开始取定长(该字符串的长度以字节数而不是字符数计算)的字符串
/// </summary>
/// <param name="SStr">源字符串</param>
/// <param name="nStart">取字符串的起始位置</param>
/// <param name="nByte">字符串的长度</param>
/// <returns>返回字符串</returns>
public static string GetSubString(string SStr,int nStart,int nByte)
{
string Tstr = "";
byte[] sbytes = System.Text.Encoding.GetEncoding("gb2312").GetBytes(SStr);//转换为字节数组
if (sbytes.Length == 0)
return Tstr;
if (nStart > sbytes.Length)
return Tstr;
byte[] tbytes = new byte[nByte];
int i = nStart;
int j = 0;
while (i < sbytes.Length && j < nByte)
{
tbytes[j++] = sbytes[i++];
}
try
{
Tstr = System.Text.Encoding.GetEncoding("gb2312").GetString(tbytes);//转换为字符串
}
catch (System.Exception ex)
{
throw ex;
}
return Tstr;
}
上面的程序写好了,开始读取数据,输出结果如下:
人名:年龄
熊选文:28
张三?2:0
第一行数据是没问题,但第二行就有问题了。本来应该是"张三?"的年龄是"20"岁,读出来的结果是"张三?2"的年龄是"0"岁,明显有误。为什么会出现这样的结果呢?
通过调试,就会发现c#读取该行的十六进制为:D5 C5 C8 FD 3F 32 30,而原本应该是D5 C5 C8 FD C6 32 32 30(见图2)。原本的C6 32变成了3F(3F对应的字符就是英文的问号),所以字节数就少了一个,截取前6个字节就是D5 C5 C8 FD 3F 32,对应的字符串就是“张三?2”,后面只有一个字节30,对应的字符就是0。所以最后得到的结果是“张三?2”的年龄是“0”。
通过对比分析发现,第一行数据中没有像“C6 32”这样的字符,所以用c#读出来结果很正常。第二行中有“C6 32”这样的字符,用C#读就会自动转换为3F。如果把“C6 32”换成其他的字符,如“BD 32”,都会有同样的问题。这是为什么呢?我想,应该是在GB2312中没有这种字符编码,所以无法识别,将该字符自动转换为英文的”?”,当然也无法正确读和显示了。
像上面这种文本数据(文本编码方式未知,但包含中英文,甚至有除中英文之外的其他字符),用C#如何才能无误的读取数据呢?