史上最騷最全最詳細的IO流教程,沒有之一!

  • 2019 年 11 月 1 日
  • 筆記

目錄

前言
io流用到的地方很多,就比如上傳下載,傳輸,設計模式等....基礎打紮實了,才能玩更高端的。

在部落客認為真正懂IO流的優秀程式設計師每次在使用IO流之前都會明確分析如下四點:

(1)明確要操作的數據是數據源還是數據目的(也就是要讀還是要寫)
(2)明確要操作的設備上的數據是位元組還是文本
(3)明確數據所在的具體設備
(4)明確是否需要額外功能(比如是否需要轉換流、高效流等)

以上四點將會在文章告白IO流的四點明確裡面小結一下,如果各位真能熟練以上四點,我覺得這篇文章你就沒必要看了,因為你已經把IO玩弄與股掌之中,萬物皆可被你盤也就也不再話下了。

@

1、告白IO流的四點明確

(1)明確要操作的數據是數據源還是數據目的(要讀還是要寫)

    

 源:
InputStream  Reader

目的:
OutputStream  Writer

(2)明確要操作的設備上的數據是位元組還是文本

     

 源:

          位元組: InputStream

          文本: Reader

      目的:

          位元組: OutputStream

          文本: Writer

(3)明確數據所在的具體設備

   

   源設備:

        硬碟:文件 File開頭

        記憶體:數組,字元串

        鍵盤:System.in

        網路:Socket

      對應目的設備:

        硬碟:文件 File開頭

        記憶體:數組,字元串

        螢幕:System.out

        網路:Socket

(4)明確是否需要額外功能

  

  需要轉換—— 轉換流 InputStreamReader 、OutputStreamWriter

    需要高效—— 緩衝流Bufferedxxx

    多個源—— 序列流 SequenceInputStream

    對象序列化—— ObjectInputStream、ObjectOutputStream

    保證數據的輸出形式—— 列印流PrintStream 、Printwriter

    操作基本數據,保證位元組原樣性——DataOutputStream、DataInputStream

到這裡,我們再來看看IO流的分類吧
在这里插入图片描述
OK,準備好了告白IO流了咩?

2、File類

至於IO流,也就是輸入輸出流,從文件出發到文件結束,至始至終都離不開文件,所以IO流還得從文件File類講起。

1.1 File概述

java.io.File 類是專門對文件進行操作的類,只能對文件本身進行操作,不能對文件內容進行操作。
java.io.File 類是文件和目錄路徑名的抽象表示,主要用於文件和目錄的創建、查找和刪除等操作。

怎麼理解上面兩句話?其實很簡單!

第一句就是說File跟流無關,File類不能對文件進行讀和寫也就是輸入和輸出!
第二句就是說File主要表示類似D:\文件目錄1與D:\文件目錄1\文件.txt,前者是文件夾(Directory)後者則是文件(file),而File類就是操作這兩者的類。

1.2 構造方法

在java中,一切皆是對象,File類也不例外,不論是哪個對象都應該從該對象的構造說起,所以部落客來分析分析File類的構造方法。首先從API開始著手
在这里插入图片描述
我們主要來學習一下比較常用的三個:

1、 public File(String pathname) :通過將給定的路徑名字元串轉換為抽象路徑名來創建新的 File實例。
2、 public File(String parent, String child) :從父路徑名字元串和子路徑名字元串創建新的 File實例。
3、 public File(File parent, String child) :從父抽象路徑名和子路徑名字元串創建新的 File實例。

看字描述不夠生動不夠形象不得勁?沒得事,下面進行構造舉例,馬上就生動形象了,程式碼如下:

1. 一個File對象代表硬碟中實際存在的一個文件或者目錄。  2.  File類構造方法不會給你檢驗這個文件或文件夾是否真實存在,因此無論該路徑下是否存在文件或者目錄,都不影響File對象的創建。  // 文件路徑名  String path = "D:\123.txt";  File file1 = new File(path);    // 文件路徑名  String path2 = "D:\1\2.txt";  File file2 = new File(path2);     -------------相當於D:\1\2.txt    // 通過父路徑和子路徑字元串   String parent = "F:\aaa";   String child = "bbb.txt";   File file3 = new File(parent, child);  --------相當於F:\aaa\bbb.txt    // 通過父級File對象和子路徑字元串  File parentDir = new File("F:\aaa");  String child = "bbb.txt";  File file4 = new File(parentDir, child); --------相當於F:\aaa\bbb.txt

File類的注意點:

  1. 一個File對象代表硬碟中實際存在的一個文件或者目錄。
  2. File類構造方法不會給你檢驗這個文件或文件夾是否真實存在,因此無論該路徑下是否存在文件或者目錄,都不影響File對象的創建。

1.3 常用方法

File的常用方法主要分為獲取功能、獲取絕對路徑和相對路徑、判斷功能、創建刪除功能的方法

1.3.1 獲取功能的方法

1、public String getAbsolutePath() :返回此File的絕對路徑名字元串。

2、public String getPath() :將此File轉換為路徑名字元串。

3、public String getName() :返回由此File表示的文件或目錄的名稱。

4、public long length() :返回由此File表示的文件的長度。

以上方法測試,程式碼如下【注意測試以自己的電腦文件夾為準】:

public class FileGet {      public static void main(String[] args) {          File f = new File("d:/aaa/bbb.java");          System.out.println("文件絕對路徑:"+f.getAbsolutePath());          System.out.println("文件構造路徑:"+f.getPath());          System.out.println("文件名稱:"+f.getName());          System.out.println("文件長度:"+f.length()+"位元組");            File f2 = new File("d:/aaa");          System.out.println("目錄絕對路徑:"+f2.getAbsolutePath());          System.out.println("目錄構造路徑:"+f2.getPath());          System.out.println("目錄名稱:"+f2.getName());          System.out.println("目錄長度:"+f2.length());      }  }  輸出結果:  文件絕對路徑:d:aaabbb.java  文件構造路徑:d:aaabbb.java  文件名稱:bbb.java  文件長度:2116位元組    目錄絕對路徑:d:aaa  目錄構造路徑:d:aaa  目錄名稱:aaa  目錄長度:3236

注意:length(),表示文件的長度。但是File對象表示目錄,則返回值未指定。

1.3.2 絕對路徑和相對路徑

絕對路徑:一個完整的路徑,以盤符開頭,例如F://aaa.txt。
相對路徑:一個簡化的路徑,不以盤符開頭,例如//aaa.txt//b.txt。

1、路徑是不區分大小寫
2、路徑中的文件名稱分隔符windows使用反斜杠,反斜杠是轉義字元,兩個反斜杠代表一個普通的反斜杠

//絕對路徑  public class FilePath {      public static void main(String[] args) {          // D盤下的bbb.java文件          File f = new File("D:\bbb.java");          System.out.println(f.getAbsolutePath());            // 項目下的bbb.java文件          File f2 = new File("bbb.java");          System.out.println(f2.getAbsolutePath());      }  }  輸出結果:  D:bbb.java  D:javabbb.java

1.3.3判斷功能的方法

1、 public boolean exists() :此File表示的文件或目錄是否實際存在。
2、 public boolean isDirectory() :此File表示的是否為目錄。
3、public boolean isFile() :此File表示的是否為文件。

方法演示,程式碼如下:

public class FileIs {      public static void main(String[] args) {          File f = new File("d:\aaa\bbb.java");          File f2 = new File("d:\aaa");          // 判斷是否存在          System.out.println("d:\aaa\bbb.java 是否存在:"+f.exists());          System.out.println("d:\aaa 是否存在:"+f2.exists());          // 判斷是文件還是目錄          System.out.println("d:\aaa 文件?:"+f2.isFile());          System.out.println("d:\aaa 目錄?:"+f2.isDirectory());      }  }  輸出結果:  d:aaabbb.java 是否存在:true  d:aaa 是否存在:true  d:aaa 文件?:false  d:aaa 目錄?:true

1.3.4 創建刪除功能的方法

