Java之IO流技術詳解
何為IO?
首先,我們看看百度給出的解釋。
I/O輸入/輸出(Input/Output),分為IO裝置和IO介面兩個部分。
i是寫入,Input的首字母。o是輸出,Output的首字母。
IO 也稱為IO流,IO = 流,它的核心就是對檔案的操作,對於 位元組 、字元型別的輸入和輸出流。
IO分類
IO流主要分為兩大類,位元組流和字元流。而按照作用分類,可以分為輸入流和輸出流。
流?
在電腦上的資料有三種儲存方式,一種是外存,一種是記憶體,一種是快取。比如電腦上的硬碟,磁碟,U盤等都是外存,在電腦上有記憶體條,快取是在CPU裡面的。外存的儲存量最大,其次是記憶體,最後是快取,但是外存的資料的讀取最慢,其次是記憶體,快取最快。這裡總結從外存讀取資料到記憶體以及將資料從記憶體寫到外存中。對於記憶體和外存的理解,我們可以簡單的理解為容器,即外存是一個容器,記憶體又是另外一個容器。那又怎樣把放在外存這個容器內的資料讀取到記憶體這個容器以及怎麼把記憶體這個容器裡的資料存到外存中呢?
我們可以將這個整個看成一個水池。水池裡面連線了出水口管與注水管。出水相當於我們的輸出流。注水相當於我們的輸入流。
File(檔案類)
首先我們如果需要用IO流的話,我們肯定是需要建立一個我們所謂的**“水池”**的。
怎麼建立呢?我們直接建立一個File類物件。
package IoDemo;
import java.io.*;
public class IoDemo {
public static void main(String[] args) {
//建立File物件
File file = new File("D:\\test.txt");
}
}
實際上這個物件,說白了就是用來儲存一個IO流的檔案地址的。
建立了物件後,它也沒有任何什麼操作,操作得使用這個物件呼叫方法。
我們先使用createNewFile()
方法建立我們上面那個路徑的檔案。
file.createNewFile();
注意定義檔案路徑時,可以用“/”或者“\”。
並且在建立一個檔案時,如果目錄下有同名檔案將被覆蓋。
因為有時候,可能我們的路徑下已經存在了相對應的同名檔案,所以我們要使用exists()
方法判斷檔案是否已經存在。
//建立File物件
File file = new File("D:\\test.txt");
//建立檔案
try {
//判斷檔案是否存在
if(!file.exists()){
file.createNewFile();
}
} catch (IOException e) {
e.printStackTrace();
}
其實,File類裡面還存在許多方法,用法都是可以直接呼叫的,作為一個合格的程式設計師,我們可以直接閱讀相關說明而在合適的時候使用對應方法。
下面列舉一些常用方法。
①、建立方法
1.boolean createNewFile() 不存在返回true 存在返回false 2.boolean mkdir() 建立目錄,如果上一級目錄不存在,則會建立失敗 3.boolean mkdirs() 建立多級目錄,如果上一級目錄不存在也會自動建立
②、刪除方法
1.boolean delete() 刪除檔案或目錄,如果表示目錄,則目錄下必須為空才能刪除 2.boolean deleteOnExit() 檔案使用完成後刪除
③、判斷方法
1.boolean canExecute()判斷檔案是否可執行 2.boolean canRead()判斷檔案是否可讀 3.boolean canWrite() 判斷檔案是否可寫 4.boolean exists() 判斷檔案或目錄是否存在 5.boolean isDirectory() 判斷此路徑是否為一個目錄 6.boolean isFile() 判斷是否為一個檔案 7.boolean isHidden() 判斷是否為隱藏檔案 8.boolean isAbsolute()判斷是否是絕對路徑 檔案不存在也能判斷
④、獲取方法
1.String getName() 獲取此路徑表示的檔案或目錄名稱 2.String getPath() 將此路徑名轉換為路徑名字串 3.String getAbsolutePath() 返回此抽象路徑名的絕對形式 4.String getParent()//如果沒有父目錄返回null 5.long lastModified()//獲取最後一次修改的時間 6.long length() 返回由此抽象路徑名錶示的檔案的長度。 7.boolean renameTo(File f) 重新命名由此抽象路徑名錶示的檔案。 8.File[] liseRoots()//獲取機器碟符 9.String[] list() 返回一個字串陣列,命名由此抽象路徑名錶示的目錄中的檔案和目錄。 10.String[] list(FilenameFilter filter) 返回一個字串陣列,命名由此抽象路徑名錶示的目錄中滿足指定過濾器的檔案和目錄。
位元組流的使用
我們現在已經建好這個**“水池”了,同時還可以使用方法來獲取到“水池”**的一些資訊。
那接下來,我們可以試著建立一套流。
位元組流,相當於一滴滴的水在一個管道里運輸。這個管道我們可以抽象的稱之為流。
我們還是先看看知乎上,某些大佬的解釋。
大佬的解釋
圖中藍色為主要對應部分,紅色為不對應部分,黑色的虛線部分代表這些流一般需要搭配使用。從上面的圖中可以看出Java IO中的位元組流是非常對稱的。我們來看看這些位元組流中不對稱的幾個類。
LineNumberInputStream 主要完成從流中讀取資料時,會得到相應的行號,至於什麼時候分行、在哪裡分行是由改類主動確定的,並不是在原始中有這樣一個行號。在輸出部分沒有對應的部分,我們完全可以自己建立一個LineNumberOutputStream,在最初寫入時會有一個基準的行號,以後每次遇到換行時會在下一行新增一個行號,看起來也是可以的。好像更不入流了。
PushbackInputStream 的功能是檢視最後一個位元組,不滿意就放入緩衝區。主要用在編譯器的語法、詞法分析部分。輸出部分的BufferedOutputStream 幾乎實現相近的功能。
StringBufferInputStream 已經被Deprecated,本身就不應該出現在InputStream 部分,主要因為String 應該屬於字元流的範圍。已經被廢棄了,當然輸出部分也沒有必要需要它了!還允許它存在只是為了保持版本的向下相容而已。
SequenceInputStream 可以認為是一個工具類,將兩個或者多個輸入流當成一個輸入流依次讀取。完全可以從IO 包中去除,還完全不影響IO 包的結構,卻讓其更“純潔”――純潔的Decorator 模式。
PrintStream 也可以認為是一個輔助工具。主要可以向其他輸出流,或者FileInputStream 寫入資料,本身內部實現還是帶緩衝的。本質上是對其它流的綜合運用的一個工具而已。一樣可以踢出IO 包!System.out 和System.out 就是PrintStream 的例項。
對於不完全學透的看法
聽起來有些難度哈,這些高階用法我們先不管。我們先來看看它到底怎麼用的。
那有人又說了,那如果不去學完整,以後要是不會用怎麼辦?
答:其實只需要掌握主要方法就可以,因為你如果需要實現一個非常用的東西,你肯定是事先就需要去查閱相關資料,而日常開發中,常用的也就可以信手拈來啦。
OutputStream(位元組輸出流)
這個抽象類是表示輸出位元組流的所有類的超類。
!!!這裡的輸出可不是我們正常的輸出,它反而想法,是將位元組寫入到檔案,可以理解為將位元組輸出到檔案。
看看裡面有些啥常用方法。
我們先演示一下,位元組輸出流如何使用?
//定義一個String值
String str = "Hello World";
等下我們利用位元組流將這個String寫入我們的檔案裡面。
等等!這裡是位元組流,我怎麼可以直接寫入**String
**?
嘿嘿,我們使用String類中的getBytes()方法將String轉換成位元組陣列。
//將String值轉換成位元組陣列
byte[] bytes = str.getBytes();
整個程式碼是這樣的:
package IoDemo;
import java.io.*;
public class IoDemo {
public static void main(String[] args) {
//建立File物件
File file = new File("D:\\test.txt");
//建立檔案
try {
//判斷檔案是否存在
if(!file.exists()){
file.createNewFile();
}
} catch (IOException e) {
e.printStackTrace();
}
//定義一個String值
String str = "Hello World";
//建立位元組流
FileOutputStream fos = null;
try {
//將File物件(即地址)給到FileInputStream
fos = new FileOutputStream(file);
//將String值轉換成位元組陣列
byte[] bytes = str.getBytes();
//迴圈將位元組陣列寫入到檔案中
for (int i = 0; i < bytes.length; i++) {
//將bytes寫入檔案
fos.write(bytes[i]);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
我們進我們的檔案看看輸出效果。
就已經寫入了我們的String裡面的內容了。
InputStream(位元組輸入流)
這裡我也就不解釋了,他這個輸入不是將內容輸入到檔案,而是將檔案裡面的內容輸入到我們的程式碼中。
//建立位元組輸入流
FileInputStream fis = null;
try {
//將File物件(即地址)給到FileInputStream
fis = new FileInputStream(file);
//建立位元組陣列
byte[] bytes = new byte[1024];
//輸出位元組陣列
int len = 0;
while ((len = fis.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, len));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
在上文,我們已經寫入了一個Hello Word
我們現在這段程式碼,我們將會輸出檔案中的內容。
len = fis.read(bytes)) != -1
我現在還是先講解一下這一段。
我們的
len
是定義的一個數值。而read()
方法,是為了讀取到整個檔案裡面內容的位元組長度,就像陣列的lenth()
一樣。而
!= -1
是因為,如果值為-1
那麼,這個檔案可以說是沒有資料的,空的你也沒必要輸出。
而我們位元組流呢,一般是用於讀取二進位制檔案,如音訊、圖片這些,大片的文字內容還是交給字元流吧。
字元流的使用
上面的位元組流,它是一個個的輸出的,而我們現在的字元流,是大水管輸出,一次可以運輸一段。
一般可以用記事本開啟的檔案,我們可以看到內容不亂碼的。就是文字檔案,可以使用字元流。而操作二進位制檔案(比如圖片、音訊、視訊)必須使用位元組流。
FileWriter(字元輸出流)
老規矩哈,方法自己看看。
先寫入一個String
試試。
package IoDemo;
import java.io.*;
public class IoDemo {
public static void main(String[] args) {
String str = "嘿嘿!我是字元流·········";
//建立File物件
File file = new File("D:\\test.txt");
try {
//將str用writer寫入檔案
FileWriter fw = new FileWriter(file);
fw.write(str);
//關閉流,字元流必須關閉流才可以輸出
fw.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
寫入效果如下:
實際上,寫入字串就幾個步驟。
//1.將str用writer寫入檔案
FileWriter fw = new FileWriter(file);
//2.使用write()方法寫入字串
fw.write(str);
//3.關閉流,字元流必須關閉流才可以輸出
fw.close();
FileReader(字元輸出流)
//輸出檔案內容
try {
//建立輸入字元流
FileReader fr = new FileReader(file);
//輸出檔案內容
int ch = 0;
while ((ch = fr.read()) != -1){
System.out.print((char)ch);
}
//關閉流
fr.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
這裡採用
Char
挨個字元遍歷資料。
包裝流
緩衝流
為什麼使用緩衝流呢?
簡單地說就是,寫入資料更快,可以加速寫入。
緩衝流,也叫高效流。 能夠高效讀寫緩衝流,能夠轉換編碼的轉換流,能夠持久化儲存物件的序列化物件等等。 它是四個基本File流的增強,所以也是4個流,按照資料型別分類。 緩衝流的基本原理,是在建立流物件時,會建立一個內建的預設大小的緩衝區陣列,通過緩衝區讀寫,減少系統IO讀取次數,從而提高讀寫的效率。
package IoDemo;
import java.io.*;
public class IoDemo {
public static void main(String[] args) {
//建立File物件
File file = new File("D:\\test.txt");
//建立緩衝流
BufferedWriter bw = null;
try {
//建立FileWriter物件
FileWriter fw = new FileWriter(file);
//建立BufferedWriter物件
bw = new BufferedWriter(fw);
//寫入一首詩分四次寫入
bw.write("窗前明月光,");
bw.write("疑是地上霜。");
bw.write("舉頭望明月,");
bw.write("低頭思故鄉。");
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
這個很簡單,自己看就可以看懂。
轉換流
InputStreamReader:把位元組輸入流轉換為字元輸入流
OutputStreamWriter:把位元組輸出流轉換為字元輸出流
我就不寫示範了,網上找了一段。
//轉換流實現將 a.txt 檔案 複製到 b.txt 中
//1、建立源和目標
File srcFile = new File("io"+File.separator+"a.txt");
File descFile = new File("io"+File.separator+"b.txt");
//2、建立位元組輸入輸出流物件
InputStream in = new FileInputStream(srcFile);
OutputStream out = new FileOutputStream(descFile);
//3、建立轉換輸入輸出物件
Reader rd = new InputStreamReader(in);
Writer wt = new OutputStreamWriter(out);
//3、讀取和寫入操作
char[] buffer = new char[10];//建立一個容量為 10 的字元陣列,儲存已經讀取的資料
int len = -1;//表示已經讀取了多少個字元,如果是 -1,表示已經讀取到檔案的末尾
while((len=rd.read(buffer))!=-1){
wt.write(buffer, 0, len);
}
//4、關閉流資源
rd.close();
wt.close();
擴充套件
然後,找相關資料時,還漲了個知識。大家可以看看。
合併流
合併流:把多個輸入流合併為一個流,也叫順序流,因為在讀取的時候是先讀第一個,讀完了在讀下面一個流。
SequenceInputStream seinput = new SequenceInputStream();
new FileInputStream("io/1.txt"), new FileInputStream("io/2.txt"));
byte[] buffer = new byte[10];
int len = -1;
while((len=seinput.read(buffer))!=-1){
System.out.println(new String(buffer,0,len));
}
seinput.close();
這裡先是建立了一個SequenceInputStream
物件,然後new
了兩個FileInputStream
物件,我們使用合併流讀取,先使用**seinput.read方法
**讀取1.txt
的內容,然後再對其2.txt
的內容。