2014-07-01 1 views
3

J'ai un gros problème avec le ciblage gzipstream .Net 3.5. C'est la première fois que je travaille avec gzipstream, mais j'ai modélisé après un certain nombre de tutoriels, y compris here et je suis toujours coincé.GZipStream - écrit n'écrit pas toutes les données compressées même avec flush?

Mon application sérialise une datatable à xml et insère dans une base de données, stockant les données compressées dans un champ varbinary (max) ainsi que la longueur d'origine du tampon non compressé. Ensuite, quand j'en ai besoin, je récupère ces données et les décomprime et recrée le datatable. Le décompresseur est ce qui semble échouer.

EDIT: Malheureusement après avoir changé le GetBuffer à ToArray comme suggéré, mon problème reste. Code mis à jour ci-dessous

Code Compress:

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(); 

     } 
    } 

Code Décompresser:

/* 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; 
    } 

Notez le commentaire ci-dessus ... bytesdecompressed est toujours 0. Toutes les idées? Est-ce que je le fais mal?

EDIT 2:

C'est donc bizarre. J'ai ajouté le code de débogage suivant à la routine de décompression:

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++; 
    } 

Lors du débogage, parfois cette boucle compléterait, mais d'autres fois il pendrait. Quand j'arrêterais le débogage, ce serait à l'octet 1600 sur 1616. Je continuerais, mais ça ne bougerait pas du tout.

EDIT 3: Le bogue semble être dans le code de compression. Pour une raison quelconque, il ne sauvegarde pas toutes les données. Lorsque j'essaie de décompresser les données à l'aide d'un mécanisme gzip tiers, je ne reçois qu'une partie des données d'origine.

Je commencerais une prime, mais je ne vois vraiment pas beaucoup de réputation de donner dès maintenant :-(

Répondre

8

Enfin trouvé la réponse. Les données compressées n'étaient pas complètes car GZipStream.Flush() ne fait absolument rien pour s'assurer que toutes les données sont hors de la mémoire tampon - vous devez utiliser GZipStream.Close() comme pointed out here. Bien sûr, si vous obtenez une mauvaise compression, tout va en descente - si vous essayez de la décompresser, vous aurez toujours 0 retourné par Read().

3

Je dirais que cette ligne, au moins, est le plus mal:

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

MemoryStream.GetBuffer.

Notez que le tampon contient alloué octets qui pourrait être utilisé, par exemple, si la chaîne « test » est écrit dans l'objet MemoryStream, la longueur du tampon renvoyé de GetBuffer est 256, pas 4, avec 252 octets inutilisés. Pour obtenir uniquement les données dans le tampon, utilisez la méthode ToArray.

Il convient de noter que dans le format zip, il commence par des œuvres de localisation des données stockées à la fin du fichier - donc si vous avez enregistré plus de données que ce qui était nécessaire, les entrées nécessaires au " fin "du fichier n'existe pas.


En aparté, je vous recommande aussi un nom différent pour votre colonne compressedlength - je pris d'abord (en dépit de votre récit) comme étant destiné à stocker, eh bien, la longueur des données compressées (et une partie écrite de ma réponse pour répondre à cela). Peut-être que originalLength serait un meilleur nom?

+0

Excellent commentaire. Je vais faire cet ajustement et voir comment ça se passe. – longofest

+0

Donc, c'était apparemment un problème, mais pas le problème ... mise à jour de la question d'origine maintenant avec le dernier code, mais toujours obtenir 0 pour lire décompressé. – longofest

+0

... mais j'ai eu quelques fois plus que j'ai utilisé le tampon au lieu de toarray ... hmm ... laissez-moi travailler un peu plus ... – longofest

Questions connexes