小師妹學(xué)JavaIO之:文件讀取那些事

簡(jiǎn)介

小師妹最新對(duì)java IO中的reader和stream產(chǎn)生了一點(diǎn)點(diǎn)困惑蹋半,不知道到底該用哪一個(gè)才對(duì),怎么讀取文件才是正確的姿勢(shì)呢弃甥?今天F師兄現(xiàn)場(chǎng)為她解答惶室。

字符和字節(jié)

小師妹最近很迷糊:F師兄,上次你講到IO的讀取分為兩大類帘靡,分別是Reader知给,InputStream,這兩大類有什么區(qū)別嗎描姚?為什么我看到有些類即是Reader又是Stream涩赢?比如:InputStreamReader?

小師妹,你知道哲學(xué)家的終極三問嗎轩勘?你是誰筒扒?從哪里來?到哪里去绊寻?

F師兄花墩,你是不是迷糊了,我在問你java澄步,你扯什么哲學(xué)冰蘑。

小師妹,其實(shí)吧村缸,哲學(xué)是一切學(xué)問的基礎(chǔ)祠肥,你知道科學(xué)原理的英文怎么翻譯嗎?the philosophy of science梯皿,科學(xué)的原理就是哲學(xué)仇箱。

你看計(jì)算機(jī)中代碼的本質(zhì)是什么县恕?代碼的本質(zhì)就是0和1組成的一串長長的二進(jìn)制數(shù),這么多二進(jìn)制數(shù)組合起來就成了計(jì)算機(jī)中的代碼工碾,也就是JVM可以識(shí)別可以運(yùn)行的二進(jìn)制代碼弱睦。

更多內(nèi)容請(qǐng)?jiān)L問www.flydean.com

小師妹一臉崇拜:F師兄說的好像很有道理,但是這和Reader渊额,InputStream有什么關(guān)系呢况木?

別急,冥冥中自有定數(shù)旬迹,先問你一個(gè)問題火惊,java中存儲(chǔ)的最小單位是什么?

小師妹:容我想想奔垦,java中最小的應(yīng)該是boolean屹耐,true和false正好和二進(jìn)制1,0對(duì)應(yīng)椿猎。

對(duì)了一半惶岭,雖然boolean也是java中存儲(chǔ)的最小單位,但是它需要占用一個(gè)字節(jié)Byte的空間犯眠。java中最小的存儲(chǔ)單位其實(shí)是字節(jié)Byte按灶。不信的話可以用之前我介紹的JOL工具來驗(yàn)證一下:

[main] INFO com.flydean.JolUsage - java.lang.Boolean object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0    12           (object header)                           N/A
     12     1   boolean Boolean.value                             N/A
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

上面是裝箱過后的Boolean,可以看到雖然Boolean最后占用16bytes筐咧,但是里面的boolean只有1byte鸯旁。

byte翻譯成中文就是字節(jié),字節(jié)是java中存儲(chǔ)的基本單位量蕊。

有了字節(jié)铺罢,我們就可以解釋字符了,字符就是由字節(jié)組成的残炮,根據(jù)編碼方式的不同韭赘,字符可以有1個(gè),2個(gè)或者多個(gè)字節(jié)組成吉殃。我們?nèi)祟惪梢匀庋圩R(shí)別的漢字呀辞居,英文什么的都可以看做是字符。

而Reader就是按照一定編碼格式讀取的字符蛋勺,而InputStream就是直接讀取的更加底層的字節(jié)。

小師妹:我懂了鸠删,如果是文本文件我們就可以用Reader抱完,非文本文件我們就可以用InputStream。

孺子可教刃泡,小師妹進(jìn)步的很快巧娱。

按字符讀取的方式

小師妹碉怔,接下來F師兄給你講下按字符讀取文件的幾種方式,第一種就是使用FileReader來讀取File禁添,但是FileReader本身并沒有提供任何讀取數(shù)據(jù)的方法撮胧,想要真正的讀取數(shù)據(jù),我們還是要用到BufferedReader來連接FileReader老翘,BufferedReader提供了讀取的緩存芹啥,可以一次讀取一行:

