javaio流_万字长文+思维导图帮你梳理 Java IO 流,还学不会你来打我(值得收藏)...
?前言
在上一篇的文章獲取不錯的瀏覽量后,繼續(xù)加更的念頭一直徘徊在心中,本來是想花段時間深入學習tomcat的,可是tomcat的源碼中就有至關(guān)重要的NIO,于是得先整一下NIO,但是NIO的基礎(chǔ)是BIO,于是這篇文章就先寫IO流吧。
學習NIO(非阻塞IO),千萬不能被IO阻塞住哇!
IO流在java中其實是很重要的一塊知識點,難度還好,但是容易被忽視,因為工作中真正寫IO的代碼少之又少。
IO的難點在于
導致很多開發(fā)學過io流一段時間后,寫不出一段正確的io流代碼。
本文將從理論+代碼的方式由淺入深的帶大家學習IO流,通過圖解的方式來記憶常用的IO流。
文末有IO總結(jié)的思維導圖,很多博文采用的都是上來一張圖,我覺得對于閱讀者來說很容易陷進去,所以建議理清各個流后再去看思維導圖。
File
在正式的介紹IO流之前,我覺得應(yīng)該介紹一下File類,該類主要是對文件和目錄的抽象表示,因為學習io流第一反應(yīng)就是文件,該類提供了對文件的創(chuàng)建、刪除、查找等操作。主要有以下特點
File類的常用構(gòu)造方法
| File(String pathname) | 將路徑字符串抽象為File實例,路徑字符串可以是相對路徑,也可以為絕對路徑 |
| File(String parent, String child) | 從父路徑名和子路徑名來構(gòu)建File實例 |
| File(File parent, String child) | 根據(jù)父File實例和子路徑名來構(gòu)建File實例 |
如下示例,表示這幾種文件和目錄的代碼
//?pathnameFile?liuBei?=?new?File("D:/三國/劉備.jpg");
//?String?parent,?String?child
File?guanYu?=?new?File("D:/三國",?"關(guān)羽.jpg");
//?目錄
File?sanGuo?=?new?File("D:/三國");
//?File?parent,?String?child
File?zhangFei?=?new?File(sanGuo,?"張飛.txt");
//?可以聲明不存在的文件
File?zhuGeLiang?=?new?File(sanGuo,?"諸葛亮.txt");
絕對路徑和相對路徑
絕對路徑:從盤符開始的路徑,表示一個完整的路徑。(經(jīng)常使用) 相對路徑:相對于當前項目目錄的路徑
File?f?=?new?File("D:/bbb.java");//?D:\bbb.java
System.out.println(f.getAbsolutePath());
File?f2?=?new?File("bbb.java");
//?F:\code\ad\bbb.java
System.out.println(f2.getAbsolutePath());
路徑分隔符和換行符
路徑分隔符
java有常量separator表示路徑分隔符
public?static?final?String?separator?=?""?+?separatorChar;換行符
File的常用方法
創(chuàng)建、刪除
| boolean createNewFile() throws IOException | 當該名稱的文件不存在時,創(chuàng)建一個由該抽象路徑名的空文件并返回true,當文件存在時,返回false |
| boolean mkdir() | 創(chuàng)建由此抽象路徑名命名的目錄 |
| boolean mkdirs() | 創(chuàng)建由此抽象路徑名命名的目錄,包括任何必需但不存在的父目錄。級聯(lián)創(chuàng)建目錄 |
| boolean delete() | 刪除由此抽象路徑名表示的文件或目錄 |
上述方法比較簡單,其中需要注意的是
- 創(chuàng)建多級目錄時,mkdir創(chuàng)建失敗,返回false,mkdirs創(chuàng)建成功,返回true(推薦使用mkdirs)
- 刪除目錄時,目錄內(nèi)不為空時,刪除失敗,返回false, 即只能刪除文件或者空目錄
//?返回false?創(chuàng)建失敗
boolean?mkdir?=?shuiHu.mkdir();
//?返回true?創(chuàng)建失敗
boolean?mkdirs?=?shuiHu.mkdirs();
File?four?=?new?File("D:/四大名著");
//?返回false?刪除目錄時必須目錄為空才能刪除成功
boolean?delete?=?four.delete();
File?shuiHu?=?new?File("D:/四大名著/水滸傳");
//?true?正確刪除了水滸傳目錄
boolean?delete1?=?shuiHu.delete();
File?liuBei?=?new?File("D:/三國/劉備.jpg");
//?返回true?正確刪除了劉備.jpg文件
boolean?delete2?=?liuBei.delete();
文件檢測
| boolean isDirectory() | 判斷是否是目錄 |
| boolean isFile() | 判斷是否是文件 |
| boolean exists() | 判斷文件或目錄是否存在 |
| boolean canWrite() | 文件是否可寫 |
| boolean canRead() | 文件是否可讀 |
| boolean canExecute() | 文件是否可執(zhí)行 |
| long lastModified() | 返回文件的上次修改時間 |
注意的是
- 文件或目錄不存在時, isDirectory() 或 isFile() ?返回false
- 可讀、可寫、可執(zhí)行是對操作系統(tǒng)給文件賦予的權(quán)限
//?文件或目錄不存在時?返回false
System.out.println(xiYou.isDirectory());
文件獲取
| String getAbsolutePath() | 返回File對象的絕對路徑字符串 |
| String getPath() | 將此抽象路徑名轉(zhuǎn)換為路徑名字符串 |
| String getName() | 返回文件或目錄的名稱 |
| long length() | 返回由此File表示的文件的字節(jié)數(shù) |
| String[] list() | 返回目錄中的文件和目錄的名稱字符串數(shù)組 |
| File[] listFiles() | 返回目錄中的文件和目錄的File對象數(shù)組 |
注意
- length() 返回的是文件的字節(jié)數(shù),目錄的 長度是0
- getPath()在用絕對路徑表示的文件時相同,用相對路徑表示的文件時不同
- listFiles和list方法的調(diào)用,必須是實際存在的目錄,否則返回null
- listFiles和list 可以傳入FilenameFilter的實現(xiàn)類,用于按照文件名稱過濾文件
//?0
System.out.println(shuiHu.length());
File?liuBei?=?new?File("D:/三國/劉備.jpg");
//?24591
System.out.println(liuBei.length());
File?f?=?new?File("D:/bbb.java");
?//?D:\bbb.java
System.out.println(f.getPath());
File?f2?=?new?File("bbb.java");
//?bbb.java
System.out.println(f2.getPath());
File?sanGuo2?=?new?File("D:/三國2");
//?該目錄不存在,返回null
String[]?list?=?sanGuo2.list();
過濾文件的接口
@FunctionalInterfacepublic?interface?FilenameFilter?{
???//?參數(shù)為目錄和指定過濾名稱
????boolean?accept(File?dir,?String?name);
}
擴展(由讀者自己實現(xiàn))
讀取目錄下所有的文件以及目錄,包括子目錄下所有的文件及目錄
IO流
上一章節(jié)學習了使用File類創(chuàng)建、查找、刪除文件,但是無法讀取、傳輸文件中的內(nèi)容。
IO流主要是讀取、傳輸、寫入數(shù)據(jù)內(nèi)容的。
I: input,O:output
這里的主體說的都是程序(即內(nèi)存),從外部設(shè)備中讀取數(shù)據(jù)到程序中 即為輸入流,從程序中寫出到外部程序中即為輸出流
IO的分類
本地IO和網(wǎng)絡(luò)IO
本地IO主要是操作本地文件,例如在windows上復制粘貼操作文件,都可以使用java的io來操作
網(wǎng)絡(luò)IO主要是通過網(wǎng)絡(luò)發(fā)送數(shù)據(jù),或者通過網(wǎng)絡(luò)上傳、下載文件,我們每天上網(wǎng)無時無刻不在體驗著IO的傳輸
按流向分,輸入流和輸出流
按數(shù)據(jù)類型分:字節(jié)流和字符流
按功能分:節(jié)點流和處理流
- 程序直接操作目標設(shè)備的類稱為節(jié)點流
- 對節(jié)點流進行裝飾,功能、性能進行增強,稱為處理流
IO流主要的入口是數(shù)據(jù)源,下面列舉常見的源設(shè)備和目的設(shè)備
源設(shè)備
目的設(shè)備
本文先探討本地IO的字節(jié)流和字符流,先列舉字節(jié)流和字符流的公共方法
| void close() throws IOException | 流操作完畢后,必須釋放系統(tǒng)資源,調(diào)用close方法,一般放在finally塊中保證一定被執(zhí)行! |
注意:
- 程序中打開的IO資源不屬于內(nèi)存資源,垃圾回收機制無法回收該資源,需要顯式的關(guān)閉文件資源
- 下面的代碼示例中就不顯示的調(diào)用close方法,也不會處理IOException,只是為了代碼的簡潔,方便閱讀
字節(jié)流
一切皆為字節(jié)
一切文件數(shù)據(jù)(文本、圖片、視頻等)在存儲時,都是以二進制的形式保存,都可以通過使用字節(jié)流傳輸。
InputStream是字節(jié)輸入流的頂層抽象
//?Closeable有close()方法public?abstract?class?InputStream?implements?Closeable?{}
核心方法如下
| int read() throws IOException; | 每次讀取一個字節(jié)的數(shù)據(jù),提升為int類型,讀取到文件末尾時返回 -1 |
| int read(byte b[])throws IOException | 每次讀取到字節(jié)數(shù)組中,返回讀取到的有效字節(jié)個數(shù),讀取到末尾時返回 -1(常用) |
| int read(byte b[], int off, int len) | 每次讀取到字節(jié)數(shù)組中,從偏移量off開始,長度為len,返回讀取到的有效字節(jié)個數(shù),讀取到末尾時返回 -1 |
OutputStream是字節(jié)輸出流的頂層抽象
//?Flushable里面有flush()方法public?abstract?class?OutputStream?implements?Closeable,?Flushable?{}
核心方法如下
| void write(int b) throws IOException; | 將int值寫入到輸出流中 |
| void write(byte[] b) throws IOException; | 將字節(jié)數(shù)組寫入到輸出流中 |
| void write(byte b[], int off, int len) throws IOException | 將字節(jié)數(shù)組從偏移量off開始,寫入len個長度到輸出流中 |
| void flush() throws IOException | 刷新輸出流并強制緩沖的字節(jié)被寫出 |
文件節(jié)點流
InputStream有很多的實現(xiàn)類,先介紹下文件節(jié)點流,即目標設(shè)備是文件,輸入流和輸出流對應(yīng)的是
FileInputStream和FileOutputStream
FileInputStream主要從磁盤文件中讀取數(shù)據(jù),常用構(gòu)造方法如下
public?FileInputStream(File?file)?throws?FileNotFoundException{}public?FileInputStream(String?name)?throws?FileNotFoundException{};
當傳入的文件不存在時,運行時會拋出FileNotFoundException異常
FileInputStream?fileInputStream?=?new?FileInputStream(file);
//?核心代碼
int?b;
while?((b?=?fileInputStream.read())?!=?-1?){
????System.out.print((char)?b);
}
//?輸出結(jié)果
abcde
FileInputStream?fileInputStream?=?new?FileInputStream(file);
//?核心代碼
byte[]?data?=?new?byte[2];
while?(fileInputStream.read(data)?!=?-1)?{
????System.out.println(new?String(data));
}
//?輸出結(jié)果
ab
cd
ed
上述代碼由于最后一次讀取時,只讀取一個字節(jié) e ,數(shù)組中還是上次的數(shù)據(jù)cd,只替換了e,所以最后輸出了ed
下面是使用FileInputStream讀取的正確姿勢
File?file?=?new?File("D:/三國/諸葛亮.txt");FileInputStream?fileInputStream?=?new?FileInputStream(file);
//?核心代碼
byte[]?data?=?new?byte[2];
int?len;
while?((len?=?fileInputStream.read(data))?!=?-1)?{
????//?len?為每次讀取的有效的字節(jié)個數(shù)
????System.out.println(new?String(data,?0,?len));
}
//?輸出結(jié)果
ab
cd
e
注意:使用數(shù)組讀取,每次讀取多個字節(jié),減少了系統(tǒng)間的IO操作次數(shù),從而提高了效率,建議使用
源碼解析
public?int?read()?throws?IOException?{????return?read0();
}
private?native?int?read0()?throws?IOException;
public?int?read(byte?b[])?throws?IOException?{
???return?readBytes(b,?0,?b.length);
}
private?native?int?readBytes(byte?b[],?int?off,?int?len)?throws?IOException;
上面列了read()和read(byte[])的源碼,可見都是調(diào)用native的方法,涉及底層的系統(tǒng)調(diào)用。
如果用read()讀取文件,每讀取一個字節(jié)就要訪問一次硬盤,這種效率較低。
如果用read(byte[])讀取文件,一次讀取多個字節(jié),當文件很大時,也會頻繁訪問硬盤。如果一次讀取超多字節(jié),效率也不會高。
FileOutputStream主要是向磁盤文件中寫出數(shù)據(jù),常用構(gòu)造方法如下
| FileOutputStream(File file) throws FileNotFoundException | 使用一個File對象來構(gòu)建一個FileOutputStream |
| FileInputStream(String name) throws FileNotFoundException | 使用一個文件名來構(gòu)建一個FileOutputStream |
| FileOutputStream(File file, boolean append) ? ? throws FileNotFoundException | append傳true時,會對文件進行追加 |
| FileOutputStream(String name, boolean append) ? ? throws FileNotFoundException | append傳true時,會對文件進行追加 |
注意:
上述構(gòu)造方法執(zhí)行后,如果file不存在,會自動創(chuàng)建該文件
如果file存在,append沒有傳或者傳了false,會清空文件的數(shù)據(jù)
如果file存在,append傳了true,不會清空文件的數(shù)據(jù)
FileOutputStream?fos?=?new?FileOutputStream(file);
FileOutputStream?fos1?=?new?FileOutputStream("D:/三國/司馬懿.txt");
//?上述代碼中執(zhí)行完后,趙云.txt和司馬懿.txt都會自動創(chuàng)建出來
向文件寫數(shù)據(jù)
FileOutputStream?fos?=?new?FileOutputStream("D:/三國/司馬懿.txt");fos.write(96);
fos.write(97);
fos.write(98);
//?文件內(nèi)容為?abc
FileOutputStream?fos?=?new?FileOutputStream("D:/三國/趙云.txt");
fos.write("三國趙云".getBytes());
//?文件內(nèi)容為?三國趙云
上述代碼每執(zhí)行一次,文件里的內(nèi)容就會被覆蓋,有時候這不是我們想要的場景,我們一般是想追加文件
FileOutputStream?fos?=?new?FileOutputStream("D:/三國/趙云.txt",?true);fos.write("有萬夫不當之勇".getBytes());
fos.close();
//?文件內(nèi)容為?三國趙云有萬夫不當之勇
應(yīng)用場景
開發(fā)中涉及文件的上傳、下載、傳輸都是用的這個節(jié)點流,會結(jié)合裝飾后的處理流一起使用,在緩沖流部分有介紹。
擴展(由讀者自己實現(xiàn))
利用文件節(jié)點流實現(xiàn)文件的復制
內(nèi)存節(jié)點流
ByteArrayInputStream是從內(nèi)存的字節(jié)數(shù)組中讀取數(shù)據(jù)
public?ByteArrayInputStream(byte?buf[])?{}注意:不需要close數(shù)據(jù)源和拋出IOException,因為不涉及底層的系統(tǒng)調(diào)用
ByteArrayOutputStream是向內(nèi)存字節(jié)數(shù)組中寫數(shù)據(jù),內(nèi)部維護了一個數(shù)組
public?ByteArrayOutputStream()?{???//?內(nèi)部維護了一個可變的字節(jié)數(shù)組
???//?protected?byte?buf[];
????this(32);
}
內(nèi)存節(jié)點流代碼示例
ByteArrayInputStream?bis?=?new?ByteArrayInputStream("data".getBytes());ByteArrayOutputStream?bos?=?new?ByteArrayOutputStream();
int?len?=?0;
while?((len?=?bis.read())?!=?-1){
????bos.write(len);
}
//?輸出data
System.out.println(new?String(bos.toByteArray()));
應(yīng)用場景
字符流
字符流封裝了更加適合操作文本字符的方法
Reader用于讀取文本字符
public?abstract?class?Reader?implements?Readable,?Closeable?{}核心方法
| int read() throws IOException | 從輸入流中讀取一個字符,讀到文件末尾時返回-1 |
| int read(char cbuf[]) throws IOException | 從輸入流中讀取字符到char數(shù)組中 |
Writer用于寫出文本字符
public?abstract?class?Writer?implements?Appendable,?Closeable,?Flushable?{}核心方法
| void write(int c) throws IOException | 寫入單個字符到輸出流中 |
| void write(char[] cbuf) throws IOException | 寫入字符數(shù)組到輸出流中 |
| void write(char[] cbuf, int off, int len) ?throws IOException | 寫入字符數(shù)組的一部分,偏移量off開始,長度為len到輸出流中 |
| void write(String str) throws IOException | 直接寫入字符串到輸出流中(常用) |
| void write(String str, int off, int len) throws IOException | 寫入字符串的一部分,偏移量off開始,長度為len |
| Writer append(char c) throws IOException | 追加字符到輸出流中 |
文件節(jié)點流
字符流操作純文本字符的文件是最合適的,主要有FileReader和FileWriter
FileReader主要是向磁盤文件中寫出數(shù)據(jù),常用構(gòu)造方法如下
public?FileReader(String?fileName)?throws?FileNotFoundException{}public?FileReader(File?file)?throws?FileNotFoundException?{}
注意:當讀取的文件不存在時,會拋出FileNotFoundException,這點和FileInputStream一致
int?b;
while?((b?=?fileReader.read())?!=?-1)?{
????System.out.println((char)?b);
}
int?len;
char[]?data?=?new?char[2];
while?((len?=?fileReader.read(data))?!=?-1)?{
????System.out.println(new?String(data,?0,?len));
}
//?兩個字符兩個字符依次讀取
FileWriter構(gòu)造方法如下,和FileOutStream構(gòu)造方法類似,和FileOutputStream類似。
public?FileWriter(String?fileName)?throws?IOException?{}public?FileWriter(String?fileName,?boolean?append)?throws?IOException?{}
public?FileWriter(File?file)?throws?IOException{}
public?FileWriter(File?file,?boolean?append)?throws?IOException?{}
常用的寫數(shù)據(jù)進文件的方法
FileWriter?fileWriter?=?new?FileWriter("D:/三國/孫權(quán).txt");fileWriter.write(97);?
fileWriter.write('b');?
fileWriter.write('C');?
fileWriter.write("權(quán)");?
fileWriter.append("力");
注意:
- 如果不執(zhí)行close()或者flush()方法,數(shù)據(jù)只是保存到緩沖區(qū),不會保存到文件。這點和與FileOutputStream不同,原因見 字節(jié)流和字符流的共同點章節(jié)
應(yīng)用場景
純文本文件的io操作,配合處理流一起實現(xiàn)。
內(nèi)存節(jié)點流
字符流也有對應(yīng)的內(nèi)存節(jié)點流,常用的有StringWriter和CharArrayWriter
StringWriter是向內(nèi)部的StringBuffer對象寫數(shù)據(jù)。
//?定義public?class?StringWriter?extends?Writer?{
????private?StringBuffer?buf;
????public?StringWriter()?{
????????buf?=?new?StringBuffer();
????????lock?=?buf;
????}
}
//?應(yīng)用
StringWriter?sw?=?new?StringWriter();
sw.write("hello");
StringBuffer?buffer?=?sw.getBuffer();
//?輸出hello
System.out.println(buffer.toString());
CharArrayWriter是向內(nèi)部的char數(shù)組寫數(shù)據(jù)
//?定義public?class?CharArrayWriter?extends?Writer?{
????protected?char?buf[];
}
//?應(yīng)用?
CharArrayWriter?caw?=?new?CharArrayWriter();
caw.write("hello");
char[]?chars?=?caw.toCharArray();
for?(char?c?:?chars)?{
?//?輸出了h?e?l?l?o
?System.out.println(c);
}
四種常用節(jié)點流的使用總結(jié)
字節(jié)流和字符流的共同點
注意到,OutputStream、Reader、Writer都實現(xiàn)了Flushable接口,Flushable接口有flush()方法
flush():強制刷新緩沖區(qū)的數(shù)據(jù)到目的地,刷新后流對象還可以繼續(xù)使用
close(): 強制刷新緩沖區(qū)后關(guān)閉資源,關(guān)閉后流對象不可以繼續(xù)使用
緩沖區(qū):可以理解為內(nèi)存區(qū)域,程序頻繁操作資源(如文件)時,性能較低,因為讀寫內(nèi)存較快,利用內(nèi)存緩沖一部分數(shù)據(jù),不要頻繁的訪問系統(tǒng)資源,是提高效率的一種方式
具體的流只要內(nèi)部有維護了緩沖區(qū),必須要close()或者flush(),不然不會真正的輸出到文件中
處理流
上面的章節(jié)介紹了字節(jié)流和字符流的常用節(jié)點流,但是真正開發(fā)中都是使用更為強大的處理流
處理流是對節(jié)點流在功能上、性能上的增強
字節(jié)流的處理流的基類是FilterInputStream和FilterOutputStream
緩沖流(重點)
前面說了節(jié)點流,都是直接使用操作系統(tǒng)底層方法讀取硬盤中的數(shù)據(jù),緩沖流是處理流的一種實現(xiàn),增強了節(jié)點流的性能,為了提高效率,緩沖流類在初始化對象的時候,內(nèi)部有一個緩沖數(shù)組,一次性從底層流中讀取數(shù)據(jù)到數(shù)組中,程序中執(zhí)行read()或者read(byte[])的時候,就直接從內(nèi)存數(shù)組中讀取數(shù)據(jù)。
分類
字節(jié)緩沖流:BufferedInputStream , BufferedOutputStream
字符緩沖流:BufferedReader , BufferedWriter
字節(jié)緩沖流
可見構(gòu)造方法傳入的是節(jié)點流,是對節(jié)點流的裝飾
//?內(nèi)部默認8192?=8*1024?即8M的緩沖區(qū)public?BufferedInputStream(InputStream?in)?{
???//?8192????
???//?內(nèi)部維護了下面這樣的字節(jié)數(shù)組
????//?protected?volatile?byte?buf[];
????this(in,?DEFAULT_BUFFER_SIZE);
}
public?BufferedOutputStream(OutputStream?out)?{
????????this(out,?8192);
}
這里使用復制一部1G的電影來感受緩沖流的強大
FileInputStream?fis?=?new?FileInputStream("D:/三國/視頻.mp4");
FileOutputStream?fos?=?new?FileOutputStream("D:/三國/拷貝.mp4");
int?data;
while?((data?=?fis.read())?!=?-1)?{
????fos.write(data);
}
log.info("拷貝電影耗時:{}ms",?System.currentTimeMillis()?-?start);
//?五分鐘還沒拷好,關(guān)閉程序了...
FileInputStream?fis?=?new?FileInputStream("D:/三國/視頻.mp4");
FileOutputStream?fos?=?new?FileOutputStream("D:/三國/拷貝.mp4");
int?len;
byte[]?data?=?new?byte[1024?*?1024?*?1024];
while?((len?=?fis.read(data))?!=?-1)?{
????fos.write(data,?0,?len);
}
log.info("拷貝電影耗時:{}ms",?System.currentTimeMillis()?-?start);
//?拷貝電影耗時:4651ms
BufferedInputStream?fis?=?new?BufferedInputStream(new?FileInputStream("D:/三國/視頻.mp4"));
BufferedOutputStream?fos?=?new?BufferedOutputStream(new?FileOutputStream("D:/三國/拷貝.mp4"));
int?data;
while?((data?=?fis.read())?!=?-1)?{
????fos.write(data);
}
log.info("拷貝電影耗時:{}ms",?System.currentTimeMillis()?-?start);
//?拷貝電影耗時:39033ms
BufferedInputStream?fis?=?new?BufferedInputStream(new?FileInputStream("D:/三國/視頻.mp4"));
BufferedOutputStream?fos?=?new?BufferedOutputStream(new?FileOutputStream("D:/三國/拷貝.mp4"));
int?len;
byte[]?data?=?new?byte[8?*?1024];
while?((len?=?fis.read(data))?!=?-1)?{
????fos.write(data,?0,?len);
}
log.info("拷貝電影耗時:{}ms",?System.currentTimeMillis()?-?start);
//?拷貝電影耗時:1946ms
由上述四個例子可以得出結(jié)論,緩沖流讀取數(shù)據(jù)比普通流讀取數(shù)據(jù)快很多!
注意:采用邊讀邊寫的方式,一次傳輸幾兆的數(shù)據(jù)效率比較高,如果采用先把文件的數(shù)據(jù)都讀入內(nèi)存,在進行寫出,這樣讀寫的次數(shù)是較小,但是占用太大的內(nèi)存空間,一次讀太大的數(shù)據(jù)也嚴重影響效率!
字符緩沖流
對字符節(jié)點流的裝飾,下面是字符緩沖流的構(gòu)造方法
public?BufferedReader(Reader?in)?{?//?private?static?int?defaultCharBufferSize?=?8192;
?//?內(nèi)部維護了一個字符數(shù)組
????//?private?char?cb[];
????this(in,?defaultCharBufferSize);
}
public?BufferedWriter(Writer?out)?{
????????this(out,?defaultCharBufferSize);
}
字符緩沖流的特有方法
| BufferedReader | String readLine() throws IOException | 一行行讀取,讀取到最后一行返回null |
| BufferedWriter | void newLine() throws IOException | 寫一個換行符到文件中,實現(xiàn)換行 |
BufferedReader?br?=?new?BufferedReader(new?FileReader("D:/三國/趙云.txt"));
BufferedWriter?bw?=?new?BufferedWriter(new?FileWriter("D:/三國/趙子龍.txt"));
String?line?=?null;
while?((line?=?br.readLine())!=null)?{
??System.out.println(line);
??bw.write(line);
??bw.newLine();
}
//?結(jié)果
我乃常山趙子龍
于萬軍從中,取上將首級
緩沖流的正確姿勢
緩沖流是IO流中最重要的知識點,下面通過代碼實現(xiàn)正確用IO流的姿勢
BufferedInputStream?bis?=?null;BufferedOutputStream?bos?=?null;
try?{
????bis?=?new?BufferedInputStream(new?FileInputStream(new?File("D:/三國/視頻.mp4")));
????bos?=?new?BufferedOutputStream(new?FileOutputStream(new?File("D:/三國/拷貝.mp4")));
????int?len;
????//?一次傳輸8M的文件,實際測試這里傳輸?shù)拇笮〔⒉挥绊憘鬏數(shù)乃俣?br />????byte[]?data?=?new?byte[8?*?1024];
????while?((len?=?bis.read(data))?!=?-1)?{
????????bos.write(data,?0,?len);
????}
}?catch?(IOException?e)?{
????log.error("error",?e);
}?finally?{
???//?finally塊中關(guān)閉流,確保資源一定被關(guān)閉
????if?(bis?!=?null)?{
????????try?{
????????????bis.close();
????????}?catch?(IOException?e)?{
????????????log.error("error",?e);
????????}
????}
????if?(bos?!=?null)?{
????????try?{
????????????bos.close();
????????}?catch?(IOException?e)?{
????????????log.error("error",?e);
????????}
????}
}
轉(zhuǎn)換流
字符編碼與字符集
字符編碼
計算機存儲的數(shù)據(jù)都是二進制的,而我們在電腦上看到的數(shù)字、英文、漢字等都是二進制轉(zhuǎn)換的結(jié)果
將字符轉(zhuǎn)換成二進制,為編碼
將二進制轉(zhuǎn)換為字符,為解碼
字符編碼 就是 自然語言和二進制的對應(yīng)規(guī)則
字符集
就是一個編碼表,常見的字符集有ASCII字符集、GBK字符集、Unicode字符集等,具體各個編碼的介紹在這里就不介紹了。
IDEA中,使用 FileReader 讀取項目中的文本文件。IDEA可以設(shè)置為GBK 編碼,當讀取Windows系統(tǒng)中創(chuàng)建的默認的UTF8文本文件時,就會出現(xiàn)亂碼 。
如下例
idea的字符集設(shè)置 默認是UTF-8,這里修改為GBK
運行的代碼及結(jié)果
FileReader?fileReader?=?new?FileReader("D:/sanguo/utf8.txt");int?read;
while?((read?=?fileReader.read())?!=?-1)?{
????System.out.print((char)read);
}
//?浣犲ソ
InputStreamReader
Reader的子類,讀取字節(jié),并使用指定的字符集將其解碼為字符。字符集可以自己指定,也可以使用平臺的默認字符集。
構(gòu)造方法如下
//?使用平臺默認字符集public?InputStreamReader(InputStream?in)?{}
//?指定字符集
public?InputStreamReader(InputStream?in,?String?charsetName)throws?UnsupportedEncodingException{}
讀取文件的“你好",文件默認的字符集是UTF8
//?創(chuàng)建流對象,默認UTF8編碼InputStreamReader?isr?=?new?InputStreamReader(new?FileInputStream("D:/三國/utf8.txt"));
//?創(chuàng)建流對象,指定GBK編碼
InputStreamReader?isr2?=?new?InputStreamReader(new?FileInputStream("D:/三國/utf8.txt"),?"GBK");
int?read;
while?((read?=?isr.read())?!=?-1)?{
????System.out.println((char)?read);
}
while?((read?=?isr2.read())?!=?-1)?{
????System.out.println((char)?read);
}
//?輸出結(jié)果
你好
浣犲ソ
OutputStreamWriter
Writer的子類,使用指定的字符集將字符編碼為字節(jié)。字符集可以自己指定,也可以使用平臺的默認字符集。
構(gòu)造方法如下
//?使用平臺默認字符集public?OutputStreamWriter(OutputStream?out)?{}
//?使用平臺默認字符集
public?OutputStreamWriter(OutputStream?out,?String?charsetName)throws?UnsupportedEncodingException{}
如下面的代碼,將你好寫入文件。寫入后兩個文件的字符集不一樣,文件大小也不同
//?創(chuàng)建流對象,默認UTF8編碼OutputStreamWriter?osw?=?new?OutputStreamWriter(new?FileOutputStream("D:/三國/黃忠.txt"));
osw.write("你好");?//?保存為6個字節(jié)
//?創(chuàng)建流對象,指定GBK編碼
OutputStreamWriter?osw2?=?new?OutputStreamWriter(new?FileOutputStream("D:/三國/馬超.txt"),"GBK");
osw2.write("你好");//?保存為4個字節(jié)
對象流
序列化
jdk提供了對象序列化的方式,該序列化機制將對象轉(zhuǎn)為二進制流,二進制流主要包括對象的數(shù)據(jù)、對象的類型、對象的屬性。可以將java對象轉(zhuǎn)為二進制流寫入文件中。文件會持久保存了對象的信息。
同理,從文件中讀出對象的信息為反序列化的過程
對象想序列化,滿足的條件:
ObjectOutputStream
該類實現(xiàn)將對象序列化后寫出到外部設(shè)備,如硬盤文件
public?ObjectOutputStream(OutputStream?out)?throws?IOException{}常用方法
| void writeObject(Object obj) throws IOException | 將指定的對象寫出 |
如下代碼,將User對象寫入文件中
public?class?User?implements?Serializable?{????private?static?final?long?serialVersionUID?=?8289102797441171947L;
????private?String?name;
????private?Integer?age;
}
//?下面是將對象輸出到文件的核心代碼
User?user?=?new?User("馬超",20);
//?創(chuàng)建序列化流對象
ObjectOutputStream?out?=?new?ObjectOutputStream(new?FileOutputStream("D:/三國/馬超.txt"));
//?寫出對象
out.writeObject(user);
注意:
生成的文件內(nèi)容如下
ObjectInputStream
該類將ObjectOutputStream寫出的對象反序列化成java對象
public?ObjectInputStream(InputStream?in)?throws?IOException?常用方法
| Object readObject() ? ? throws IOException, ClassNotFoundException | 讀取對象 |
//?強轉(zhuǎn)為user
User?user?=?(User)?in.readObject();
System.out.println(user);
//?輸出內(nèi)容
User(name=馬超,?age=20)
對象和字節(jié)數(shù)組的轉(zhuǎn)換
利用對象流和字節(jié)數(shù)組流結(jié)合 ,可以實現(xiàn)java對象和byte[]之間的互轉(zhuǎn)
//?將對象轉(zhuǎn)為byte[]public?static??byte[]?t1(T?t)?{
????ByteArrayOutputStream?bos?=?new?ByteArrayOutputStream();
????ObjectOutputStream?oos?=?new?ObjectOutputStream(bos);
????oos.writeObject(t);return?bos.toByteArray();
}//?將byte[]轉(zhuǎn)為對象public?static??T?t2(byte[]?data)?throws?IOException,?ClassNotFoundException?{
????ByteArrayInputStream?bos?=?new?ByteArrayInputStream(data);
????ObjectInputStream?oos?=?new?ObjectInputStream(bos);return?(T)?oos.readObject();
}
管道流(了解)
管道流主要用于兩個線程間的通信,即一個線程通過管道流給另一個線程發(fā)數(shù)據(jù)
注意:線程的通信一般使用wait()/notify(),使用流也可以達到通信的效果,并且可以傳遞數(shù)據(jù)
使用的類是如下
- PipedInputStream和PipedOutStream
- PipedReader和PipedWriter
這里使用字節(jié)流為例
class?Sender?implements?Runnable?{????private?PipedOutputStream?pos;
????private?String?msg;
????public?Sender(String?msg)?{
????????this.pos?=?new?PipedOutputStream();
????????this.msg?=?msg;
????}
????@Override
????public?void?run()?{
?????? pos.write(msg.getBytes());
????}
????public?PipedOutputStream?getPos()?{
????????return?pos;
????}
}
class?Receiver?implements?Runnable?{
????private?PipedInputStream?pis;
????public?Receiver()?{
????????this.pis?=?new?PipedInputStream();
????}
????@Override
????public?void?run()?{
?????????byte[]?data?=?new?byte[1024];
????????????int?len;
????????????while?((len?=?pis.read(data))?!=?-1)?{
????????????????System.out.println(new?String(data,?0,?len));
????????????}
????}
}
????
Sender?sender?=?new?Sender("hello");
Receiver?receiver?=?new?Receiver();
receiver.getPis().connect(sender.getPos());
new?Thread(sender).start();
new?Thread(receiver).start();
//?控制臺輸出??hello
輸入與輸出流(了解)
System.in和System.out代表了系統(tǒng)標準的輸入、輸出設(shè)備
默認輸入設(shè)備是鍵盤,默認輸出設(shè)備是控制臺
可以使用System類的setIn,setOut方法對默認設(shè)備進行改變
我們開發(fā)中經(jīng)常使用的輸出到控制臺上的內(nèi)容的方法。
System.out.println("a");System.out.print("b");
class?System{
????public?final?static?InputStream?in?=?null;
????public?final?static?PrintStream?out?=?null;
}
public?PrintStream(String?fileName)?throws?FileNotFoundException{}
數(shù)據(jù)流(了解)
主要方便讀取Java基本類型以及String的數(shù)據(jù),有DataInputStream 和 DataOutputStream兩個實現(xiàn)類
DataOutputStream?dos?=?new?DataOutputStream(new?FileOutputStream("D:/三國/周瑜.txt"));dos.writeUTF("周瑜");
dos.writeBoolean(false);
dos.writeLong(1234567890L);
DataInputStream?dis?=?new?DataInputStream(new?FileInputStream("D:/三國/周瑜.txt"));
String?s?=?dis.readUTF();
System.out.println(s);
boolean?b?=?dis.readBoolean();
System.out.println(b);
//?輸出
周瑜
false
IO流總結(jié)
以上各個章節(jié)詳細介紹了各個流,可見流的種類比較多,記憶確實增加了困難。但是可以通過思維導圖的方式整理出來,方便記憶。
字節(jié)流的導圖
字符流的導圖
按照功能劃分
輸入、輸出對應(yīng)關(guān)系
結(jié)語
短期的加更計劃
文章篇幅較長,給看到這里的小伙伴點個大大的贊!由于作者水平有限,文章中難免會有錯誤之處,歡迎小伙伴們反饋指正。
如果覺得文章對你有幫助,麻煩 點贊、評論、轉(zhuǎn)發(fā)、在看 、關(guān)注 走起
你的支持是我加更最大的動力!!!
另
瑣碎時間想看一些技術(shù)文章,可以去公眾號菜單欄翻一翻我分類好的內(nèi)容,應(yīng)該對部分童鞋有幫助。同時看的過程中發(fā)現(xiàn)問題歡迎留言指出,不勝感謝~。另外,有想多了解哪些方面內(nèi)容的可以留言(什么時候,哪篇文章下留言都行),附菜單欄截圖(PS:很多人不知道公眾號菜單欄是什么)
END
我知道你 “在看”
總結(jié)
以上是生活随笔為你收集整理的javaio流_万字长文+思维导图帮你梳理 Java IO 流,还学不会你来打我(值得收藏)...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 系统接口502异常_基于SpringBo
- 下一篇: java condition_死磕Jav