跳到内容

Julia 入门 / 处理文本文件

来自维基教科书,开放书籍,开放世界
Previous page
字符串和字符
Julia 入门 Next page
处理日期和时间
处理文本文件

从文件读取

[编辑 | 编辑源代码]

从文本文件获取信息的标准方法是使用 open()read()close() 函数。

要从文件读取文本,首先获取文件句柄

f = open("sherlock-holmes.txt")

f 现在是 Julia 与磁盘上文件的连接。当你完成对文件的操作后,你应该使用以下方法关闭连接:

close(f)

一般来说,在 Julia 中处理文件的推荐方法是将任何文件处理函数包装在 do 块中

open("sherlock-holmes.txt") do file
    # do stuff with the open file
end

当该块完成时,打开的文件会自动关闭。有关 do 块的更多信息,请参见 控制流程

由于块中局部变量的范围,你可能希望保留一些已处理的信息

totaltime, totallines = open("sherlock-holmes.txt") do f
    linecounter = 0
    timetaken = @elapsed for l in eachline(f)
        linecounter += 1
    end
    (timetaken, linecounter)
end
julia> totaltime, totallines
(0.004484679, 76803)

吸取 - 一次性读取整个文件

[编辑 | 编辑源代码]

你可以使用 read() 一次性读取打开文件的全部内容

julia> s = read(f, String)

这将文件的全部内容存储在 s

s = open("sherlock-holmes.txt") do file
    read(file, String)
end

你可以使用 readlines() 将整个文件读入数组,每行是一个元素

julia> f = open("sherlock-holmes.txt");

julia> lines = readlines(f)
76803-element Array{String,1}:
"THE ADVENTURES OF SHERLOCK HOLMES by SIR ARTHUR CONAN DOYLE\r\n"
"\r\n"
"   I. A Scandal in Bohemia\r\n"
"  II. The Red-headed League\r\n"
...
"Holmes, rather to my disappointment, manifested no further\r\n"
"interest in her when once she had ceased to be the centre of one\r\n"
"of his problems, and she is now the head of a private school at\r\n"
"Walsall, where I believe that she has met with considerable success.\r\n"
julia> close(f)

现在你可以遍历这些行

counter = 1
for l in lines
   println("$counter $l")
   counter += 1
end
1 THE ADVENTURES OF SHERLOCK HOLMES by SIR ARTHUR CONAN DOYLE
2
3    I. A Scandal in Bohemia
4   II. The Red-headed League
5  III. A Case of Identity
6   IV. The Boscombe Valley Mystery
...
12638 interest in her when once she had ceased to be the centre of one
12639 of his problems, and she is now the head of a private school at
12640 Walsall, where I believe that she has met with considerable success.

有一个更好的方法 - 参见下面的 enumerate()

你可能会发现 chomp() 函数很有用 - 它会从字符串中删除尾部的换行符。

eachline() 函数将源代码转换为迭代器。这允许你逐行处理文件

open("sherlock-holmes.txt") do file
    for ln in eachline(file)
        println("$(length(ln)), $(ln)")
    end
end
1, THE ADVENTURES OF SHERLOCK HOLMES by SIR ARTHUR CONAN DOYLE
2,
28,    I. A Scandal in Bohemia
29,   II. The Red-headed League
26,  III. A Case of Identity
35,   IV. The Boscombe Valley Mystery
…
62, the island of Mauritius. As to Miss Violet Hunter, my friend
60, Holmes, rather to my disappointment, manifested no further
66, interest in her when once she had ceased to be the centre of one
65, of his problems, and she is now the head of a private school at
70, Walsall, where I believe that she has met with considerable success.

另一种方法是读取直到到达文件末尾。你可能想要跟踪你正在处理的哪一行

 open("sherlock-holmes.txt") do f
   line = 1
   while !eof(f)
     x = readline(f)
     println("$line $x")
     line += 1
   end
 end

更好的方法是对可迭代对象使用 enumerate() - 你将“免费”获得行号

open("sherlock-holmes.txt") do f
    for i in enumerate(eachline(f))
      println(i[1], ": ", i[2])
    end
end

如果你想对文件调用特定函数,可以使用这种替代语法

function shout(f::IOStream)
    return uppercase(read(f, String))
end
julia> shoutversion = open(shout, "sherlock-holmes.txt");
julia> shoutversion[30237:30400]
"ELEMENTARY PROBLEMS. LET HIM, ON MEETING A\nFELLOW-MORTAL, LEARN AT A GLANCE TO DISTINGUISH THE HISTORY OF THE\nMAN, AND THE TRADE OR  PROFESSION TO WHICH HE BELONGS. "

这将打开文件,对它运行 shout() 函数,然后再次关闭它,并将处理后的内容分配给变量。

你可以使用 CSV.jl 来读取和写入逗号分隔值 (.csv) 文件,并且推荐使用它,而不是使用 DelimitedFiles.readdlm() 函数(它处理更多边界情况并且速度更快,尤其是在处理大型文件时)来读取以特定字符分隔的行,例如数据文件、存储为文本文件的数组和表格。如果你使用 DataFrames 包,还有一个专门用于将数据读入表格的 readtable() 函数。

处理路径和文件名

[编辑 | 编辑源代码]

以下函数将有助于处理文件名

  • cd(path) 更改当前目录。
  • pwd() 获取当前工作目录。
  • readdir(path) 返回命名目录或当前目录的内容列表。
  • abspath(path) 将当前目录的路径添加到文件名,以创建绝对路径名。
  • joinpath(str, str, ...) 从多个部分组装路径名。
  • isdir(path) 告诉你路径是否是一个目录。
  • splitdir(path) - 将路径拆分为目录名和文件名的元组。
  • splitdrive(path) - 在 Windows 上,将路径拆分为驱动器号部分和路径部分。在 Unix 系统上,第一个组件始终为空字符串。
  • splitext(path) - 如果路径的最后一个组件包含点号,则将路径拆分为点号之前的部分和包括点号及之后的全部部分。否则,返回一个元组,其中包含未修改的参数和空字符串。
  • expanduser(path) - 将路径开头的波浪号替换为当前用户的家目录。
  • normpath(path) - 规范化路径,删除 "." 和 ".." 条目。
  • realpath(path) - 通过扩展符号链接并删除 "." 和 ".." 条目来规范化路径。
  • homedir() - 获取当前用户的家目录。
  • dirname(path) - 获取路径的目录部分。
  • basename(path) - 获取路径的文件名部分。

要处理目录中的一组受限的文件,请使用 filter() 和匿名函数来筛选文件名,只保留你想要的文件。(filter() 更像是一个捕鱼网或筛子,而不是咖啡过滤器,因为它捕获了你想保留的东西。)

for f in filter(x -> endswith(x, "jl"), readdir())
    println(f)
end

Astro.jl
calendar.jl
constants.jl
coordinates.jl
...
pseudoscience.jl
riseset.jl
sidereal.jl
sun.jl
utils.jl
vsop87d.jl

如果你想使用正则表达式匹配一组文件,那么请使用 occursin()。让我们查找后缀为 ".jpg" 或 ".png" 的文件(记住要转义 ".")。

for f in filter(x -> occursin(r"(?i)\.jpg|\.png", x), readdir())
    println(f)
end
034571172750.jpg
034571172750.png
51ZN2sCNfVL._SS400_.jpg
51bU7lucOJL._SL500_AA300_.jpg
Voronoy.jpg
kblue.png
korange.png
penrose.jpg
r-home-id-r4.png
wave.jpg

要检查文件层次结构,请使用 walkdir(),它允许你遍历目录,并依次检查每个目录中的文件。

文件信息

[编辑 | 编辑源代码]

如果你想要有关特定文件的信息,请使用 stat("pathname"),然后使用其中一个字段来查找信息。以下是如何获取有关文件“i”的所有信息以及列出的字段名称:

 for n in fieldnames(typeof(stat("i")))
    println(n, ": ", getfield(stat("i"),n))
end
device: 16777219
inode: 2955324
mode: 16877
nlink: 943
uid: 502
gid: 20
rdev: 0
size: 32062
blksize: 4096
blocks: 0
mtime:1.409769933e9
ctime:1.409769933e9

你可以通过“stat”结构访问这些字段

julia> s = stat("Untitled1.ipynb")
StatStruct(mode=100644, size=64424)
julia> s.ctime
1.446649269e9

你也可以直接使用其中一些字段

julia> ctime("Untitled2.ipynb")
1.446649269e9

尽管不是 size

julia> s.size
64424

要处理满足条件的特定文件 - 例如,所有 Jupyter 文件(即扩展名为“ipynb”的文件)在特定日期后修改的文件 - 你可以使用类似以下的内容

using Dates
function output_file(path)
    println(stat(path).size, ": ", path)
end 

for afile in filter!(f -> endswith(f, "ipynb") && (mtime(f) > Dates.datetime2unix(DateTime("2015-11-03T09:00"))),
    readdir())
    output_file(realpath(afile))
end

与文件系统交互

[编辑 | 编辑源代码]

cp()mv()rm()touch() 函数与其 Unix shell 对应项具有相同的名称和功能。

要将文件名转换为路径名,请使用 abspath()。你可以将它映射到目录中的文件列表上

julia> map(abspath, readdir())
67-element Array{String,1}:
"/Users/me/.CFUserTextEncoding"
"/Users/me/.DS_Store"
"/Users/me/.Trash"
"/Users/me/.Xauthority"
"/Users/me/.ahbbighrc"
"/Users/me/.apdisk"
"/Users/me/.atom"
...