  • public boolean createNewFile() :文件不存在,創建一個新的空文件並返回true,文件存在,不創建文件並返回false。
  • public boolean delete() :刪除由此File表示的文件或目錄。
  • public boolean mkdir() :創建由此File表示的目錄。
  • public boolean mkdirs() :創建由此File表示的目錄,包括任何必需但不存在的父目錄。

其中,mkdirs()和mkdir()方法類似,但mkdir(),只能創建一級目錄,mkdirs()可以創建多級目錄比如//a//b//c,所以開發中一般用mkdirs();

這些方法中值得注意的是createNewFile方法以及mkdir與mkdirs的區別

方法測試,程式碼如下:

public class FileCreateDelete {      public static void main(String[] args) throws IOException {          // 文件的創建          File f = new File("aaa.txt");          System.out.println("是否存在:"+f.exists()); // false          System.out.println("是否創建:"+f.createNewFile()); // true          System.out.println("是否創建:"+f.createNewFile()); // 以及創建過了所以再使用createNewFile返回false          System.out.println("是否存在:"+f.exists()); // true            // 目錄的創建          File f2= new File("newDir");          System.out.println("是否存在:"+f2.exists());// false          System.out.println("是否創建:"+f2.mkdir()); // true          System.out.println("是否存在:"+f2.exists());// true            // 創建多級目錄          File f3= new File("newDira\newDirb");          System.out.println(f3.mkdir());// false          File f4= new File("newDira\newDirb");          System.out.println(f4.mkdirs());// true            // 文件的刪除          System.out.println(f.delete());// true            // 目錄的刪除          System.out.println(f2.delete());// true          System.out.println(f4.delete());// false      }  }

注意:delete方法,如果此File表示目錄,則目錄必須為空才能刪除。

1.4 目錄的遍歷

  • public String[] list() :返回一個String數組,表示該File目錄中的所有子文件或目錄。

  • public File[] listFiles() :返回一個File數組,表示該File目錄中的所有的子文件或目錄。

public class FileFor {      public static void main(String[] args) {          File dir = new File("G:游標");            //獲取當前目錄下的文件以及文件夾的名稱。          String[] names = dir.list();          for(String name : names){              System.out.println(name);          }          //獲取當前目錄下的文件以及文件夾對象,只要拿到了文件對象,那麼就可以獲取更多資訊          File[] files = dir.listFiles();          for (File file : files) {              System.out.println(file);          }      }  }

在这里插入图片描述
listFiles在獲取指定目錄下的文件或者文件夾時必須滿足下面兩個條件

1,指定的目錄必須存在

2,指定的必須是目錄。否則容易引發返回數組為null,出現NullPointerException異常

1.5 遞歸遍歷文件夾下所有文件以及子文件

不說啥了,直接上程式碼:

package File;    import java.io.File;    //遞歸遍歷文件夾下所有的文件  public class RecursionDirectory {      public static void main(String[] args) {        File file=new File("D:\java專屬IO測試");          Recursion(file);      }      public static void Recursion(File file){          //1、判斷傳入的是否是目錄          if(!file.isDirectory()){              //不是目錄直接退出              return;          }          //已經確保了傳入的file是目錄          File[] files = file.listFiles();          //遍歷files          for (File f: files) {              //如果該目錄下文件還是個文件夾就再進行遞歸遍歷其子目錄              if(f.isDirectory()){                  //遞歸                  Recursion(f);              }else {                  //如果該目錄下文件是個文件,則列印對應的名字                  System.out.println(f.getName());              }            }      }  }

如果對上面的程式碼有疑問,可以隨時聯繫我,部落客一直都在!

3、初探IO流

1.1 什麼是IO

我想在座各位肯定經歷都過這樣的場景。當你編輯一個文本文件也好用eclipse打程式碼也罷,忘記了ctrl+s ,在你關閉文件的哪一瞬間手殘點了個不該點的按鈕,但你反應過來,心早已拔涼拔涼的了。

我們把這種數據的傳輸,可以看做是一種數據的流動,按照流動的方向,以記憶體為基準,分為輸入input 和輸出output ,即流向記憶體是輸入流,流出記憶體的輸出流。

Java中I/O操作主要是指使用java.io包下的內容,進行輸入、輸出操作。輸入也叫做讀取數據,輸出也叫做作寫出數據。

1.2 IO的分類

根據數據的流向分為:輸入流 和 輸出流。

  • 輸入流 :把數據從其他設備上讀取到記憶體中的流。
  • 輸出流 :把數據從記憶體 中寫出到其他設備上的流。

根據數據的類型分為:位元組流 和 字元流。

