Silverlight 简易 GB2312/GBK 编码器
一个用于 Silverlight 的简单 GB2312/GBK 编码器
引言
本文介绍了一个为 Silverlight 设计的 GBK 编码器。 它被实现为一个名为 GBKEncoder
的单个类,该类包含一些 public static
方法,用于使用 GBK 编码对 string
进行编码和解码字节数组。 它非常简单,您可以轻松地自定义它。
背景
在 Silverlight 4 中,您可以将数据保存到本地磁盘上的文件中。 您可以使用此功能将 DataGrid
导出到本地 CSV 文件。 如果您使用的是 Microsoft Excel 2003 并且打开包含中文字符的 CSV 文档,您会发现 Excel 2003 无法通过直接双击 CSV 文件来正确打开 utf8 CSV 文件。 为了通过双击直接打开 CSV 文件,您必须使用 GB2312/GBK 编码导出 CSV 文件。 不幸的是,Silverlight 4 仅支持 UTF-8 和 UTF-16 编码格式。 针对此问题有以下几种解决方案
- 将要导出的数据发送回服务器,在服务器上生成 CSV 文件,然后重定向用户以下载它。
- 将数据发送回服务器,然后将生成的 CSV 文件的编码二进制格式返回到 Silverlight 应用程序,然后将其保存到本地文件。
- 告诉您的客户:“请使用 Excel 2007!”,这是最简单的方法,但我非常确定我们不会这样做 :)
- 在 Microsoft 提供解决方案之前,实现一些方法以直接在 Silverlight 中导出 GBK 编码的文件。 这正是本文要讨论的内容。
GBKEncoder 类
public static class GBKEncoder
{
/// <summary>
/// Writes a string to the stream.
/// </summary>
/// <param name="s">The stream to write into.</param>
/// <param name="str">The string to write. </param>
static public void Write(System.IO.Stream s, string str);
/// <summary>
/// Encodes string into a sequence of bytes.
/// </summary>
/// <param name="str">The string to encode.</param>
/// <returns>A byte array containing the results of encoding
/// the specified set of characters.</returns>
static public byte[] ToBytes(string str);
/// <summary>
/// Decodes a sequence of bytes into a string.
/// </summary>
/// <param name="buffer">The byte array containing the sequence of bytes to decode.
/// </param>
/// <returns>A string containing the results of decoding
/// the specified sequence of bytes.
/// </returns>
static public string Read(byte[] buffer);
/// <summary>
/// Decodes a sequence of bytes into a string.
/// </summary>
/// <param name="buffer">The byte array containing the sequence of bytes to decode.
/// </param>
/// <param name="iStartIndex">The index of the first byte to decode.</param>
/// <param name="iLength">The number of bytes to decode. </param>
/// <returns>A string containing the results of decoding
/// the specified sequence of bytes.
/// </returns>
static public string Read(byte[] buffer, int iStartIndex, int iLength);
/// <summary>
/// Read string from stream.
/// </summary>
/// <param name="s">The stream to read from.</param>
/// <returns>A string containing all characters in the stream.</returns>
static public string Read(Stream s);
}
Using the Code
GBKEncoder
类非常直观易用,首先下载 GBKEncoder
,将 *GBKEncoder.cs* 添加到您的项目中,也许您还想重命名命名空间。 虽然这个类是为 Silverlight 设计的,但您可以在任何类型的 C# 项目中对其进行测试。 对于控制台应用程序,就像这样:
using (System.IO.FileStream fs = new System.IO.FileStream
("test.txt", System.IO.FileMode.Create))
{
GBKEncoder.Write
(fs, "This is a test for GBKEncoder.这是一段用来测试GBKEncoder类的代码。 ");
}
运行代码并打开输出的 *test.txt*,您会发现它被编码为 GBK。
在 Silverlight 中,它可能如下所示:
SaveFileDialog dlg = new SaveFileDialog() {
DefaultExt = "csv",
Filter = "CSV Files (*.csv)|*.csv|All files (*.*)|*.*",
FilterIndex = 1
};
StringBuilder sb = new StringBuilder();
// some code to fill sb ...
if (dlg.ShowDialog() == true)
{
using (Stream s = dlg.OpenFile())
{
GBKEncoder.Write(s, sb.ToString());
}
}
性能
以下代码旨在测试 GBKEncoder
类的性能
static void PerformanceTest()
{
StringBuilder sb = new StringBuilder();
Random r = new Random();
for (int i = 0; i < 200; i++)
{
for (int u = 0x4E00; u < 0x9Fa0; u++)
{
sb.Append((char)u);
if (r.Next(0, 5) == 0)
{
sb.Append((char)r.Next(32, 126));
}
}
}
string str = sb.ToString();
Console.WriteLine("Total character count : {0}", str.Length);
HighPrecisionTimer timer = new HighPrecisionTimer();
timer.Start();
using (System.IO.FileStream fs = new System.IO.FileStream
("test1.txt", System.IO.FileMode.Create))
{
GBKEncoder.Write(fs, str);
}
timer.Stop();
timer.ShowDurationToConsole();
timer.Start();
using (StreamWriter sw = new StreamWriter("test2.txt", false,
Encoding.GetEncoding("GBK")))
{
sw.Write(str);
}
timer.Stop();
timer.ShowDurationToConsole();
timer.Start();
string str2 = "";
using (FileStream fs = new FileStream("test1.txt", FileMode.Open))
{
str2 = GBKEncoder.Read(fs);
}
timer.Stop();
timer.ShowDurationToConsole();
timer.Start();
string str3 = File.ReadAllText("test2.txt", Encoding.GetEncoding("GBK"));
timer.Stop();
timer.ShowDurationToConsole();
if (str == str2 && str2 == str3)
{
Console.WriteLine("Success!");
}
else
{
Console.WriteLine("Error!!!");
}
}
测试环境:Vista 32bit, Q6600 OC3.0GHz, 4G Mem
结果
单位:毫秒
字符数:5014060
编码 | 解码 | |
GBKEncoder | 39.9 | 62.4 |
.NET Native | 75.6 | 63.6 |
由于 GBKEncoder
的实现非常简单直接,并且没有考虑可能存在的复杂情况,因此它的编码速度优于 .NET native 编码器。
GBKEncoder
将占用您 xap 文件中 50KB 的空间,并在运行时消耗约 260KB 的内存。
实现细节
GBK 是简体中文字符集 GB2312 的扩展,一个字符被编码为 1 个或 2 个字节,1 个字节用于标准 ASCII 码,2 个字节用于中文字符和标点符号。
GBKEncoder
使用两个数组实现,一个用于 unicode 到 GBK 的映射,名为 sm_mapUnicode2GBK
,另一个用于 GBK 到 unicode 的映射,名为 sm_mapGBK2Unicode
。 这些映射是根据 http://www.cs.nyu.edu/~yusuke/tools/unicode_to_gb2312_or_gbk_table.html 生成的。 sm_mapUnicode2GBK
的值硬编码在源代码中,而 sm_mapGBK2Unicode
的值是根据 sm_mapUnicode2GBK
在运行时生成的。
生成 sm_mapUnicode2GBK
值的方法
/// <summary>
/// Generate unicode to GBK mapping according to
/// http://www.cs.nyu.edu/~yusuke/tools/unicode_to_gb2312_or_gbk_table.html
/// </summary>
static private void GenUnicode2GBKMapping()
{
XmlDocument doc = new XmlDocument();
doc.Load("Unicode2GBK.xml");
int iCount = 0;
byte[] sm_mapUnicode2GBK = new byte[0xFFFF * 2];
foreach (XmlNode n in doc.DocumentElement.ChildNodes)
{
if (n.ChildNodes.Count == 18)
{
string strUnicode = n.ChildNodes[0].InnerText;
if (strUnicode.Substring(0, 2) != "U+")
throw new ApplicationException(string.Format("{0}不是有效的Unicode编码",
strUnicode));
int u = int.Parse(n.ChildNodes[0].InnerText.Substring(2),
System.Globalization.NumberStyles.HexNumber);
for (int i = 2; i < 18; i++)
{
int j = (i - 2 + u) * 2;
foreach (XmlNode subNode in n.ChildNodes[i])
{
if (subNode.Name.ToLower() == "small")
{
string str = subNode.InnerText.Trim().Trim('*');
if (str.Length == 2)
{
sm_mapUnicode2GBK[j] = 0;
sm_mapUnicode2GBK[j + 1] = byte.Parse
(str, System.Globalization.NumberStyles.HexNumber);
iCount++;
}
else if (str.Length == 4)
{
sm_mapUnicode2GBK[j] = byte.Parse(str.Substring(0, 2),
System.Globalization.NumberStyles.HexNumber);
sm_mapUnicode2GBK[j + 1] = byte.Parse(str.Substring(2),
System.Globalization.NumberStyles.HexNumber);
iCount++;
}
else
{
throw new ApplicationException
(string.Format("{0}不是有效的编码", n.ChildNodes[i].OuterXml));
}
}
}
}
}
}
Console.WriteLine("共计转换{0}个字符", iCount);
StringBuilder sb = new StringBuilder();
sb.AppendLine("static byte[] sm_mapUnicode2GBK = new byte[] {");
for (int i = 0; i < sm_mapUnicode2GBK.Length; i++)
{
if (i != 0 && i % 16 == 0) sb.AppendLine();
sb.Append(sm_mapUnicode2GBK[i]);
if (i < sm_mapUnicode2GBK.Length - 1) sb.Append(", ");
}
sb.AppendLine("};");
File.WriteAllText("sm_mapUnicode2GBK.cs", sb.ToString());
}
*Unicode2GBK.xml* 是一个 XML 文件,其中包含 unicode 到 gbk 的映射信息,该信息是根据 http://www.cs.nyu.edu/~yusuke/tools/unicode_to_gb2312_or_gbk_table.html 生成的。
历史
- 2011-03-29 初始版本,仅编码
- 2011-03-31 提高性能,编码和解码