C #을 사용한 압축 / 압축 해제 문자열


144

나는 .net의 초보자입니다. C #에서 압축 및 압축 해제 문자열을 수행하고 있습니다. XML이 있고 문자열로 변환 한 후 압축 및 압축 해제를하고 있습니다. 코드를 압축 해제하고 문자열을 반환 할 때를 제외하고는 코드의 컴파일 오류가 없습니다 .XML의 절반 만 반환합니다.

아래는 내 코드입니다. 잘못된 부분을 수정하십시오.

암호:

class Program
{
    public static string Zip(string value)
    {
        //Transform string into byte[]  
        byte[] byteArray = new byte[value.Length];
        int indexBA = 0;
        foreach (char item in value.ToCharArray())
        {
            byteArray[indexBA++] = (byte)item;
        }

        //Prepare for compress
        System.IO.MemoryStream ms = new System.IO.MemoryStream();
        System.IO.Compression.GZipStream sw = new System.IO.Compression.GZipStream(ms, System.IO.Compression.CompressionMode.Compress);

        //Compress
        sw.Write(byteArray, 0, byteArray.Length);
        //Close, DO NOT FLUSH cause bytes will go missing...
        sw.Close();

        //Transform byte[] zip data to string
        byteArray = ms.ToArray();
        System.Text.StringBuilder sB = new System.Text.StringBuilder(byteArray.Length);
        foreach (byte item in byteArray)
        {
            sB.Append((char)item);
        }
        ms.Close();
        sw.Dispose();
        ms.Dispose();
        return sB.ToString();
    }

    public static string UnZip(string value)
    {
        //Transform string into byte[]
        byte[] byteArray = new byte[value.Length];
        int indexBA = 0;
        foreach (char item in value.ToCharArray())
        {
            byteArray[indexBA++] = (byte)item;
        }

        //Prepare for decompress
        System.IO.MemoryStream ms = new System.IO.MemoryStream(byteArray);
        System.IO.Compression.GZipStream sr = new System.IO.Compression.GZipStream(ms,
            System.IO.Compression.CompressionMode.Decompress);

        //Reset variable to collect uncompressed result
        byteArray = new byte[byteArray.Length];

        //Decompress
        int rByte = sr.Read(byteArray, 0, byteArray.Length);

        //Transform byte[] unzip data to string
        System.Text.StringBuilder sB = new System.Text.StringBuilder(rByte);
        //Read the number of bytes GZipStream red and do not a for each bytes in
        //resultByteArray;
        for (int i = 0; i < rByte; i++)
        {
            sB.Append((char)byteArray[i]);
        }
        sr.Close();
        ms.Close();
        sr.Dispose();
        ms.Dispose();
        return sB.ToString();
    }

    static void Main(string[] args)
    {
        XDocument doc = XDocument.Load(@"D:\RSP.xml");
        string val = doc.ToString(SaveOptions.DisableFormatting);
        val = Zip(val);
        val = UnZip(val);
    }
} 

내 XML 크기는 63KB입니다.


1
UTF8Encoding (또는 UTF16 또는 기타) 및 GetBytes / GetString을 사용하는 경우 문제가 "고정"될 것으로 생각됩니다 . 또한 코드를 크게 단순화합니다. 을 사용하는 것이 좋습니다 using.

char을 바이트로 변환 할 수 없으며 그 반대로 변환 할 수 없습니다 (간단한 캐스트 사용). 압축 / 압축 해제에는 인코딩과 동일한 인코딩을 사용해야합니다. 아래의 xanatos 답변을 참조하십시오.
Simon Mourier

@pst 아니요 그렇지 않습니다. 당신은 Encoding잘못된 길을 가고 있을 것 입니다. xanatos의 답변에 따라 base-64가 필요합니다
Marc Gravell

@Marc Gravell True, 서명 / 의도 부분을 놓쳤습니다. 필자가 처음으로 서명을 선택한 것은 아닙니다.

