65.9K
CodeProject 正在变化。 阅读更多。
Home

Silverlight 简易 GB2312/GBK 编码器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (3投票s)

2011年3月29日

CDDL

3分钟阅读

viewsIcon

259329

downloadIcon

561

一个用于 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 编码格式。 针对此问题有以下几种解决方案

  1. 将要导出的数据发送回服务器,在服务器上生成 CSV 文件,然后重定向用户以下载它。
  2. 将数据发送回服务器,然后将生成的 CSV 文件的编码二进制格式返回到 Silverlight 应用程序,然后将其保存到本地文件。
  3. 告诉您的客户:“请使用 Excel 2007!”,这是最简单的方法,但我非常确定我们不会这样做 :)
  4. 在 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 提高性能,编码和解码
© . All rights reserved.