  • 位元組流 :以位元組為單位,讀寫數據的流。
  • 字元流 :以字元為單位,讀寫數據的流。

分類之後對應的超類(V8提示:超類也就是父類的意思)
| | 輸入流 | 輸出流
|--|--|--|
| 位元組流 | 位元組輸入流 InputStream |位元組輸出流 OutputStream |
| 字元流 | 字元輸入流 Reader|字元輸出流 Writer|

注:
由這四個類的子類名稱基本都是以其父類名作為子類名的後綴。
如:InputStream的子類FileInputStream。
如:Reader的子類FileReader。
在这里插入图片描述

1.3 關於IO的分流向說明

啥都不說了,看圖吧
在这里插入图片描述
在这里插入图片描述

位元組流OutputStream與InputStream的故事

OutputStream與InputStream的繼承關係
在这里插入图片描述

2.1 文件的世界裡一切皆為位元組

我們必須明確一點的是,一切文件數據(文本、圖片、影片等)在存儲時,都是以二進位數字的形式保存,都一個一個的位元組,那麼傳輸時一樣如此。所以,位元組流可以傳輸任意文件數據。在操作流的時候,我們要時刻明確,無論使用什麼樣的流對象,底層傳輸的始終為二進位數據。

2.2 位元組輸出流(OutputStream)

java.io.OutputStream抽象類是表示位元組輸出流的所有類的超類(父類),將指定的位元組資訊寫出到目的地。它定義了位元組輸出流的基本共性功能方法,不要問我OutputStream為啥可以定義位元組輸出流的基本共性功能方法,熊dei啊,上一句說過了OutputStream是位元組輸出流的所有類的超類,繼承知識,懂?(如果是真的不理解的小白同學,可以點擊藍色字體繼承進入補習)

位元組輸出流的基本共性功能方法:

1、 public void close() :關閉此輸出流並釋放與此流相關聯的任何系統資源。
2、 public void flush() :刷新此輸出流並強制任何緩衝的輸出位元組被寫出。
3、 public void write(byte[] b):將 b.length個位元組從指定的位元組數組寫入此輸出流。
4、 public void write(byte[] b, int off, int len) :從指定的位元組數組寫入 len位元組,從偏移量 off開始輸出到此輸出流。 也就是說從off個位元組數開始讀取一直到len個位元組結束
5、 public abstract void write(int b) :將指定的位元組輸出流。

以上五個方法則是位元組輸出流都具有的方法,由父類OutputStream定義提供,子類都會共享以上方法

FileOutputStream類

OutputStream有很多子類,我們從最簡單的一個子類FileOutputStream開始。看名字就知道是文件輸出流,用於將數據寫出到文件。

FileOutputStream構造方法

不管學啥子,只有是對象,就從構造方法開始!

1、 public FileOutputStream(File file):根據File對象為參數創建對象。
2、 public FileOutputStream(String name): 根據名稱字元串為參數創建對象。

推薦第二種構造方法【開發常用】:

FileOutputStream outputStream = new FileOutputStream("abc.txt");

就以上面這句程式碼來講,類似這樣創建位元組輸出流對象都做了三件事情:
1、調用系統功能去創建文件【輸出流對象才會自動創建】
2、創建outputStream對象
3、把foutputStream對象指向這個文件

注意:
創建輸出流對象的時候,系統會自動去對應位置創建對應文件,而創建輸出流對象的時候,文件不存在則會報FileNotFoundException異常,也就是系統找不到指定的文件異常。

當你創建一個流對象時,必須直接或者間接傳入一個文件路徑。比如現在我們創建一個FileOutputStream流對象,在該路徑下,如果沒有這個文件,會創建該文件。如果有這個文件,會清空這個文件的數據。有興趣的童鞋可以測試一下,具體程式碼如下:

public class FileOutputStreamConstructor throws IOException {      public static void main(String[] args) {          // 使用File對象創建流對象          File file = new File("G:\自動創建的文件夾\a.txt");          FileOutputStream fos = new FileOutputStream(file);            // 使用文件名稱創建流對象          FileOutputStream fos = new FileOutputStream("G:\b.txt");      }  }

FileOutputStream寫出位元組數據

使用FileOutputStream寫出位元組數據主要通過Write方法,而write方法分如下三種

public void write(int b)  public void write(byte[] b)  public void write(byte[] b,int off,int len)  //從`off`索引開始,`len`個位元組
  1. 寫出位元組:write(int b) 方法,每次可以寫出一個位元組數據,程式碼如下:
public class IoWrite {      public static void main(String[] args) throws IOException {          // 使用文件名稱創建流對象          FileOutputStream fos = new FileOutputStream("fos.txt");          // 寫出數據          fos.write(97); // 寫出第1個位元組          fos.write(98); // 寫出第2個位元組          fos.write(99); // 寫出第3個位元組          // 關閉資源          fos.close();      }  }  輸出結果:  abc
  1. 雖然參數為int類型四個位元組,但是只會保留一個位元組的資訊寫出。
  2. 流操作完畢後,必須釋放系統資源,調用close方法,千萬記得。
  1. 寫出位元組數組:write(byte[] b),每次可以寫出數組中的數據,程式碼使用演示:
public class FOSWrite {      public static void main(String[] args) throws IOException {          // 使用文件名稱創建流對象          FileOutputStream fos = new FileOutputStream("fos.txt");          // 字元串轉換為位元組數組          byte[] b = "麻麻我想吃烤山藥".getBytes();          // 寫出位元組數組數據          fos.write(b);          // 關閉資源          fos.close();      }  }  輸出結果:  麻麻我想吃烤山藥
  1. 寫出指定長度位元組數組:write(byte[] b, int off, int len) ,每次寫出從off索引開始,len個位元組,程式碼如下:
public class FOSWrite {      public static void main(String[] args) throws IOException {          // 使用文件名稱創建流對象          FileOutputStream fos = new FileOutputStream("fos.txt");          // 字元串轉換為位元組數組          byte[] b = "abcde".getBytes();          // 寫出從索引2開始,2個位元組。索引2是c,兩個位元組,也就是cd。          fos.write(b,2,2);          // 關閉資源          fos.close();      }  }  輸出結果:  cd

FileOutputStream實現數據追加續寫、換行

經過以上的程式碼測試,每次程式運行,每次創建輸出流對象,都會清空目標文件中的數據。如何保留目標文件中數據,還能繼續追加新數據呢?並且實現換行呢?其實很簡單,這個時候我們又要再學習FileOutputStream的另外兩個構造方法了,如下:

1、public FileOutputStream(File file, boolean append)

2、public FileOutputStream(String name, boolean append)

這兩個構造方法,第二個參數中都需要傳入一個boolean類型的值,true 表示追加數據,false 表示不追加也就是清空原有數據。這樣創建的輸出流對象,就可以指定是否追加續寫了,至於Windows換行則是 nr ,下面將會詳細講到。

實現數據追加續寫程式碼如下:

public class FOSWrite {      public static void main(String[] args) throws IOException {          // 使用文件名稱創建流對象          FileOutputStream fos = new FileOutputStream("fos.txt",true);          // 字元串轉換為位元組數組          byte[] b = "abcde".getBytes();          // 寫出從索引2開始,2個位元組。索引2是c,兩個位元組,也就是cd。          fos.write(b);          // 關閉資源          fos.close();      }  }  文件操作前:cd  文件操作後:cdabcde

Windows系統里,換行符號是rn ,具體程式碼如下:

public class FOSWrite {      public static void main(String[] args) throws IOException {          // 使用文件名稱創建流對象          FileOutputStream fos = new FileOutputStream("fos.txt");          // 定義位元組數組          byte[] words = {97,98,99,100,101};          // 遍曆數組          for (int i = 0; i < words.length; i++) {              // 寫出一個位元組              fos.write(words[i]);              // 寫出一個換行, 換行符號轉成數組寫出              fos.write("rn".getBytes());          }          // 關閉資源          fos.close();      }  }    輸出結果:  a  b  c  d  e
  • 回車符r和換行符n :
    • 回車符:回到一行的開頭(return)。
    • 換行符:下一行(newline)。
  • 系統中的換行:
    • Windows系統里,每行結尾是 回車+換行 ,即rn;
    • Unix系統里,每行結尾只有 換行 ,即n;
    • Mac系統里,每行結尾是 回車 ,即r。從 Mac OS X開始與Linux統一。

2.3 位元組輸入流(InputStream)

java.io.InputStream抽象類是表示位元組輸入流的所有類的超類(父類),可以讀取位元組資訊到記憶體中。它定義了位元組輸入流的基本共性功能方法。

位元組輸入流的基本共性功能方法:

1、 public void close() :關閉此輸入流並釋放與此流相關聯的任何系統資源。
2、public abstract int read(): 從輸入流讀取數據的下一個位元組。

3、 public int read(byte[] b): 該方法返回的int值代表的是讀取了多少個位元組,讀到幾個返回幾個,讀取不到返回-1

FileInputStream類

java.io.FileInputStream類是文件輸入流,從文件中讀取位元組。

FileInputStream的構造方法

1、 FileInputStream(File file): 通過打開與實際文件的連接來創建一個 FileInputStream ,該文件由文件系統中的 File對象 file命名。
2、 FileInputStream(String name): 通過打開與實際文件的連接來創建一個 FileInputStream ,該文件由文件系統中的路徑名name命名。

同樣的,推薦使用第二種構造方法:

 FileInputStream inputStream = new FileInputStream("a.txt");

當你創建一個流對象時,必須傳入一個文件路徑。該路徑下,如果沒有該文件,會拋出FileNotFoundException 。

構造舉例,程式碼如下:

public class FileInputStreamConstructor throws IOException{      public static void main(String[] args) {          // 使用File對象創建流對象          File file = new File("a.txt");          FileInputStream fos = new FileInputStream(file);            // 使用文件名稱創建流對象          FileInputStream fos = new FileInputStream("b.txt");      }  }

FileInputStream讀取位元組數據