답변:


257

문자열을 압축 / 압축 해제하는 코드

public static void CopyTo(Stream src, Stream dest) {
    byte[] bytes = new byte[4096];

    int cnt;

    while ((cnt = src.Read(bytes, 0, bytes.Length)) != 0) {
        dest.Write(bytes, 0, cnt);
    }
}

public static byte[] Zip(string str) {
    var bytes = Encoding.UTF8.GetBytes(str);

    using (var msi = new MemoryStream(bytes))
    using (var mso = new MemoryStream()) {
        using (var gs = new GZipStream(mso, CompressionMode.Compress)) {
            //msi.CopyTo(gs);
            CopyTo(msi, gs);
        }

        return mso.ToArray();
    }
}

public static string Unzip(byte[] bytes) {
    using (var msi = new MemoryStream(bytes))
    using (var mso = new MemoryStream()) {
        using (var gs = new GZipStream(msi, CompressionMode.Decompress)) {
            //gs.CopyTo(mso);
            CopyTo(gs, mso);
        }

        return Encoding.UTF8.GetString(mso.ToArray());
    }
}

static void Main(string[] args) {
    byte[] r1 = Zip("StringStringStringStringStringStringStringStringStringStringStringStringStringString");
    string r2 = Unzip(r1);
}

그 기억 Zip반환 byte[], 동안 Unzip리턴한다 string. 문자열을 원한다면 ZipBase64로 인코딩 할 수 있습니다 (예 :을 사용하여 Convert.ToBase64String(r1)) (결과 Zip는 매우 이진입니다! 화면에 인쇄하거나 XML로 직접 쓸 수있는 것은 아닙니다)

제안 된 버전은 .NET 2.0, .NET 4.0의 경우 MemoryStream.CopyTo.

중요 : 압축 된 내용 GZipStream은 모든 입력이 있음을 알 때까지 (즉, 효과적으로 압축하려면 모든 데이터가 필요함) 출력 스트림에 기록 할 수 없습니다 . 당신은 확실히 당신이 있는지 확인 필요 Dispose()GZipStream출력 스트림을 검사하기 전에 (예 mso.ToArray()). 이것은 using() { }위 의 블록 으로 수행됩니다 . 참고는 것을 GZipStream가장 안쪽 블록이며, 내용은 외부에서 액세스 할 수 있습니다. 동일은 압축 해제에 간다 : Dispose()GZipStream액세스를 시도하기 전에 데이터입니다.


답장을 보내 주셔서 감사합니다. 코드를 사용할 때 컴파일 오류가 발생합니다. "CopyTo ()에 네임 스페이스 또는 어셈블리 참조가 없습니다." 그 후 Google에서 검색하여 .NET 4 Framework의 CopyTo () 부분을 찾았습니다. 그러나 .net 2.0 및 3.5 프레임 워크에서 작업하고 있습니다. 제안 해주세요. :)
Mohit Kumar

출력 스트림에서 ToArray ()를 호출하기 전에 GZipStream을 폐기해야한다는 점을 강조하고 싶습니다. 그 비트를 무시했지만 차이가 있습니다!
젖은 국수

1
.net 4.5에서 가장 효과적인 압축 방법은 무엇입니까?
MonsterMMORPG

1
대리 쌍을 포함하는 문자열의 경우 (예 : 압축 해제 된 문자열! = 원본) 실패합니다 string s = "X\uD800Y". 인코딩을 UTF7로 변경하면 작동하지만 UTF7을 사용하면 모든 문자를 표현할 수 있습니까?
digEmAll

@digEmAll 잘못된 대리 쌍이 있으면 작동하지 않는다고 말할 것입니다 (귀하의 경우와 마찬가지로). UTF8 GetByes 변환은 유효하지 않은 대리 쌍을 0xFFFD로 자동 대체합니다.
xanatos

103

이 코드 조각 에 따르면 이 코드를 사용하고 정상적으로 작동합니다.

