在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。读取id3v1标签和File.stream!对于id3v2标签?

2)我得到了一个错误的行:

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

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

我认为由于对流的依赖,您的实现变得不必要地复杂。让它工作,让它漂亮,让它快速(但只有在必要的时候)。

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

另外,非常重要的一点是,不要使用String模块。字符串意味着工作UTF-8编码的二进制文件。您需要使用:binary模块来执行所有字节级操作。

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

最新更新