2026a

# 文件 I/O


文件 I/O,详见 Julia 中文文档Base > I/O与网络章节。

# 基础函数

功能 函数
文件是否可写 iswritable(io)
文件是否可读 isreadable(io)
文件是否只读 isreadonly(io)
打开文件 open(filename::AbstractString, [mode::AbstractString]; lock = true) -> IOStream
文件是否打开 isopen(object)
读取整个文件内容 read(io::IO, String)
read(filename::AbstractString, String)
读取直到遇到 readuntil(stream::IO, delim; keep::Bool = false)
readuntil(filename::AbstractString, delim; keep::Bool = false)
读取所有行 readlines(io::IO=stdin; keep::Bool=false)
readlines(filename::AbstractString; keep::Bool=false)
文件行数 countlines(io::IO; eol::AbstractChar = '\n')
迭代读取每行 eachline(io::IO=stdin; keep::Bool=false)
eachline(filename::AbstractString; keep::Bool=false)
读取一行 readline(io::IO=stdin; keep::Bool=false)
readline(filename::AbstractString; keep::Bool=false)
读取多个字节 read(io::IO, T) # 读取类型T的单个值
read(s::IO, nb=typemax(Int))
read(s::IOStream, nb::Integer; all=true)
readbytes!(stream::IO, b::AbstractVector{UInt8}, nb=length(b))
readbytes!(stream::IOStream, b::AbstractVector{UInt8}, nb=length(b); all::Bool=true)
写文件 write(io, x, y...)
刷新 flush(stream)
关闭文件 close(stream)
跳到文件开头 seekstart(s)
跳到指定位置 seek(s, pos)
跳到文件结尾 seekend(s)
从当前位置跳过偏移量 skip(s, offset)
获取当前位置 position(s)
判断是否文件结尾 eof(stream)

文件打开模式:

模式 描述 关键参数
r read none
w write, create, truncate write = true
a write, create, append append = true
r+ read, write read = true, write = true
w+ read, write, create, truncate truncate = true, read = true
a+ read, write, create, append append = true, read = true

# 示例 - 读/写文本文件

假设我们需要将某些变量数据保持为文本文件,其格式如下所示:

"时间变量(单位)"  "变量名(单位)"
时间值  变量值
时间值  变量值
时间值  变量值

不妨构造两个变量xy,将其保存为文本文件,代码如下:

# 数据
x = (-pi):(pi / 10):pi
y = tan.(sin.(x)) - sin.(tan.(x))
mtx = [x;;y]

# 写文本文件
file_path = joinpath(tempdir(), "data.txt")
open(file_path, "w") do io

    # 写title
    write(io, "\"Time(s)\"", "  ", "\"revolute2.w(rad/s)\"", "\n")

    # 写内容
    rows = size(mtx, 1)
    for i in 1:rows
        write(io, join(mtx[i, :], "  "), "\n")
    end
end

println("生成文本文件,路径为 ", file_path)

运行上述代码,将在临时目录下生成data.txt文件,其内容为:

"Time(s)"  "revolute2.w(rad/s)"
-3.141592653589793  -2.4492935982947064e-16
-2.827433388230814  -0.6384766592717526
-2.5132741228718345  -1.3306419949303199
-2.199114857512855  -2.0295505350696814
-1.8849555921538759  -1.4653753373094411
-1.5707963267948966  -2.51610448758345
-1.2566370614359172  -1.3376440963156417
-0.9424777960769379  -0.06722859548921256
-0.6283185307179586  -0.0020635257378796013
-0.3141592653589793  -1.128946315220869e-5
0.0  0.0
0.3141592653589793  1.128946315220869e-5
0.6283185307179586  0.0020635257378796013
0.9424777960769379  0.06722859548921256
1.2566370614359172  1.3376440963156417
1.5707963267948966  2.51610448758345
1.8849555921538759  1.4653753373094411
2.199114857512855  2.0295505350696814
2.5132741228718345  1.3306419949303199
2.827433388230814  0.6384766592717526
3.141592653589793  2.4492935982947064e-16

为了展示如何读取文本文件数据,我们读取前面示例生成的文件,并对文件数据进行绘图,代码如下:

using TyPlot

# 读文本文件
function readtxt(file_path::AbstractString)
    vars = String[]
    values = Matrix{Float64}[]

    io = open(file_path, "r")
    if isopen(io)
        for line in eachline(io)
            str_line = strip(line)
            if isempty(str_line)
                continue # 忽略空行
            end

            str_values = split(str_line)

            # 读取首行
            if isempty(vars)
                vars = strip.(str_values, '\"') # 去掉双引号
                continue
            end

            # 读取数据行
            cur_values = parse.(Float64, str_values)
            cur_values = reshape(cur_values, 1, :) # 向量转矩阵
            if isempty(values)
                values = cur_values
            else
                values = vcat(values, cur_values) # 追加一行数据
            end
        end

        close(io)
    end

    return vars, values
