在 Elixir 中搜索文件字节模式的最有效方法

Most efficient way to search file for byte patterns in Elixir

我正在搜索歌曲文件中的 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) 是一个函数,而不是二进制。如何将其转换为可以进行模式匹配的二进制文件?

我认为由于对流的依赖,您的实施过于复杂。使其工作,使其美观然后使其快速(但仅在必要时)。

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

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

最后,Stream.take/2 总是 returns 因为它是懒惰的。您想改用 Enum.take/2 (它接受流,因为流也是可枚举的)。虽然,正如我所说,我会完全跳过流内容。