C#中的字符編碼問題
該文件的編碼為GB18030,每行的寬度為23個字符,其中第1-8列為員工姓名,第10-23列為工資額。現在我們要寫一個C#程序求出該單位員工的平均工資,如下所示:
1
using System;
2
using System.IO;
3
using System.Text;
4
5
namespace Skyiv.Ben.Test
6
{
7
sealed class Avg
8
{
9
static void Main()
10
{
11
try
12
{
13
Encoding encode = Encoding.GetEncoding(GB18030);
14
using (StreamReader sr = new StreamReader(salary.txt, encode))
15
{
16
decimal avg = 0;
17
long rows = 0;
18
for (; ; rows++)
19
{
20
string s = sr.ReadLine();
21
if (s == null) break;
22
decimal salary = Convert.ToDecimal(s.Substring(9, 14));
23
avg += salary;
24
}
25
avg /= rows;
26
Console.WriteLine(avg.ToString(N2));
27
}
28
}
29
catch (Exception ex)
30
{
31
Console.WriteLine(錯誤: + ex.Message);
32
}
33
}
34
}
35
}
36
運行結果如下所示:
錯誤: 索引和長度必須引用該字符串內的位置
參數名: length
稍一分析(或者使用debug工具),就知道是該程序的第22行出錯:
decimal salary = Convert.ToDecimal(s.Substring(9, 14));
實際上,C#中的string的編碼是Unicode,每個全角的漢字也只能算一個字符,所以salary.txt中的第一行只有20個字符,第二行是21個字符,第三行是19個字符,均沒有達到23個字符,所以s.Substring(9, 14)會拋出異常。實際上,只要把這一行改為以下語句就行了:
decimal salary = Convert.ToDecimal(encode.GetString(encode.GetBytes(s), 9, 14));
重新編譯後再運行就可以得到正確的結果了: 329,218,792.83。
其實,更好的辦法是把該程序的13-27行替換為以下語句:
const int bytesPerRow = 23 + 2;
Encoding encode = Encoding.GetEncoding(GB18030);
using (BinaryReader br = new BinaryReader(new FileStream(salary.txt, FileMode.Open)))
{
if (br.BaseStream.Length % bytesPerRow != 0) throw new Exception(文件長度錯);
decimal avg = 0;
long rows = br.BaseStream.Length / bytesPerRow;
for (long i = 0; i < rows; i++)
{
byte [] bs = br.ReadBytes(bytesPerRow);
decimal salary = Convert.ToDecimal(encode.GetString(bs, 9, 14));
avg += salary;
}
avg /= rows;
Console.WriteLine(avg.ToString(N2));
}
現在,假設我們的任務是生成salary.txt,以下程序能工作嗎?
1
using System;
2
using System.IO;
3
using System.Text;
4
5
namespace Skyiv.Ben.Test
6
{
7
sealed class Salary
8
{
9
static void Main()
10
{
11
try
12
{
13
Encoding encode = Encoding.GetEncoding(GB18030);
14
string [] names = {李富貴, 容闳, 歐陽吹雪};
15
decimal [] salarys = {0.01m, 2057.38m, 987654321.09m};
16
using (StreamWriter sw = new StreamWriter(salary.txt, false, encode))
17
{
18
for (int i = 0; i < names.Length; i++)
19
sw.WriteLine({0,-8} {1,14:N2}, names[i], salarys[i]);
20
}
21
}
22
catch (Exception ex)
23
{
24
Console.WriteLine(錯誤: + ex.Message);
25
}
26
}
27
}
28
}
29
運行結果表明生成的文件中各行的寬度長短不一。怎麼辦呢?只要把程序中第19行改為:
sw.WriteLine({0} {1,14:N2}, encode.GetString(encode.GetBytes(names[i].PadRight(8)), 0, 8), salarys[i]);
就行了。
假如salary.txt文件的編碼是UTF-16,是否把程序中的
Encoding encode = Encoding.GetEncoding(GB18030);
改為:
Encoding encode = Encoding.Unicode;
就可以了呢?這個問題就留給讀者們去思考了。
設想一下,如果在不遠的將來能夠實現在所有的操作系統中,字符編碼都采用UTF-16,並且一個全角字符和一個半角在屏幕顯示和在打印機上打印出來時所占的寬度都一樣的(等寬字體的情況下,如果不是等寬字體,半角的A和i所占的寬度也不一樣)。這時,也就不需要全角和半角概念了(反正大家都一樣,全角也就是半角),也就不存在本文中所討論的問題了,就象現在英語國家的程序員不會有這個問題一樣(對他們來說根本就不存在全角字符的概念)。