end

file_path = joinpath(tempdir(), "data.txt") # 前面示例生成的文件
vars, values = readtxt(file_path)

# 绘图
plot(values[:, 1], values[:, 2], "--go"; markeredgecolor="b")
xlabel(vars[1])
ylabel(vars[2])

运行完成后,绘图结果如下所示:

# 示例 - 读/写二进制文件

假设需要将一个复数矩阵写成二进制文件,可以有多种保存形式。例如,我们将其设计为以下格式:

复数矩阵的行数(Int64),复数矩阵的列数(Int64),复数矩阵的实部(Float64 数组),复数矩阵的虚部(Float64 数组)

将复数矩阵保存为二进制文件,示例代码如下:

# 写二进制文件
z=[0.8147 + 0.7577im   0.0975 + 0.7060im  0.1576 + 0.8235im  0.1419 + 0.4387im  0.6557 + 0.4898im
   0.9058 + 0.7431im   0.2785 + 0.0318im  0.9706 + 0.6948im  0.4218 + 0.3816im  0.0357 + 0.4456im
   0.1270 + 0.3922im   0.5469 + 0.2769im  0.9572 + 0.3171im  0.9157 + 0.7655im  0.8491 + 0.6463im
   0.9134 + 0.6555im   0.9575 + 0.0462im  0.4854 + 0.9502im  0.7922 + 0.7952im  0.9340 + 0.7094im
   0.6324 + 0.1712im   0.9649 + 0.0971im  0.8003 + 0.0344im  0.9595 + 0.1869im  0.6787 + 0.7547im]

file_path = joinpath(tempdir(), "complex_adj.bin")
open(file_path, "w") do io
    nrows = size(z,1)
    ncols = size(z,2)

    z_real = real(z) # 复数矩阵的实部
    z_imag = imag(z) # 复数矩阵的虚部

    write(io, nrows, ncols)
    write(io, z_real, z_imag)
end

println("生成二进制文件,路径为 ", file_path)

运行上述代码,在临时目录下将生成复数矩阵的二进制文件complex_adj.bin

为了展示如何读取二进制文件,我们将前面生成的文件读入并组装成复数矩阵,与前面的复数矩阵结果一致。

# 读二进制文件
file_path = joinpath(tempdir(), "complex_adj.bin") # 前面示例生成的文件
io = open(file_path, "r")
if isopen(io)
    # 读取矩阵行列
    nrows = read(io, Int64)
    ncols = read(io, Int64)
    num = nrows*ncols

    # 读实部
    vec_bytes = read(io, 8*num)
    z_real = reinterpret(Float64, vec_bytes)
    z_real = collect(z_real)

    # 读虚部
    vec_bytes = read(io, 8*num)
    z_imag = reinterpret(Float64, vec_bytes)
    z_imag = collect(z_imag)

    # 组装成复数矩阵
    same_z = complex.(z_real, z_imag)
    same_z = reshape(same_z, nrows, nrows)

    close(io)
end

# 验证原始数据与读取后数据的一致性
z == same_z # true

# 示例 - 读/写 jld2 文件

JLD2 通常是指 Julia Data Format 2,是一种专为 Julia 编程语言设计的文件格式和序列化库。

JLD2.jl 函数库主要用于 *.jld2 文件的读取和写入。主要函数接口声明如下:

功能 说明
将当前作用域中的指定变量写入文件 @save filename var1 [var2 ...]
@save filename {compress=true} var1 name2=var2
将当前作用域中的所有变量写入文件 @save filename
从文件中加载指定变量到当前作用域 @load filename var1 [var2 ...]
从文件中加载所有变量到当前作用域 @load filename

例如,生成 jld2 文件的代码如下:

using JLD2

x = [1,2,3]
y = 2.5
z = "abc"

# 保存为 jld2,变量名分别为:x b z
@save "example.jld2" x b=y z

读取 jld2 文件的代码如下:

# 加载文件中全部变量
@load "example.jld2"

# 加载文件中指定变量
@load "example.jld2" b

# 示例 - 读/写 csv 文件

CSV 是 Comma-Separated Values(逗号分隔值) 的缩写,是一种简单的文本文件格式,用于存储表格数据。

CSV.jl 函数库主要用于 *.csv 文件的写入和读取。

功能 说明
读文件 CSV.read(source, sink::T; kwargs...) => T
写文件 CSV.write(file, table; kwargs...) => file
创建一个迭代器,为输入表中的每一行生成 CSV 格式的字符串 CSV.RowWriter(table; kwargs...)

