1

我将各种格式的图像(.png、.jpg、.bmp 等)作为压缩文本存储在 SQL Server 2005 表的文本列中。我需要读取该行,解压缩图像并将其存储在另一个表的图像列中。

我正在使用 SharpZip 库,所有示例都处理文件源和目标。我找不到任何涵盖从一个变量解压缩到另一个变量的内容。非常感谢说明此内容的代码片段或指向相关资源的链接。

编辑:更多信息 - 数据存储在 TEXT 列中。如下所示(为显示而缩写的文本栏):

ImageID  ImageData
1       FORMAT-ZIPV3 UEsDBBQAAAAIAOV6wzxdTnDvshs...
2       FORMAT-ZIPV3 UEsDBBQAAAAIAAF2yjxGncjOLgA...
3       FORMAT-ZIPV3 UEsDBBQAAAAIAKd6yjyjnQNr6gg...
4       FORMAT-ZIPV3 UEsDBBQAAAAIALdNyzyrPC8EMJw...
5       FORMAT-ZIPV3 UEsDBBQAAAAIAA1rOD1nZY1t0f0...
6       FORMAT-ZIPV3 UEsDBBQAAAAIANZplj2seyJ+VmM...
7       FORMAT-ZIPV3 UEsDBBQAAAAIAC5vhD27LPbPcv8...
8       FORMAT-ZIPV3 UEsDBBQAAAAIAK1qKz5DJNH3xMg...
9       FORMAT-ZIPV3 UEsDBBQAAAAIAHVkEztC3th/9hs...
10     FORMAT-ZIPV3 UEsDBBQAAAAIAEtXKz7DXHUdvow...

我可以肯定的是,图像在插入表格之前使用 SharpZip 在过程中的某个时间点进行了压缩。似乎格式信息在插入之前已添加到数据的开头。

看看这些数据,有没有人知道这些图像数据是如何被操纵的?同样,我需要将未压缩的图像数据放入有利于阅读的数据类型列中,以便在网页上显示。

编辑:好的,我很难过。执行以下代码会产生错误“无法将参数值从 Int32 转换为 Byte[]”。它似乎将字节数组的长度放入字节数组的值中......

        commandUncompressed.Connection = connectionUncompressed;
        commandUncompressed.Parameters.Add("@Image_k", SqlDbType.VarChar, 10);
        commandUncompressed.Parameters.Add("@ImageContents", SqlDbType.Image);
        commandUncompressed.CommandText = sqlSaveImage;

        connectionUncompressed.Open();
        reader = command.ExecuteReader();

        if (reader.HasRows)
        {
            while (reader.Read())
            {
                Console.WriteLine(reader["Image_k"].ToString());  // Merely for testing
                String format = reader["ImageContents_Compressed"].ToString().Substring(0, 12);
                var offset = 13; //"FORMAT-ZIPV3 ".Length;
                var s = reader["ImageContents_Compressed"].ToString().Substring(offset);
                var bytes = Convert.FromBase64String(s);
                if (format == "FORMAT-ZIPV2 ")
                {
                    bytes = ConvertStringToBytes(s);    // Not a Base-64 encoded string? External conversion function utilized.
                }

                using (var zis = new ZipInputStream(new MemoryStream(bytes)))
                {
                    ZipEntry zipEntry = zis.GetNextEntry();   // Doesn't seem to work unless an entry has been referenced
                    byte[] buffer = new byte[zis.Length];
                    commandUncompressed.Parameters["@Image_k"].Value = reader["Image_k"].ToString();
                    commandUncompressed.Parameters["@ImageContents"].Value = zis.Read(buffer, 0, buffer.Length);
                    commandUncompressed.ExecuteNonQuery();

                }
            }
        }

它似乎可以很好地从源文本列中读取数据。我只是无法弄清楚如何将其放入图像类型参数中。缓冲区变量的值显示字节数组的长度,而不是实际字节。也许这就是 value 属性通常显示的字节数组?我如此接近,却又如此遥远。:/

编辑:好的,我是个傻瓜。我做了以下更正,它有效!

zis.Read(buffer, 0, buffer.Length)                            
commandUncompressed.Parameters["@ImageContents"].Value = buffer;

此时我只能处理 FORMAT-ZIPV3 数据,因为我还没有弄清楚如何解码 FORMAT-ZIP2 字符串。以下是 V2 数据的样本。如果有人能够确定编码,请告诉我。如果使用 BZIP 而不是 ZIP 格式进行压缩,会有所不同吗?

ImageID ImageData
1          FORMAT-ZIPV2 504B03041400020008005157422A2E25FDBAF26701008D6901000E...
2          FORMAT-ZIPV2 504B03041400020008009159422A7FC94BA2B2540500D35705000E...
3          FORMAT-ZIPV2 504B0304140002000800685A422A0CAA51F4473A0600B97206000E...
4          FORMAT-ZIPV2 504B03041400020008001D5D422A770BD3ED201902002C4A02000E...
5          FORMAT-ZIPV2 504B0304140002000800325E422A4B6C2FB4045001001C6E01000E...
6          FORMAT-ZIPV2 504B03041400020008006F72422A5F793AC1A1F00200ECF302000E...
7          FORMAT-ZIPV2 504B0304140002000800D572422A1B348A731DE5000085EB00000E...
8          FORMAT-ZIPV2 504B03041400020008003D73422A8AEBB7F855640300DD1B04000E...
9          FORMAT-ZIPV2 504B03041400020008006368D528C5D0A6BA794900004A2502000E...
10         FORMAT-ZIPV2 504B03041400020008008E5B6C2A2D9E9C33D7AF05005CEC05000E...
4

