Документация Engee

Формат данных Julia — JLD2

Пакет JLD2 сохраняет и загружает структуры данных Julia в формате, представляющем собой подмножество стандарта HDF5, без зависимости от библиотеки C HDF5. Как правило, по производительности он превосходит пакет JLD (иногда на несколько порядков), а зачастую и встроенный сериализатор Julia. В то время как другие реализации HDF5, поддерживающие спецификацию формата файлов HDF5 версии 3.0 (то есть libhdf5 1.10 и более поздние версии), должны иметь возможность чтения файлов, создаваемых JLD2, пакет JLD2, скорее всего, не сможет прочесть файлы, созданные или измененные другими реализациями HDF5. При разработке JLD2 не ставилась цель обеспечить прямую или обратную совместимость с пакетом JLD.

Чтение и запись данных

Новый интерфейс: jldsave

Функция jldsave использует синтаксис Julia на основе именованных аргументов для сохранения файлов, что позволяет полагаться на анализатор, а не на макросы. Используется она так:

x = 1
y = 2
z = 42

# Простейший случай:
jldsave("example.jld2"; x, y, z)
# это равносильно
jldsave("example.jld2"; x=x, y=y, z=z)

# Новые имена можно присваивать лишь части аргументов
jldsave("example.jld2"; x, a=y, z)

# а чтобы внести полную путаницу, можно сделать так
jldsave("example.jld2"; z=x, x=y, y=z)

Сжатие и нестандартные типы ввода-вывода можно задавать в виде позиционных аргументов.

Функции save_object и load_object

Если в файле требуется сохранить только один объект для последующей загрузки, можно воспользоваться функциями save_object и load_object.

save_object(filename, x)

Сохраняет объект x в новом JLD2-файле в filename. Если файл существует по этому пути, он будет перезаписан.

Поскольку формат JLD2 требует, чтобы у всех объектов было имя, объект будет сохранен как single_stored_object. Чтобы сохранить несколько объектов, используйте макрос @save, jldopen или API FileIO.

Пример

Сохранение строки hello в JLD2-файле example.jld2:

hello = "world"
save_object("example.jld2", hello)
load_object(filename)

Возвращает единственный доступный объект из JLD2-файла filename (имя хранимого объекта не имеет значения). Если файл содержит несколько объектов или не содержит ни одного, эта функция выводит ошибку ArgumentError.

Чтобы загрузить несколько объектов, используйте макрос @load, jldopen или API FileIO.

Пример

Загрузка только одного объекта из JLD2-файла example.jld2:

hello = "world"
save_object("example.jld2", hello)
hello_loaded = load_object("example.jld2")

Функции save и load

Функции save и load из пакета FileIO предоставляют механизм для записи данных в файл JLD2 и их считывания. Для их использования можно написать using FileIO или using JLD2. FileIO определяет соответствующий пакет автоматически.

Функция save принимает тип AbstractDict, состоящий из пар «ключ-значение», где ключ — это строка, представляющая имя набора данных, а значение представляет его содержимое:

using FileIO
save("example.jld2", Dict("hello" => "world", "foo" => :bar))

Функция save также может принимать имена и содержимое наборов данных в виде аргументов:

save("example.jld2", "hello", "world", "foo", :bar)

При использовании функции save файл должен иметь расширение .jld2, так как расширение .jld в настоящее время связано с предыдущим пакетом JLD.

Если в вызове функции load из аргументов присутствует только имя файла, она загружает все наборы данных из этого файла в словарь (Dict):

load("example.jld2") # -> Dict{String,Any}("hello" => "world", "foo" => :bar)

Если в вызове функции load передано одно имя набора данных, из файла возвращается содержимое этого набора:

load("example.jld2", "hello") # -> "world"

Если в вызове функции load передано несколько имен наборов данных, содержимое этих наборов возвращается в виде кортежа:

load("example.jld2", "hello", "foo") # -> ("world", :bar)

Файловый интерфейс

С файлами JLD2 также можно взаимодействовать посредством файлового интерфейса. Функция jldopen принимает имя файла и аргумент, определяющий способ открытия файла:

using JLD2

