Java核心教程6: 文件讀寫(xiě)

在丑陋的 Java I/O 編程方式誕生多年以后盐捷,Java終于簡(jiǎn)化了文件讀寫(xiě)的基本操作偶翅。

很多學(xué)得比較快的同學(xué)可能學(xué)習(xí)過(guò)Java的文件讀寫(xiě),就是諸如 InputStream, OutputStream 一樣的東西碉渡,如上面所說(shuō)的一樣聚谁,他們既丑陋又難用,學(xué)習(xí)路線非常曲折滞诺。不過(guò)好在 Java 設(shè)計(jì)者終于意識(shí)到了 Java 使用者多年來(lái)的痛苦形导,在 Java7 中對(duì)此引入了巨大的改進(jìn),這些新特性被放在 java.nio.file 包下面习霹。除此之外朵耕,Java8 新增的 streams 與文件結(jié)合使得文件操作變得更加優(yōu)雅易用,可以說(shuō)從此以后我們幾乎再也不需要見(jiàn)到 InputStream 這種難用的東西了淋叶。

首先讓我們將看一下文件操作的兩個(gè)基本概念:

  1. 文件的路徑
  2. 文件本身

可能有同學(xué)要問(wèn)我這里為什么只提到了文件而沒(méi)有提到目錄阎曹,我這里說(shuō)的文件是指廣義的文件(File),包括我們認(rèn)為的文件(RegularFile)和目錄(Directory)煞檩,為了避免歧義处嫌,之后盡量用英文說(shuō)明狹義的文件(RegularFile)。



一斟湃、文件的路徑

路徑熏迹,也就是 Path ,例如"C://Windows/Fonts"凝赛、"/Users/tommy0607/Downloads/InputFix.jar"等等都是路徑癣缅。

java.nio.file.Paths 類(lèi)包含一個(gè)重載方法 static get(String first, String... more),該方法通過(guò)輸入一系列路徑片段可以返回一個(gè)路徑哄酝。

例如想要獲得 /Users/tommy0607/Downloads 的路徑,可以用下面三種方式:

  • 直接用全路徑:Paths.get("/Users/tommy0607/Downloads")
  • 用父目錄的路徑名和文件名字:Paths.get("/Users/tommy0607","Downloads")
  • 將路徑進(jìn)行任意分段:Paths.get("/Users","tommy0607","Downloads")

當(dāng)然除了絕對(duì)路徑之外祷膳,相對(duì)路徑也是可以的陶衅,例如Path.get("test/abc.png")就是一個(gè)相對(duì)路徑。

它還有一個(gè)重載方法直晨,接受一個(gè)統(tǒng)一資源定位符URI為參數(shù)搀军,在本節(jié)課中用不到就直接略過(guò)膨俐。

Path 對(duì)象的主要方法如下:

  • getParent() 獲取該路徑的父路徑
  • isAbsolute() 返回該路徑是否是絕對(duì)路徑
  • toAbsolutePath() 獲取該路徑的絕對(duì)路徑
  • normalize() 將絕對(duì)路徑正常化罩句,如果當(dāng)前路徑是相對(duì)路徑則會(huì)報(bào)錯(cuò)
  • toUri() 將其轉(zhuǎn)換成URI
  • resolve(String/Path other) 將當(dāng)前路徑與參數(shù)中的路徑進(jìn)行拼接焚刺,一般用于獲取子目錄的路徑
  • getFileName() 獲取路徑的文件名,分段路徑的最右邊那部分就是文件名

關(guān)于normalize()方法的正趁爬茫化我要特別講解一下:
假設(shè)當(dāng)前目錄是 /Users/tommy0607/Downloads乳愉,現(xiàn)有這樣的對(duì)象Path path = Paths.get("../../test.jar")
那么path.toAbsolutePath()返回的路徑是 /Users/tommy0607/Downloads/../../test.jar屯远,而path.toAbsolutePath().normoalize()返回的路徑則是 /User/test.jar蔓姚,也就是它會(huì)把絕對(duì)路徑里所有的"../"都去掉,這就是正晨ぃ化

還有要特別注意的就是 Path 類(lèi)進(jìn)行的所有操作都只是基于字符串處理坡脐,也就是說(shuō)如果你輸入一個(gè)不存在的路徑也不會(huì)有任何的報(bào)錯(cuò),這點(diǎn)要多加注意房揭!



