【问题标题】:How do I find Byte pattern in a byte array in C#?如何在 C# 的字节数组中找到字节模式?
【发布时间】:2012-07-07 21:15:32
【问题描述】:

我正在从事一个从文件中读取数据的项目,我需要处理其中的一些数据。

数据是二进制的,其中包含一些 ASCII 编码的文本。 如果这很重要,数据也会保存在 Big Endian 中。

我想要完成的是在这些数据中找到一个模式并操作该模式的一部分。

示例:(09 49 6E 76 65 6E 74 6F 72 79 0A 00 00 00 02 01 00)

这表示 Inventory 中有多少个字母的数字 9,后跟不带引号的 ASCII“Inventory”。 “0A”标记该 ASCII 文本的结尾,后跟 00 00 00 02 标记我们库存的大小“2”。 “01 00”标志着整个库存区域的结束。

示例 2:(04 53 6C 6F 74 00 02 00)

这表示 Slot 中有多少个字母的数字 4,后跟不带引号的 ASCII“Slot”。 “00”是 ASCII 文本和插槽号“02”之间的空格,后跟区域“00”的结尾。

我需要在文件中找到这些模式和其他几个模式。 然后我需要修改部分模式并写入磁盘。

ExampleModify: (04 53 6C 6F 74 00 02 00) "From above" to (04 53 6C 6F 74 00 07 00) 改变 "slot number '02' to '07'.

另一个需要注意的是,虽然我需要在文件中搜索不同大小、长度和包含的数据的多个模式,但这些模式的多个部分可能包含需要单独修改的不同数据一个整体。

澄清:(Inventory, Slot, id, Count) - 被视为一个人的信息。

记录的每个人都可以有多个 (Inventory, Slot, id, Count) 副本。

我想向用户显示此信息,并让他们选择修改组中的每个元素。

我不是一个优秀的程序员,想学习,如果你有例子我很感激,如果你有建议,请给它。如果你能把它调低就更好了,谢谢。 我现在有一个正在进行的工作,但我现在卡住了。如果你想看看我有什么,请告诉我。

我所拥有的总结:将文件读入 byte[] 然后将整个数组显示到控制台。就是这样。带有一点格式和一些调试信息,用于定位我读入数组的那个块。

这是我在 pastebin 上的代码的链接。 LINK

我意识到我没有得到所有出现的 (Inventory, Slot, id, Count) 我也需要解决这个问题。

编辑:示例 (09 49 6E 76 65 6E 74 6F 72 79 0A 00 00 00 02 01 00) 这是我正在读取的文件中固定长度的二进制数据块。如前所述,09 表示字符串的长度。字符串后面是 (0A 00 00 00 02 01 00) 的重要部分是 (02) 这是因为这是二进制数据片中唯一改变的字节。 “02”表示“2”,表示该特定人员记录有 2 个 (Slot, id, Count) 实例。

(File)
    (09 Inventory 0A 00 00 00 02 01 00) // Start of person 1's record with 2 instances.
        (Slot)
        (id)
        (Count)
        (Slot)
        (id)
        (Count)
    (Rotation)  // End of person 1's record

    (09 Inventory 0A 00 00 00 04 01 00) // Start of person 2's record with 4 instances.
        (Slot)
        (id)
        (Count)
        (Slot)
        (id)
        (Count)
        (Slot)
        (id)
        (Count)
        (Slot)
        (id)
        (Count)
    (Rotation)  // End of person 2's record
(File End)

我的想法是我想编辑“id”或通过添加到库存中来增加插槽数量并添加更多 (Slot, id, Count) 实例。

"id" - 包含项目 ID

“Slot” - 包含库存槽号

“计数” - 包含该插槽中有多少项目。 编辑说明:如果我不清楚,请告诉我,再次感谢。

【问题讨论】:

  • 我不太确定该尝试什么。但是,我将发布我拥有的代码,希望有人能指出我不知道更好的做法是多么愚蠢。我将在帖子的更新中添加代码链接。

标签: c# arrays search byte


【解决方案1】:

你说你不是一个好的程序员。然后我会提出一些基本的建议。

首先,对于任何问题,将其分解。尝试将其分解为您可以解决的更简单的子问题,或者认为会更容易解决。您说您想检查文件中的模式,然后可能会修改一些数据并将其写回。作为第一步,我想说这些是您需要解决的主要子问题才能获得完整的解决方案。

  • 读取文件
  • 在二进制数据中搜索模式
  • 进行修改
  • 写出修改后的数据。

您主要关注“搜索模式”部分,因此我将进一步关注 cmets。所有这些其他部分也可能很棘手,但您可以自己解决它们,或者在 stackoverflow 或其他地方寻找其他人如何解决它们的提示。

好的,现在,关于“搜索模式”。在我阅读您的描述时,

在我看来,字节数组中的第一个字节是文本长度。在 C# 代码中,展示您的模式的数据数组可以像这样显式声明:

byte[] data = new byte[] { 9, 0x49, 0x6E, 0x76, 0x65, 0x6E, 0x74,
               0x6F, 0x72, 0x79, 0x0A, 0, 0, 0, 2, 1, 0 } ;

当然,您不会显式声明数组。您将通过文件读取操作创建或填充数组。在这一点上,文件读取仍然是一个悬而未决的问题,但没关系 - 您可以单独解决它。而且,效果是一样的——当您成功读取文件时,您将拥有一个看起来像上面明确声明的数组的字节数组。

好的,现在您如何处理这些数据?第一个字节是字符串数据的长度。接下来的 N 个字节是字符串数据。然后你有一些其他的东西。

您没有准确描述您要进行哪些“修改”,但根据您提供的信息,应该很容易浏览数据。

如果您想获取数组中数据的“切片”,您可以这样做。

int length = (int) data[0];
byte[] s = new byte[length];
Array.Copy(data, 0, s, 0, length);
// now, s contains N bytes, representing the string

您可能想要做的下一件事是从您切出的那些字节中获取一个实际的字符串。为此,请使用the Encoding class

String word = System.Text.Encoding.UTF8.GetString(s);

我不理解字符串数据后面的数据模式。在某些情况下是00,在某些情况下是0A

但也许你知道这种模式。遵循相同的方式,您可以遍历一个“记录”的所有数据。当您到达记录的末尾时,请重新开始并处理下一个。进入下一个“记录”的关键是知道从你从文件中读取的数据中取出多大的数据片段。


这让我们回到了“读取文件”的子问题。从文件中获取适量数据的一种方法是只读取第一个字节,然后再读取文件中的 N 个字节。 (见System.IO.Stream.Read)。

byte[] size = new byte[1];
var fileStream = File.OpenRead(path);
int offset = 0;
int n;
n = fileStream.Read(size, offset, 1);
// size[0] now contains the byte of data indicating the number of bytes to follow

此时可以读取接下来的N个字节:

int length = (int)size[0];
byte[] stringData = new byte[length];
n = fileStream.Read(stringData, offset, length);

现在你可以得到字符串了。

String word = System.Text.Encoding.UTF8.GetString(stringData);

...等等。读取所有尾随字节。此时,fileStream 隐式持有的游标指向文件中的下一条记录。

【讨论】:

  • 感谢您的回复。感谢您的积极回应。我将更新我的帖子底部,并进行一些有希望的澄清。
  • 我不会对您拥有的内容进行完整的代码审查,但我会制作一些额外的 cmets。看起来您正在读取数据。您正在阅读 chunkSize 和 chunkEnd,等等。根据最初的问题,你比我想象的要走得更远。你有一堆代码,但你还没有发现你遇到的具体问题。什么是障碍?
  • 我是一名编程初学者,到目前为止,我只是想做这个项目并阅读昨天的内容以了解我现在的位置。但是,如何找到我的解决方案以能够以有意义的方式“切片”和操作数据的逻辑超出了我的知识水平。我的知识水平远远超出我的知识水平,可能应该先学习更多基本的东西,但它们并没有引起我的注意,所以这就是我从事这个项目的原因。这有意义吗?
  • 是的,这是有道理的,我认为您的学习方式是一种很好的学习方式。我仍然认为打破问题是做事的好方法。你似乎在路上。如果有障碍,您很可能可以在这里获得帮助。如果这只是一个一般性的问题,例如“我想阅读这个文件然后修改它,你能帮帮我吗?”那么……你会得到更少的具体帮助。
【解决方案2】:

我同意@Cheeso 上面所说的,这是一个好的开始。我将描述一个替代解决方案:

  1. 为所有已知部分定义操作码(一个以 0x4 0x56 开头,一个以 0xA 0x2 开头,等等)。
  2. 读取内存中的文件。
  3. 对内存中的字节数组应用匹配算法并提取每个匹配模式的起始位置。出于性能原因,我建议使用一种众所周知的搜索算法(或其变体),例如 Knuth-Morris-PrattBoyer-Moore
  4. 此时您应该在应用程序中缓存您找到的所有模式的所有开始位置。您可以为此使用列表字典(其中键是模式类型),但您可以将其包含到自定义类(在文件中具有模式 id 和模式偏移量),然后只需使用列表并使用 LINQ 访问所有内容.
  5. 在需要时访问和解码实际模式,因此不要在读取文件后立即进行。我想你对它们有不同的用途,并不是所有的人都会在有人加载文件后立即出现。

因此,这已经为您提供了文件中所有模式出现的列表。您可以使用它向用户显示某种列表。

我快速浏览了您的代码。它真的很大,都塞满了几个大功能。您需要对其进行一些结构化。每个任务需要一个类来加载和解析文件,然后是许多较小的函数。

当您在应用程序中使用模式时,例如当您让用户可以对其进行修改时,使用这种机制应该相当容易:

  • 在您已有的偏移量处读取数据。
  • 您知道偏移量,也知道长度。当需要写回修改时,您可以将自己定位在加载流中的特定位置并覆盖。

必须不断地处理文件中的数据以进行各种修改,我会说最好将其保存在内存中并在需要时将其保存,同时仍将修改后的版本保存在内存中。有时可能需要在内存缓存中更新模式偏移量,因此您可以将初始解析过程仅应用于文件的一小部分(以加快处理速度)。

如果您在加载文件后立即需要模式数据,那么您可以在第一次遍历文件时应用模式解码过程(当您提取偏移量时)。我建议仅将重要的内容保留在单独的集合中,以便向用户显示(例如仅 ID 和您需要显示的任何文本)。在您真正需要时加载整个条目。

【讨论】:

  • 我理解您所说的大部分内容,并感谢您的建议,并将调查您的回答和@Cheeso。如果它有助于添加更多说明。我正在读取的文件的总大小小于 25Mb。其中,我的阵列使用不超过 3.5Kb。我不确定 lINQ 是什么,并且会尝试阅读一些内容。同样就重构代码而言,我同意。我只是不知道如何做到这一点。我里面有代码,我不太明白。我只是让它做我需要它为该过程的那一部分做的事情。再次感谢您的回复。
  • 在我看来,寻找起始位置可能是完成需要完成的事情的最简单方法。
  • @DrewBirmingham:是的,您可以使用任何模式匹配算法来做到这一点。好的部分是您当时不必解码模式,只需存储它的偏移量。所以文件加载会更快。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-04-27
  • 1970-01-01
  • 1970-01-01
  • 2021-01-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多