要将列表限制为包含特定子字符串的文件名,请在 filter() 中使用匿名函数 - 类似于以下内容

julia> filter(x -> occursin("re", x), map(abspath, readdir()))
4-element Array{String,1}:
"/Users/me/.DS_Store"
"/Users/me/.gitignore"
"/Users/me/.hgignore_global"
"/Users/me/Pictures"
...

要将列表限制为正则表达式匹配项,请尝试以下操作

julia> filter(x -> occursin(r"recur.*\.jl", x), map(abspath, readdir()))
2-element Array{String,1}:
 "/Users/me/julia/recursive-directory-scan.jl"
 "/Users/me/julia/recursive-text.jl"

写入文件

[编辑 | 编辑源代码]

要写入文本文件,请使用“w”标志打开它,并确保你具有在指定目录中创建文件的权限

open("/tmp/t.txt", "w") do f
    write(f, "A, B, C, D\n")
end

以下是如何写入 20 行,每行包含 4 个介于 1 到 10 之间的随机数,并以逗号分隔

function fourrandom()
    return rand(1:10,4)
end

open("/tmp/t.txt", "w") do f
           for i in 1:20
              n1, n2, n3, n4 = fourrandom()
              write(f, "$n1, $n2, $n3, $n4 \n")
           end
       end

比这更快的选择是使用 DelimitedFiles.writedlm() 函数,接下来将介绍它。

using DelimitedFiles
writedlm("/tmp/test.txt", rand(1:10, 20, 4), ", ")

将数组写入文件并从文件读取数组

[编辑 | 编辑源代码]

在 DelimitedFiles 包中,有两个方便的函数,writedlm()readdlm()。它们允许你将数组或集合写入文件或从文件读取数组或集合。

writedlm() 将对象的全部内容写入文本文件,而 readdlm() 将数据从文件读入数组

julia> numbers = rand(5,5)
5x5 Array{Float64,2}:
0.913583  0.312291  0.0855798  0.0592331  0.371789
0.13747   0.422435  0.295057   0.736044   0.763928
0.360894  0.434373  0.870768   0.469624   0.268495
0.620462  0.456771  0.258094   0.646355   0.275826
0.497492  0.854383  0.171938   0.870345   0.783558

julia> writedlm("/tmp/test.txt", numbers)

你可以使用 shell 查看该文件(键入分号“;”切换)

<shell> cat "/tmp/test.txt"
.9135833328830523	.3122905420350348	.08557977218948465	.0592330821115965	.3717889559226475
.13747015238054083	.42243494637594203	.29505701073304524	.7360443978397753	.7639280496847236
.36089432672073607	.43437288984307787	.870767989032692	.4696243851552686	.26849468736154325
.6204624598015906	.4567706404666232	.25809436255988105	.6463554854347682	.27582613759302377
.4974916625466639	.8543829989347014	.17193814498701587	.8703447748713236	.783557793485824

除非你指定其他分隔符,否则元素将以制表符分隔。这里使用冒号作为数字分隔符

julia> writedlm("/tmp/test.txt", rand(1:6, 10, 10), ":")
shell> cat "/tmp/test.txt"
3:3:3:2:3:2:6:2:3:5
3:1:2:1:5:6:6:1:3:6
5:2:3:1:4:4:4:3:4:1
3:2:1:3:3:1:1:1:5:6
4:2:4:4:4:2:3:5:1:6
6:6:4:1:6:6:3:4:5:4
2:1:3:1:4:1:5:4:6:6
4:4:6:4:6:6:1:4:2:3
1:4:4:1:1:1:5:6:5:6
2:4:4:3:6:6:1:1:5:5

要从文本文件读取数据,可以使用 readdlm()

julia> numbers = rand(5,5)
5x5 Array{Float64,2}:
0.862955  0.00827944  0.811526  0.854526  0.747977
0.661742  0.535057    0.186404  0.592903  0.758013
0.800939  0.949748    0.86552   0.113001  0.0849006
0.691113  0.0184901   0.170052  0.421047  0.374274
0.536154  0.48647     0.926233  0.683502  0.116988
julia> writedlm("/tmp/test.txt", numbers)

julia> numbers = readdlm("/tmp/test.txt")
5x5 Array{Float64,2}:
0.862955  0.00827944  0.811526  0.854526  0.747977
0.661742  0.535057    0.186404  0.592903  0.758013
0.800939  0.949748    0.86552   0.113001  0.0849006
0.691113  0.0184901   0.170052  0.421047  0.374274
0.536154  0.48647     0.926233  0.683502  0.116988

还有一些专门用于读取和写入文件数据的 Julia 包,包括 DataFrames.jl 和 CSV.jl。您可以在 JuliaHubJuliaPackages 中搜索这些包和其他包。许多这些包都位于 JuliaData 组织的网站上。

华夏公益教科书