public void withFileReader() throws IOException {
        File file = new File("src/main/resources/www.flydean.com");

        try (FileReader fr = new FileReader(file); BufferedReader br = new BufferedReader(fr)) {
            String line;
            while ((line = br.readLine()) != null) {
                if (line.contains("www.flydean.com")) {
                    log.info(line);
                }
            }
        }
    }

每次讀取一行,可以把這些行連起來就組成了stream铺峭,通過Files.lines墓怀,我們獲取到了一個(gè)stream,在stream中我們就可以使用lambda表達(dá)式來讀取文件了卫键,這是謂第二種方式:

public void withStream() throws IOException {
        Path filePath = Paths.get("src/main/resources", "www.flydean.com");
        try (Stream<String> lines = Files.lines(filePath))
        {
            List<String> filteredLines = lines.filter(s -> s.contains("www.flydean.com"))
                    .collect(Collectors.toList());
            filteredLines.forEach(log::info);
        }
    }

第三種其實(shí)并不常用傀履,但是師兄也想教給你。這一種方式就是用工具類中的Scanner莉炉。通過Scanner可以通過換行符來分割文件钓账,用起來也不錯(cuò):

public void withScanner() throws FileNotFoundException {
        FileInputStream fin = new FileInputStream(new File("src/main/resources/www.flydean.com"));
        Scanner scanner = new Scanner(fin,"UTF-8").useDelimiter("\n");
        String theString = scanner.hasNext() ? scanner.next() : "";
        log.info(theString);
        scanner.close();
    }

按字節(jié)讀取的方式

小師妹聽得很滿足,連忙催促我:F師兄絮宁,字符讀取方式我都懂了梆暮,快將字節(jié)讀取吧。

我點(diǎn)了點(diǎn)頭羞福,小師妹惕蹄,哲學(xué)的本質(zhì)還記得嗎?字節(jié)就是java存儲(chǔ)的本質(zhì)治专。掌握到本質(zhì)才能勘破一切虛偽卖陵。

還記得之前講過的Files工具類嗎?這個(gè)工具類提供了很多文件操作相關(guān)的方法张峰,其中就有讀取所有bytes的方法泪蔫,小師妹要注意了,這里是一次性讀取所有的字節(jié)喘批!一定要慎用撩荣,只可用于文件較少的場(chǎng)景,切記切記饶深。

public void readBytes() throws IOException {
        Path path = Paths.get("src/main/resources/www.flydean.com");
        byte[] data = Files.readAllBytes(path);
        log.info("{}",data);
    }

如果是比較大的文件餐曹,那么可以使用FileInputStream來一次讀取一定數(shù)量的bytes:

public void readWithStream() throws IOException {
        File file = new File("src/main/resources/www.flydean.com");
        byte[] bFile = new byte[(int) file.length()];
        try(FileInputStream fileInputStream  = new FileInputStream(file))
        {
            fileInputStream.read(bFile);
            for (int i = 0; i < bFile.length; i++) {
                log.info("{}",bFile[i]);
            }
        }
    }

Stream讀取都是一個(gè)字節(jié)一個(gè)字節(jié)來讀的,這樣做會(huì)比較慢敌厘,我們使用NIO中的FileChannel和ByteBuffer來加快一些讀取速度:

public void readWithBlock() throws IOException {
        try (RandomAccessFile aFile = new RandomAccessFile("src/main/resources/www.flydean.com", "r");
             FileChannel inChannel = aFile.getChannel();) {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            while (inChannel.read(buffer) > 0) {
                buffer.flip();
                for (int i = 0; i < buffer.limit(); i++) {
                    log.info("{}", buffer.get());
                }
                buffer.clear();
            }
        }
    }

小師妹:如果是非常非常大的文件的讀取台猴,有沒有更快的方法呢?

當(dāng)然有,記得上次我們講過的虛擬地址空間的映射吧:

image

我們可以直接將用戶的地址空間和系統(tǒng)的地址空間同時(shí)map到同一個(gè)虛擬地址內(nèi)存中饱狂,這樣就免除了拷貝帶來的性能開銷:

public void copyWithMap() throws IOException{
        try (RandomAccessFile aFile = new RandomAccessFile("src/main/resources/www.flydean.com", "r");
             FileChannel inChannel = aFile.getChannel()) {
             MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
             buffer.load();
            for (int i = 0; i < buffer.limit(); i++)
            {
                log.info("{}", buffer.get());
            }
            buffer.clear();
        }
    }

尋找出錯(cuò)的行數(shù)

小師妹:好贊曹步!F師兄你講得真好,小師妹我還有一個(gè)問題:最近在做文件解析休讳,有些文件格式不規(guī)范讲婚,解析到一半就解析失敗了,但是也沒有個(gè)錯(cuò)誤提示到底錯(cuò)在哪一行俊柔,很難定位問題呀筹麸,有沒有什么好的解決辦法?

看看天色已經(jīng)不早了婆咸,師兄就再教你一個(gè)方法竹捉,java中有一個(gè)類叫做LineNumberReader,使用它來讀取文件可以打印出行號(hào)尚骄,是不是就滿足了你的需求:

public void useLineNumberReader() throws IOException {
        try(LineNumberReader lineNumberReader = new LineNumberReader(new FileReader("src/main/resources/www.flydean.com")))
        {
            //輸出初始行數(shù)
            log.info("Line {}" , lineNumberReader.getLineNumber());
            //重置行數(shù)
            lineNumberReader.setLineNumber(2);
            //獲取現(xiàn)有行數(shù)
            log.info("Line {} ", lineNumberReader.getLineNumber());
            //讀取所有文件內(nèi)容
            String line = null;
            while ((line = lineNumberReader.readLine()) != null)
            {
                log.info("Line {} is : {}" , lineNumberReader.getLineNumber() , line);
            }
        }
    }

總結(jié)

今天給小師妹講解了字符流和字節(jié)流块差,還講解了文件讀取的基本方法,不虛此行倔丈。

本文的例子https://github.com/ddean2009/learn-java-io-nio

本文作者:flydean程序那些事

本文鏈接:http://www.flydean.com/io-file-reader/

本文來源:flydean的博客

歡迎關(guān)注我的公眾號(hào):程序那些事憨闰,更多精彩等著您!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末需五,一起剝皮案震驚了整個(gè)濱河市鹉动,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宏邮,老刑警劉巖泽示,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蜜氨,居然都是意外死亡械筛,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門飒炎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來埋哟,“玉大人,你說我怎么就攤上這事郎汪〕嗌蓿” “怎么了?”我有些...
    開封第一講書人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵煞赢,是天一觀的道長抛计。 經(jīng)常有香客問我,道長照筑,這世上最難降的妖魔是什么爷辱? 我笑而不...
    開封第一講書人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任录豺,我火速辦了婚禮朦肘,結(jié)果婚禮上饭弓,老公的妹妹穿的比我還像新娘。我一直安慰自己媒抠,他們只是感情好弟断,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著趴生,像睡著了一般阀趴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上苍匆,一...
    開封第一講書人閱讀 51,245評(píng)論 1 299
  • 那天刘急,我揣著相機(jī)與錄音,去河邊找鬼浸踩。 笑死叔汁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的检碗。 我是一名探鬼主播据块,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼折剃!你這毒婦竟也來了另假?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤怕犁,失蹤者是張志新(化名)和其女友劉穎边篮,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奏甫,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡戈轿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扶檐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凶杖。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖款筑,靈堂內(nèi)的尸體忽然破棺而出智蝠,到底是詐尸還是另有隱情,我是刑警寧澤奈梳,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布杈湾,位于F島的核電站,受9級(jí)特大地震影響攘须,放射性物質(zhì)發(fā)生泄漏漆撞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望浮驳。 院中可真熱鬧悍汛,春花似錦、人聲如沸至会。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奉件。三九已至宵蛀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間县貌,已是汗流浹背术陶。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留煤痕,地道東北人梧宫。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像杭攻,于是被迫代替她去往敵國和親祟敛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354