using System;
using System.IO;
using System.IO.Compression;
using System.Text;

namespace CompressString
{
    internal static class StringCompressor
    {
        /// <summary>
        /// Compresses the string.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <returns></returns>
        public static string CompressString(string text)
        {
            byte[] buffer = Encoding.UTF8.GetBytes(text);
            var memoryStream = new MemoryStream();
            using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
            {
                gZipStream.Write(buffer, 0, buffer.Length);
            }

            memoryStream.Position = 0;

            var compressedData = new byte[memoryStream.Length];
            memoryStream.Read(compressedData, 0, compressedData.Length);

            var gZipBuffer = new byte[compressedData.Length + 4];
            Buffer.BlockCopy(compressedData, 0, gZipBuffer, 4, compressedData.Length);
            Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gZipBuffer, 0, 4);
            return Convert.ToBase64String(gZipBuffer);
        }

        /// <summary>
        /// Decompresses the string.
        /// </summary>
        /// <param name="compressedText">The compressed text.</param>
        /// <returns></returns>
        public static string DecompressString(string compressedText)
        {
            byte[] gZipBuffer = Convert.FromBase64String(compressedText);
            using (var memoryStream = new MemoryStream())
            {
                int dataLength = BitConverter.ToInt32(gZipBuffer, 0);
                memoryStream.Write(gZipBuffer, 4, gZipBuffer.Length - 4);

                var buffer = new byte[dataLength];

                memoryStream.Position = 0;
                using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
                {
                    gZipStream.Read(buffer, 0, buffer.Length);
                }

                return Encoding.UTF8.GetString(buffer);
            }
        }
    }
}

2
이 코드를 게시 해 주셔서 감사합니다. 나는 그것을 내 프로젝트에 떨어 뜨 렸고 아무런 문제없이 즉시 작동했습니다.
BoltBait

3
그렇습니다. 또한 처음 4 바이트로 추가 길이의 아이디어를 좋아했다
JustADev

2
이것이 가장 좋은 대답입니다. 이것은 답변으로 표시되어야합니다!
Eriawan Kusumawardhono

1
@ 매트가 .zip 파일을 압축하는 등의 - .PNG 이미 압축 된 내용입니다
푸보

2
답변으로 표시된 답변이 안정적이지 않습니다. 이것이 가장 좋은 대답입니다.
Sari

38

Stream.CopyTo () 메서드와 함께 .NET 4.0 이상이 출현하면서 업데이트 된 접근 방식을 게시 할 것이라고 생각했습니다.

또한 아래 버전은 일반 문자열을 Base64 인코딩 문자열로 압축하고 그 반대의 경우를 포함하는 자체 포함 클래스의 명확한 예로 유용하다고 생각합니다.

public static class StringCompression
{
    /// <summary>
    /// Compresses a string and returns a deflate compressed, Base64 encoded string.
    /// </summary>
    /// <param name="uncompressedString">String to compress</param>
    public static string Compress(string uncompressedString)
    {
        byte[] compressedBytes;

        using (var uncompressedStream = new MemoryStream(Encoding.UTF8.GetBytes(uncompressedString)))
        {
            using (var compressedStream = new MemoryStream())
            { 
                // setting the leaveOpen parameter to true to ensure that compressedStream will not be closed when compressorStream is disposed
                // this allows compressorStream to close and flush its buffers to compressedStream and guarantees that compressedStream.ToArray() can be called afterward
                // although MSDN documentation states that ToArray() can be called on a closed MemoryStream, I don't want to rely on that very odd behavior should it ever change
                using (var compressorStream = new DeflateStream(compressedStream, CompressionLevel.Fastest, true))
                {
                    uncompressedStream.CopyTo(compressorStream);
                }

                // call compressedStream.ToArray() after the enclosing DeflateStream has closed and flushed its buffer to compressedStream
                compressedBytes = compressedStream.ToArray();
            }
        }

        return Convert.ToBase64String(compressedBytes);
    }

    /// <summary>
    /// Decompresses a deflate compressed, Base64 encoded string and returns an uncompressed string.
    /// </summary>
    /// <param name="compressedString">String to decompress.</param>
    public static string Decompress(string compressedString)
    {
        byte[] decompressedBytes;

        var compressedStream = new MemoryStream(Convert.FromBase64String(compressedString));

        using (var decompressorStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
        {
            using (var decompressedStream = new MemoryStream())
            {
                decompressorStream.CopyTo(decompressedStream);

                decompressedBytes = decompressedStream.ToArray();
            }
        }

        return Encoding.UTF8.GetString(decompressedBytes);
    }

다음은 확장 메서드 기술을 사용하여 String 클래스를 확장하여 문자열 압축 및 압축 해제를 추가하는 다른 방법입니다. 아래 클래스를 기존 프로젝트에 드롭 한 다음 사용할 수 있습니다.

var uncompressedString = "Hello World!";
var compressedString = uncompressedString.Compress();

var decompressedString = compressedString.Decompress();

재치 :

public static class Extensions
{
    /// <summary>
    /// Compresses a string and returns a deflate compressed, Base64 encoded string.
    /// </summary>
    /// <param name="uncompressedString">String to compress</param>
    public static string Compress(this string uncompressedString)
    {
        byte[] compressedBytes;

        using (var uncompressedStream = new MemoryStream(Encoding.UTF8.GetBytes(uncompressedString)))
        {
            using (var compressedStream = new MemoryStream())
            { 
                // setting the leaveOpen parameter to true to ensure that compressedStream will not be closed when compressorStream is disposed
                // this allows compressorStream to close and flush its buffers to compressedStream and guarantees that compressedStream.ToArray() can be called afterward
                // although MSDN documentation states that ToArray() can be called on a closed MemoryStream, I don't want to rely on that very odd behavior should it ever change
                using (var compressorStream = new DeflateStream(compressedStream, CompressionLevel.Fastest, true))
                {
                    uncompressedStream.CopyTo(compressorStream);
                }

                // call compressedStream.ToArray() after the enclosing DeflateStream has closed and flushed its buffer to compressedStream
                compressedBytes = compressedStream.ToArray();
            }
        }

        return Convert.ToBase64String(compressedBytes);
    }

    /// <summary>
    /// Decompresses a deflate compressed, Base64 encoded string and returns an uncompressed string.
    /// </summary>
    /// <param name="compressedString">String to decompress.</param>
    public static string Decompress(this string compressedString)
    {
        byte[] decompressedBytes;

        var compressedStream = new MemoryStream(Convert.FromBase64String(compressedString));

        using (var decompressorStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
        {
            using (var decompressedStream = new MemoryStream())
            {
                decompressorStream.CopyTo(decompressedStream);

                decompressedBytes = decompressedStream.ToArray();
            }
        }

        return Encoding.UTF8.GetString(decompressedBytes);
    }

2
Jace : usingMemoryStream 인스턴스에 대한 명령문 이 누락 된 것 같습니다 . 그리고 F # 개발자들에게 : usecompressorStream / decompressorStream 인스턴스에 키워드 를 사용하지 ToArray()
마십시오. 그것들

1
추가적인 검증을 추가 할 때 GZipStream을 사용하는 것이 더 좋습니까? GZipStream 또는 DeflateStream 클래스?
Michael Freidgeim

2
@Michael Freidgeim 나는 메모리 스트림을 압축하고 압축 해제하기 위해 그렇게 생각하지 않을 것입니다. 파일 또는 신뢰할 수없는 전송의 경우에는 의미가 있습니다. 특정 사용 사례에서 고속이 매우 바람직하므로 피할 수있는 오버 헤드가 모두 더 좋습니다.
Jace

고체. JSON의 20MB 문자열을 4.5MB로 줄였습니다. 🎉
James Esh

1
훌륭하게 작동하지만 사용 후에는 메모리 스트림을 폐기하거나 @knocte가 제안한대로 모든 스트림을 사용해야합니다.
Sebastian

8

이 버전은 async / await 및 IEnumerables를 사용하는 .NET 4.5 이상에서 업데이트 된 버전입니다.

public static class CompressionExtensions
{
    public static async Task<IEnumerable<byte>> Zip(this object obj)
    {
        byte[] bytes = obj.Serialize();

        using (MemoryStream msi = new MemoryStream(bytes))
        using (MemoryStream mso = new MemoryStream())
        {
            using (var gs = new GZipStream(mso, CompressionMode.Compress))
                await msi.CopyToAsync(gs);

            return mso.ToArray().AsEnumerable();
        }
    }

    public static async Task<object> Unzip(this byte[] bytes)
    {
        using (MemoryStream msi = new MemoryStream(bytes))
        using (MemoryStream mso = new MemoryStream())
        {
            using (var gs = new GZipStream(msi, CompressionMode.Decompress))
            {
                // Sync example:
                //gs.CopyTo(mso);

                // Async way (take care of using async keyword on the method definition)
                await gs.CopyToAsync(mso);
            }

            return mso.ToArray().Deserialize();
        }
    }
}

public static class SerializerExtensions
{
    public static byte[] Serialize<T>(this T objectToWrite)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(stream, objectToWrite);

            return stream.GetBuffer();
        }
    }

    public static async Task<T> _Deserialize<T>(this byte[] arr)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            await stream.WriteAsync(arr, 0, arr.Length);
            stream.Position = 0;

            return (T)binaryFormatter.Deserialize(stream);
        }
    }