  1. 讀取位元組:read方法,每次可以讀取一個位元組的數據,提升為int類型,讀取到文件末尾,返回-1,程式碼測試如下【read.txt文件中內容為abcde】:
public class FISRead {      public static void main(String[] args) throws IOException{          // 使用文件名稱創建流對象          FileInputStream fis = new FileInputStream("read.txt");//read.txt文件中內容為abcde          // 讀取數據,返回一個位元組          int read = fis.read();          System.out.println((char) read);          read = fis.read();          System.out.println((char) read);          read = fis.read();          System.out.println((char) read);          read = fis.read();          System.out.println((char) read);          read = fis.read();          System.out.println((char) read);          // 讀取到末尾,返回-1          read = fis.read();          System.out.println( read);          // 關閉資源          fis.close();      }  }  輸出結果:  a  b  c  d  e  -1

循環改進讀取方式,程式碼使用演示:

public class FISRead {      public static void main(String[] args) throws IOException{          // 使用文件名稱創建流對象          FileInputStream fis = new FileInputStream("read.txt");          // 定義變數,保存數據          int b ;          // 循環讀取          while ((b = fis.read())!=-1) {              System.out.println((char)b);          }          // 關閉資源          fis.close();      }  }  輸出結果:  a  b  c  d  e
  1. 使用位元組數組讀取:read(byte[] b),每次讀取b的長度個位元組到數組中,返回讀取到的有效位元組個數,讀取到末尾時,返回-1 ,程式碼使用演示:
public class FISRead {      public static void main(String[] args) throws IOException{          // 使用文件名稱創建流對象.          FileInputStream fis = new FileInputStream("read.txt"); // read.txt文件中內容為abcde          // 定義變數,作為有效個數          int len ;          // 定義位元組數組,作為裝位元組數據的容器          byte[] b = new byte[2];          // 循環讀取          while (( len= fis.read(b))!=-1) {              // 每次讀取後,把數組變成字元串列印              System.out.println(new String(b));          }          // 關閉資源          fis.close();      }  }    輸出結果:  ab  cd  ed

由於read.txt文件中內容為abcde,而錯誤數據d,是由於最後一次讀取時,只讀取一個位元組e,數組中,上次讀取的數據沒有被完全替換【注意是替換,看下圖】,所以要通過len ,獲取有效的位元組
在这里插入图片描述
程式碼如下:

public class FISRead {      public static void main(String[] args) throws IOException{          // 使用文件名稱創建流對象.          FileInputStream fis = new FileInputStream("read.txt"); // 文件中為abcde          // 定義變數,作為有效個數          int len ;          // 定義位元組數組,作為裝位元組數據的容器          byte[] b = new byte[2];          // 循環讀取          while (( len= fis.read(b))!=-1) {              // 每次讀取後,把數組的有效位元組部分,變成字元串列印              System.out.println(new String(b,0,len));//  len 每次讀取的有效位元組個數          }          // 關閉資源          fis.close();      }  }    輸出結果:  ab  cd  e

在開發中一般強烈推薦使用數組讀取文件,程式碼如下:

package io;    import java.io.FileInputStream;  import java.io.FileNotFoundException;  import java.io.IOException;    public class input2 {      public static void main(String args[]){          FileInputStream inputStream = null;          try {              inputStream = new FileInputStream("a.txt");              int len = 0 ;              byte[] bys = new byte[1024];              while ((len = inputStream.read(bys)) != -1) {                  System.out.println(new String(bys,0,len));              }            } catch (IOException e) {              e.printStackTrace();          }finally {              try {                  inputStream.close();              } catch (IOException e) {                  e.printStackTrace();              }          }        }  }

位元組流FileInputstream複製圖片

複製圖片原理
在这里插入图片描述

程式碼實現

複製圖片文件,程式碼如下:

public class Copy {      public static void main(String[] args) throws IOException {          // 1.創建流對象          // 1.1 指定數據源          FileInputStream fis = new FileInputStream("D:\test.jpg");          // 1.2 指定目的地          FileOutputStream fos = new FileOutputStream("test_copy.jpg");            // 2.讀寫數據          // 2.1 定義數組          byte[] b = new byte[1024];          // 2.2 定義長度          int len;          // 2.3 循環讀取          while ((len = fis.read(b))!=-1) {              // 2.4 寫出數據              fos.write(b, 0 , len);          }            // 3.關閉資源          fos.close();          fis.close();      }  }

注:複製文本、圖片、mp3、影片等的方式一樣。

到這裡,已經從File類講到了位元組流OutputStream與InputStream,而現在將主要從字元流Reader和Writer的故事開展。

字元流Reader和Writer的故事

字元流Reader和Writer的故事從它們的繼承圖開始,啥都不說了,直接看圖
在这里插入图片描述

字元流

字元流的由來:因為數據編碼的不同,因而有了對字元進行高效操作的流對象,字元流本質其實就是基於位元組流讀取時,去查了指定的碼錶,而位元組流直接讀取數據會有亂碼的問題(讀中文會亂碼),這個時候小白同學就看不懂了,沒事,咋們先來看個程式:

package IO;    import java.io.FileInputStream;  import java.io.FileNotFoundException;  import java.io.FileOutputStream;    public class CharaterStream {      public static void main(String[] args) throws Exception {          //FileInputStream為操作文件的字元輸入流          FileInputStream inputStream = new FileInputStream("a.txt");//內容為哥敢摸屎            int len;          while ((len=inputStream.read())!=-1){             System.out.print((char)len);          }        }  }  運行結果:   ??¥??¢????±

具體現狀分析
在这里插入图片描述
話說,就是你哥我敢摸si,那你哥我肯定也不認識這玩意啊: ??¥??¢????±

位元組流讀取中文字元時,可能不會顯示完整的字元,那是因為一個中文字元佔用多個位元組存儲。

那位元組流就沒辦法了嗎?不,位元組流依舊有辦法,只是麻煩了點,程式碼如下:

public class CharaterStream {      public static void main(String[] args) throws Exception {            FileInputStream inputStream = new FileInputStream("a.txt");          byte[] bytes = new byte[1024];          int len;          while ((len=inputStream.read(bytes))!=-1){             System.out.print(new String(bytes,0,len));          }      }  }  運行結果: 哥敢摸屎

這是為啥呢?沒錯解碼的正是String,查看new String()的源碼,String構造方法有解碼功能,並且默認編碼是utf-8,程式碼如下:

this.value = StringCoding.decode(bytes, offset, length);     再點進decode,循序漸進發現,默認編碼是UTF-8

儘管位元組流也能有辦法決絕亂碼問題,但是還是比較麻煩,於是java就有了字元流,字元為單位讀寫數據,字元流專門用於處理文本文件。如果處理純文本的數據優先考慮字元流,其他情況就只能用位元組流了(圖片、影片、等等只文本例外)。

從另一角度來說:字元流 = 位元組流 + 編碼表

1、 字元輸入流(Reader)

java.io.Reader抽象類是字元輸入流的所有類的超類(父類),可以讀取字元資訊到記憶體中。它定義了字元輸入流的基本共性功能方法。

字元輸入流的共性方法:

1、public void close() :關閉此流並釋放與此流相關聯的任何系統資源。
2、 public int read(): 從輸入流讀取一個字元。
3、 public int read(char[] cbuf): 從輸入流中讀取一些字元,並將它們存儲到字元數組 cbuf中

FileReader類

java.io.FileReader類是讀取字元文件的便利類。構造時使用系統默認的字元編碼和默認位元組緩衝區。

構造方法

1、FileReader(File file): 創建一個新的 FileReader ,給定要讀取的File對象。
2、 FileReader(String fileName): 創建一個新的 FileReader ,給定要讀取的文件的字元串名稱。

構造方法的使用就算不寫應該都很熟悉了吧,程式碼如下:

public class FileReaderConstructor throws IOException{      public static void main(String[] args) {          // 使用File對象創建流對象          File file = new File("a.txt");          FileReader fr = new FileReader(file);            // 使用文件名稱創建流對象          FileReader fr = new FileReader("b.txt");      }  }

FileReader讀取字元數據

  1. 讀取字元:read方法,每次可以讀取一個字元的數據,提升為int類型,讀取到文件末尾,返回-1,循環讀取,程式碼使用演示:
public class FRRead {      public static void main(String[] args) throws IOException {          // 使用文件名稱創建流對象          FileReader fr = new FileReader("a.txt");          // 定義變數,保存數據          int b ;          // 循環讀取          while ((b = fr.read())!=-1) {              System.out.println((char)b);          }          // 關閉資源          fr.close();      }  }

至於讀取的寫法類似位元組流的寫法,只是讀取單位不同罷了。

2、字元輸出流(Writer)

java.io.Writer抽象類是字元輸出流的所有類的超類(父類),將指定的字元資訊寫出到目的地。它同樣定義了字元輸出流的基本共性功能方法。

字元輸出流的基本共性功能方法:

1、void write(int c) 寫入單個字元。
2、void write(char[] cbuf)寫入字元數組。
3、 abstract void write(char[] cbuf, int off, int len)寫入字元數組的某一部分,off數組的開始索引,len寫的字元個數。
4、 void write(String str)寫入字元串。
5、void write(String str, int off, int len) 寫入字元串的某一部分,off字元串的開始索引,len寫的字元個數。
6、void flush()刷新該流的緩衝。
7、void close() 關閉此流,但要先刷新它。

FileWriter類

java.io.FileWriter類是寫出字元到文件的便利類。構造時使用系統默認的字元編碼和默認位元組緩衝區。

構造方法

1、 FileWriter(File file): 創建一個新的 FileWriter,給定要讀取的File對象。
2、FileWriter(String fileName): 創建一個新的 FileWriter,給定要讀取的文件的名稱。

依舊是熟悉的構造舉例,程式碼如下:

public class FileWriterConstructor {      public static void main(String[] args) throws IOException {          // 第一種:使用File對象創建流對象          File file = new File("a.txt");          FileWriter fw = new FileWriter(file);            // 第二種:使用文件名稱創建流對象          FileWriter fw = new FileWriter("b.txt");      }  }

FileWriter寫出數據

寫出字元:write(int b) 方法,每次可以寫出一個字元數據,程式碼使用演示:

public class FWWrite {      public static void main(String[] args) throws IOException {          // 使用文件名稱創建流對象          FileWriter fw = new FileWriter("fw.txt");          // 寫出數據          fw.write(97); // 寫出第1個字元          fw.write('b'); // 寫出第2個字元          fw.write('C'); // 寫出第3個字元            //關閉資源時,與FileOutputStream不同。 如果不關閉,數據只是保存到緩衝區,並未保存到文件。          // fw.close();      }  }  輸出結果:  abC

【注意】關閉資源時,與FileOutputStream不同。 如果不關閉,數據只是保存到緩衝區,並未保存到文件。

關閉close和刷新flush

因為內置緩衝區的原因,如果不關閉輸出流,無法寫出字元到文件中。但是關閉的流對象,是無法繼續寫出數據的。如果我們既想寫出數據,又想繼續使用流,就需要flush 方法了。

flush :刷新緩衝區,流對象可以繼續使用。
close:先刷新緩衝區,然後通知系統釋放資源。流對象不可以再被使用了。

flush還是比較有趣的,童鞋們不自己運行一下還真不好體會,現在部落客就寫個程式讓你體會體會:
字元流

public class FlushDemo {      public static void main(String[] args) throws Exception {          //源   也就是輸入流【讀取流】 讀取a.txt文件          FileReader fr=new FileReader("a.txt");  //必須要存在a.txt文件,否則報FileNotFoundException異常          //目的地  也就是輸出流          FileWriter fw=new FileWriter("b.txt");  //系統會自動創建b.txt,因為它是輸出流!          int len;          while((len=fr.read())!=-1){             fw.write(len);          }     注意這裡是沒有使用close關閉流,開發中不能這樣做,但是為了更好的體會flush的作用      }  }

在这里插入图片描述
運行效果是怎麼樣的呢?答案是b.txt文件中依舊是空的,是的並沒有任何東西,為啥呢?熊dei啊,我在上面就用紅色字體特別標註過了,就是這句話: 【注意】關閉資源時,與FileOutputStream不同。 如果不關閉,數據只是保存到緩衝區,並未保存到文件。這個時候反應過來了吧,可見實踐例子的重要性,編程就是這樣,不去敲,永遠學不會!!!所以一定要去敲,部落客沒敲過10萬行程式碼真的沒有臉出去說自己是學java的。所以,大家一定要多思考,多敲啊!!!

所以,我們在以上的程式碼中再添加下面三句程式碼,就完美了,b.txt文件就能複製到源文件的數據了!

  fr.close();    fw.flush();    fw.close();

flush()這個函數是清空的意思,用於清空緩衝區的數據流,進行流的操作時,數據先被讀到記憶體中,然後再用數據寫到文件中,那麼當你數據讀完時,我們如果這時調用close()方法關閉讀寫流,這時就可能造成數據丟失,為什麼呢?因為,讀入數據完成時不代表寫入數據完成,一部分數據可能會留在快取區中,這個時候flush()方法就格外重要了。

好了,接下來close使用程式碼如下:

public class FWWrite {      public static void main(String[] args) throws IOException {          // 使用文件名稱創建流對象          FileWriter fw = new FileWriter("fw.txt");          // 寫出數據,通過flush          fw.write('刷'); // 寫出第1個字元          fw.flush();          fw.write('新'); // 繼續寫出第2個字元,寫出成功          fw.flush();            // 寫出數據,通過close          fw.write('關'); // 寫出第1個字元          fw.close();          fw.write('閉'); // 繼續寫出第2個字元,【報錯】java.io.IOException: Stream closed          fw.close();      }  }

即便是flush方法寫出了數據,操作的最後還是要調用close方法,釋放系統資源。

FileWriter的續寫和換行

續寫和換行:操作類似於FileOutputStream操作(上一篇部落格講到過),直接上程式碼:

public class FWWrite {      public static void main(String[] args) throws IOException {          // 使用文件名稱創建流對象,可以續寫數據          FileWriter fw = new FileWriter("fw.txt",true);          // 寫出字元串          fw.write("哥敢");          // 寫出換行          fw.write("rn");          // 寫出字元串          fw.write("摸屎");          // 關閉資源          fw.close();      }  }  輸出結果:  哥敢  摸屎

FileReader和FileWriter類完成文本文件複製

直接上程式碼:

import java.io.FileReader;  import java.io.FileWriter;  import java.io.IOException;    public class CopyFile {      public static void main(String[] args) throws IOException {          //創建輸入流對象          FileReader fr=new FileReader("F:\新建文件夾\aa.txt");//文件不存在會拋出java.io.FileNotFoundException          //創建輸出流對象          FileWriter fw=new FileWriter("C:\copyaa.txt");          /*創建輸出流做的工作:           *      1、調用系統資源創建了一個文件           *      2、創建輸出流對象           *      3、把輸出流對象指向文件           * */          //文本文件複製,一次讀一個字元          copyMethod1(fr, fw);          //文本文件複製,一次讀一個字元數組          copyMethod2(fr, fw);            fr.close();          fw.close();      }        public static void copyMethod1(FileReader fr, FileWriter fw) throws IOException {          int ch;          while((ch=fr.read())!=-1) {//讀數據              fw.write(ch);//寫數據          }          fw.flush();      }        public static void copyMethod2(FileReader fr, FileWriter fw) throws IOException {          char chs[]=new char[1024];          int len=0;          while((len=fr.read(chs))!=-1) {//讀數據              fw.write(chs,0,len);//寫數據          }          fw.flush();      }  }    CopyFile

在这里插入图片描述

最後再次強調:
字元流,只能操作文本文件,不能操作圖片,影片等非文本文件。當我們單純讀或者寫文本文件時 使用字元流 其他情況使用位元組流

IO異常的處理

我們在學習的過程中可能習慣把異常拋出,而實際開發中並不能這樣處理,建議使用try...catch...finally 程式碼塊,處理異常部分,格式程式碼如下:

public class HandleException1 {      public static void main(String[] args) {          // 聲明變數          FileWriter fw = null;          try {              //創建流對象              fw = new FileWriter("fw.txt");              // 寫出數據              fw.write("哥敢摸si"); //哥敢摸si          } catch (IOException e) {              e.printStackTrace();          } finally {              try {                  if (fw != null) {                      fw.close();                  }              } catch (IOException e) {                  e.printStackTrace();              }          }      }  }

如果對異常不是特別熟練的童鞋可以參考這篇文章【java基礎之異常】死了都要try,不淋漓盡致地catch我不痛快!

好了,到這裡,字元流Reader和Writer的故事的到這裡了!

前面主要寫了一些基本的流作為IO流的入門。從這裡開始將要見識一些更強大的流。比如能夠高效讀寫的緩衝流,能夠轉換編碼的轉換流,能夠持久化存儲對象的序列化流等等,而這些強大的流都是在基本的流對象基礎之上而來的!這些強大的流將伴隨著我們今後的開發!

1、緩衝流【掌握】

1.1 簡要概述

首先我們來認識認識一下緩衝流,也叫高效流,是對4個FileXxx 流的「增強流」。

緩衝流的基本原理:

1、使用了底層流對象從具體設備上獲取數據,並將數據存儲到緩衝區的數組內。
2、通過緩衝區的read()方法從緩衝區獲取具體的字元數據,這樣就提高了效率。
3、如果用read方法讀取字元數據,並存儲到另一個容器中,直到讀取到了換行符時,將另一個容器臨時存儲的數據轉成字元串返回,就形成了readLine()功能。

也就是說在創建流對象時,會創建一個內置的默認大小的緩衝區數組,通過緩衝區讀寫,減少系統IO次數,從而提高讀寫的效率。

緩衝書寫格式為BufferedXxx,按照數據類型分類:

  • 位元組緩衝流:BufferedInputStream,BufferedOutputStream
  • 字元緩衝流:BufferedReader,BufferedWriter

1.2 位元組緩衝流

構造方法

  • public BufferedInputStream(InputStream in) :創建一個新的緩衝輸入流,注意參數類型為InputStream。
  • public BufferedOutputStream(OutputStream out): 創建一個新的緩衝輸出流,注意參數類型為OutputStream。

構造舉例程式碼如下:

//構造方式一: 創建位元組緩衝輸入流【但是開發中一般常用下面的格式申明】  FileInputStream fps = new FileInputStream(b.txt);  BufferedInputStream bis = new BufferedInputStream(fps)    //構造方式一: 創建位元組緩衝輸入流  BufferedInputStream bis = new BufferedInputStream(new FileInputStream("b.txt"));    ///構造方式二: 創建位元組緩衝輸出流  BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("b.txt"));

感受緩衝流的高效

緩衝流讀寫方法與基本的流是一致的,我們通過複製370多MB的大文件,測試它的效率。

  1. 基本流,程式碼如下:
public class BufferedDemo {      public static void main(String[] args) throws FileNotFoundException {          // 記錄開始時間          long start = System.currentTimeMillis();          // 創建流對象          try (              FileInputStream fis = new FileInputStream("py.exe");//exe文件夠大              FileOutputStream fos = new FileOutputStream("copyPy.exe")          ){              // 讀寫數據              int b;              while ((b = fis.read()) != -1) {                  fos.write(b);              }          } catch (IOException e) {              e.printStackTrace();          }          // 記錄結束時間          long end = System.currentTimeMillis();          System.out.println("普通流複製時間:"+(end - start)+" 毫秒");      }  }  不好意思十分鐘過去了還在玩命複製中...
  1. 緩衝流,程式碼如下:
public class BufferedDemo {      public static void main(String[] args) throws FileNotFoundException {          // 記錄開始時間          long start = System.currentTimeMillis();          // 創建流對象          try (           BufferedInputStream bis = new BufferedInputStream(new FileInputStream("py.exe"));           BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copyPy.exe"));          ){          // 讀寫數據              int b;              while ((b = bis.read()) != -1) {                  bos.write(b);              }          } catch (IOException e) {              e.printStackTrace();          }          // 記錄結束時間          long end = System.currentTimeMillis();          System.out.println("緩衝流複製時間:"+(end - start)+" 毫秒");      }  }    緩衝流複製時間:8016 毫秒

有的童鞋就要說了,我要更快的速度!最近看速度與激情7有點上頭,能不能再快些?答案是當然可以

想要更快可以使用數組的方式,程式碼如下:

public class BufferedDemo {      public static void main(String[] args) throws FileNotFoundException {          // 記錄開始時間          long start = System.currentTimeMillis();          // 創建流對象          try (           BufferedInputStream bis = new BufferedInputStream(new FileInputStream("py.exe"));           BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copyPy.exe"));          ){              // 讀寫數據              int len;              byte[] bytes = new byte[8*1024];              while ((len = bis.read(bytes)) != -1) {                  bos.write(bytes, 0 , len);              }          } catch (IOException e) {              e.printStackTrace();          }          // 記錄結束時間          long end = System.currentTimeMillis();          System.out.println("緩衝流使用數組複製時間:"+(end - start)+" 毫秒");      }  }  緩衝流使用數組複製時間:521 毫秒  

1.3 字元緩衝流

構造方法

相同的來看看其構造,其格式以及原理和位元組緩衝流是一樣一樣的!

  • public BufferedReader(Reader in) :創建一個新的緩衝輸入流,注意參數類型為Reader。
  • public BufferedWriter(Writer out): 創建一個新的緩衝輸出流,注意參數類型為Writer。

構造舉例,程式碼如下:

// 創建字元緩衝輸入流  BufferedReader br = new BufferedReader(new FileReader("b.txt"));  // 創建字元緩衝輸出流  BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));

字元緩衝流特有方法

字元緩衝流的基本方法與普通字元流調用方式一致,這裡不再闡述,我們來看字元緩衝流具備的特有方法。

  • BufferedReader:public String readLine(): 讀一行數據。 讀取到最後返回null
  • BufferedWriter:public void newLine(): 換行,由系統屬性定義符號。

readLine方法演示程式碼如下:

public class BufferedReaderDemo {      public static void main(String[] args) throws IOException {           // 創建流對象          BufferedReader br = new BufferedReader(new FileReader("a.txt"));          // 定義字元串,保存讀取的一行文字          String line  = null;          // 循環讀取,讀取到最後返回null          while ((line = br.readLine())!=null) {              System.out.print(line);              System.out.println("------");          }          // 釋放資源          br.close();      }  }

newLine方法演示程式碼如下:

public class BufferedWriterDemo throws IOException {    public static void main(String[] args) throws IOException  {        // 創建流對象        BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));        // 寫出數據        bw.write("哥");        // 寫出換行        bw.newLine();        bw.write("敢");        bw.newLine();        bw.write("摸屎");        bw.newLine();        bw.write("你敢嗎?");        bw.newLine();        // 釋放資源        bw.close();    }  }  輸出效果:  哥  敢  摸屎  你敢嗎?

1.4 字元緩衝流練習

字元緩衝流練習啥捏?先放鬆一下吧各位,先欣賞欣賞我寫的下面的詩篇

6.你說你的程式叫簡單,我說我的程式碼叫詩篇
1.一想到你我就哦豁豁豁豁豁豁豁豁豁豁....哦nima個頭啊,完全不理人家受得了受不了
8.Just 簡單你和我 ,Just 簡單程式設計師
3.約了地點卻忘了見面 ,懂得寂寞才明白浩瀚
5.沉默是最大的發言權
2.總是喜歡坐在電腦前, 總是喜歡工作到很晚
7.向左走 又向右走,我們轉了好多的彎
4.你從來就不問我,你還是不是那個程式設計師

欣賞完了咩?沒錯咋們就練習如何使用緩衝流的技術把上面的詩篇歸順序,都編過號了~就是前面的1到8的編號~

分析:首先用字元輸入緩衝流創建個源,裡面放沒有排過序的文字,之後用字元輸出緩衝流創建個目標接收,排序的過程就要自己寫方法了哦,可以從每條詩詞的共同點「.」符號下手!

程式碼實現

public class BufferedTest {      public static void main(String[] args) throws IOException {          // 創建map集合,保存文本數據,鍵為序號,值為文字          HashMap<String, String> lineMap = new HashMap<>();            // 創建流對象  源          BufferedReader br = new BufferedReader(new FileReader("a.txt"));          //目標          BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));            // 讀取數據          String line  = null;          while ((line = br.readLine())!=null) {              // 解析文本              String[] split = line.split("\.");              // 保存到集合              lineMap.put(split[0],split[1]);          }          // 釋放資源          br.close();            // 遍歷map集合          for (int i = 1; i <= lineMap.size(); i++) {              String key = String.valueOf(i);              // 獲取map中文本              String value = lineMap.get(key);              // 寫出拼接文本              bw.write(key+"."+value);              // 寫出換行              bw.newLine();          }          // 釋放資源          bw.close();      }  }

運行效果

1.一想到你我就哦豁豁豁豁豁豁豁豁豁豁…哦nima個頭啊,完全不理人家受得了受不了  2.總是喜歡坐在電腦前, 總是喜歡工作到很晚  3.約了地點卻忘了見面 ,懂得寂寞才明白浩瀚  4.你從來就不問我,你還是不是那個程式設計師  5.沉默是最大的發言權  6.你說你的程式叫簡單,我說我的程式碼叫詩篇  7.向左走 又向右走,我們轉了好多的彎  8.Just 簡單你和我 ,Just 簡單程式設計師

2、轉換流【掌握】

何謂轉換流?為何由來?暫時帶著問題讓我們先來了解了解字元編碼和字符集!
在这里插入图片描述

2.1 字元編碼與解碼

眾所周知,電腦中儲存的資訊都是用二進位數表示的,而我們在螢幕上看到的數字、英文、標點符號、漢字等字元是二進位數轉換之後的結果。按照某種規則,將字元存儲到電腦中,稱為編碼 。反之,將存儲在電腦中的二進位數按照某種規則解析顯示出來,稱為解碼 。比如說,按照A規則存儲,同樣按照A規則解析,那麼就能顯示正確的文本符號。反之,按照A規則存儲,再按照B規則解析,就會導致亂碼現象。

簡單一點的說就是:

編碼:字元(能看懂的)--位元組(看不懂的)

解碼:位元組(看不懂的)-->字元(能看懂的)

程式碼解釋則是

String(byte[] bytes, String charsetName):通過指定的字符集解碼位元組數組  byte[] getBytes(String charsetName):使用指定的字符集合把字元串編碼為位元組數組    編碼:把看得懂的變成看不懂的  String -- byte[]    解碼:把看不懂的變成看得懂的  byte[] -- String
  • 字元編碼 Character Encoding: 就是一套自然語言的字元與二進位數之間的對應規則。

    而編碼表則是生活中文字和電腦中二進位的對應規則

字符集

  • 字符集 Charset:也叫編碼表。是一個系統支援的所有字元的集合,包括各國家文字、標點符號、圖形符號、數字等。

電腦要準確的存儲和識別各種字符集符號,需要進行字元編碼,一套字符集必然至少有一套字元編碼。常見字符集有ASCII字符集、GBK字符集、Unicode字符集等。
在这里插入图片描述

可見,當指定了編碼,它所對應的字符集自然就指定了,所以編碼才是我們最終要關心的。

  • ASCII字符集 :
    • ASCII(American Standard Code for Information Interchange,美國資訊交換標準程式碼)是基於拉丁字母的一套電腦編碼系統,用於顯示現代英語,主要包括控制字元(回車鍵、退格、換行鍵等)和可顯示字元(英文大小寫字元、阿拉伯數字和西文符號)。
    • 基本的ASCII字符集,使用7位(bits)表示一個字元,共128字元。ASCII的擴展字符集使用8位(bits)表示一個字元,共256字元,方便支援歐洲常用字元。
  • ISO-8859-1字符集:
    • 拉丁碼錶,別名Latin-1,用於顯示歐洲使用的語言,包括荷蘭、丹麥、德語、義大利語、西班牙語等。
    • ISO-8859-1使用單位元組編碼,兼容ASCII編碼。
  • GBxxx字符集:
    • GB就是國標的意思,是為了顯示中文而設計的一套字符集。
    • GB2312:簡體中文碼錶。一個小於127的字元的意義與原來相同。但兩個大於127的字元連在一起時,就表示一個漢字,這樣大約可以組合了包含7000多個簡體漢字,此外數學符號、羅馬希臘的字母、日文的假名們都編進去了,連在ASCII里本來就有的數字、標點、字母都統統重新編了兩個位元組長的編碼,這就是常說的"全形"字元,而原來在127號以下的那些就叫"半形"字元了。
    • GBK:最常用的中文碼錶。是在GB2312標準基礎上的擴展規範,使用了雙位元組編碼方案,共收錄了21003個漢字,完全兼容GB2312標準,同時支援繁體漢字以及日韓漢字等。
    • GB18030:最新的中文碼錶。收錄漢字70244個,採用多位元組編碼,每個字可以由1個、2個或4個位元組組成。支援中國中國少數民族的文字,同時支援繁體漢字以及日韓漢字等。
  • Unicode字符集 :
    • Unicode編碼系統為表達任意語言的任意字元而設計,是業界的一種標準,也稱為統一碼、標準萬國碼。
    • 它最多使用4個位元組的數字來表達每個字母、符號,或者文字。有三種編碼方案,UTF-8、UTF-16和UTF-32。最為常用的UTF-8編碼。
    • UTF-8編碼,可以用來表示Unicode標準中任何字元,它是電子郵件、網頁及其他存儲或傳送文字的應用中,優先採用的編碼。互聯網工程工作小組(IETF)要求所有互聯網協議都必須支援UTF-8編碼。所以,我們開發Web應用,也要使用UTF-8編碼。它使用一至四個位元組為每個字元編碼,編碼規則:
      1. 128個US-ASCII字元,只需一個位元組編碼。
      2. 拉丁文等字元,需要二個位元組編碼。
      3. 大部分常用字(含中文),使用三個位元組編碼。
      4. 其他極少使用的Unicode輔助字元,使用四位元組編碼。

2.2 編碼問題導致亂碼

在java開發工具IDEA中,使用FileReader 讀取項目中的文本文件。由於IDEA的設置,都是默認的UTF-8編碼,所以沒有任何問題。但是,當讀取Windows系統中創建的文本文件時,由於Windows系統的默認是GBK編碼,就會出現亂碼。

public class ReaderDemo {      public static void main(String[] args) throws IOException {          FileReader fileReader = new FileReader("C:\a.txt");          int read;          while ((read = fileReader.read()) != -1) {              System.out.print((char)read);          }          fileReader.close();      }  }  輸出結果:���

那麼如何讀取GBK編碼的文件呢? 這個時候就得講講轉換流了!

從另一角度來講:字元流=位元組流+編碼表

2.3 InputStreamReader類-----(位元組流到字元流的橋樑)

轉換流java.io.InputStreamReader,是Reader的子類,從字面意思可以看出它是從位元組流到字元流的橋樑。它讀取位元組,並使用指定的字符集將其解碼為字元。它的字符集可以由名稱指定,也可以接受平台的默認字符集。

構造方法

InputStreamReader(InputStream in): 創建一個使用默認字符集的字元流。
InputStreamReader(InputStream in, String charsetName): 創建一個指定字符集的字元流。

構造程式碼如下:

InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));  InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");

使用轉換流解決編碼問題

public class ReaderDemo2 {      public static void main(String[] args) throws IOException {          // 定義文件路徑,文件為gbk編碼          String FileName = "C:\A.txt";          // 創建流對象,默認UTF8編碼          InputStreamReader isr = new InputStreamReader(new FileInputStream(FileName));          // 創建流對象,指定GBK編碼          InputStreamReader isr2 = new InputStreamReader(new FileInputStream(FileName) , "GBK");          // 定義變數,保存字元          int read;          // 使用默認編碼字元流讀取,亂碼          while ((read = isr.read()) != -1) {              System.out.print((char)read); // �����ʺ          }          isr.close();            // 使用指定編碼字元流讀取,正常解析          while ((read = isr2.read()) != -1) {              System.out.print((char)read);// 哥敢摸屎          }          isr2.close();      }  }

2.4 OutputStreamWriter類-----(字元流到位元組流的橋樑)

轉換流java.io.OutputStreamWriter ,是Writer的子類,字面看容易混淆會誤以為是轉為字元流,其實不然,OutputStreamWriter為從字元流到位元組流的橋樑。使用指定的字符集將字元編碼為位元組。它的字符集可以由名稱指定,也可以接受平台的默認字符集。

構造方法

OutputStreamWriter(OutputStream in): 創建一個使用默認字符集的字元流。
OutputStreamWriter(OutputStream in, String charsetName): 創建一個指定字符集的字元流。

構造舉例,程式碼如下:

OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("a.txt"));  OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("b.txt") , "GBK");

指定編碼構造程式碼

public class OutputDemo {      public static void main(String[] args) throws IOException {          // 定義文件路徑          String FileName = "C:\s.txt";          // 創建流對象,默認UTF8編碼          OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));          // 寫出數據          osw.write("哥敢"); // 保存為6個位元組          osw.close();            // 定義文件路徑          String FileName2 = "D:\A.txt";          // 創建流對象,指定GBK編碼          OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK");          // 寫出數據          osw2.write("摸屎");// 保存為4個位元組          osw2.close();      }  }

在这里插入图片描述
為了達到最高效率,可以考慮在 BufferedReader 內包裝 InputStreamReader

BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

3、序列化流【理解】

(1)可以把對象寫入文本文件或者在網路中傳輸
(2)如何實現序列化呢?
讓被序列化的對象所屬類實現序列化介面。
該介面是一個標記介面。沒有功能需要實現。
(3)注意問題:
把數據寫到文件後,在去修改類會產生一個問題。
如何解決該問題呢?
在類文件中,給出一個固定的序列化id值。
而且,這樣也可以解決黃色警告線問題
(4)面試題:
什麼時候序列化?
如何實現序列化?
什麼是反序列化?

3.1 何謂序列化

Java 提供了一種對象序列化的機制。用一個位元組序列可以表示一個對象,該位元組序列包含該對象的數據、對象的類型和對象中存儲的屬性等資訊。位元組序列寫出到文件之後,相當於文件中持久保存了一個對象的資訊。

反之,該位元組序列還可以從文件中讀取回來,重構對象,對它進行反序列化。對象的數據、對象的類型和對象中存儲的數據資訊,都可以用來在記憶體中創建對象。看圖理解序列化:
在这里插入图片描述

3.2 ObjectOutputStream類

java.io.ObjectOutputStream 類,將Java對象的原始數據類型寫出到文件,實現對象的持久存儲。

構造方法

public ObjectOutputStream(OutputStream out): 創建一個指定OutputStream的ObjectOutputStream。

構造程式碼如下:

FileOutputStream fileOut = new FileOutputStream("aa.txt");  ObjectOutputStream out = new ObjectOutputStream(fileOut);

序列化操作

  1. 一個對象要想序列化,必須滿足兩個條件:

該類必須實現java.io.Serializable 介面,Serializable 是一個標記介面,不實現此介面的類將不會使任何狀態序列化或反序列化,會拋出NotSerializableException 。

該類的所有屬性必須是可序列化的。如果有一個屬性不需要可序列化的,則該屬性必須註明是瞬態的,使用transient 關鍵字修飾。

public class Employee implements java.io.Serializable {      public String name;      public String address;      public transient int age; // transient瞬態修飾成員,不會被序列化      public void addressCheck() {          System.out.println("Address  check : " + name + " -- " + address);      }  }

2.寫出對象方法

public final void writeObject (Object obj) : 將指定的對象寫出。

public class SerializeDemo{      public static void main(String [] args)   {          Employee e = new Employee();          e.name = "zhangsan";          e.address = "beiqinglu";          e.age = 20;          try {              // 創建序列化流對象            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.txt"));              // 寫出對象              out.writeObject(e);              // 釋放資源              out.close();              fileOut.close();              System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年齡沒有被序列化。          } catch(IOException i)   {              i.printStackTrace();          }      }  }  輸出結果:  Serialized data is saved

3.3 ObjectInputStream類

ObjectInputStream反序列化流,將之前使用ObjectOutputStream序列化的原始數據恢復為對象。

構造方法

public ObjectInputStream(InputStream in): 創建一個指定InputStream的ObjectInputStream。

反序列化操作1

如果能找到一個對象的class文件,我們可以進行反序列化操作,調用ObjectInputStream讀取對象的方法:

  • public final Object readObject () : 讀取一個對象。
public class DeserializeDemo {     public static void main(String [] args)   {          Employee e = null;          try {               // 創建反序列化流               FileInputStream fileIn = new FileInputStream("employee.txt");               ObjectInputStream in = new ObjectInputStream(fileIn);               // 讀取一個對象               e = (Employee) in.readObject();               // 釋放資源               in.close();               fileIn.close();          }catch(IOException i) {               // 捕獲其他異常               i.printStackTrace();               return;          }catch(ClassNotFoundException c)  {              // 捕獲類找不到異常               System.out.println("Employee class not found");               c.printStackTrace();               return;          }          // 無異常,直接列印輸出          System.out.println("Name: " + e.name);  // zhangsan          System.out.println("Address: " + e.address); // beiqinglu          System.out.println("age: " + e.age); // 0      }  }

對於JVM可以反序列化對象,它必須是能夠找到class文件的類。如果找不到該類的class文件,則拋出一個 ClassNotFoundException 異常。

反序列化操作2

另外,當JVM反序列化對象時,能找到class文件,但是class文件在序列化對象之後發生了修改,那麼反序列化操作也會失敗,拋出一個InvalidClassException異常。發生這個異常的原因如下:

1、該類的序列版本號與從流中讀取的類描述符的版本號不匹配
2、該類包含未知數據類型
2、該類沒有可訪問的無參數構造方法

Serializable 介面給需要序列化的類,提供了一個序列版本號。serialVersionUID 該版本號的目的在於驗證序列化的對象和對應類是否版本匹配。

public class Employee implements java.io.Serializable {       // 加入序列版本號       private static final long serialVersionUID = 1L;       public String name;       public String address;       // 添加新的屬性 ,重新編譯, 可以反序列化,該屬性賦為默認值.       public int eid;         public void addressCheck() {           System.out.println("Address  check : " + name + " -- " + address);       }  }

3.4 序列化集合練習

  1. 將存有多個自定義對象的集合序列化操作,保存到list.txt文件中。
  2. 反序列化list.txt ,並遍歷集合,列印對象資訊。

案例分析

  1. 把若干學生對象 ,保存到集合中。
  2. 把集合序列化。
  3. 反序列化讀取時,只需要讀取一次,轉換為集合類型。
  4. 遍歷集合,可以列印所有的學生資訊

案例程式碼實現

public class SerTest {      public static void main(String[] args) throws Exception {          // 創建 學生對象          Student student = new Student("老王", "laow");          Student student2 = new Student("老張", "laoz");          Student student3 = new Student("老李", "laol");            ArrayList<Student> arrayList = new ArrayList<>();          arrayList.add(student);          arrayList.add(student2);          arrayList.add(student3);          // 序列化操作          // serializ(arrayList);            // 反序列化          ObjectInputStream ois  = new ObjectInputStream(new FileInputStream("list.txt"));          // 讀取對象,強轉為ArrayList類型          ArrayList<Student> list  = (ArrayList<Student>)ois.readObject();            for (int i = 0; i < list.size(); i++ ){              Student s = list.get(i);              System.out.println(s.getName()+"--"+ s.getPwd());          }      }        private static void serializ(ArrayList<Student> arrayList) throws Exception {          // 創建 序列化流          ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("list.txt"));          // 寫出對象          oos.writeObject(arrayList);          // 釋放資源          oos.close();      }  }

4、列印流【掌握】

4.1 何謂列印流

平時我們在控制台列印輸出,是調用print方法和println方法完成的,各位用了這麼久的輸出語句肯定沒想過這兩個方法都來自於java.io.PrintStream類吧,哈哈。該類能夠方便地列印各種數據類型的值,是一種便捷的輸出方式。

列印流分類:

位元組列印流PrintStream,字元列印流PrintWriter

列印流特點:

A:只操作目的地,不操作數據源
B:可以操作任意類型的數據
C:如果啟用了自動刷新,在調用println()方法的時候,能夠換行並刷新
D:可以直接操作文件

這個時候有同學就要問了,哪些流可以直接操作文件呢?答案很簡單,如果該流的構造方法能夠同時接收File和String類型的參數,一般都是可以直接操作文件的!

PrintStream是OutputStream的子類,PrintWriter是Writer的子類,兩者處於對等的位置上,所以它們的API是非常相似的。二者區別無非一個是位元組列印流,一個是字元列印流。

4.2 位元組輸出列印流PrintStream複製文本文件

import java.io.BufferedReader;  import java.io.FileReader;  import java.io.IOException;  import java.io.PrintStream;    public class PrintStreamDemo {      public static void main(String[] args) throws IOException {          BufferedReader br=new BufferedReader(new FileReader("copy.txt"));          PrintStream ps=new PrintStream("printcopy.txt");          String line;          while((line=br.readLine())!=null) {              ps.println(line);          }          br.close();          ps.close();      }  }

4.3 字元輸出列印流PrintWriter複製文本文件

import java.io.BufferedReader;  import java.io.FileReader;  import java.io.FileWriter;  import java.io.IOException;  import java.io.PrintWriter;  /**   * 使用列印流複製文本文件   */  public class PrintWriterDemo {      public static void main(String[] args) throws IOException {          BufferedReader br=new BufferedReader(new FileReader("aa.txt"));          PrintWriter pw=new PrintWriter("printcopyaa.txt");          String line;          while((line=br.readLine())!=null) {              pw.println(line);          }          br.close();          pw.close();      }  }

5、Properties屬性類

我想各位對這個Properties類多多少少也接觸過了,首先Properties類並不在IO包下,那為啥要和IO流一起講呢?原因很簡單因為properties類經常和io流的聯合一起使用。

(1)是一個集合類,Hashtable的子類
(2)特有功能
A:public Object setProperty(String key,String value)
B:public String getProperty(String key)
C:public Set stringPropertyNames()
(3)和IO流結合的方法
把鍵值對形式的文本文件內容載入到集合中
public void load(Reader reader)
public void load(InputStream inStream)
把集合中的數據存儲到文本文件中
public void store(Writer writer,String comments)
public void store(OutputStream out,String comments)

5.1 Properties概述

java.util.Properties 繼承於Hashtable ,來表示一個持久的屬性集。它使用鍵值結構存儲數據,每個鍵及其對應值都是一個字元串。該類也被許多Java類使用,比如獲取系統屬性時,System.getProperties 方法就是返回一個Properties對象。

5.2 Properties類

構造方法

public Properties() :創建一個空的屬性列表。

基本的存儲方法

  • public Object setProperty(String key, String value) : 保存一對屬性。
  • public String getProperty(String key) :使用此屬性列表中指定的鍵搜索屬性值。
  • public Set<String> stringPropertyNames() :所有鍵的名稱的集合。
public class ProDemo {      public static void main(String[] args) throws FileNotFoundException {          // 創建屬性集對象          Properties properties = new Properties();          // 添加鍵值對元素          properties.setProperty("filename", "a.txt");          properties.setProperty("length", "209385038");          properties.setProperty("location", "D:\a.txt");          // 列印屬性集對象          System.out.println(properties);          // 通過鍵,獲取屬性值          System.out.println(properties.getProperty("filename"));          System.out.println(properties.getProperty("length"));          System.out.println(properties.getProperty("location"));            // 遍歷屬性集,獲取所有鍵的集合          Set<String> strings = properties.stringPropertyNames();          // 列印鍵值對          for (String key : strings ) {              System.out.println(key+" -- "+properties.getProperty(key));          }      }  }  輸出結果:  {filename=a.txt, length=209385038, location=D:a.txt}  a.txt  209385038  D:a.txt  filename -- a.txt  length -- 209385038  location -- D:a.txt

與流相關的方法

public void load(InputStream inStream): 從位元組輸入流中讀取鍵值對。

參數中使用了位元組輸入流,通過流對象,可以關聯到某文件上,這樣就能夠載入文本中的數據了。現在文本數據格式如下:

filename=Properties.txt  length=123  location=C:Properties.txt

載入程式碼演示:

public class ProDemo {      public static void main(String[] args) throws FileNotFoundException {          // 創建屬性集對象          Properties pro = new Properties();          // 載入文本中資訊到屬性集          pro.load(new FileInputStream("Properties.txt"));          // 遍歷集合併列印          Set<String> strings = pro.stringPropertyNames();          for (String key : strings ) {              System.out.println(key+" -- "+pro.getProperty(key));          }       }  }  輸出結果:  filename -- Properties.txt  length -- 123  location -- C:Properties.txt

文本中的數據,必須是鍵值對形式,可以使用空格、等號、冒號等符號分隔。

怎麼說呢,io流的基礎回顧就先告一段落了,淺嘗輒止。循序漸進,實踐中慢慢總結!更何況我還很low,依舊任重而道遠。

現在jdk已經出到13了,io流也有了許多的變化。有時間會從頭整理一下,一定會有機會的!

最後,歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術...

在这里插入图片描述

Tags: