【问题标题】:Most efficient way to search file for byte patterns in Elixir在 Elixir 中搜索文件以查找字节模式的最有效方法
【发布时间】:2015-08-09 11:50:57
【问题描述】:

我正在歌曲文件中搜索 id3 标签。一个文件可以有 id3v1、id3v1 扩展标签(位于文件末尾)以及 id3v2 标签(通常位于开头)。对于 id3v1 标签,我可以使用 File.read(song_file) 并提取最后 355 个字节(扩展标签为 128 + 227)。但是,对于 id3v2 标签,我需要从头开始搜索文件,寻找 10 字节的 id3v2 模式。我想避免在搜索不同标签时重复打开和关闭同一个文件的任何开销,所以我认为最好的方法是使用 File.stream!(song_file) 并将文件流发送到不同的函数来搜索不同的标签。

def parse(file_name) do
  file_stream = File.stream!(file_name, [], 1)
  id3v1_tags(file_stream)
  |> add_tags(id3v2_tags(file_stream))
end

def id3v1_tags(file_stream) do
  tags = Tags%{} #struct containing desired tags
  << id3_extended_tag :: binary-size(227), id3_tag :: binary-size(128) >> = Stream.take(file_stream, -355)
  id3_tag = to_string(id3_tag)
  if String.slice(id3_tag,0, 3) == "TAG" do
    Map.put(tags, :title, String.slice(id3_tag, 3, 30))
    Map.put(tags, :track_artist, String.slice(id3_tag, 33, 30))
    ...
  end
  if String.slice(id3_extended_tag, 0, 4) == "TAG+" do
    Map.put(tags, :title, tags.title <> String.slice(id3_extended_tag, 4, 60))
    Map.put(tags, :track_artist, tags.track_artist <> String.slice(id3_extended_tag, 64, 60))
    ...
  end
end

def id3v2_tags(file_stream) do
  search for pattern:
  <<0x49, 0x44, 0x33, version1, version2, flags, size1, size2, size3, size4>>
end

1) 我是否通过创建 File.stream 来节省任何运行时!一次并将其发送到不同的功能(我将扫描数万个文件,因此节省一点时间很重要)?或者我应该只使用 File.read 作为 id3v1 标签和 File.stream!对于 id3v2 标签?

2) 我在一行中得到一个错误:

  << id3_extended_tag :: binary-size(227), id3_tag :: binary-size(128) >> = Stream.take(file_stream, -355)

因为 Stream.take(file_stream, -355) 是一个函数,而不是二进制文件。如何将其转换为可以进行模式匹配的二进制文件?

【问题讨论】:

  • 我没有答案,但如果我是你,我真的会避免使用幻数。我假设 -355 是 128 和 227 的总和?如果我是你,我会将名为 @id_extended_tag_binsize 的模块属性设置为 227,将 @id3_tag_binsize 设置为 128,然后在 Stream.take 中使用这些属性,如下所示: Stream.take(file_stream, -(@id_extended_tag_binsize + @987654326 @)) 不易出错,更容易阅读和理解。
  • 首先,需要告知 File.stream 使用语法 File.stream!(path, [:read ], :bytes) 读取字节而不是行。接下来,我认为 -355 不能与 Stream.take 一起使用,因为 stream 不是列表,而是需要通过 Stream.to_list 调用来完成的潜在列表。
  • 感谢您的建议。我已经相应地更改了我的代码。

标签: elixir id3


【解决方案1】:

我认为由于依赖流,您的实现变得不必要地复杂。让它发挥作用,让它变得漂亮,然后让它变得更快(但仅在必要时)。

为简单起见,我首先将所有内容加载到内存中。只需使用File.read!/1。然后您可以使用 :binary 模块中的函数来搜索模式(:binary.match/2)、拆分模式(:binary.split/2)或抓取某个部分(:binary.part/3)。也不需要混合 File.stream 和 File.read,只需读取一次并传递相同的二进制文件。

另外,非常重要的是,不要使用字符串模块。字符串用于处理 UTF-8 编码的二进制文件。您想将 :binary 模块用于所有字节级别的操作。

最后,Stream.take/2 总是返回函数,因为它是惰性的。您想改用Enum.take/2(它接受流,因为流也是可枚举的)。不过,正如我所说,我会完全跳过直播内容。

【讨论】:

  • 感谢您的回答。我会听取您的建议并使用 File.read!/1。我不知道 String 模块。我认为如果字节是字符,那么您可以将它们视为二进制文件或字符串。
猜你喜欢
  • 2017-10-22
  • 1970-01-01
  • 2018-08-12
  • 2011-02-11
  • 2010-09-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-17
相关资源
最近更新 更多