CSV.jl 需要与 DataFrames 一起工作。例如,生成 csv 文件的代码如下:

using CSV, DataFrames

# 创建示例数据
df = DataFrame(
    Name=["Alice", "Bob", "Charlie", "David", "Eve"],
    Age=[23, 34, 45, 29, 31],
    Score=[85.5, 92.0, 78.3, 90.1, 88.7]
)

# 基本写入
CSV.write("basic_data.csv", df;
    delim=',',        # 分隔符,默认为','
    append=false,     # 追加模式,默认为 false
    quotestrings=true # 字符串加引号,默认为 false
    )

运行后生成的 csv 文件如下所示:

"Name","Age","Score"
"Alice",23,85.5
"Bob",34,92.0
"Charlie",45,78.3
"David",29,90.1
"Eve",31,88.7

假设用户对生成的 csv 文件进行了手工编辑,可能存在一些空行或非法字符,如下所示:

"Name","Age","Score"
"Alice",23,85.5
"Bob",34,92.0
"Charlie",45,78.3
"David",29,90.1
"Eve",31,88.7

"Tom",30,NA

我们需要对编辑后的 csv 文件进行读取,忽略空行,并将缺失值替换为 0。

df2 = CSV.read("basic_data.csv", DataFrame;
    delim=',',                  # 分隔符,默认为','
    ignoreemptyrows = true,     # 忽略空行,默认为true
    quoted=true,                # 默认为true,表示识别带引号的字段
    missingstring="NA"          # 缺失标记字符串
)

df2.Score = replace(df2.Score, missing => 0) # 将缺失值替换为 0

println(df2.Score) # 输出 [85.5, 92.0, 78.3, 90.1, 88.7, 0.0]

其中,df2 的值为:

6×3 DataFrame
 Row │ Name     Age    Score
     │ String7  Int64  Float64
─────┼─────────────────────────
   1 │ Alice       23     85.5
   2 │ Bob         34     92.0
   3 │ Charlie     45     78.3
   4 │ David       29     90.1
   5 │ Eve         31     88.7
   6 │ Tom         40      0.0

# 示例 - 读/写 mat 文件

MAT 文件是 MATLAB 数据文件(MATLAB Data File)的缩写,是由 MathWorks 公司开发的一种二进制文件格式,专门用于存储和交换 MATLAB 中的数据。

MAT.jl 是 Julia 语言中用于读写 MATLAB 数据文件(.mat 文件)的第三方包,它提供了与 MAT 文件交互的便捷接口,使 Julia 能够与 MATLAB 进行数据交换。支持不同版本的 MAT 文件格式,包括 v4、v5、v6、v7 和 v7.3 等。

MAT.ji 提供了以下主要接口,函数声明如下:

功能 说明
打开文件 matopen(filename [, mode]; compress = false) -> handle
matopen(f::Function, filename [, mode]; compress = false) -> f(handle)
读文件 matread(filename) -> Dict
写文件 matwrite(filename, d::Dict; compress::Bool = false, version::String)
关闭文件 close(matfile_handle)

例如,使用matwrite可以生成 mat 文件:

using MAT

@kwdef mutable struct Point2D
    x::Float64 = 0.0
    y::Float64 = 0.0
end

matwrite("matfile.mat", Dict(
        "x" => [1, 2, 3],
        "y" => 2.5,
        "z" => "abc",
        "pt" => Point2D(3, 4)
    ); compress=true)

接着,可以使用matread来读取 mat 文件,读取后全部存入一个字典中。此时,我们可以遍历该字典,并通过估值自动生成相应变量。

# 读取 mat 文件
result_dict = matread("matfile.mat")

# 遍历字典,并通过估值生成对应变量
for (k, v) in result_dict
    if isempty(k)
        continue
    end

    if k === "__opaque__"
        @warn("MATLAB some values are currently not supported")
        continue
    end

    var_name = Symbol(k)
    eval(:($var_name = $v))
end

运行上述代码后,生成以下变量,如下图所示。

需要注意的是,M 结构体变量将转为 Julia 字典变量,如pt。如果用户需要将字典转为结构体,可以参考以下实现:

# 将字典转为指定结构体
function convert_dict_to_struct(dict::AbstractDict, T::Type)
    obj = T()

    # 获取结构体成员
    syms = fieldnames(T)

    for sym in syms
        k = string(sym)
        if haskey(dict, k)
            setfield!(obj, sym, dict[k])
        else
            @warn "没有找到结构体成员 $k 的值。"
        end
    end

    return obj
end

real_pt = convert_dict_to_struct(pt, Point2D) # 输出 Point2D(3.0, 4.0)

# 示例 - 读/写 xlsx 文件

