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

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

在博主認爲真正懂IO流的優秀程序員每次在使用IO流以前都會明確分析以下四點:html

(1)明確要操做的數據是數據源仍是數據目的(也就是要讀仍是要寫) (2)明確要操做的設備上的數據是字節仍是文本 (3)明確數據所在的具體設備 (4)明確是否須要額外功能(好比是否須要轉換流、高效流等)java

以上四點將會在文章告白IO流的四點明確裏面小結一下,若是各位真能熟練以上四點,我以爲這篇文章你就不必看了,由於你已經把IO玩弄與股掌之中,萬物皆可被你盤也就也再也不話下了。程序員

@[toc]面試

一、告白IO流的四點明確

(1)明確要操做的數據是數據源仍是數據目的(要讀仍是要寫)編程

    

 源: <font color=red> InputStream  Reader</font>windows

目的: <font color=red> OutputStream  Writer</font>設計模式

(2)明確要操做的設備上的數據是字節仍是文本數組

     

 源:緩存

          字節:<font color=red> InputStream</font>

          文本:<font color=red> Reader</font>

      目的:

          字節:<font color=red> OutputStream</font>

          文本:<font color=red> Writer</font>

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

   

   源設備:

        硬盤:文件 File開頭

        內存:數組,字符串

        鍵盤:System.in

        網絡:Socket

      對應目的設備:

        硬盤:文件 File開頭

        內存:數組,字符串

        屏幕:System.out

        網絡:Socket

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

  

  須要轉換——<font color=red> 轉換流 InputStreamReader 、OutputStreamWriter</font>

    須要高效——<font color=red> 緩衝流Bufferedxxx</font>

    多個源—— 序列流 SequenceInputStream

    對象序列化—— ObjectInputStream、ObjectOutputStream

    保證數據的輸出形式——<font color=red> 打印流PrintStream 、Printwriter</font>

    操做基本數據,保證字節原樣性——DataOutputStream、DataInputStream

到這裏,咱們再來看看IO流的分類吧 在這裏插入圖片描述 OK,準備好了告白IO流了咩?

二、File類

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

1.1 File概述

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

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

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

1.2 構造方法

在java中,一切皆是對象,File類也不例外,不管是哪一個對象都應該從該對象的構造提及,因此博主來分析分析File類的構造方法。首先從API開始着手 在這裏插入圖片描述 咱們主要來學習一下比較經常使用的三個:

一、 public File(String pathname) :經過將給定的路徑名字符串轉換爲抽象路徑名來建立新的 File實例。
二、 public File(String parent, String child) :從父路徑名字符串和子路徑名字符串建立新的 File實例。 三、 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 獲取功能的方法

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

二、public String getPath() :將此File轉換爲路徑名字符串。

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

四、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:\aaa\bbb.java
文件構造路徑:d:\aaa\bbb.java
文件名稱:bbb.java
文件長度:2116字節

目錄絕對路徑:d:\aaa
目錄構造路徑:d:\aaa
目錄名稱:aaa
目錄長度:3236

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

1.3.2 絕對路徑和相對路徑

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

一、<font color=red>路徑是不區分大小寫</font> 二、路徑中的文件名稱分隔符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:\java\bbb.java

1.3.3判斷功能的方法

一、 public boolean exists() :此File表示的文件或目錄是否實際存在。 二、 public boolean isDirectory() :此File表示的是否爲目錄。 三、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:\aaa\bbb.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,因此<font color=red>開發中通常用mkdirs()</font>;

這些方法中值得注意的是<font color=red>createNewFile</font>方法以及<font color=red>mkdir</font>與<font color=red>mkdirs</font>的區別

方法測試,代碼以下:

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);
        }
    }
}

在這裏插入圖片描述 <font color=red>listFiles</font>在獲取指定目錄下的文件或者文件夾時必須知足下面兩個條件

1,<font color=red>指定的目錄必須存在</font>

2,<font color=red>指定的必須是目錄。不然容易引起返回數組爲null,出現NullPointerException異常</font>

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){
        //一、判斷傳入的是不是目錄
        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());
            }

        }
    }
}

若是對上面的代碼有疑問,能夠隨時聯繫我,博主一直都在!

三、初探IO流

1.1 什麼是IO

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

咱們把這種數據的傳輸,能夠看作是一種數據的流動,按照流動的方向,之內存爲基準,分爲輸入input輸出output ,即流向內存是輸入流,流出內存的輸出流。

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

1.2 IO的分類

根據數據的流向分爲:<font color=red>輸入流</font> 和 <font color=red>輸出流</font>。

  • 輸入流 :把數據從其餘設備上讀取到內存中的流。
  • 輸出流 :把數據從內存 中寫出到其餘設備上的流。

