Erlang文件讀寫

2009-03-29
Organization of the Libraries
操作文件的函數被組織成如下4個模塊:
file:打開、關閉、讀寫文件、列取目錄等;
filename:跨平台的文件名操作模塊;
filelib:係file的擴展模塊,包含一系列用于列取文件、檢查文件類型等的函數;
io:用于操作已經打開的文件,例如格式化輸出數据到文件等。

The Different Ways of Reading a File
例如我們要操作包含如下內容的一個文件data1.dat:
{person, "oscar", "tang",
[{occupation, programmer}, {favoriteLanguage, erlang}]}.
{cat, {name, "ml"},{owner, "oscar"}}.

下面我們用几种方式來讀取上述文件:

Reading All the Terms in the File
函數file:consult可讀取文件里包含的所有Erlang terms:
1> file:consult("data1.dat").
{ok, [{person, "oscar", "tang",
[{occupation, programmer}, {favoriteLanguage, erlang}]},
{cat, {name, "ml"}, {owner, "oscar"}}]}

file:consult(File)假設文件由一堆Erlang terms組成,如成功讀取文件里的所有terms則返回{ok, [Term]},否則返回{error, Reason}。

Reading the Terms in the File One at a Time
如果想逐個讀取文件里的erlang term,可首先用file:open來打開文件,然后用io:read讀取一個erlang term,直到去到文件尾,最后用file:close關閉文件:
1> {ok, S} = file:open("data1.dat", read).
{ok, <0.36.0>}
2> io:read(S, '').
{ok, {person, "oscar", "tang",
[{occupation, programmer}, {favoriteLanguage, erlang}]}}
3> io:read(S, '').
{ok, {cat, {name, "ml"}, {owner, "oscar"}}}
4> io:read(S, '').
eof
5> file:close(S)

上例用到的函數用法如下:
@spec file:open(File, read) -> {ok, IoDevice} | {error, Why}
嘗試以讀取方式打開文件,如果成功打開則返回{ok, IoDevice},否則返回{error, Reason},IoDevice係一個用于操作文件的標識符。

@spec io:read(IoDevice, Prompt) -> {ok, Term} | {error, Why} | eof
從IoDevice處讀取一個erlang term,如果係讀取文件,參數Prompt會被省略,當我們用io:read從標准輸入處獲取用戶輸入時,Prompt負責輸出一句提示輸入的信息。

@spec file:close(IoDevice) -> ok | {error, Why}
關閉文件。

使用上述的几個函數我們就可以自己實現file:consult了:
consult(File) ->
case file:open(File, read) of
{ok, S} ->
Val = consult1(S),
file:close(S),
{ok, Val};
{error, Why} ->
{error, Why}
end.

consult1(S) ->
case io:read(S, '') of
{ok, Term} -> [Term | consult1(S)];
eof -> [];
Error -> Error
end.

BUT file:consult其實并非這樣定義的,它有更好的錯誤報告功能,如果你明白上面的代碼的話,可以去看看真正的file:consult係怎樣的。不過還有一個小問題,我們去哪里找file.erl的源文件?Well,我們可以使用函數code:which,該函數可以定位任何已加載模塊的源文件所在路徑:
1> code:which(file).
"/usr/local/lib/erlang/lib/kernel-2.11.2/ebin/file.beam"

在標准發布(standard release)里,每個庫都包含有兩個子目錄,一個叫做src,包含源文件,另一個叫ebin,包含已編譯的Erlang代碼,所以根据上面的返回結果,我們可以知道file.erl的源文件應該在:
/usr/local/lib/erlang/lib/kernel-2.11.2/src/file.erl

Reading the Lines in a File One at a Time
如果我們將io:read換成io:get_line,我們就可以每次讀取文件的一行數据,例如:
1> {ok, S} = file:open("data1.dat", read).
{ok, <0.43.0>}
2> io:get_line(S, '').
"{person, \"oscar\", \"tang\", \n"
3> io:get_line(S, '').
"\t[{occupation, programmer}, {favoriteLanguage, erlang}]}.\n"
....
6> io:get_line(S, '').
eof
7> file:close(S).
ok

Reading the Entire File into a Binary
你可以用函數file:read_file(File)將整個文件當作一個二進製數据來讀取:
1> file:read_file("data1.dat").
{ok, <<"{person, \"oscar\", \"tang\"" ....>>}

如果讀取成功的話,file:read_file(File)返回{ok, Bin},否則返回{error, Why}。這種方式係至今讀取文件最高效的方式。

Reading a File with Random Access
如果我們想要讀取的文件非常大不能一次過進行讀取,那么我們可以打開raw模式用file:pread來讀取文件的任何部分,例如:
1> file:open("data1.dat",[read, binary, raw]).
{ok, {file_descriptor, prim_file, {#Port<0.106>, 5}}}
2> file:pread(S, 22, 46).
{ok, <<"ng\", \n\t[{occupation, progr ....>>}
3> file:pread(S, 1, 10).
{ok, <<"person, \"o">>}
4> file:pread(S, 2, 10).
{ok, <<"erson, \"os">>}
5> file:close(S).

file:pread(IoDevice, Start, Len)從IoDevice處讀取以Start為開頭讀取Len個字節的數据(第一個字節的位置為1,不是0),返回{ok, Bin}或{error, Why}。

The Different Ways of Writing to a File
寫文件的過程跟讀文件差不多~~

Write a List of Terms to a File
假設我們想要創建一個能用file:consult來讀取的文件,標准庫里并沒有提供這樣的一個函數,所以我們要自己實現,看下面的例子:
unconsult(File, L) ->
{ok, S} = file:open(File, write),
lists:foreach(fun(X) -> io:format(S, "~p.\n", [X] end, L),
file:close(S).

然后在shell里運行上述代碼:
1> lib_misc:unconsult("test1.dat",
[{cats, ["zorrow", "daisy"]},
{weather, snowing}]).
ok

看看是否寫入成功并且可以正常打開:
2> file:consult("test1.dat").
{ok, [{cats, ["zorrow", "daisy"]}, {weather, snowing}]}

unconsult首先以write模式來打開文件,然后用io:format(S, "~p\n", [X])來將erlang terms輸出到文件里。

Writing Lines to a File
1> {ok, S} = file:open("test2.dat", write).
{ok, <0.62.0>}
2> io:format(S, "~s\n", ["Hello readers"]).
ok
3> io:format(S, "~w\n", [123]).
ok
4> io:format(S, "~s\n", ["that's it"]).
ok
5> file:close(S).

上述代碼會生成一個名為test2.dat的文件,并寫入如下內容到文件里:
Hello readers
123
that's it

Writing an Entire File in One Operation
用函數file:write_file(Filename, Bytes)一次過寫入整個文件,係最有效率的寫文件操作,如果指定的文件不存在則自動創建一個。

Writing to a Random-Access File
首先我們要先把文件以write模式打開,然后用函數file:pwrite(Position, Bin)來寫入文件,例如:
1> {ok, S} = file:open("....", [raw, write, binary]).
{ok, ....}
2> file:pwrite(S, 10, <<"new">>).
ok
3> file:close(S).
ok

上述代碼在文件的第10個字節位置處寫入字符串"new",如果原先已經有舊的內容,則舊內容會被覆蓋掉~~

譯自《Programming Erlang》- Chapter 13 - Programming with Files

M-OSCAR | Powered by Blogger | Entries (RSS) | Comments (RSS) | Designed by MB Web Design | XML Coded By Cahayabiru.com