Страницы

Поиск по вопросам

вторник, 10 декабря 2019 г.

C#. Разжатие файла при помощи GZip в многопоточной среде

#c_sharp #gzip


Пытаюсь разобраться как написать процесс сжатия и разжатия при помощи GZip в многопоточной
среде.

При разжатии файла в многопоточной среде выдает постоянно следующие ошибки: StackOverFlow,
System.OutOfMemory.

В режиме отладки на строках:

blockLength = BitConverter.ToInt32(lengthBuffer, 4);


и

_dataSize = BitConverter.ToInt32(compressedData[i], blockLength - 4)


во втором потоке присваиваются космические переменные, которые и ведут к фатальным
ошибкам. Пробовал ставить 4 байта, всячески "игрался", пытаясь реализовать по-разному.
Итог один. Прошу помощи!. Код ниже.

public abstract class GZip
    {
        protected static bool _cancelled = false;
        protected static bool _success = false;
        protected string sourceFile, destinationFile;
        protected static int _threads = Environment.ProcessorCount;
        protected const int buffer_size = 1024 * 1024;

        protected static int blockSize = 10000000;
        protected static byte[][] lastBuffer = new byte[_threads][];
        protected static byte[][] compressedData = new byte[_threads][];


        public GZip(string input, string output)
        {
            this.sourceFile = input;
            this.destinationFile = output;
        }

        public int CallBackResult()
        {
            if (!_cancelled && _success)
                return 0;
            return 1;
        }

        public void Cancel()
        {
            _cancelled = true;
        }

        public abstract void Launch();
    }

    class Decompressor : GZip
    {
        public Decompressor(string input, string output) : base(input, output)
        {


        }
        public override void Launch()
        {
            Console.Write("Decompressing");
            try
            {
                using (FileStream _compressedFile = new FileStream(sourceFile, FileMode.Open))
                {
                    using (FileStream _decompressedFile = new FileStream(sourceFile.Remove(sourceFile.Length
- 3), FileMode.Append))
                    {
                        int blockLength;
                        int _dataSize;
                        byte[] lengthBuffer = new byte[8];
                        Thread[] tPool = new Thread[_threads];

                        while (_compressedFile.Position < _compressedFile.Length)
                        {

                            for (int i = 0; (i < _threads) && (_compressedFile.Position
< _compressedFile.Length); i++)
                            {
                                Console.Write(".");

                                _compressedFile.Read(lengthBuffer, 0, lengthBuffer.Length);
                                blockLength = BitConverter.ToInt32(lengthBuffer, 4);
                                compressedData[i] = new byte[blockLength];
                                lengthBuffer.CopyTo(compressedData[i], 0);

                                _compressedFile.Read(compressedData[i], 8, blockLength
- 8);
                                _dataSize = BitConverter.ToInt32(compressedData[i],
blockLength - 4);
                                lastBuffer[i] = new byte[_dataSize];



                                tPool[i] = new Thread(Decompress);
                                tPool[i].Start(i);
                            }

                            for (int i = 0; (i < _threads) && (tPool[i] != null);)
                            {
                                if (tPool[i].ThreadState == ThreadState.Stopped)
                                {
                                    _decompressedFile.Write(lastBuffer[i], 0, lastBuffer[i].Length);
                                    i++;
                                }
                            }
                        }
                    }
                }
            }

            catch (Exception ex)
            {
                Console.WriteLine("Error is occured!\n Method: {0}\n Error description
{1}", ex.TargetSite, ex.Message);
                _cancelled = true;
            }
        }

        public static void Decompress(object i)
        {
            using (MemoryStream _memoryStream = new MemoryStream(compressedData[(int)i]))
            {
                using (GZipStream cs = new GZipStream(_memoryStream, CompressionMode.Decompress))
                {
                    cs.Read(lastBuffer[(int)i], 0, lastBuffer[(int)i].Length);
                }
            }
        }
    }
}

    


Ответы

Ответ 1



Ваш код неявно предполагает, что GZipStream'у можно скормить случайный фрагмент сжатого файла, и он его сможет разжать. Это не так. В сжатом файле есть служебная информация, которую GZipStream не может найти, если ему достаётся лишь кусок файла. Судя по всему, случайные данные воспринимаются кодом как управляющая информация, что и приводит к проблемам. Формат gzip состоит из нескольких, кладущихся впритык кусков. Какие бы ни были размеры каждого куска, архиватор распаковывает куски один за одним. В начале каждого куска есть десятибайтный заголовок, содержащий информацию о куске. Но в этом заголовке нет информации о том, какого размера упакованные данные. За заголовком следует информация, упакованная алгоритмом Deflate, после которой — контрольная сумма и размер распакованных данных. Поэтому склеивать файл из кусков легко, просто можно укладывать их один за другим. А вот для того, чтобы разбить файл на правильные куски, требуется узнать границы куска. Я покамест не вижу, как сделать это, не осуществляя по сути распаковку Deflate. Ваш код по сути берёт случайные границы кусков, поэтому интерпретировать данные на границе куска как длину нельзя.

Ответ 2



После долгих мучений я все же разобрался как это реализовать по-человечески. Посмотреть код можно в моем профиле на Git: https://github.com/Mikezar/GZip

Комментариев нет:

Отправить комментарий