.xlsx 是 Microsoft Excel 2007 及以上版本使用的电子表格文件格式,属于 Office Open XML 格式标准的一部分,以 XML 为基础进行数据存储。

XLSX.jl 是 Julia 语言中用于处理 .xlsx 格式文件的主流开源包,提供了读取、写入和修改 Excel 文件的功能。

功能 说明
读文件 readxlsx(source::Union{AbstractString, IO}) :: XLSXFile
打开文件以供读或写 openxlsx(f::F, source::Union{AbstractString, IO}; mode::AbstractString="r", enable_cache::Bool=true) where {F<:Function}
写文件 writexlsx(output_source, xlsx_file; [overwrite=false])
获取所有工作表 sheetnames(xl::XLSXFile)
获取工作表数量 sheetcount(xlsfile)
是否存在指定工作表 hassheet(wb::Workbook, sheetname::AbstractString)
hassheet(xl::XLSXFile, sheetname::AbstractString)
读取数据 readdata(source, sheet, ref)
readdata(source, sheetref)
返回工作表单元格的内部表示形式 getcell(xlsxfile, cell_reference_name) :: AbstractCell
getcell(worksheet, cell_reference_name) :: AbstractCell
getcell(sheetrow, column_name) :: AbstractCell
getcell(sheetrow, column_number) :: AbstractCell
获取单元格的矩阵 getcellrange(sheet, rng)
返回给定单元格引用的行号 row_number(c::CellRef) :: Int
返回给定单元格引用的列号 column_number(c::CellRef) :: Int
为工作表创建一个行迭代器 eachrow(sheet)
构建表格行的迭代器 eachtablerow(sheet, [columns]; [first_row], [column_labels], [header], [stop_in_empty_row], [stop_in_row_function], [keep_empty_rows])
将 Tables.jl 表格写入文件 writetable(filename, table; [overwrite], [sheetname])
将 Tables.jl 表格写入指定工作表 writetable!(sheet::Worksheet, table; anchor_cell::CellRef=CellRef("A1")))
重命名工作表 rename!(ws::Worksheet, name::AbstractString)
创建新工作表 addsheet!(workbook, [name]) :: Worksheet

例如,生成一个 xlsx 文件:

using XLSX

XLSX.openxlsx("my_new_file.xlsx", mode="w") do xf
    sheet = xf[1]
    XLSX.rename!(sheet, "new_sheet")
    sheet["A1"] = "this"
    sheet["A2"] = "is a"
    sheet["A3"] = "new file"
    sheet["A4"] = 100

    # will add a row from "A5" to "E5"
    sheet["A5"] = collect(1:5) # equivalent to `sheet["A5", dim=2] = collect(1:4)`

    # will add a column from "B1" to "B4"
    sheet["B1", dim=1] = collect(1:4)

    # will add a matrix from "A7" to "C9"
    sheet["A7:C9"] = [ 1 2 3 ; 4 5 6 ; 7 8 9 ]
end

运行后,生成的 excel 文件如下所示:

可以使用XLSX.readxlsx来读取 excel 文件,包括访问 sheet 页、单元格、指定区域、指定行、指定列、所有数据等。

# 读文件
xf = XLSX.readxlsx("my_new_file.xlsx")

# 获取所有 sheet 页
XLSX.sheetnames(xf)
#=
1-element Vector{String}:
 "new_sheet"
=#

# 访问指定 sheet 页
sh = xf["new_sheet"]

# 访问指定单元格
sh[2,2] == sh["B2"]

# 访问指定区域
mtx = Float64.(sh["A7:C9"])
#=
3×3 Matrix{Float64}:
 1.0  2.0  3.0
 4.0  5.0  6.0
 7.0  8.0  9.0
=#

# 访问指定行
sh[5,:]
#=
1×5 Matrix{Any}:
 1  2  3  4  5
=#

# 访问指定列
sh[:,3]
#=
9×1 Matrix{Any}:
  missing
  missing
  missing
  missing
 3
  missing
 3
 6
 9
=#

# 访问所有数据
sh[:]
#=
9×5 Matrix{Any}:
    "this"      1          missing   missing   missing
    "is a"      2          missing   missing   missing
    "new file"  3          missing   missing   missing
 100            4          missing   missing   missing
   1            2         3         4         5
    missing      missing   missing   missing   missing
   1            2         3          missing   missing
   4            5         6          missing   missing
   7            8         9          missing   missing
=#

# 参考

[1] https://juliaio.github.io/JLD2.jl/dev/ (opens new window)

[2] https://csv.juliadata.org/stable/ (opens new window)

[3] https://juliaio.github.io/MAT.jl/stable/ (opens new window)

[4] https://felipenoris.github.io/XLSX.jl/stable/ (opens new window)