根據數據的類型分爲:<font color=red>字節流</font> 和 <font color=red>字符流</font>。

  • 字節流 :以字節爲單位,讀寫數據的流。
  • 字符流 :以字符爲單位,讀寫數據的流。

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

: <font color=red>由這四個類的子類名稱基本都是以其父類名做爲子類名的後綴</font>。 如:InputStream的子類FileInputStream。 如:Reader的子類FileReader。 在這裏插入圖片描述

1.3 關於IO的分流向說明

啥都不說了,看圖吧 在這裏插入圖片描述 在這裏插入圖片描述

字節流OutputStream與InputStream的故事

OutputStream與InputStream的繼承關係 在這裏插入圖片描述

2.1 文件的世界裏一切皆爲字節

咱們必須明確一點的是,一切文件數據(文本、圖片、視頻等)在存儲時,都是以二進制數字的形式保存,都一個一個的字節,那麼傳輸時同樣如此。因此,字節流能夠傳輸任意文件數據。在操做流的時候,咱們要時刻明確,不管使用什麼樣的流對象,底層傳輸的始終爲二進制數據。

2.2 字節輸出流(OutputStream)

java.io.OutputStream 抽象類是表示字節輸出流的全部類的超類(父類),將指定的字節信息寫出到目的地。它定義了字節輸出流的基本共性功能方法,不要問我OutputStream爲啥能夠定義字節輸出流的基本共性功能方法,熊dei啊,上一句說過了<font color=red>OutputStream是字節輸出流的全部類的超類</font>,繼承知識,懂?(若是是真的不理解的小白同窗,能夠點擊藍色字體繼承進入補習)

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

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

<font color=red>以上五個方法則是字節輸出流都具備的方法,由父類OutputStream定義提供,子類都會共享以上方法</font>

FileOutputStream類

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

FileOutputStream構造方法

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

一、 public FileOutputStream(File file):根據<font color=red>File對象</font>爲參數建立對象。 </font> 二、 public FileOutputStream(String name): 根據<font color=red>名稱字符串</font>爲參數建立對象。</font>

<font color=red>推薦第二種構造方法</font>【開發經常使用】:

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

就以上面這句代碼來說,相似這樣建立字節輸出流對象都作了<font color=red>三件事情</font>: 一、調用系統功能去建立文件【輸出流對象纔會自動建立】 二、建立outputStream對象 三、把foutputStream對象指向這個文件

<font color=red>注意</font>: 建立輸出流對象的時候,系統會自動去對應位置建立對應文件,而建立輸出流對象的時候,文件不存在則會報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的另外兩個構造方法了,以下:

一、public FileOutputStream(File file, boolean append)

二、public FileOutputStream(String name, boolean append)

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

實現數據追加續寫代碼以下:

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系統裏,換行符號是\r\n ,具體代碼以下:

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("\r\n".getBytes());
        }
      	// 關閉資源
        fos.close();
    }
}

輸出結果:
a
b
c
d
e
  • 回車符\r和換行符\n
    • 回車符:回到一行的開頭(return)。
    • 換行符:下一行(newline)。
  • 系統中的換行:
    • Windows系統裏,每行結尾是 回車+換行 ,即\r\n
    • Unix系統裏,每行結尾只有 換行 ,即\n
    • Mac系統裏,每行結尾是 回車 ,即\r。從 Mac OS X開始與Linux統一。

2.3 字節輸入流(InputStream)

java.io.InputStream 抽象類是表示字節輸入流的全部類的超類(父類),能夠讀取字節信息到內存中。它定義了字節輸入流的基本共性功能方法。

字節輸入流的基本共性功能方法:

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

三、 public int read(byte[] b): 該方法返回的int值表明的是讀取了多少個字節,讀到幾個返回幾個,讀取不到返回-1

FileInputStream類

java.io.FileInputStream 類是文件輸入流,從文件中讀取字節。

FileInputStream的構造方法

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

一樣的,<font color=red>推薦使用第二種構造方法</font>:

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();
    }
}

複製文本、圖片、mp三、視頻等的方式同樣

到這裏,已經從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就有了字符流,字符爲單位讀寫數據,字符流專門用於處理文本文件。若是處理純文本的數據優先考慮字符流,其餘狀況就只能用字節流了(圖片、視頻、等等只文本例外)。

從另外一角度來講:字符流 = 字節流 + 編碼表

一、 字符輸入流(Reader)

java.io.Reader抽象類是字符輸入流的全部類的超類(父類),能夠讀取字符信息到內存中。它定義了字符輸入流的基本共性功能方法。

