導語:
打開簡書,看到自己的文章被瀏覽了五十多次的時候真的很開心,然后發現有幾個喜歡一個粉絲的時候,真的是非常開心,同時謝謝你們給了我繼續寫下去的動力,非常感謝。這篇文章寫一寫關于IO 流中自己學到的一些基本知識,以及遇到的問題,希望能夠幫助到大家。
1.IO 流的引入
我們可以利用 File 類將 java 程序跟硬盤上的文件聯系起來。我們可以獲取其中某些屬性。但是,文件的內容我們不能操作,讀取。這時候就引入了 對文件進行操作的 IO 流。
我們可以生動形象的把 IO 流當作一根根管子,管子的一端懟到目標文件,另一端懟到程序中。我們寫的程序在這里相當于一個數據傳輸的中轉站。
2. IO 流分類
2.1 按照讀取單位劃分:
字節流 | 字符流 | |
---|---|---|
輸入流 | InputStream | Reader |
輸出流 | OutputStream | Writer |
2.2 按照功能劃分
- 節點流:直接從源文件讀取數據到程序中 —— 一根管。
- 處理流:需要多個流結合使用 —— 多根管。
在我目前所看到的所有程序中,想要利用 IO 流,必須先用輸入輸出字節流,或者輸入輸出字符流連接到目標文件?。?!
下面主要寫一下各個流的用法。如有疑問以及不妥的地方歡迎指正,感激不盡!
3. 字節流--FileInputStream,FileOutputStream
3.1 文件 ---> 程序(以程序為主體,對于程序來說屬于對內輸入,所以要用輸入流)
3.1.1 利用單個字節
1 public class Test {
2 public static void main(String[] args) throws IOException {
3 //1.確定文件:
4 File f=new File("i:/test/haha.txt");
5 //2.創建一根管,然后連接文件:
6 FileInputStream fis=new FileInputStream(f);
7 //3.進行動作: 吸 (流:讀取)
8 int n=fis.read();
9 while(n!=-1){//讀到文件末尾就是-1
10 System.out.print(n+"\t");
11 n=fis.read();
12 }
13 //4.關閉流(無論什么時候,關閉流是必須的):
14 fis.close();
15 }
16 }
首先要確定被讀取文件的地址(代碼第 4 行),然后創建一根管(就是 IO 流),一端懟到該文件上去,另一端懟到程序中,進行吸的動作(也就是程序讀取文件)。(怎么樣,理解的還可以不,哈哈)
從上面的代碼第 8 行和第 11 行可以看出 這種方法是一個字節一個字節的將文件中的信息讀入到程序中。
缺點:
- 運行結果會出現亂碼;
- 一個字節一個字節讀取 ,效率太低;
3.1.2 利用數組緩沖區
1. public class Test02 {
2. public static void main(String[] args) throws IOException {
3. //創建文件:
4. File f=new File("i:/test/haha.txt");
5. //創建一根管,然后連接文件:
6. FileInputStream fis=new FileInputStream(f);
7. //進行動作: 吸 (流:讀取)
8. byte[] b=new byte[8];// 隨便創建了一個byte數組,長度為8
9. int len=fis.read(b);// len是數組中被占用的長度
10. while(len!=-1){// 讀到文件末尾就是-1
11. System.out.print(len+"\t");
12. len=fis.read(b);
13. }
14. //關閉流:
15. fis.close();
16. }
}
這種方法是定義了一個數組(第 8 行)8個字節,通俗一點說,就是每 8 個字節為一組進行讀取,用數組將文件中的信息讀入到程序中,效率比比上一種方法高。
3.2 程序 ---> 文件(以程序為主體,對于程序來說屬于對外輸出的,所以要用輸出流)
3.2.1 利用單個字節
1 public class Test03 {
2 public static void main(String[] args) throws IOException {
3 //創建文件:
4 File f=new File("i:/test/haha.txt");
5 //創建一根管,然后連接文件:
6 FileOutputStream fos=new FileOutputStream(f);
7 //進行動作: 吐 (流:寫入)
8 String str="abc123你好";
9 //將字符串轉化成字節:
10 byte[] bytes=str.getBytes();
11 for (int i = 0; i < b.length; i++) {
12 fos.write(b[i]);
13 }
14 //關閉流:
15 fos.close();
16 }
17 }
首先要確定要把文件讀取到哪兒(代碼第 4 行),然后創建一根管(就是 IO 輸出流),一端懟到該文件上去,另一端懟到程序中,進行吐的動作(也就是把內容從程序中寫出去)。因為字節輸出流只能一個字節一個字節的向外讀取,所以要調用 String 的 getBytes() 方法將 String 類型數據準換成 byte 類型,而該方法返回值為一個 byte 類型數組。
3.2.2 利用緩沖數組
1 public class Test04 {
2 public static void main(String[] args) throws IOException {
3 //創建文件:
4 File f=new File("i:/test/haha.txt");
5 //創建一根管,然后連接文件:
6 FileOutputStream fos=new FileOutputStream(f);
7 //進行動作: 吐 (流:寫入)
8 String str="abc123你好";
9 byte[] bytes=str.getBytes();//將字符串轉化成字節:因為fos只能一個字節一個字節的寫
10 for(byte b:bytes){
11 fos.write(b);
12 }
13 //關閉流:
14 fos.close();
15 }
16 }
定義了一個數組(第 9 行),調用 String 類型的 getBytes() 方法,將字符串轉化為字節,用一個數組接住該方法的返回值。然后再用循環遍歷將數組中的信息讀取到文件中,效率比比上一種方法高。
一定要記得關流?。?!
3.3 文件的復制
3.3.1 利用單個字節進行復制
1 public class TestCopyDoc {
2 public static void main(String[] args) throws IOException {
3 //先確定文件:
4 File f1=new File("i:/test/haha.txt");
5 File f2=new File("i:/test/demo.txt");
6 //創建兩個輸入輸出流(兩個管):
7 FileInputStream fis=new FileInputStream(f1);//輸入流
8 FileOutputStream fos=new FileOutputStream(f2);//輸出流
9 //開始動作: 吸-----吐
10 int n=fis.read();//吸;
11 while(n!=-1){
12 fos.write(n);//吐;
13 n=fis.read();//吸;
14 }
15 //關閉流:
16 fis.close();
17 fos.close();
18 }
19 }
功能:就是利用輸入輸出字節流,一個字節一個字節的將 i:/test/haha.txt 文件中的內容復制到 i:/test/demo.txt 中去。
程序相當于一個中轉站(開篇的 IO 流示意圖),利用字節輸入流(FileInputStream)將 haha.txt 中的內容讀取到程序中,然后利用字節輸出流(FileOutputStream)從程序中寫出到目標文件 demo.txt.
3.3.2 利用數組緩沖區
1 public class TestCopyDoc02 {
2 public static void main(String[] args) throws IOException {
3 //先確定文件:
4 File f1=new File("i:/test/haha.txt");
5 File f2=new File("i:/test/demo.txt");
6 //創建兩個輸入輸出流(兩個管):
7 FileInputStream fis=new FileInputStream(f1);//輸入流
8 FileOutputStream fos=new FileOutputStream(f2);//輸出流
9 //開始動作: 吸-----吐
10 byte[] b=new byte[8];
11 int len=fis.read(b);//len---是這個數組中被占用的數量
12 while(len!=-1){
13 fos.write(b,0,len);
14 len=fis.read(b);
15 }
16 //關閉流:
17 fis.close();
18 fos.close();
19 }
20 }
功能:定義了一個數組(第 10 行)8個字節,每 8 個字節為一組進行讀取,FileInputStream 利用數組將 haha.txt 中的信息讀入到程序中(第 11 行),其中 len 表示這個數組中被占用的數量,當讀取到文件結尾的時候 len = -1(這是規定,我也不知道為啥是 -1 ,不是其他的數字,這個記住就好。。。);然后 FileOutputStream 把程序中讀取到的信息寫入到目標文件中(第 14 行)。
4. 字符流--FileReader,FileWriter
- 和字節流一樣,用字符流進行文件的復制也分為兩種方法,一種是利用單個字符進行讀取和寫出,另一種是利用數組進行讀取和寫出。
4.1 利用單個字符
1 public class TestFileCopy001 {
2 public static void main(String[] args) throws IOException {
3 //1.創建目標文件,源文件
4 File f1=new File("i:/test/haha.txt");
5 File f2=new File("i:/test/demo.txt");
6 //2.創建字符流
7 FileReader fr=new FileReader(f1);
8 FileWriter fw=new FileWriter(f2);
9 //3.讀取
10 int n=fr.read();
11 while(n!=-1){
12 fw.write(n);
13 n=fr.read();
14 }
15 //4.關閉流
16 fw.close;
17 fr.close;
18 }
19 }
- 功能:和 3.3.1 中一樣,只不過是利用字符流來進行操作。
4.2 利用數組緩沖區---char[]
1 public class TestFileCopy001 {
2 public static void main(String[] args) throws IOException {
3 //1.創建目標文件,源文件
4 File f1=new File("i:/test/haha.txt");
5 File f2=new File("i:/test/demo.txt");
6 //2.創建字符流
7 FileReader fr=new FileReader(f1);
8 FileWriter fw=new FileWriter(f2);
9 //3.讀取
10 char[] ch=new char[8];
11 int len=fr.read(ch); //len---是這個數組中被占用的數量
12 while(len!=-1){
13 fw.write(ch,0,len);
14 len=fr.read(ch);
15 }
16 //4.關閉流
17 fw.close;
18 fr.close;
19 }
20 }
- 功能:和 3.3.2 類似,只不過是定義了一個字符數組(代碼第 10 行)進行讀取和寫出操作。
另外要說明的是:用字符流復制非純文本的文件都是不行的,都是耍流氓!因為用字符流復制的時候,它會按照系統的字符碼表進行查找和替換,把二進制數據全部按照碼表替換了,但是圖片的一些代碼能在碼表中找到相對應的編碼,就轉換成編碼,另外還有一些找不到,JVM就會用類似的編碼代替,那么你再打開的時候就肯定不是圖片了。
總之,記住千萬不要耍流氓啊!!
5. 緩沖字節流--BufferedInputStream,BufferedOutputStream
先上圖(以下圖都是按照個人理解畫出來的,如有錯誤還請指正):
我們上面寫的代碼都是利用字節流進行文件的復制。以上圖為例,每次讀取或寫出都會對硬盤上的文件訪問一次,缺點就是對硬盤的訪問次數太多,對硬盤來說這是有害的。這時,就可以利用緩沖字節流。
再上圖:
根據下面的代碼來理解一下這張圖:
1 public class Test {
2 public static void main(String[] args) throws IOException {
3 //1.源文件,目標文件
4 File f1=new File("i:/test/haha.txt");
5 File f2=new File("i:/test/demo.txt");
6 //2.創建流:4個
7 FileInputStream fis =new FileInputStream(f1);
8 FileOutputStream fos=new FileOutputStream(f2);
9 BufferedInputStream bis=new BufferedInputStream(fis);//創建緩沖流bis
10 BufferedOutputStream bos=new BufferedOutputStream(fos);//創建緩沖流bos
11 //3.讀取
12 byte[] b=new byte[8];
13 int len=bis.read(b);//bis 讀取數據
14 while(len!=-1){
15 bos.write(b,0,len);//bos 寫出數據
16 len=bis.read(b);
17 }
18 //4.關閉流:
19 bis.close();
20 bos.close();
21 fis.close();
22 fos.close();
23 }
24 }
程序解釋:先創建輸入字節流 fis (第 7 行)懟到目標文件(i:/test/haha.txt),然后創建緩沖字節流 bis(如圖紅色),bis 套在 fis 上使用(相當于一根管子上套了另一根管子)。使用緩沖流會有一個緩沖區,fis(字節輸入流)會盡可能多的把源文件中的數據讀取到緩沖區,然后 bis 再利用緩沖數組從緩沖區中每 8 個字節為一組的讀取。寫出的過程和讀取的過程正好相反就不在此贅述啦。(如果還不理解可以留下評論)
這種方式屬于一根流套在另一根流,也就是管套管。上使用這樣的話就減少了對硬盤的訪問次數。
6. 緩沖字符流--BufferedReader,BufferedWriter
緩沖字符流和上面的緩沖字節流的運行原理一樣,只不過一個使用字節流,一個使用字符流而已。
1 public class Test {
2 public static void main(String[] args) throws IOException {
3 //1.源文件,目標文件
4 File f1=new File("i:/test/haha.txt");
5 File f2=new File("i:/test/demo.txt");
6 //2.創建流:4個
7 FileReader fis =new FileReader(f1);
8 FileWriter fos=new FileWriter(f2);
9 BufferedReader bis=new BufferedReader(fis);//創建緩沖字符輸入流 bis
10 BufferedWriter bos=new BufferedWriter(fos);//創建緩沖字符輸出流 bos
11 //3.讀取
12 char[] b=new char[8];
13 int len=bis.read(b);//bis 讀取數據
14 while(len!=-1){
15 bos.write(b,0,len);//bos 寫出數據
16 len=bis.read(b);
17 }
18 //4.關閉--先關高級流,再關閉低級流
19 bis.close();
20 bos.close();
21 fis.close();
22 fos.close();
23 }
24 }
這種方式也是屬于管套管來操作數據,效率比上面的緩沖字節流要高(因為使用的是字符流)。
之前操作數據,要么是一個字節一個字節的讀取,或者是一個數組一個數組的讀取。那么下面介紹一種效率更高的方式:一整行一整行的讀取數據。
1 //3.讀取
2 String str=bis.readLine();//利用緩沖字符輸入流 bis 整行整行的讀取數據;
3 while(str!=null){
4 bos.write(str);
5 bos.newLine();//在目標文件中換行
6 str=bis.readLine();//利用緩沖字符輸出流 bos 整行整行的寫出數據;
7 }
程序其他部分不變,只是讀取的方式不一樣。
7. System對 IO 的支持
在這里補充一下,我們寫程序的時候經常會用到鍵盤輸入這個語句:Scanner sc=new Scanner(System.in);
那么我有沒有考慮過鍵盤輸入這件事到底是誰來完成的呢,是 Scanner 還是 Sytem.in ?
其實,鍵盤錄入這個功能是由 System.in 來完成的,Scanner 只是起到一個掃描器的作用。System.in 會返回一個 InputStream 類型的變量,也就是返回一個流。那么Scanner sc=new Scanner(System.in);
這條語句可以通俗的理解為有一個掃描器 Scanner,一個鍵盤,它們倆之間是用一根管子(流)連接起來,鍵盤錄入的數據通過這根管子傳進掃描器中。
還有一個比較坑的地方,寫出來給大家做個提醒:
1 public class TestIO {
2 public static void main(String[] args) throws IOException {
3 InputStream in = System.in;
4 byte[] b=new byte[8];
5 int n = in.read(b);//用數組讀取鍵盤輸入的數據,返回結果為 b 被占用的長度
6 System.out.print(n);
7 }
8 }
代碼第 5 行中,in.read(b) 按理說返回結果為 b 被占用的長度啊,那么,我輸入數字 1,結果輸出 3,也就是說,占用了 3 個字節!哇,當時整的我很郁悶,不就占用了一個字節嗎,應該是 1 才對嘛。
后來才搞清楚,運行這段代碼的時候,你輸入 1 之后,按下了 Enter 鍵,也就是輸入完數據之后進行了回車、換行,而回車和換行在 ASCII 表中對應的數值分別是 13 和 10,它們倆又各占了一個字節,所以是占用了 3 個字節,輸出結果為 3。不知道我有沒有表達清楚?
- 補充一點:字節流轉化為字符流
1 public class Test {
2 public static void main(String[] args) throws IOException {
3 //輸入:
4 InputStream in = System.in;
5 //轉換流--單向轉換:字節流-->字符流轉換
6 InputStreamReader isr=new InputStreamReader(in);
7 BufferedReader br=new BufferedReader(isr);
8 //輸出:
9 FileWriter fw=new FileWriter("d:/bjsxt/t.txt");
10 BufferedWriter bw=new BufferedWriter(fw);
11 //讀取:
12 String str=br.readLine();
13 while(!str.equals("byebye")){
14 bw.write(str);
15 bw.newLine();
16 str=br.readLine();
17 }
18 //關閉流:
19 bw.close();
20 br.close();
21 fw.close();
22 isr.close();
23 in.close();
24 }
25 }
這段代碼中,主要想表達的就是第 6 行,將字節流轉化為字符流,其他的就是和之前的代碼差不多,都是讀取寫入。
8. 數據流--對基本數據類型處理--DataInputStream,DataOutputStream
8.1 將基本數據類型的東西輸入到目標文件中去:
1 public class Test001 {
2 public static void main(String[] args) throws IOException {
3 DataOutputStream dos=new DataOutputStream(new 4FileOutputStream(new File("d:/haha/demo001.txt")));//將流套在一起使用
5 dos.writeInt(12);//int
6 dos.writeChar('\n');
7 dos.writeDouble(12.0);//double
8 dos.writeChar('\n');
9 dos.writeUTF("hellojava你好");//
10 //關閉流
11 dos.close();
12 }
13 }
代碼第 3 行是將各個需要的流套在一起直接一句代碼寫出來了,應該能看懂吧。
執行完這段程序之后如果打開目標文件,將會看到...亂碼,哈哈。別慌,因為這是給程序看的,咱可能看不懂的。那么要是想看該怎么辦呢?有辦法,在一個程序中執行下段代碼:
1 DataInputStream dis=new DataInputStream(new FileInputStream(new File("d:/bjsxt/demo001.txt")));
3 System.out.println(dis.readInt());//int
4 System.out.println(dis.readChar());//char
5 System.out.println(dis.readDouble());//double
6 System.out.println(dis.readChar());//char
7 System.out.println(dis.readUTF());
8 dis.close();//關閉流
這段代碼是為了將文件中的內容寫入到程序中,然后在控制臺輸出。第一句不用解釋了吧。而且,重要的是寫進文件的順序和讀到程序中的順序必須一致!就是說,假如你寫進文件的第一個是 int 類型的,那么輸出的第一個也必須是 int 型的,這樣一一對應才能保證不出錯。
9. 對象流--對引用數據類型處理--ObjectInputStream,ObjectOutputStream
9.1 放入String 類型數據:
1 public class Test002 {
2 public static void main(String[] args) throws FileNotFoundException, IOException {
3 ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(new File("d:/haha/t.txt")));
4 oos.writeObject("java");
5 oos.close();//關閉流
6 }
7 }
這段代碼就是將 “java” 寫到目標文件中去。
9.3 現在要寫入 Person 的一個對象:
假如說我自定義了一個 Person 類。,然后我現在要把一個 Person 類型的對象放進目標文件中去,按照上面的放 String 類型數據的方法:
1 public class Test002 {
2 public static void main(String[] args) throws FileNotFoundException, IOException {
3 ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(new File("d:/bjsxt/t.txt")));
4 oos.writeObject(new Person("lili", 18));
5 oos.close();
6 }
7 }
這段程序運行結果如下:
發現出錯了,“NotSerializableExeption”,啥意思呢,就是說** Person 類沒有序列化!!**
那么怎么解決呢?方法:實現 Serializable 方法,加序列號。
謹記:以后如果要對類的對象進行網絡傳輸一定要實現序列化?。?!
如果您能看到這里,我真的表示萬分感謝,這篇文章如果有什么錯誤的地方,或者您不理解的地方,歡迎留言。另外我這兒有這部分學習的視頻,如果需要的話,也可以留下郵箱,我發給您。就這樣,謝謝各位!