【问题标题】:How to write/read array of strings to .bin with good performance如何以良好的性能将字符串数组写入/读取到 .bin
【发布时间】:2018-09-21 11:47:43
【问题描述】:

将字符串数组写入.bin格式如下

out =  open("string_array.bin","w")
a = ["first string","second string","third string"]
write(out,a)
close(out)

但是在读回数组 a 时,事情开始变得棘手。

out =  open("string_array.bin","r")
a = read(out)
close(out)
typeof(a) # returns Array{UInt8,1}

如何将 Array{UInt8,1} 转换回原来的 Array{String,1} 类型的数组?

当字符串数组有 300+ 百万个元素时,它也需要工作,即解决方案必须表现良好。

【问题讨论】:

  • 您上面的代码将无法工作,因为write(out, a) 将失败,因为a 包含一个字符串并且不支持将此类结构(非 isbits)写入流。此评论适用于 Julia 1.0。在 Julia 0.6 上它可以工作,但它只是在输出中连接字符串,因此不可能解析回它们的原始值。
  • 您对 Julia 0.6 的评论完全正确。
  • 在 Julia 0.6 中使用 JLD2.jl 应该是安全的,因为下面提到的 JLD2.jl 的问题与将其移植到 Julia 1.0 有关。下面列出的其他选项也可以使用。
  • @BogumiłKamiński 出于好奇,在 v0.7+ 上,使用unsafe_wrapString 转换为Vector{UInt8} 是多么糟糕的想法,将Vector{UInt8} 写入a二进制文件,然后您可以从文件中读取Vector{UInt8},并将其转换为String?如果您有Vector{String},那么您可以按顺序执行此操作并使用额外的UInt8 来存储每个Vector{UInt8} 的长度。 (如果字符串真的很长,则为更大的整数类型)。这是一个可怕的想法吗? (可能只有 5 或 6 行代码)
  • @Mr_Holm 另外,我对Matrix{Float64} 进行了类似的练习,发现写入和读取二进制文件是总体上最好的解决方案(目前)。都在this question here

标签: julia binaryfiles


【解决方案1】:

所以 Bogumil 是对的,它有点 hacky,但如果你热衷于读写二进制文件,那么这里有一个读写 Vector{String} 的实现,它通过将每个 String 转换为 @987654323 来工作@,然后将每个Vector{UInt8} 写入文件,为每个Vector{UInt8} 使用初始Int64 来存储其长度。该文件还以额外的Int64 开头,存储Vector{String} 的长度。然后读取例程使用此信息将其全部拉回并将其转换回Vector{String}

my_write(fid1::IOStream, x::Vector{UInt8}) = begin ; write(fid1, Int64(length(x))) ; write(fid1, x) ; end
my_write(fid1::IOStream, x::Vector{Vector{UInt8}}) = begin ; write(fid1, Int64(length(x))) ; [ my_write(fid1, y) for y in x ] ; end
my_read(fid1::IOStream, ::Type{Vector{UInt8}})::Vector{UInt8} = begin i = read(fid1, Int64) ; [ read(fid1, UInt8) for a = 1:i ] ; end
my_read(fid1::IOStream, ::Type{Vector{Vector{UInt8}}})::Vector{Vector{UInt8}} = begin i = read(fid1, Int64) ; [ my_read(fid1, Vector{UInt8}) for a = 1:i ] ; end
my_write(myfilepath::String, x::Vector{String}) = open(fid1 -> my_write(fid1, [ Vector{UInt8}(codeunits(y)) for y in x ]), myfilepath, "w")
function my_read(myfilepath::String, ::Type{Vector{String}})::Vector{String}
    x = open(fid1 -> my_read(fid1, Vector{Vector{UInt8}}), myfilepath, "r")
    return [ String(y) for y in x ]
end

我可能包含了比必要更多的类型信息,但它可能会让您更清楚地了解事情。另外,对不起,我有一个用单线做这种事情的坏习惯,但如果需要,你可以很容易地打开它。这是一些测试代码(只需调整文件路径):

myfilepath = "/home/colin/Temp/test_file.bin"
x = ["abc", "de", "f", "", "ghij"]
my_write(myfilepath, x)
my_read(myfilepath, Vector{String})

注意,只要稍加努力,这段代码就可以变得更通用,这样只要T 是可写的,它就可以适用于几乎任何Vector{Vector{T}}。事实上,如果你真的很聪明,它应该可以推广到任何Vector{Vector{Vector{...{T}}}},只要你能得到正确的递归。

【讨论】:

  • 不错!顺便说一句,如果我确实需要存储函数的结果,我喜欢使用 foreach 而不是推导式。
  • 谢谢!我不知道foreach。在我自己的代码中,如果我不需要结果,我通常只使用for blah ; do something ; end,但foreach 更简洁。在这种情况下,我很想离开理解,因为它会导致最终输出是 Vector{Int},其中每个 String 的长度都是写入的,这并不是一个完全无用的输出(尽管我承认我想不出一个应用程序)。
  • 很好的答案@ColinTBowers!您的解决方案完成了工作,唯一的方法是使用会话中所有可用的工作人员写入二进制文件。可以使用 pmap 做到这一点,还是自定义 @spawn/remove_fetch 方法?
  • @Mr_Holm 老实说,我以前从来没有需要在 Julia 中并行或线程的东西,所以我绝对不是问错的人。话虽如此,我认为例程中的大部分运行时将在 UInt8 到文件的实际读取和写入上,而不是转换,并且必须按顺序完成,所以我看不出 pmap 将如何帮助那里.但是您绝对可以轻松地 pmap 转换。
【解决方案2】:

这里有一些选项:

  • serialization,它的局限性在于它是为短期存储而设计的(它要求 Julia 读写文件具有相同的系统映像)。
  • JLD2.jl,需要注意的是,在撰写本文时,Julia 1.0 上的软件包存在一些未解决的问题(这些问题应该很快得到解决 - 如果它们影响到您,您可以查看问题 - 主要是处理 missing;给出你写的你不应该被他们影响)
  • 最后,您可以使用设计用于处理表格数据的读取器/写入器,例如 CSV.jlFeather.jl,因为您的数据可以被视为单列表

【讨论】:

  • 谢谢博古米尔!如果您的数据不在 300+ 百万个元素范围内,这些都是不错的选择。对于较小的数据大小 Feather.jl 将是一个快速的选择。我希望写入二进制文件会随时击败 Feather。但是如何做到这一点呢?
  • 您是否尝试过实际测试它们?我刚刚测试了serialize 在笔记本电脑上节省 100,000,000 个大小为 10 个字符的字符串大约需要 10 秒,所以我想这对于大多数用例来说应该足够快了。
  • 是的,但是如果读写是由不同版本的Julia完成的,这个过程会起作用吗?
  • 不保证。这就是为什么我列出了不同的选项并给出了它们最重要的限制,因为我不知道你到底需要什么。根据经验,越通用的解决方案就越慢。鉴于您指定的 JLD2.jl 可以为您取得良好的平衡。
  • 仅供参考,我刚刚检查了使用来自DelimitedFiles 标准库的writedlm 将这样的数组(1 亿行)编写为 CSV 需要大约 23 秒,这也不错。
猜你喜欢
  • 2013-12-31
  • 2015-01-31
  • 2022-01-20
  • 2021-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-18
相关资源
最近更新 更多