字符輸入流的共性方法

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

FileReader類

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

構造方法

一、FileReader(File file): 建立一個新的 FileReader ,給定要讀取的File對象
二、 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();
    }
}

至於讀取的寫法相似字節流的寫法,只是讀取單位不一樣罷了。

二、字符輸出流(Writer)

java.io.Writer 抽象類是字符輸出流的全部類的超類(父類),將指定的字符信息寫出到目的地。它一樣定義了字符輸出流的基本共性功能方法。

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

一、void write(int c) 寫入單個字符。 二、void write(char[] cbuf) 寫入字符數組。 三、 abstract void write(char[] cbuf, int off, int len) 寫入字符數組的某一部分,off數組的開始索引,len寫的字符個數。 四、 void write(String str) 寫入字符串。 五、void write(String str, int off, int len) 寫入字符串的某一部分,off字符串的開始索引,len寫的字符個數。 六、void flush() 刷新該流的緩衝。
七、void close() 關閉此流,但要先刷新它。

FileWriter類

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

構造方法

一、 FileWriter(File file): 建立一個新的 FileWriter,給定要讀取的File對象。
二、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

<font color=red>【注意】關閉資源時,與FileOutputStream不一樣。 若是不關閉,數據只是保存到緩衝區,並未保存到文件。</font>

關閉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啊,我在上面就用紅色字體特別標註過了,就是這句話: <font color=red>【注意】關閉資源時,與FileOutputStream不一樣。 若是不關閉,數據只是保存到緩衝區,並未保存到文件。</font>這個時候反應過來了吧,可見實踐例子的重要性,<font color=red>編程就是這樣,不去敲,永遠學不會</font>!!!因此必定要去敲,博主沒敲過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("\r\n");
      	// 寫出字符串
  		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");
        /*建立輸出流作的工做:
         *      一、調用系統資源建立了一個文件
         *      二、建立輸出流對象
         *      三、把輸出流對象指向文件        
         * */
        //文本文件複製,一次讀一個字符
        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 簡要概述

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

<font color=red>緩衝流的基本原理</font>:

一、使用了底層流對象從具體設備上獲取數據,並將數據存儲到緩衝區的數組內。 二、經過緩衝區的read()方法從緩衝區獲取具體的字符數據,這樣就提升了效率。 三、若是用read方法讀取字符數據,並存儲到另外一個容器中,直到讀取到了換行符時,將另外一個容器臨時存儲的數據轉成字符串返回,就造成了readLine()功能。

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

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

  • 字節緩衝流BufferedInputStreamBufferedOutputStream
  • 字符緩衝流BufferedReaderBufferedWriter

1.2 字節緩衝流

構造方法

  • public BufferedInputStream(InputStream in) :建立一個新的緩衝輸入流,注意參數類型爲<font color=red>InputStream</font>。
  • public BufferedOutputStream(OutputStream out): 建立一個新的緩衝輸出流,注意參數類型爲<font color=red>OutputStream</font>。

構造舉例代碼以下:

//構造方式一: 建立字節緩衝輸入流【可是開發中通常經常使用下面的格式申明】
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) :建立一個新的緩衝輸入流,注意參數類型爲<font color=red>Reader</font>。
  • public BufferedWriter(Writer out): 建立一個新的緩衝輸出流,注意參數類型爲<font color=red>Writer</font>。

構造舉例,代碼以下:

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

字符緩衝流特有方法

字符緩衝流的基本方法與普通字符流調用方式一致,這裏再也不闡述,咱們來看字符緩衝流具有的<font color=red>特有</font>方法。

  • BufferedReader:public String readLine(): 讀一行數據。 <font color=red>讀取到最後返回null</font>
  • 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.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-八、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();
    }
}

在這裏插入圖片描述 爲了達到<font color=red>最高效率</font>,能夠考慮在 BufferedReader 內包裝 InputStreamReader

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

三、序列化流【理解】

(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異常。發生這個異常的緣由以下:

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

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.1 何謂打印流

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

打印流分類

字節打印流PrintStream,字符打印流PrintWriter

打印流特色

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

這個時候有同窗就要問了,哪些流能夠直接操做文件呢?答案很簡單,<font color=red>若是該流的構造方法可以同時接收File和String類型的參數,通常都是能夠直接操做文件的</font>!

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();
    }
}

五、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<String> 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流也有了許多的變化。有時間會從頭整理一下,必定會有機會的!

最後,歡迎各位關注個人公衆號,一塊兒探討技術,嚮往技術,追求技術...

在這裏插入圖片描述

原文出處:https://www.cnblogs.com/yichunguo/p/11775270.html

相關文章
相關標籤/搜索