Java字節(jié)流和字符流及IO流的總結(jié)
從接收輸入值說(shuō)起
在日常的開發(fā)應(yīng)用中,有時(shí)候需要直接接收外部設(shè)備如鍵盤等的輸入值,而對(duì)于這種數(shù)據(jù)的接收方式,我們一般有三種方法:字節(jié)流讀取,字符流讀取,Scanner 工具類讀取。
字節(jié)流讀取
直接看一個(gè)例子:
public class Demo01SystemIn { public static void main(String[] args) throws IOException { int a = System.in.read(); System.out.println(a); char c = 'a'; System.out.println((int) c); } }
運(yùn)行程序之后,會(huì)被 read 方法阻塞,這時(shí)候在控制臺(tái)輸入一個(gè)字符 a,那么上面的程序兩句話都會(huì)輸出 97,這個(gè)沒(méi)問(wèn)題,因?yàn)樾懽帜?a 對(duì)應(yīng)的就是 97,那么假如我們輸入一個(gè)中文會(huì)出現(xiàn)什么結(jié)果呢?
把上面示例中的 a 修改為 中,然后運(yùn)行程序,在控制臺(tái)同樣輸入 中,則會(huì)得到 228 和 20013,這就說(shuō)明我們控制臺(tái)輸入的 中 并沒(méi)有全部讀取,原因就是 read 只能讀取 1 個(gè)字節(jié),為了進(jìn)一步驗(yàn)證結(jié)論,我們將上面的例子進(jìn)行改寫:
public class Demo01SystemIn { public static void main(String[] args) throws IOException { char a = (char) System.in.read();//讀取一個(gè)字節(jié) System.out.println(a); char c = '中'; System.out.println(c); } }
運(yùn)行之后得到如下結(jié)果:
可以看到,第一個(gè)輸出亂碼了,因?yàn)?System.in.read() 一次只能讀取一個(gè)字節(jié),而中文在 utf-8 編碼下占用了 3 個(gè)字節(jié)。正因?yàn)?read 方法一次只能讀取一個(gè)字節(jié),所以其范圍只能在 -1~255 之間,-1 表示已經(jīng)讀取到了結(jié)尾。
那么如果想要完整的讀取中文應(yīng)該怎么辦呢?
字符流讀取
我們先看下面一個(gè)例子:
public class Demo01SystemIn { public static void main(String[] args) throws IOException { InputStreamReader inputStreamReader1 = new InputStreamReader(System.in); int b = inputStreamReader1.read();//只能讀一個(gè)字符 System.out.println(b); InputStreamReader inputStreamReader2 = new InputStreamReader(System.in); char[] chars = new char[2]; int c = inputStreamReader2.read(chars);//讀入到指定char數(shù)組,返回當(dāng)前讀取到的字符數(shù) System.out.println("讀取的字符數(shù)為:" + c); System.out.println(chars[0]); System.out.println(chars[1]); } }
運(yùn)行之后,輸出結(jié)果如下所示:
這個(gè)時(shí)候我們已經(jīng)能完成的讀取到一個(gè)字符了,當(dāng)然,有時(shí)候?yàn)榱藘?yōu)化,我們需要使用 BufferedReader 進(jìn)行進(jìn)一步的包裝:
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
這種方式雖然解決了讀取中文會(huì)亂碼問(wèn)題,但是使用起來(lái)也不是很方便,所以一般讀取鍵盤輸入信息我們都會(huì)采用 Scnner 來(lái)讀取。
Scanner 讀取
Scanner 實(shí)際上還是對(duì) System.in 進(jìn)行了封裝,并提供了一系列方法來(lái)讀取不同的字符類型,比如 nextInt,nextFloat,以及 next 等。
public class Demo02Scnner { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); while (scanner.hasNextInt()){ System.out.println(scanner.nextInt()); } } }
什么是 IO 流
流是一種抽象概念,它代表了數(shù)據(jù)的無(wú)結(jié)構(gòu)化傳輸(摘自百度百科)。IO 流對(duì)應(yīng)的就是 InPut 和 Output,也就是輸入和輸出。輸入和輸出這個(gè)概念是針對(duì)于應(yīng)用程序而言,比如當(dāng)前程序中需要讀取文件中的內(nèi)容,那么這就是輸入,而如果需要將應(yīng)用程序本身的數(shù)據(jù)發(fā)送到其他應(yīng)用,就對(duì)應(yīng)了輸出。
字節(jié)流和字符流
根據(jù)流的處理方式又可以將流可以分為兩種類型:字節(jié)流和字符流。
字節(jié)流
字節(jié)流讀取的基本單位為字節(jié),采用的是 ASCII 編碼,通常用來(lái)處理二進(jìn)制數(shù)據(jù),其頂層抽象類為 InputStream 和 OutputStream,比如上面示例中的 System.in 實(shí)際上就是獲取到了一個(gè) InputStream 類。
Java 中的流家族非常龐大,提供了非常多的具有不同功能的流,在實(shí)際應(yīng)用中我們可以選擇不同的組合達(dá)到目的。
字節(jié)輸入流
下圖為字節(jié)輸入流家族關(guān)系示意圖:
從上圖可以看出這些結(jié)構(gòu)非常清晰,首先是一個(gè)最頂層的接口,其次就是一些不同功能的基礎(chǔ)流,比如我們最常用的 FileInputStream 就是用來(lái)讀取文件的,這其中有一個(gè) FilterInputStream 流,這個(gè)流主要是用來(lái)擴(kuò)展基礎(chǔ)流功能,其本身只是簡(jiǎn)單的覆蓋了父類 InputStream 中的所有方法,并沒(méi)有做什么特殊處理,真正的功能擴(kuò)展需要依賴于其眾多的子類,比如最常用的 BufferedInputStream 提供了數(shù)據(jù)的緩沖,從而提升讀取流的效率,而 DataInputStream 是可以用來(lái)處理二進(jìn)制數(shù)據(jù)等等。
通過(guò)這些眾多不同功能的流來(lái)組合,可以靈活的讀取我們需要的數(shù)據(jù)。比如當(dāng)我們需要讀取一個(gè)二進(jìn)制文件,那么就需要使用 DataInputStream,而 DataInputStream 本身不具備直接讀取文件內(nèi)容的功能,所以需要結(jié)合 FileInputStream:
FileInputStream fin = new FileInputStream("E:\\test.txt"); DataInputStream din = new DataInputStream(fin); System.out.println(din.readInt());
同時(shí),如果我們想要使用緩沖機(jī)制,又可以進(jìn)一步組裝 BufferedInputStream:
FileInputStream fin = new FileInputStream("E:\\test.txt"); DataInputStream din = new DataInputStream(new BufferedInputStream(fin)); System.out.println(din.readInt());
還有一種流比較有意思,那就是 PushbackInputStream,這個(gè)流可以將讀出來(lái)的數(shù)據(jù)重新推回到流中:
public class Demo03 { public static void main(String[] args) throws IOException { FileInputStream fin = new FileInputStream("E:\\test.txt");//文檔內(nèi)存儲(chǔ) abcd PushbackInputStream pin = new PushbackInputStream(new BufferedInputStream(fin)); int a = pin.read();//讀取到a System.out.println(a); if (a != 'b'){ pin.unread(a);//將 a 推回流中 } System.out.println(pin.read());//再次讀取到 a System.out.println(pin.read());//讀取到 b System.out.println(pin.read());// 讀取到 c } }
字節(jié)輸出流
下圖為字節(jié)輸出流家族關(guān)系示意圖:
這個(gè)結(jié)構(gòu)和輸入流的結(jié)構(gòu)基本類似,同樣的我們也可以通過(guò)組合來(lái)實(shí)現(xiàn)不同的輸出。
比如普通的輸出文件,可以使用 FileOutputStream 流:
FileOutputStream fout = new FileOutputStream("E:\\test2.txt"); fout.write(1); fout.write(2);
如果想要輸出二進(jìn)制格式,那么就可以組合 DataOutputStream 流:
FileOutputStream fout = new FileOutputStream("E:\\test2.txt"); DataOutputStream dout = new DataOutputStream(fout); dout.write(9); dout.write(10);
緩沖流的原理
IO 操作是一個(gè)比較耗時(shí)的操作,而字節(jié)流的 read 方法一次只能返回一個(gè)字節(jié),那么當(dāng)我們需要讀取多個(gè)字節(jié)時(shí)就會(huì)出現(xiàn)每次讀取都要進(jìn)行一次 IO 操作,而緩沖流內(nèi)部定義了一個(gè)大小為 8192 的 byte 數(shù)組,當(dāng)我們使用了緩沖流時(shí),讀取數(shù)據(jù)的時(shí)候則會(huì)一次性最多讀取 8192 個(gè)字節(jié)放到內(nèi)存,然后一個(gè)個(gè)依次返回,這樣就大大減少了 IO 次數(shù);同樣的,寫數(shù)據(jù)時(shí),緩沖流會(huì)將數(shù)據(jù)先寫到內(nèi)存,當(dāng)我們寫完需要寫的數(shù)據(jù)時(shí)再一次性刷新到指定位置,如磁盤等。
字符流
字符流讀取的基本單位為字符,采用的是 Unicode 編碼,其 read 方法返回的是一個(gè) Unicode 碼元(0~65535)。
字符流通常用來(lái)處理文本數(shù)據(jù),其頂層抽象類為 Reader 和 Write,比如文中最開始的示例中的 InputStreamReader 就是繼承自 Reader 類。
字符輸入流
下圖為字符輸入流家族關(guān)系示意圖:
上圖可以看出,除頂層 Reader 類之外,字符流也提供了一些基本的字符流來(lái)處理文本數(shù)據(jù),比如我們需要從文本讀取內(nèi)容:
public class Demo05Reader { public static void main(String[] args) throws Exception { //字節(jié)流 FileInputStream fin = new FileInputStream("E:\\test.txt");//文本內(nèi)容為“雙子孤狼” System.out.println(fin.read());//372 //字符流 InputStreamReader ir = new InputStreamReader(new FileInputStream("E:\\test.txt"));//文本內(nèi)容為“雙子孤狼” System.out.println(ir.read());//21452 char s = '雙'; System.out.println((int)s);//21452 } }
輸出之后可以很明顯看出區(qū)別,字節(jié)流一次讀入一個(gè)字節(jié),而字符流一次讀入一個(gè)字符。
當(dāng)然,我們也可以采用自由組合的方式來(lái)更靈活的進(jìn)行字符讀取,比如我們結(jié)合 BufferedReader 來(lái)讀取一整行數(shù)據(jù):
public class Demo05Reader { public static void main(String[] args) throws Exception { InputStreamReader ir = new InputStreamReader(new FileInputStream("E:\\test.txt"));//文本內(nèi)容為“雙子孤狼” BufferedReader br = new BufferedReader(ir); String s; while (null != (s = br.readLine())){ System.out.println(s);//輸出雙子孤狼 } } }
字符輸出流
下圖為字符輸出流家族關(guān)系示意圖:
文本輸出,我們用的最多的就是 PrintWriter,這個(gè)類我想絕大部分朋友都使用過(guò):
public class Demo06Writer { public static void main(String[] args) throws Exception{ PrintWriter printWriter = new PrintWriter("E:\\test3.txt"); printWriter.write("雙子孤狼"); printWriter.flush(); } }
這里和字節(jié)流的區(qū)別就是寫完之后需要手動(dòng)調(diào)用 flush 方法,否則數(shù)據(jù)就會(huì)丟失,并不會(huì)寫到文件中。
為什么字符流需要 flush,而字節(jié)流不需要
字節(jié)流不需要 flush 操作是因?yàn)樽止?jié)流直接操作的是字節(jié),中途不需要做任何轉(zhuǎn)換,所以直接就可以操作文件,而字符流,說(shuō)到底,其底層還是字節(jié)流,但是字符流幫我們將字節(jié)轉(zhuǎn)換成了字符,這個(gè)轉(zhuǎn)換需要依賴字符表,所以就需要在字符和字節(jié)完成轉(zhuǎn)換之后通過(guò) flush 操作刷到磁盤中。
需要注意的是,字節(jié)輸出流最頂層類 OutputStream 中也提供了 flush 方法,但是它是一個(gè)空的方法,如果有子類有需要,也可以實(shí)現(xiàn) flush 方法。
RandomAccessFile
RandomAccessFile 是一個(gè)隨機(jī)訪問(wèn)文件類,其可以在文件中的任意位置查找或者寫入數(shù)據(jù)。
public class Demo07RandomAccessFile { public static void main(String[] args) throws Exception { //文檔內(nèi)容為 lonely wolf RandomAccessFile inOut = new RandomAccessFile(new File("E:\\test.txt"),"rw"); System.out.println("當(dāng)前指針在:" + inOut.getFilePointer());//默認(rèn)在0 System.out.println((char) inOut.read());//讀到 l System.out.println("當(dāng)前指針在:" + inOut.getFilePointer()); inOut.seek(7L);//指針跳轉(zhuǎn)到7的位置 System.out.println((char) inOut.read());//讀到 w inOut.seek(7);//跳回到 7 inOut.write(new byte[]{'c','h','i','n','a'});//寫入 china,此時(shí) wolf被覆蓋 inOut.seek(7);//繼續(xù)跳回到 7 System.out.println((char) inOut.read());//此時(shí)因?yàn)?wolf 被 china覆蓋,所以讀到 c } }
根據(jù)上面的示例中的輸出結(jié)果,可以看到 RandomAccessFile 類可以隨機(jī)指定指針,并隨機(jī)進(jìn)行讀寫,功能非常強(qiáng)大。
另外需要說(shuō)明的是,構(gòu)造 RandomAccessFile 時(shí)需要傳入一個(gè)模式,模式主要有 4 種:
- r:只讀模式。此時(shí)調(diào)用任何 write 相關(guān)方法,會(huì)拋出 IOException。
- rw:讀寫模式。支持讀寫,如果文件不存在,則會(huì)創(chuàng)建。
- rws:讀寫模式。每當(dāng)進(jìn)行寫操作,會(huì)將內(nèi)容或者元數(shù)據(jù)同步刷新到磁盤。
- rwd:讀寫模式。每當(dāng)進(jìn)行寫操作時(shí),會(huì)將變動(dòng)的內(nèi)容用同步刷新到磁盤。
以上就是Java字節(jié)流和字符流及IO流的總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于Java字節(jié)流和字符流的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot中@ConfigurationProperties無(wú)效果的解決方法
本文主要介紹了springboot中@ConfigurationProperties無(wú)效果,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06Springboot熱部署實(shí)現(xiàn)原理及實(shí)例詳解
這篇文章主要介紹了Springboot熱部署實(shí)現(xiàn)原理及實(shí)例詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(8)
下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你2021-07-07Java easyui樹形表格TreeGrid的實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了Java easyui樹形表格TreeGrid的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03簡(jiǎn)單實(shí)現(xiàn)java數(shù)獨(dú)游戲
這篇文章主要教大家如何簡(jiǎn)單實(shí)現(xiàn)java數(shù)獨(dú)游戲,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12Java設(shè)計(jì)模式之單例模式簡(jiǎn)介
這篇文章主要介紹了Java設(shè)計(jì)模式之單例模式簡(jiǎn)介,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)Java的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04