二备闲、文件本身

雖然 Java 中也有用來(lái)代表一個(gè)文件(包括RegularFile和Directory)的類(lèi),叫做 File捅暴,但這個(gè)類(lèi)的設(shè)計(jì)有些問(wèn)題恬砂,它與其說(shuō)是文件還不如說(shuō)是路徑,更何況在 Java7 之后就基本用不到這個(gè)類(lèi)了伶唯,現(xiàn)在用得比較多的是 nio 中的 Files 類(lèi)觉既。

Files 工具類(lèi)包含一系列完整的方法用于獲得路徑對(duì)應(yīng)的文件的相關(guān)信息,下面用一個(gè)簡(jiǎn)單的示例來(lái)展示:

import java.nio.file.*;
import java.io.IOException;

public class PathAnalysis {
    public static void main(String[] args) throws IOException {
        Path p = Paths.get("PathAnalysis.java").toAbsolutePath();
        say("該文件存在", Files.exists(p));
        say("該文件是目錄", Files.isDirectory(p));
        say("該文件可執(zhí)行", Files.isExecutable(p));
        say("該文件可讀", Files.isReadable(p));
        say("該文件是RegularFile", Files.isRegularFile(p));
        say("該文件可寫(xiě)", Files.isWritable(p));
        say("該文件不存在", Files.notExists(p));
        say("該文件是隱藏的", Files.isHidden(p));
        say("文件尺寸", Files.size(p));
        say("文件最后修改日期: ", Files.getLastModifiedTime(p));
        say("文件擁有者", Files.getOwner(p));
    }
    
    static void say(String id, Object result) {
        System.out.print(id + ": ");
        System.out.println(result);
    }
}



三乳幸、文件查找

在查找文件方面瞪讼,Java 也提供了非常方便實(shí)用的API。但在介紹文件查找之前不得不先介紹一下Files.walk()方法:它可以返回某個(gè)路徑下所有子目錄和文件的路徑的流Stream<Path>粹断。舉個(gè)例子:

Files.walk(Paths.get("/Users/tommy0607/Downloads"))
        .forEach(System.out::println);

它輸出了/Users/tommy0607/Downloads 下面所有子目錄和文件的路徑(包括當(dāng)前目錄本身)


文件查找有兩種模式符欠,globregex,這里只講解前一種模式瓶埋,后一種是使用正則表達(dá)式希柿,感興趣的同學(xué)可以自學(xué)。

在這里养筒,我們使用 glob 查找以 .tmp.txt 結(jié)尾的所有路徑:

import java.nio.file.*;

public class Find {
    public static void main(String[] args) throws Exception {
        Path test = Paths.get("test");
        // 創(chuàng)建一個(gè)叫dir.tmp的文件夾
        Files.createDirectory(test.resolve("dir.tmp"));

        
        PathMatcher matcher = FileSystems.getDefault()
          .getPathMatcher("glob:**/*.{tmp,txt}");
        Files.walk(test)
          .filter(matcher::matches)
          .forEach(System.out::println);
        System.out.println("***************");

        
        
        PathMatcher matcher2 = FileSystems.getDefault()
          .getPathMatcher("glob:*.tmp");
        Files.walk(test)
          .map(Path::getFileName)
          .filter(matcher2::matches)
          .forEach(System.out::println);
        System.out.println("***************");

        
        
        Files.walk(test) 
          .filter(Files::isRegularFile) //只查找RegularFile
          .map(Path::getFileName)
          .filter(matcher2::matches)
          .forEach(System.out::println);
    }
}
/* 輸出:
test\bag\foo\bar\baz\5208762845883213974.tmp
test\bag\foo\bar\baz\File.txt
test\bar\baz\bag\foo\7918367201207778677.tmp
test\bar\baz\bag\foo\File.txt
test\baz\bag\foo\bar\8016595521026696632.tmp
test\baz\bag\foo\bar\File.txt
test\dir.tmp
test\foo\bar\baz\bag\5832319279813617280.tmp
test\foo\bar\baz\bag\File.txt
***************
5208762845883213974.tmp
7918367201207778677.tmp
8016595521026696632.tmp
dir.tmp
5832319279813617280.tmp
***************
5208762845883213974.tmp
7918367201207778677.tmp
8016595521026696632.tmp
5832319279813617280.tmp
*/