    public static async Task<object> Deserialize(this byte[] arr)
    {
        object obj = await arr._Deserialize<object>();
        return obj;
    }
}

이를 통해 BinaryFormatter문자열 대신 모든 지원을 직렬화 할 수 있습니다 .

편집하다:

당신이 돌봐 해야하는 경우, 당신 Encoding은 그냥 Convert.ToBase64String (byte []) 사용할 수 있습니다 ...

예가 필요한 경우이 답변을 살펴보십시오!


샘플을 디시 리얼 라이즈하고 편집하기 전에 스트림 위치를 재설정해야합니다. 또한 XML 주석은 관련이 없습니다.
Magnus Johansson

이것은 주목할만한 가치가 있지만 UTF8 기반의 경우에만 작동합니다. 예를 들어, åäö와 같은 스웨덴어 문자를 직렬화 / 역 직렬화하는 문자열 값에 추가하면 왕복 테스트에 실패합니다. : /
bc3tech

이 경우을 사용할 수 있습니다 Convert.ToBase64String(byte[]). 이 답변을 참조하십시오 ( stackoverflow.com/a/23908465/3286975 ). 그것이 도움이되기를 바랍니다!
z3nth10n

6

여전히 GZip 헤더의 매직 넘버가 맞지 않는 분들 . GZip 스트림을 전달하고 있는지 확인하십시오. 오류 및 문자열이 PHP 를 사용하여 압축 된 경우 다음과 같은 작업을 수행해야합니다.

       public static string decodeDecompress(string originalReceivedSrc) {
        byte[] bytes = Convert.FromBase64String(originalReceivedSrc);

        using (var mem = new MemoryStream()) {
            //the trick is here
            mem.Write(new byte[] { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0, 8);
            mem.Write(bytes, 0, bytes.Length);

            mem.Position = 0;

            using (var gzip = new GZipStream(mem, CompressionMode.Decompress))
            using (var reader = new StreamReader(gzip)) {
                return reader.ReadToEnd();
                }
            }
        }

예외가 발생했습니다. 예외 발생 : System.dll의 'System.IO.InvalidDataException'추가 정보 : GZip 바닥 글의 CRC가 압축 해제 된 데이터에서 계산 된 CRC와 일치하지 않습니다.
Dainius Kreivys
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.