【问题标题】:Read binary data (image) from separated file从分离的文件中读取二进制数据(图像)
【发布时间】:2014-09-23 13:57:31
【问题描述】:

我有一个包含员工数据和图像记录的文件。一名员工的每条记录及其数据、他的图像和他的妻子图像。 我无法更改文件结构

文本数据和图像之间有分隔符。

这里是一条记录的示例

记录号D01=员工姓名!=IMG1=员工头像~\IMG2=妻子头像^! \r\n

(D01= & !=IMG1= & ~\IMG2= & ^!) 是分隔符

这是编写文件的代码:

FileStream fs = new FileStream(filePath, FileMode.Create);
StreamWriter sw = new StreamWriter(fs, Encoding.UTF8);
BinaryWriter bw = new BinaryWriter(fs);

sw.Write(employeeDataString);
sw.Write("!=IMG1=");
sw.Flush();

bw.Write(employeeImg, 0, employeeImg.Length);
bw.Flush();

sw.Write(@"~\IMG2=");
sw.Flush();

bw.Write(wifeImg, 0, wifeImg.Length);
bw.Flush();

sw.Write("^!");
sw.Flush();

sw.Write(@"\r\n");
sw.Flush();

那么如何读取那个文件呢?

【问题讨论】:

  • 从根本上说,你有问题 - 除非你读取图像数据以确定文件的长度(如果它甚至支持它!)你无法可靠地检测文件何时结束并且条目的下一部分开始。基本上,这是一种损坏的文件格式。
  • 是什么让您认为图像文件不能包含在解释为文本时为 \r\n 的字节?
  • 不,你完全不正确。图像格式可以为所欲为。想象一个“原始”图像类型,它的标题仅由尺寸组成,然后是每个 RGB 像素 3 个字节(一个字节红色,一个字节绿色,一个字节蓝色)。现在想象一个像素,其红色值为 13,绿色值为 10。您最终会得到与 \r\n 等效的 ASCII 字节嵌入文件中。您需要从根本上理解,文件的内容只是 一个字节序列。读者可以适当地解释它。
  • 哦,这可能不太可能 - 但距离不可能还有很长的路要走。文件格式从根本上被破坏了——即使你能以某种方式保证该字节序列永远不会出现,扫描它与知道要读取多少数据开始相比是很痛苦的。
  • 分隔符在二进制数据中没有意义。它是什么图像格式?具有固定尺寸的 BMP?那么你有一个很好的机会..jpg?不能可靠地工作。但是您仍然可能(并且可能需要)编写一个宽松的代码,该代码可以以半自动方式读取损坏的数据并将它们转换为适当的东西..

标签: c# encoding binaryreader binarywriter


【解决方案1】:

有很多种文件;三种最常见的存储记录方式是

  • 固定大小的记录,最好是固定大小的字段。实现随机访问非常简单。
  • 标签和数据交织在一起的标记文件。有点复杂,但非常灵活,而且可读性仍然相当高,因为标签保存了数据的位置和长度。
  • 然后是分隔文件。总是很痛苦。

两个问题:

  • 您必须确保分隔符永远不会出现在数据中。当您拥有像图像这样的二进制数据时,并非 100% 可能......
  • 没有有效的方法来访问单个记录..

忽略第一个问题,这里有一段代码会将所有记录读入类ARecord的列表中。

FileStream fs;
BinaryReader br;
List<ARecord> theRecords;

class ARecord
{
    public string name { get; set; }
    public Image img1 { get; set; }
    public Image img2 { get; set; }
}

int readFile(string filePath)
{
    fs = new FileStream(filePath, FileMode.Open);
    br = new BinaryReader(fs, Encoding.UTF8);

    theRecords = new List<ARecord>();
    ARecord record = getNextRecord();
    while (record != null)
    {
        theRecords.Add(record);
        record = getNextRecord();
    }
    return theRecords.Count;
}

ARecord getNextRecord()
{
    ARecord record = new ARecord ();

    MemoryStream ms;
    System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
    byte[] sepImg1 = enc.GetBytes(@"!=IMG1=");
    byte[] sepImg2 = enc.GetBytes(@"~\IMG2=");
    byte[] sepRec = enc.GetBytes(@"^!\r\n");

    record.name = enc.GetString(readToSep(sepImg1));

    ms = new MemoryStream(readToSep(sepImg2));
    if (ms.Length <= 0) return null;             // check for EOF
    record.img1 = Image.FromStream(ms);

    ms = new MemoryStream(readToSep(sepRec));
    record.img2 = Image.FromStream(ms);

    return record;
}

byte[] readToSep(byte[] sep)
{
    List<byte> data = new List<byte>();
    bool eor = false;
    int sLen = sep.Length;
    int sPos = 0;
    while (br.BaseStream.Position < br.BaseStream.Length && !eor )
    {
        byte b = br.ReadByte();
        data.Add(b);
        if (b != sep[sPos]) { sPos = 0; }
        else if (sPos < sLen - 1) sPos++; else eor = true;
    }
    if (data.Count > sLen ) data.RemoveRange(data.Count - sLen , sLen );
    return data.ToArray();
}

注意事项:

  • 没有任何错误检查。
  • 注意那些分隔符! @ 真的对吗??
  • 扩展代码创建记录号留给你

【讨论】:

  • 这应该可以,但我必须说你的命名约定对于 C# 来说有点奇怪(驼峰式大小写的类名,大写的字段)。
  • 你是对的。我已更正。它来自于在一个解决方案中同时拥有两个版本。不过,有时我喜欢为本地对象引用使用短大写名称,以使它们像 Label L = (Label)sender 一样突出
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-07-30
  • 2015-06-27
  • 2015-04-23
  • 2018-01-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多