簡(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)然有,記得上次我們講過的虛擬地址空間的映射吧:
我們可以直接將用戶的地址空間和系統(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):程序那些事憨闰,更多精彩等著您!