matcher 中曾撤,glob 表達(dá)式開(kāi)頭的 **/ 表示“當(dāng)前目錄及所有子目錄”,這在不僅僅要匹配當(dāng)前目錄下的路徑時(shí)非常有用晕粪。一個(gè) * 表示“任何字符串”挤悉,然后是一個(gè)點(diǎn),接著是大括號(hào)巫湘,表示一系列的可能性——我們正在尋找以 .tmp.txt 結(jié)尾的路徑装悲。

matcher2 只使用 *.tmp昏鹃,按理來(lái)說(shuō)應(yīng)該無(wú)法匹配任何路徑的,但注意看map()方法將全路徑轉(zhuǎn)換成了文件名

注意诀诊,在前兩種情況下洞渤,輸出中都會(huì)出現(xiàn) dir.tmp,即使它是一個(gè)目錄而不是一個(gè)文件(RegularFile)属瓣。如果只查找文件载迄,必須像在最后的 files.walk() 中那樣對(duì)其進(jìn)行篩選。



四奠涌、文件讀寫(xiě)

到了最后一部分才講到本課程的核心主題——文件讀寫(xiě)宪巨,不過(guò)用起來(lái)其實(shí)非常簡(jiǎn)單方便。

讀文件

如果一個(gè)文件很小溜畅,也就是說(shuō)就算把整個(gè)文件一次性全部讀到內(nèi)存中也沒(méi)問(wèn)題捏卓,那么可以用Files.readAllLines()Files.readAllBytes(),前者是返回所有行的字符串慈格,后者則是獲取返回整個(gè)文件的字節(jié)數(shù)組怠晴。

但如果文件很大,一次性讀取到內(nèi)存中不太可能浴捆,或者說(shuō)你并不需要讀取整個(gè)文本蒜田,Files.lines() 方便地將文件轉(zhuǎn)換為行的 Stream(也就是說(shuō)流中的元素是文件的每一行文本):

import java.nio.file.*;

public class ReadLineStream {
    public static void main(String[] args) throws Exception {
        Files.lines(Paths.get("PathInfo.java"))
          .skip(13)
          .findFirst()
          .ifPresent(System.out::println);
    }
}

代碼很容易理解,先跳過(guò) PathInfo.java 的前13行选泻,然后讀取接下來(lái)的一行并輸出冲粤。

寫(xiě)文件

寫(xiě)文件就更簡(jiǎn)單了,直接調(diào)用Files.write()即可页眯,支持寫(xiě)入字節(jié)數(shù)組和一行一行的字符串梯捕;

或者Files.writeString(),只寫(xiě)入一個(gè)字符串對(duì)象窝撵。這兩個(gè)方法中都有的 Charset 類(lèi)型的參數(shù)是寫(xiě)入的字符串的編碼傀顾,如果不指定的話默認(rèn)就是UTF-8編碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末碌奉,一起剝皮案震驚了整個(gè)濱河市短曾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赐劣,老刑警劉巖嫉拐,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異魁兼,居然都是意外死亡椭岩,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)判哥,“玉大人,你說(shuō)我怎么就攤上這事碉考∷疲” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵侯谁,是天一觀的道長(zhǎng)锌仅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)墙贱,這世上最難降的妖魔是什么热芹? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮惨撇,結(jié)果婚禮上伊脓,老公的妹妹穿的比我還像新娘。我一直安慰自己魁衙,他們只是感情好报腔,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著剖淀,像睡著了一般纯蛾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上纵隔,一...
    開(kāi)封第一講書(shū)人閱讀 49,816評(píng)論 1 290
  • 那天翻诉,我揣著相機(jī)與錄音,去河邊找鬼捌刮。 笑死碰煌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的糊啡。 我是一名探鬼主播拄查,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼棚蓄!你這毒婦竟也來(lái)了堕扶?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤梭依,失蹤者是張志新(化名)和其女友劉穎稍算,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體役拴,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡糊探,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片科平。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡褥紫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瞪慧,到底是詐尸還是另有隱情髓考,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布弃酌,位于F島的核電站氨菇,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏妓湘。R本人自食惡果不足惜查蓉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望榜贴。 院中可真熱鬧豌研,春花似錦、人聲如沸竣灌。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)初嘹。三九已至及汉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間屯烦,已是汗流浹背坷随。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留驻龟,地道東北人温眉。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像翁狐,于是被迫代替她去往敵國(guó)和親类溢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348