前面的基础笔记里已经零散提到过 printprintlnreadline 这些最简单的输入输出操作,但如果要真正写脚本或小工具,还需要文件读写和路径处理。

概述

Julia 中与 IO 相关的几类操作包括:

  • 控制台输入输出;
  • 文本文件和二进制文件;
  • open do 风格的资源管理;
  • 常见的路径、目录和文件系统操作。

整体上,Julia 的 IO 风格更接近“显式流对象 + open do 管理资源”,而不是 MATLAB 那种偏工作区工具式接口。

控制台输入输出

最常用的输出函数仍然是:

1
2
print("hello")
println("world")

区别很简单:

  • print 不自动换行;
  • println 自动在末尾补一个换行。

也可以连续输出多个对象:

1
println("x = ", 1, ", y = ", 2)

字符串插值

更顺手的写法通常是字符串插值:

1
2
3
x = 3
y = 4
println("x = $x, y = $y, x + y = $(x + y)")

这种需求在 Python 中通过 f-string 实现,在 Julia 则使用 $() 语法。

show

print / println 相比,show 更偏向“给程序员看的表示形式”。

例如

1
2
x = [1, 2, 3]
show(x)

在 REPL 中,很多对象默认显示效果本质上也和 show 有关。

格式化输出 Printf

如果需要更接近 C 风格的格式化,可以使用标准库 Printf 提供的宏:

1
2
3
using Printf

@printf("pi = %.4f\n", pi)

也可以生成格式化字符串:

1
s = @sprintf("x = %.2f", 3.14159)

对于简单输出,字符串插值已经足够;真正需要宽度、精度控制时,再考虑 Printf

日志输出

简单脚本里直接 println 往往就够了,但只要代码稍微复杂一点,日志接口就比随手打印更合适。

Julia 标准库里的日志功能主要来自 Logging,不过很多常用宏在日常环境里可以直接使用。

常见日志宏

最常用的几个宏包括:

  • @debug
  • @info
  • @warn
  • @error

例如:

1
2
3
@info "starting"
@warn "config file not found, using default settings"
@error "failed to load model"

输出里一般会带上:

  • 日志级别;
  • 消息内容;
  • 触发位置。

@debug

@debug 比较特殊,默认往往不会直接显示出来:

1
@debug "x = 1"

这使得它很适合放一些调试时才需要的信息,而不会污染平时输出。

带键值对的日志

日志宏支持顺手附带上下文信息:

1
2
3
4
epoch = 3
loss = 0.012

@info "training step" epoch loss

这比手工拼字符串更自然,也更利于后续统一处理。

自定义日志后端

更完整的日志控制可以通过 Logging 标准库和自定义 logger 完成,例如切换日志级别、重定向日志输出位置等。

这部分已经超出基础笔记的范围,这里只需要知道 Julia 自带了一套正式日志接口,而不是只能靠 println

控制台输入

readline

从标准输入读取一整行:

1
s = readline()

返回值是字符串。

如果需要数字,通常再配合 parse

1
2
s = readline()
x = parse(Int, s)

read

也可以直接从输入流读取数据:

1
data = read(stdin, String)

这会把标准输入剩余部分全部读成一个字符串。

文件的基本读写

一次性读取整个文本文件

最简单的方式:

1
text = read("data.txt", String)

这适合文本文件不大、希望一次性读入的情况。

一次性写入文本文件

1
write("out.txt", "hello\nworld\n")

需要注意:

  • write 返回写入的字节数;
  • 这里写入的是原始字节内容。

如果要反复追加文本,更常见的写法是用 open

opendo 语法

基本形式

文件最常见的打开方式如下:

1
2
3
4
open("data.txt", "r") do io
text = read(io, String)
println(text)
end

这里 io 就是文件流对象。

do 块结束后,文件会自动关闭。这和 Python 的 with open(...) as f: 很接近。

为什么推荐 open do

原因很简单:

  • 不容易忘记关闭文件;
  • 出异常时也更安全;
  • 写法很适合把“打开资源 -> 做事 -> 自动释放资源”合在一起。

不带 do 的形式

当然也可以手动打开和关闭:

1
2
3
io = open("data.txt", "r")
text = read(io, String)
close(io)

但除非有特殊原因,否则一般不如 open do 清晰。

常见文件打开模式

open 常见模式包括:

  • "r":只读
  • "w":写入,若文件已存在则覆盖
  • "a":追加写入
  • "r+":读写

例如追加写入:

1
2
3
open("log.txt", "a") do io
println(io, "new line")
end

这里 println(io, ...) 表示把内容写入文件流,而不是控制台。

按行读写

eachline

逐行读取文件最方便的方式之一:

1
2
3
4
5
open("data.txt", "r") do io
for line in eachline(io)
println(line)
end
end

如果只需要文件路径,也可以直接:

1
2
3
for line in eachline("data.txt")
println(line)
end

readlines

如果希望一次性读成字符串数组:

1
lines = readlines("data.txt")

这会把每一行作为一个字符串元素放进数组中。

写多行文本

1
2
3
4
5
open("out.txt", "w") do io
println(io, "line 1")
println(io, "line 2")
println(io, "line 3")
end

二进制读写

Julia 的 IO 不只适合文本,也很适合二进制数据。

写二进制

1
2
3
open("data.bin", "w") do io
write(io, Int32(123))
end

读二进制

1
2
3
4
open("data.bin", "r") do io
x = read(io, Int32)
println(x)
end

读取全部字节

1
bytes = read("data.bin")

返回值通常是 Vector{UInt8}

文件系统操作

判断文件和目录

1
2
isfile("data.txt")
isdir("mydir")

创建目录

1
2
mkdir("tmpdir")
mkpath("a/b/c")

区别:

  • mkdir 只创建一级目录;
  • mkpath 会递归创建缺失的父目录。

删除文件和目录

1
2
rm("data.txt")
rm("tmpdir"; recursive=true)

删除目录时通常要加 recursive=true

列出目录内容

1
2
3
readdir(".")
readdir(".";
join=true)

join=true 时会返回拼接后的路径,而不是单独文件名。

复制、移动

1
2
cp("a.txt", "b.txt")
mv("b.txt", "c.txt")

路径处理

pwdcd

1
2
pwd()
cd("..")

脚本里仍然更推荐显式使用 @__DIR__ 来定位当前脚本目录:

1
cd(@__DIR__)

joinpath

拼接路径时不要手写分隔符,直接使用:

1
path = joinpath("data", "images", "a.png")

这能避免不同操作系统路径分隔符差异的问题。

拆路径

常见函数包括:

1
2
3
4
5
dirname(path)
basename(path)
splitext(path)
abspath(path)
normpath(path)

例如:

1
splitext("demo.txt") # ("demo", ".txt")

临时文件与内存 IO

临时文件

可以创建临时文件:

1
(path, io) = mktemp()

或者临时目录:

1
dir = mktempdir()

这在测试和中间数据处理时很方便。

IOBuffer

有时不想真的操作磁盘文件,只想在内存里模拟一个 IO 对象,可以使用 IOBuffer

1
2
3
buf = IOBuffer()
write(buf, "hello")
String(take!(buf)) # "hello"

这在测试、字符串构造和某些接口适配里非常有用。

序列化与结构化数据

严格来说,这部分已经不只是基础文件交互了,但值得顺手提一下。

内置序列化

Julia 自带 Serialization 标准库:

1
2
3
4
using Serialization

serialize("obj.bin", [1, 2, 3])
x = deserialize("obj.bin")

这适合 Julia 内部对象的快速保存和恢复,但不适合拿来做通用交换格式。

结构化文本

如果要处理 JSON、CSV、TOML 这些结构化格式,通常会用对应的包,例如:

  • JSON.jl
  • CSV.jl
  • TOML(标准库)

这些已经超出最基础的文件 IO,但在实际项目中非常常见。

小结

基础 IO 其实没有多少花样,记住几组最常用的接口就够了:文本文件用 read / write,涉及资源管理时优先写成 open(...) do io ... end,逐行处理用 eachline,路径处理用 joinpath@__DIR__,剩下的文件系统操作按需要查标准库接口即可。