Overview of FileIO.jl
masterread and write functions by providing high-level, format-aware alternatives.repository·master·Indexed 42 days ago
https://github.com/juliaio/fileio.jlA framework for Julia that provides a common interface for detecting file formats and dispatching to appropriate readers and writers using `load` and `save` functions. It supports high-level formatted file I/O, streaming I/O via `loadstreaming` and `savestreaming` for large files, and a `query` function to inspect file formats without loading full content. The framework allows developers to register new formats using `add_format` and implement custom loaders and savers.
read and write functions by providing high-level, format-aware alternatives.Instead of using Julia's low-level read and write functions, use load and save to handle formatted files. FileIO detects the appropriate reader or writer based on the file extension or provided information.
Note: To prevent name conflicts, other packages providing format support via load and save are encouraged to register with the FileIO framework.
# High-level usage pattern
data = load("filename.ext")
save("filename.ext", data)To extend FileIO's support to a new format, you must perform two steps:
These steps can be completed in either order.
FileIO uses module qualification to handle files. When you register a format (e.g., via add_format), FileIO identifies the package associated with that format and calls its internal load or save functions directly by their module name (e.g., MyFileFormat.load(file)).
Crucial Rule: Do not extend FileIO.load or FileIO.save. Instead, define these functions as private methods within your own module. This prevents naming conflicts and ensures that if multiple packages support the same format, the one loaded last in the user's session doesn't accidentally overwrite others, making the system brittle.
By default, FileIO looks for load and save functions in your module. If your module already uses these names for other purposes, you can use an alternative naming convention.
Note: You must be consistent. You cannot mix styles within a single module; you must either use load/save for everything or fileio_load/fileio_save for everything.
FileIO uses lazy loading to improve startup performance. Instead of loading all IO backends (like PNGFiles.jl or ImageIO.jl) when you run using FileIO, it only loads the specific backend required when you actually call load() or save().
While this makes using FileIO very fast, it can trigger a world age issue if you call load inside a function. This happens because the backend packages (and their dependencies, like IndirectArrays) are loaded into a 'newer' Julia world than the one where your function was compiled. When your function tries to interact with the newly loaded types, Julia may throw a MethodError stating that the applicable method is too new.
julia> using FileIO
julia> f() = size(load("indexed_image.png"))
# This might fail with a MethodError if the backend
# packages weren't loaded when f() was compiled:
# ERROR: MethodError: ... The applicable method may be too new ...FileIO provides high-level load and save functions to detect file formats and dispatch to the appropriate readers or writers. If a format is registered, you can read data directly from a filename or a URI.
To save data, use save(filename, obj) or use the piping operator: obj |> save(filename).
using FileIO
# Reading a file
obj = load("filename.ext")
# Reading from a URI
using HTTP
img = load(HTTP.URI("https://github.com/JuliaLang/julia-logo-graphics/raw/master/images/julia-logo-color.png"));
# Saving a file
save("filename.ext", obj)
# Saving using piping
obj |> save("filename.ext")Streaming I/O allows users to obtain a decoded stream object that they can interact with using standard read or write calls.
WAVReader) that wraps the underlying IO.Base.read or Base.write: Define how to process data through your wrapper.Base.close: This is critical for resource management.File, your close method must close the underlying stream.Stream, your close method must only perform internal cleanup and must not close the stream.loadstreaming and savestreaming: Provide implementations for both File and Stream types.load/save via streamingIf you have already implemented loadstreaming and savestreaming, you can implement the standard load and save methods easily by using the Formatted abstract supertype (which covers both File and Stream).
struct WAVReader
io::IO
ownstream::Bool
end
function Base.read(reader::WAVReader, frames::Int)
# read and decode audio samples from reader.io
end
function Base.close(reader::WAVReader)
# do whatever cleanup the reader needs
reader.ownstream && close(reader.io)
end
# Implementations for FileIO to call
loadstreaming(f::File{format"WAV"}) = WAVReader(open(f), true)
loadstreaming(s::Stream{format"WAV"}) = WAVReader(s, false)
# Shortcut to implement standard load/save using the streaming logic
function save(q::Formatted{format"WAV"}, data, args...; kwargs...)
savestreaming(q, args...; kwargs...) do stream
write(stream, data)
end
end
function load(q::Formatted{format"WAV"}, args...; kwargs...)
loadstreaming(q, args...; kwargs...) do stream
read(stream)
end
endTo implement standard file loading and saving, you should provide methods for both File and Stream types.
load(f::File{format"FORMAT"}): Should open the file, handle magic bytes (e.g., by skipping them), and then call the load(s::Stream{format"FORMAT"}) method. This method must close the stream it opens (using do blocks is recommended).load(s::Stream{format"FORMAT"}; keywords...): Should perform the actual reading from the stream. The stream is assumed to be positioned after the magic bytes. This method must not close the stream.save(f::File{format"FORMAT"}, data): Should open the file for writing, write the necessary magic bytes, and then perform the save. This method must close the stream it opens.save(s::Stream{format"FORMAT"}; keywords...): Should perform the actual writing to the stream. This method must not close the stream.module MyFileFormat
using FileIO
# Private load function for Files
function load(f::File{format"PNG"})
open(f) do s
skipmagic(s) # skip over the magic bytes
load(s) # call the Stream-based load
end
end
# Private load function for Streams
function load(s::Stream{format"PNG"}; keywords...)
# s is already positioned after the magic bytes
chunklength = read(s, UInt32)
# ... read the rest
end
# Private save function for Files
function save(f::File{format"PNG"}, data)
open(f, "w") do s
write(s, magic(format"PNG"))
# ... write the rest
end
end
end # module MyFileFormatTo register a new file format in FileIO, you must add a call to add_format to FileIO's registry.
Workflow for contributors:
pkg> dev FileIO to develop locally.add_format locally to ensure the format is correctly identified and handled.Note on formats without magic bytes:
If a format uses common extensions (like .out) but lacks unique magic bytes, it cannot be registered with FileIO. In these cases, you should provide I/O directly within your own package using module-qualified names (e.g., MyPkg.load) to avoid conflicts with FileIO's load and save.
add_format(fmt, magic, extension, libraries...)You can explore the FileIO API directly from the Julia REPL using the help mode.
?FileIO to get a general overview of the package API.?function_name (e.g., ?add_format) to see documentation for specific functions.The most efficient way to avoid world age issues without sacrificing performance is to eagerly load the required backend or dependency packages. If you know your code will interact with specific types (e.g., IndirectArray), include that package in your using statements.
For package developers, this means including the necessary IO dependencies in your module's using block to ensure they are present in the user's current world age.
# Instead of just using FileIO, include the dependency causing the issue
using FileIO, IndirectArrays
f(file) = length(load(file))