【问题标题】:GZipStream - write not writing all compressed data even with flush?GZipStream - 即使刷新也不写所有压缩数据?
【发布时间】:2014-07-01 14:24:19
【问题描述】:

gzipstream 针对.Net 3.5 有一个讨厌的问题。这是我第一次使用 gzipstream,但是我模仿了包括 here 在内的许多教程,但我仍然卡住了。

我的应用程序将数据表序列化为 xml 并插入数据库,将压缩数据存储到 varbinary(max) 字段以及未压缩缓冲区的原始长度。然后,当我需要它时,我检索这些数据并解压缩并重新创建数据表。解压似乎失败了。

编辑:遗憾的是,按照建议将 GetBuffer 更改为 ToArray 后,我的问题仍然存在。代码更新如下

压缩代码:

DataTable dt = new DataTable("MyUnit");
//do stuff with dt
//okay...  now compress the table
using (MemoryStream xmlstream = new MemoryStream())
{
    //instead of stream, use xmlwriter?
    System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings();
    settings.Encoding = Encoding.GetEncoding(1252);
    settings.Indent = false;
    System.Xml.XmlWriter writer = System.Xml.XmlWriter.Create(xmlstream, settings);
    try
    {
        dt.WriteXml(writer);
        writer.Flush();
    }
    catch (ArgumentException)
    {
        //likely an encoding issue...  okay, base64 encode it
        var base64 = Convert.ToBase64String(xmlstream.ToArray());
        xmlstream.Write(Encoding.GetEncoding(1252).GetBytes(base64), 0, Encoding.GetEncoding(1252).GetBytes(base64).Length);
    }

    using (MemoryStream zipstream = new MemoryStream())
    {
        GZipStream zip = new GZipStream(zipstream, CompressionMode.Compress);
        log.DebugFormat("Compressing commands...");
        zip.Write(xmlstream.GetBuffer(), 0, xmlstream.ToArray().Length);
        zip.Flush();
        float ratio = (float)zipstream.ToArray().Length / (float)xmlstream.ToArray().Length;
        log.InfoFormat("Resulting compressed size is {0:P2} of original", ratio);

        using (SqlCommand cmd = new SqlCommand())
        {
            cmd.CommandText = "INSERT INTO tinydup (lastid, command, compressedlength) VALUES (@lastid,@compressed,@length)";
            cmd.Connection = db;
            cmd.Parameters.Add("@lastid", SqlDbType.Int).Value = lastid;
            cmd.Parameters.Add("@compressed", SqlDbType.VarBinary).Value = zipstream.ToArray();
            cmd.Parameters.Add("@length", SqlDbType.Int).Value = xmlstream.ToArray().Length;
            cmd.ExecuteNonQuery();

        }
    }

解压代码:

/* This is an encapsulation of what I get from the database
 public class DupUnit{
    public uint lastid;
    public uint complength;
    public byte[] compressed;
}*/
  //I have already retrieved my list of work to do from the database in a List<Dupunit> dupunits
foreach (DupUnit unit in dupunits)
{
    DataSet ds = new DataSet();
    //DataTable dt = new DataTable();
    //uncompress and extract to original datatable
    try
    {
        using (MemoryStream zipstream = new MemoryStream(unit.compressed))
        {
            GZipStream zip = new GZipStream(zipstream, CompressionMode.Decompress);
            byte[] xmlbits = new byte[unit.complength];
            //WHY ARE YOU ALWAYS 0!!!!!!!!
            int bytesdecompressed = zip.Read(xmlbits, 0, unit.compressed.Length);
            MemoryStream xmlstream = new MemoryStream(xmlbits);
            log.DebugFormat("Uncompressed XML against {0} is: {1}", m_source.DSN, Encoding.GetEncoding(1252).GetString(xmlstream.ToArray()));
            try{
               ds.ReadXml(xmlstream);
            }catch(Exception)
            {
                //it may have been base64 encoded...  decode first.
               ds.ReadXml(Encoding.GetEncoding(1254).GetString(
                 Convert.FromBase64String(
                 Encoding.GetEncoding(1254).GetString(xmlstream.ToArray())))
                 );
            }
            xmlstream.Dispose();
        }
    }
    catch (Exception e)
    {
        log.Error(e);
        Thread.Sleep(1000);//sleep a sec!
        continue;
    }

请注意上面的评论... bytesdecompressed 始终为 0。有什么想法吗?我做错了吗?

编辑 2:

所以这很奇怪。我在解压例程中添加了以下调试代码:

   GZipStream zip = new GZipStream(zipstream, CompressionMode.Decompress);
   byte[] xmlbits = new byte[unit.complength];
   int offset = 0;
   while (zip.CanRead && offset < xmlbits.Length)
   {
       while (zip.Read(xmlbits, offset, 1) == 0) ;
       offset++;
   }

调试时,有时该循环会完成,但有时会挂起。当我停止调试时,它将位于 1616 中的第 1600 字节。我会继续,但它根本不会移动。

编辑 3: 错误似乎在压缩代码中。无论出于何种原因,它都没有保存所有数据。当我尝试使用第三方 gzip 机制解压数据时,我只能得到原始数据的一部分。

我会开始赏金,但到目前为止我真的没有太多声誉可以提供:-(

【问题讨论】:

    标签: c# gzipstream


    【解决方案1】:

    终于找到答案了。压缩数据不完整,因为 GZipStream.Flush() 完全没有确保所有数据都在缓冲区之外 - 您需要将 GZipStream.Close() 用作pointed out here。当然,如果你得到一个糟糕的压缩,一切都会走下坡路——如果你试图解压它,你总是会从 Read() 返回 0。

    【讨论】:

      【解决方案2】:

      至少我会说这句话是最错误的:

      cmd.Parameters.Add("@compressed", SqlDbType.VarBinary).Value = zipstream.GetBuffer();
      

      MemoryStream.GetBuffer:

      请注意,缓冲区包含可能未使用的已分配字节。例如,如果将字符串“test”写入MemoryStream 对象,则从GetBuffer 返回的缓冲区长度为256,而不是4,其中252 个字节未使用。要仅获取缓冲区中的数据,请使用ToArray 方法。

      应该注意的是,在 zip 格式中,它首先通过定位存储在文件 end 的数据来工作 - 因此,如果您存储的数据多于所需的数据,则所需的条目位于文件的“结尾”不存在。


      顺便说一句,我还为您的 compressedlength 列推荐一个不同的名称 - 我最初认为它(尽管您的叙述)旨在存储压缩数据的长度(并写入我回答的一部分)。也许originalLength 会是一个更好的名字?

      【讨论】:

      • 很棒的评论。我会做出调整,看看效果如何。
      • 所以,这显然是一个问题,但不是问题......现在用最新的代码更新原始问题,但读取解压缩仍然为 0。
      • ...但我有几次使用缓冲区而不是 toarray...嗯...让我再工作一些...
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多