3 回答 3

3

在回答类似问题时,sqlmonster.com 上的某个人提供了一个漂亮的 VarBinaryStream 类。它适用于列类型varbinary(max).

如果您的数据存储在 varbinary(max) 中,并且是 zip 格式,您可以使用该类来实例化 VarBinaryStream,然后围绕实例化 ZipInputStream ,然后 ba-da-boom,您就在那里。只需从 ZipInputStream 中读取。

在 C# 中它可能看起来像这样

using (var imageSrc = new VarBinarySource(connection, 
                                          "Table.Name", 
                                          "Column",
                                          "KeyColName",
                                          1))
{
    using (var s = new VarBinaryStream(imageSrc))
    {
        using(var zis = new ZipInputStream(s))
        {
           ....
        }
    }
}

如果图像很小,那么您可能不想要所有这些流媒体内容。如果列是 abinary(n)或 avarbinary(n)其中 n 小于 8000,只需使用SqlBinary类型并将所有数据读入内存,然后围绕它实例化一个 MemoryStream。更简单。在 VB.NET 中,它看起来像这样:

Dim bytes as Bytes()
bytes = dr.GetSqlBinary(columnNumber)
Using ms As New MemoryStream(bytes)
    Using zis As New ZipInputStream(ms)
        ...
    End Using
End Using 

最后,我将质疑对 .jpg 图像和类似图像应用 zip 压缩是否明智。jpg格式已经被压缩了;在将数据放入 SQL Server 之前再次对其进行压缩不会导致数据明显变小。它只会增加处理时间。如果可能的话,我建议您重新考虑存储压缩图像的设计。

于 2011-04-16T09:44:58.197 回答
1

好的,通过您提供的更新,包含数据格式,您可以得出一些结论。

数据是一个实际的字符串。怀疑它是 Base64 编码的字符串,我做了一个小测试并用于Convert.ToBase64String()包含 zip 文件的字节流。它看起来像这样: UEsDBBQAAAAIAJJyYyk3M56F+QIAA...

啊哈!您有一个真正的 zip 文件的字节数据的 base64 编码(字符串)版本。要对其进行解码,请去除前缀,然后使用FromBase64String()获取字节数组,插入 MemoryStream,然后使用 ZipInputStream 读取它。

像这样的东西:

var offset = "FORMAT-ZIPV3 ".Length(); 
var s = sqlReader["CompressedImage"].ToString().Substring(offset);
var bytes = Convert.FromBase64String(s);    
using (var zis = new ZipInputStream(new MemoryStream(bytes))) 
{
    ...
    zis.Read(...);
    ...
}

如果数据“真的很长”,您将希望将其从该表中流出,而不是仅仅将其读入一个大字符串并进行转换。我不知道文本列可以有多大,但假设它可能是 500mb,你不想要一个 500mb 的字符串,并且你不想用 Convert.FromBase64String() 转换一个 500mb 的字符串。在这种情况下,您需要使用Base64Stream或 System.Security.Cryptography 命名空间中的FromBase64Transform类。


社论评论。压缩图像数据有点倒退。图像可能已经压缩。但是通过进行base64编码来复合这种后向性,从而扩展数据...... ??? 那是三重倒退。这根本没有意义。我知道这就是您的供应商提供的方式。


好的,随着您的进一步更新,使用以下格式:

ImageID ImageData
1          FORMAT-ZIPV2 504B03041400020008005157422A2E25FDBAF26701008D6901000E...
2          FORMAT-ZIPV2 504B03041400020008009159422A7FC94BA2B2540500D35705000E...

该数据仍然是 zipfile 数据,但它被编码为简单的十六进制数字。您需要将其转换为字节数组。这是一些代码来做到这一点。

public static class ConvertEx
{
    static readonly String prefix= "FORMAT-ZIPV2 ";

    public static string ToHexString(byte[] b)
    {
        System.Text.StringBuilder sb1 = new System.Text.StringBuilder();
        int i = 0;
        for (i = 0; i < b.Length; i++)
        {
            sb1.Append(System.String.Format("{0:X2}", b[i]));
        }
        return sb1.ToString().ToLower();
    }

    public static byte[] ToByteArray(string s)
    {
        if (s.StartsWith(prefix))
        {
            System.Console.WriteLine("removing prefix");
            s = s.Substring(prefix.Length);
        }
        s= s.Trim(); // whitespace
        System.Console.WriteLine("length: {0}", s.Length);

        var r= new byte[s.Length/2];
        for (int i = 0; i < s.Length; i+=2)
        {
            r[i/2] = (byte) Convert.ToUInt32(s.Substring(i,2), 16);
        }
        return r;
    }
}

你可以这样使用:

        string s = GetStringContentFromDatabase()
        var decoded = ConvertEx.ToByteArray(s);

        using (var ms = new MemoryStream(decoded))
        {
            // use DotNetZip to read the zip file
            // SharpZipLib is something similar...
            using (var zip = ZipFile.Read(ms))
            {
                // print out the list of entries in the zipfile
                foreach (var e in zip)
                {
                    System.Console.WriteLine("{0}", e.FileName);
                }
            }
        }
于 2011-04-16T21:32:20.023 回答
0

SharpZip Wiki上的示例使用 Stream 对象 - 虽然示例确实使用了 File,但您可以在此处轻松使用MemoryStream对象,并且示例的工作方式相同。

于 2011-04-15T15:22:44.143 回答