【问题标题】:How can I use the DeflateStream class on one line in a file?如何在文件的一行中使用 DeflateStream 类?
【发布时间】:2013-08-21 13:13:11
【问题描述】:

我有一个包含纯文本和一些压缩文本的文件,例如:

Version 01
Maker SomeCompany

l 73
mark
h�22V0P���w�/�+Q0���L)�66□ // This line was compressed using DeflateZLib
endmark

微软似乎有一个解决方案,DeflateStream 类,但他们的示例显示了如何在整个文件中使用它,而我不知道如何只在文件的一行中使用它。

到目前为止,我有以下内容:

bool isDeflate = false;

using (var fs = new FileStream(@"C:\Temp\MyFile.dat", FileMode.Open)
using (var reader = new StreamReader(fs))
{
     string line;
     while ((line = reader.ReadLine()) != null)
     {
         if (isDeflate)
         {
             if (line == "endmark")
             {
                 isDeflate = false;
             }
             else
             {
                 line = DeflateSomehow(line);
             }
         }

         if (line == "mark")
         {
             isDeflate = true;
         }

         Console.WriteLine(line);
     }
}

public string DeflateSomehow(string line)
{
    // How do I deflate just that string?
}

由于该文件不是由我创建的(我们只是在读取它),我们无法控制它的结构......但是,我并没有被我现在拥有的代码所束缚。如果我需要更改更多内容而不是简单地弄清楚如何实现 DeflateSomehow 方法,那么我也可以。

【问题讨论】:

标签: c# stream filestream deflate deflatestream


【解决方案1】:

deflate 流适用于二进制数据。文本文件中间的任意二进制块也称为:损坏的文本文件。没有理智的解码方法:

  • 你不能读“行”,因为在谈论二进制数据时没有“行”的定义; CR/LF/CRLF/etc 的任何组合都可以在二进制数据中完全随机出现
  • 您无法读取“字符串行”,因为这表明您正在通过Encoding 运行数据;但由于这不是文本数据,再说一遍:这只会给你带来无法处理的乱码(读取时会丢失数据)

现在,这两个问题中的第二个问题可以通过读取Stream API 而不是StreamReader API 来解决,因此您只能读取二进制;然后,您需要自己查找行尾,使用Encoding 来探测您能做的事情(注意,如果您使用多/可变字节编码,例如 UTF-8,这并不像听起来那么简单) .

然而,这两个问题中的第一个问题本身就无法解决。为了可靠地做到这一点,您需要某种二进制帧协议——同样,文本文件中不存在这种协议。看起来该示例使用了“mark”和“endmark” - 同样,从技术上讲,这些可能会随机发生,但对于 99.999% 的情况,您可能会侥幸逃脱.那么,诀窍是使用StreamEncoding 手动读取整个文件,寻找“标记”和“结束标记” - 并从压缩数据位中剥离编码为文本的位。然后通过正确的Encoding 运行编码为文本的片段。

但是!在您读取二进制文件时,这很简单:您只需缓冲适当的数量(使用写入数据的任何帧/哨兵协议),然后使用类似的东西:

using(var ms = new MemoryStream(bytes))
using(var inflate = new GZipStream(ms, CompressionMode.Decompress))
{
    // now read from 'inflate'
}

加上l 73 标记,以及它是ASCII 的信息,它变得更加可行。

这对我不起作用,因为 SO 上的数据已经损坏(以文本形式发布二进制文件),但基本上类似于:

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Text.RegularExpressions;
class Program
{
    static void Main()
    {
        using (var file = File.OpenRead("my.txt"))
        using (var buffer = new MemoryStream())
        {
            List<string> lines = new List<string>();
            string line;
            while ((line = ReadToCRLF(file, buffer)) != null)
            {
                lines.Add(line);
                Console.WriteLine(line);
                if (line == "mark" && lines.Count >= 2)
                {
                    var match = Regex.Match(lines[lines.Count - 2], "^l ([0-9]+)$");
                    int bytes;
                    if (match.Success && int.TryParse(match.Groups[1].Value, out bytes))
                    {
                        ReadBytes(file, buffer, bytes);
                        string inflated = Inflate(buffer);
                        lines.Add(inflated); // or something similar
                        Console.WriteLine(inflated);
                    }
                }
            }
        }

    }
    static string Inflate(Stream source)
    {
        using (var deflate = new DeflateStream(source, CompressionMode.Decompress, true))
        using (var reader = new StreamReader(deflate, Encoding.ASCII))
        {
            return reader.ReadToEnd();
        }
    }
    static void ReadBytes(Stream source, MemoryStream buffer, int count)
    {
        buffer.SetLength(count);
        int read, offset = 0;
        while (count > 0 && (read = source.Read(buffer.GetBuffer(), offset, count)) > 0)
        {
            count -= read;
            offset += read;
        }
        if (count != 0) throw new EndOfStreamException();
        buffer.Position = 0;
    }
    static string ReadToCRLF(Stream source, MemoryStream buffer)
    {
        buffer.SetLength(0);
        int next;
        bool wasCr = false;
        while ((next = source.ReadByte()) >= 0)
        {
            if(next == 10 && wasCr) { // CRLF
                // end of line (minus the CR)
                return Encoding.ASCII.GetString(
                     buffer.GetBuffer(), 0, (int)buffer.Length - 1);
            }
            buffer.WriteByte((byte)next);
            wasCr = next == 13;
        }
        // end of file
        if (buffer.Length == 0) return null;
        return Encoding.ASCII.GetString(buffer.GetBuffer(), 0, (int)buffer.Length);

    }
}

【讨论】:

  • 如果二进制数据以字节数、固定长度为前缀,或者长度存储在别处,则可以克服这些问题。
  • 你是对的。我与文件的提供者交谈,他们说以 l 开头的行是二进制长度的标记。我减少了我的文件示例,不认为l 73 很重要,但事实证明它很重要。所以,我知道乱码是73个字节,不能转成一行。
  • @Ashigore 这就是我提到框架协议的原因
  • @michael 他们能告诉你非二进制部分的编码是什么吗?是 ASCII 吗?还是更棘手的事情? l 73 确实解决了“什么时候结束”的问题。
  • @MarcGravell:ASCII,他们告诉我们,当膨胀时它是可读的 ASCII 文本。
猜你喜欢
  • 2010-10-20
  • 1970-01-01
  • 1970-01-01
  • 2011-02-05
  • 2012-05-18
  • 2022-11-01
  • 2011-04-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多