f = jldopen("example.jld2", "r")  # открытие только для чтения (по умолчанию)
f = jldopen("example.jld2", "r+") # открытие для чтения и записи; если файл не существует, происходит сбой
f = jldopen("example.jld2", "w")  # открытие для чтения и записи; существующий файл перезаписывается
f = jldopen("example.jld2", "a+") # открытие для чтения и записи; содержимое существующего файла сохраняется, либо создается новый файл

Данные можно записать в файл с помощью write(f, "name", data) или f["name"] = data, а считать из файла с помощью read(f, "name") или f["name"]. По завершении работы с файлом не забудьте вызвать close(f).

Как и функция open, jldopen также принимает функцию в качестве первого аргумента, поддерживая синтаксис с блоком do:

jldopen("example.jld2", "w") do file
    file["bigdata"] = randn(5)
end

Группы

В файле JLD2 можно создавать группы, которые могут быть полезны для упорядочения данных. Группы можно создавать явным образом:

jldopen("example.jld2", "w") do file
    mygroup = JLD2.Group(file, "mygroup")
    mygroup["mystuff"] = 42
end

Или неявным образом, сохраняя переменную с именем, разделителем пути в котором служит косая черта:

jldopen("example.jld2", "w") do file
    file["mygroup/mystuff"] = 42
end
# или save("example.jld2", "mygroup/mystuff", 42)

В обоих этих примерах получается одинаковая структура групп, которую можно просмотреть в REPL:

julia> file = jldopen("example.jld2", "r")
JLDFile /Users/simon/example.jld2 (read-only)
 └─📂 mygroup
    └─🔢 mystuff

Аналогичным образом, можно получать доступ к группам напрямую:

jldopen("example.jld2", "r") do file
    @assert file["mygroup"]["mystuff"] == 42
end

или с помощью косых черт в качестве разделителей пути:

@assert load("example.jld2", "mygroup/mystuff") == 42

API Unpack.jl

Если дополнительно загрузить пакет UnPack.jl, с помощью его макросов @unpack и @pack! можно быстро сохранять и загружать данные посредством файлового интерфейса. Пример:

using UnPack
file = jldopen("example.jld2", "w")
x, y = rand(2)

@pack! file = x, y # равносильно file["x"] = x; file["y"] = y
@unpack x, y = file # равносильно x = file["x"]; y = file["y"]

Доступ к группе file_group = Group(file, "mygroup") можно получить посредством того же файлового интерфейса, что и к «полной» структуре.

Сбои

Во время загрузки объекты кэшируются

JLD2 кэширует объекты во время загрузки. В результате один и тот же объект может быть получен дважды. Это может приводить к неожиданным результатам при изменении загруженных массивов. Обратите внимание, что исходный файл не изменяется!

julia> jldsave("demo.jld2", a=zeros(2))

julia> f = jldopen("demo.jld2")
JLDFile /home/isensee/demo.jld2 (read-only)
 └─🔢 a

julia> a = f["a"] # загруженный массив привязывается к имени `a`
2-element Vector{Float64}:
 0.0
 0.0

julia> a[1] = 42; # исходный массив изменяется

julia> f["a"]
2-element Vector{Float64}:
 42.0
  0.0

julia> a=nothing # все ссылки на загруженный массив удаляются

julia> GC.gc(true) # вызывается сборщик мусора для удаления кэша

julia> f["a"] # из файла загружается новая копия
2-element Vector{Float64}:
 0.0
 0.0

Перекрестная совместимость

JLD2 пытается записывать файлы так, чтобы их можно было загружать в разных операционных системах, в частности как в 32-разрядных, так и в 64-разрядных. Однако многие структуры Julia могут иметь внутренние отличия в разных архитектурах, что делает эту задачу невозможной. В частности, перенос данных из 64-разрядной системы в 32-разрядную гарантируется только для базовых типов данных.

Безопасность

Будьте осторожны при открытии файлов JLD2 из ненадежных источников. Вредоносный файл может запустить код на вашем компьютере. См., например, описание этой проблемы. С помощью JLD2DebugTools.jl можно проверить, какие объекты хранятся в файле.