# 文件 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 |
# 示例 - 读/写文本文件
假设我们需要将某些变量数据保持为文本文件,其格式如下所示:
"时间变量(单位)" "变量名(单位)"
时间值 变量值
时间值 变量值
时间值 变量值
不妨构造两个变量x和y,将其保存为文本文件,代码如下:
# 数据
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) -> handlematopen(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) :: AbstractCellgetcell(worksheet, cell_reference_name) :: AbstractCellgetcell(sheetrow, column_name) :: AbstractCellgetcell(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)