Java IO流詳解


參考轉載地址:https://blog.csdn.net/zhangliangzi/article/details/51226652

       http://www.cnblogs.com/ysocean/p/6870069.html

       https://www.cnblogs.com/jmsjh/p/7761272.html

       http://blog.csdn.net/hguisu/article/details/7418161

1.什么是IO

 Java中I/O操作主要是指使用Java進行輸入,輸出操作. Java所有的I/O機制都是基於數據流進行輸入輸出,這些數據流表示了字符或者字節數據的流動序列。Java的I/O流提供了讀寫數據的標准方法。任何Java中表示數據源的對象都會提供以數據流的方式讀寫它的數據的方法。 

IO又分為流IO(java.io)和塊IO(java.nio)

Java.io是大多數面向數據流的輸入/輸出類的主要軟件包。此外,Java也對塊傳輸提供支持,在核心庫 java.nio中采用的便是塊IO。

流IO的好處是簡單易用,缺點是效率較低。塊IO效率很高,但編程比較復雜。

這里先講流IO。


2.流的基本概念

在電腦上的數據有三種存儲方式,一種是外存,一種是內存,一種是緩存。比如電腦上的硬盤,磁盤,U盤等都是外存,在電腦上有內存條,緩存是在CPU里面的。外存的存儲量最大,其次是內存,最后是緩存,但是外存的數據的讀取最慢,其次是內存,緩存最快。這里總結從外存讀取數據到內存以及將數據從內存寫到外存中。對於內存和外存的理解,我們可以簡單的理解為容器,即外存是一個容器,內存又是另外一個容器。那又怎樣把放在外存這個容器內的數據讀取到內存這個容器以及怎么把內存這個容器里的數據存到外存中呢?

     在Java類庫中,IO部分的內容是很龐大的,因為它涉及的領域很廣泛:

         標准輸入輸出,文件的操作,網絡上的數據流,字符串流,對象流,zip文件流等等,java中將輸入輸出抽象稱為流,就好像水管,將兩個容器連接起來。將數據從外存中讀取到內存中的稱為輸入流,將數據從內存寫入外存中的稱為輸出流。

我的理解是:從eclipse輸出到文本文件txt中叫輸出流,而從文本文件txt輸入到eclipse叫作輸入流。(所有此時文本文件txt對應是外存,而eclipse對應是內存,可能不太准確但是便於我自己的理解,有問題請指點)。

流的分類:

一、根據流向分為輸入流和輸出流:

  注意輸入流和輸出流是相對於程序而言的。

  輸出:把程序(內存)中的內容輸出到磁盤、光盤等存儲設備中
    

 

     輸入:讀取外部數據(磁盤、光盤等存儲設備的數據)到程序(內存)中
    

  綜合起來:

   

 

二、根據傳輸數據單位分為字節流和字符流

  

  上面的也是 Java IO流中的四大基流。這四大基流都是抽象類,其他流都是繼承於這四大基流的。 

1) 字節流:數據流中最小的數據單元是字節 
2)  字符流:數據流中最小的數據單元是字符, Java中的字符是Unicode編碼,一個字符占用兩個字節( 無論中文還是英文都是兩個字節)。

三、根據功能分為節點流和包裝流

  節點流:可以從或向一個特定的地方(節點)讀寫數據,直接連接數據源。如最常見的是文件的FileReader,還可以是數組、管道、字符串,關鍵字分別為ByteArray/CharArray,Piped,String。.

  處理流(包裝流):並不直接連接數據源,是對一個已存在的流的連接和封裝,是一種典型的裝飾器設計模式,使用處理流主要是為了更方便的執行輸入輸出工作,如PrintStream,輸出功能很強大,又如BufferedReader提供緩存機制,推薦輸出時都使用處理流包裝。

      一個流對象經過其他流的多次包裝,稱為流的鏈接。

 注意:一個IO流可以即是輸入流又是字節流又或是以其他方式分類的流類型,是不沖突的。比如FileInputStream,它既是輸入流又是字節流還是文件節點流。

四、一些特別的的流類型

  轉換流:轉換流只有字節流轉換為字符流,因為字符流使用起來更方便,我們只會向更方便使用的方向轉化。如:InputStreamReader與OutputStreamWriter。

  緩沖流:有關鍵字Buffered,也是一種處理流,為其包裝的流增加了緩存功能,提高了輸入輸出的效率,增加緩沖功能后需要使用flush()才能將緩沖區中內容寫入到實際的物理節點。但是,在現在版本的Java中,只需記得關閉輸出流(調用close()方法),就會自動執行輸出流的flush()方法,可以保證將緩沖區中內容寫入。

  對象流:有關鍵字Object,主要用於將目標對象保存到磁盤中或允許在網絡中直接傳輸對象時使用(對象序列化),具體可參看博客Java序列化與反序列化

 操作 IO 流的模板:

  ①、創建源或目標對象

    輸入:把文件中的數據流向到程序中,此時文件是 源,程序是目標

    輸出:把程序中的數據流向到文件中,此時文件是目標,程序是源

 

  ②、創建 IO 流對象

    輸入:創建輸入流對象

    輸出:創建輸出流對象

 

  ③、具體的 IO 操作

 

  ④、關閉資源

    輸入:輸入流的 close() 方法

    輸出:輸出流的 close() 方法

 

 

注意:1、程序中打開的文件 IO 資源不屬於內存里的資源,垃圾回收機制無法回收該資源。如果不關閉該資源,那么磁盤的文件將一直被程序引用着,不能刪除也不能更改。所以應該手動調用 close() 方法關閉流資源

 Java IO 流的整體架構圖:

 

 


3. 標准I/O

  Java程序可通過命令行參數與外界進行簡短的信息交換,同時,也規定了與標准輸入、輸出設備,如鍵盤、顯示器進行信息交換的方式。而通過文件可以與外界進行任意數據形式的信息交換。

1. 命令行參數

public class TestArgs {  
    public static void main(String[] args) {  
        for (int i = 0; i < args.length; i++) {  
            System.out.println("args[" + i + "] is <" + args[i] + ">");  
        }  
    }  
}  
運行命令:java Java C VB

運行結果:

args[0] is <Java>

args[1] is <C>

args[2] is <VB>

2. 標准輸入,輸出數據流

java系統自帶的標准數據流:java.lang.System:

java.lang.System   
public final class System  extends Object{   
   static  PrintStream  err;//標准錯誤流(輸出)  
   static  InputStream  in;//標准輸入(鍵盤輸入流)  
   static  PrintStream  out;//標准輸出流(顯示器輸出流)  
}  

注意:
(1)System類不能創建對象,只能直接使用它的三個靜態成員。
(2)每當main方法被執行時,就自動生成上述三個對象。

1) 標准輸出流 System.out

   System.out向標准輸出設備輸出數據,其數據類型為PrintStream。方法:

 

      Void print(參數)
      Void println(參數)

 

2)標准輸入流 System.in

    System.in讀取標准輸入設備數據(從標准輸入獲取數據,一般是鍵盤),其數 據類型為InputStream。方法:

 

        int read()  //返回ASCII碼。若,返回值=-1,說明沒有讀取到任何字節讀取工作結束。
         int read(byte[] b)//讀入多個字節到緩沖區b中返回值是讀入的字節數
例如:
import java.io.*;  
public class StandardInputOutput {  
    public static void main(String args[]) {  
        int b;  
        try {  
            System.out.println("please Input:");  
            while ((b = System.in.read()) != -1) {  
                System.out.print((char) b);  
            }  
        } catch (IOException e) {  
            System.out.println(e.toString());  
        }  
    }  
}  
等待鍵盤輸入,鍵盤輸入什么,就打印出什么:

 

3)標准錯誤流

   System.err輸出標准錯誤,其數據類型為PrintStream。可查閱API獲得詳細說明。

 

    標准輸出通過System.out調用println方法輸出參數並換行,而print方法輸出參數但不換行。println或print方法都通 過重載實現了輸出基本數據類型的多個方法,包括輸出參數類型為boolean、char、int、long、float和double。同時,也重載實現 了輸出參數類型為char[]、String和Object的方法。其中,print(Object)和println(Object)方法在運行時將調 用參數Object的toString方法。

import java.io.BufferedReader;  
import java.io.IOException;  
import java.io.InputStreamReader;  
  
public class StandardInputOutput {  
    public static void main(String args[]) {  
        String s;  
        // 創建緩沖區閱讀器從鍵盤逐行讀入數據  
        InputStreamReader ir = new InputStreamReader(System.in);  
        BufferedReader in = new BufferedReader(ir);  
        System.out.println("Unix系統: ctrl-d 或 ctrl-c 退出"  
                + "\nWindows系統: ctrl-z 退出");  
        try {  
            // 讀一行數據,並標准輸出至顯示器  
            s = in.readLine();  
            // readLine()方法運行時若發生I/O錯誤,將拋出IOException異常  
            while (s != null) {  
                System.out.println("Read: " + s);  
                s = in.readLine();  
            }  
            // 關閉緩沖閱讀器  
            in.close();  
        } catch (IOException e) { // Catch any IO exceptions.  
            e.printStackTrace();  
        }  
    }  
}  

4.File類

在Java語言的java.io包中,由File類提供了描述文件和目錄的操作與管理方法。但File類不是InputStream、OutputStream或Reader、Writer的子類,因為它不負責數據的輸入輸出,而專門用來管理磁盤文件與目錄。(與之類似還有socket)

File 類:文件和目錄路徑名的抽象表示。

注意:File 類只能操作文件的屬性,文件的內容是不能操作的。

 

1、File 類的字段

 

 

 

  我們知道,各個平台之間的路徑分隔符是不一樣的。

  ①、對於UNIX平台,絕對路徑名的前綴始終為"/" 。 相對路徑名沒有前綴。 表示根目錄的抽象路徑名具有前綴"/"和空名稱序列。

  ②、對於Microsoft Windows平台,包含驅動器說明符的路徑名的前綴由后面跟着":"的驅動器號組成,如果路徑名是絕對的,則可能后跟"\\" 。 UNC路徑名的前綴為"\\\\" ; 主機名和共享名稱是名稱序列中的前兩個名稱              沒有有指定驅動器的相對路徑名沒有前綴。

  那么為了屏蔽各個平台之間的分隔符差異,我們在構造 File 類的時候(如何構造,請看下面第二點),就可以使用上述 Java 為我們提供的字段。

System.out.println(File.separator);//輸出 \  
System.out.println(File.pathSeparator);//輸出 ;

File.pathSeparator指的是分隔連續多個路徑字符串的分隔符,例如:
java   -cp   test.jar;abc.jar   HelloWorld
就是指“;”

File.separator才是用來分隔同一個路徑字符串中的目錄的,例如:
C:\Program Files\Common Files
就是指“\”

2、File 類的構造方法

如何使用上述構造方法,請看如下例子:

定義文件路徑時,可以用“/”或者“\\”。

在創建一個文件時,如果目錄下有同名文件將被覆蓋。

//不使用 Java 提供的分隔符字段,注意:這樣寫只能在 Windows 平台有效
        File f1 = new File("D:\\IO\\a.txt");或者是D:/IO/a.txt
        //使用 Java 提供的分隔符
        File f2 = new File("D:"+File.separator+"IO"+File.separator+"a.txt");
        System.out.println(f1);//輸出 D:\IO\a.txt  
        System.out.println(f2);//輸出 D:\IO\a.txt
         
        //File(File parent, String child)
        //從父抽象路徑名和子路徑名字符串創建新的 File實例。
        File f3 = new File("D:");
        File f4 = new File(f3,"IO");
        System.out.println(f4); //D:\IO
         
        //File(String pathname)
        //通過將給定的路徑名字符串轉換為抽象路徑名來創建新的 File實例。
        File f5 = new File("D:"+File.separator+"IO"+File.separator+"a.txt");
        System.out.println(f5); //D:\IO\a.txt
         
        //File(String parent, String child)
        //從父路徑名字符串和子路徑名字符串創建新的 File實例。
        File f6 = new File("D:","IO\\a.txt");
        System.out.println(f6); //D:\IO\a.txt

3、File 類的常用方法

  ①、創建方法

    1.boolean createNewFile() 不存在返回true 存在返回false
    2.boolean mkdir() 創建目錄,如果上一級目錄不存在,則會創建失敗
    3.boolean mkdirs() 創建多級目錄,如果上一級目錄不存在也會自動創建

  ②、刪除方法

    1.boolean delete() 刪除文件或目錄,如果表示目錄,則目錄下必須為空才能刪除
    2.boolean deleteOnExit() 文件使用完成后刪除

  ③、判斷方法

    1.boolean canExecute()判斷文件是否可執行
    2.boolean canRead()判斷文件是否可讀
    3.boolean canWrite() 判斷文件是否可寫
    4.boolean exists() 判斷文件或目錄是否存在
    5.boolean isDirectory()  判斷此路徑是否為一個目錄
    6.boolean isFile()  判斷是否為一個文件
    7.boolean isHidden()  判斷是否為隱藏文件
    8.boolean isAbsolute()判斷是否是絕對路徑 文件不存在也能判斷

   ④、獲取方法

    1.String getName() 獲取此路徑表示的文件或目錄名稱
    2.String getPath() 將此路徑名轉換為路徑名字符串
    3.String getAbsolutePath() 返回此抽象路徑名的絕對形式
    4.String getParent()//如果沒有父目錄返回null
    5.long lastModified()//獲取最后一次修改的時間
    6.long length() 返回由此抽象路徑名表示的文件的長度。
    7.boolean renameTo(File f) 重命名由此抽象路徑名表示的文件。
    8.File[] liseRoots()//獲取機器盤符
    9.String[] list()  返回一個字符串數組,命名由此抽象路徑名表示的目錄中的文件和目錄。
    10.String[] list(FilenameFilter filter) 返回一個字符串數組,命名由此抽象路徑名表示的目錄中滿足指定過濾器的文件和目錄。

//File(File parent, String child)
        //從父抽象路徑名和子路徑名字符串創建新的 File實例。
        File dir = new File("D:"+File.separator+"IO");
        File file = new File(dir,"a.txt");
         
        //判斷dir 是否存在且表示一個目錄
        if(!(dir.exists()||dir.isDirectory())){
            //如果 dir 不存在,則創建這個目錄
            dir.mkdirs();
            //根據目錄和文件名,創建 a.txt文件
            file.createNewFile();
 
        }
        //返回由此抽象路徑名表示的文件或目錄的名稱。 這只是路徑名稱序列中的最后一個名字。 如果路徑名的名稱序列為空,則返回空字符串。
        System.out.println(file.getName()); //a.txt
        //返回此抽象路徑名的父null的路徑名字符串,如果此路徑名未命名為父目錄,則返回null。
        System.out.println(file.getParent());//D:\IO
        //將此抽象路徑名轉換為路徑名字符串。 結果字符串使用default name-separator character以名稱順序分隔名稱。
        System.out.println(file.getPath()); //D:\IO\a.txt

4、File 的一些技巧

  ①、打印給定目錄下的所有文件夾和文件夾里面的內容 

public static void getFileList(File file){
        //第一級子目錄
        File[] files = file.listFiles();
        for(File f:files){
            //打印目錄和文件
            System.out.println(f);
            if(f.isDirectory()){
                getFileList(f);
            }
        }
    }
public static void main(String[] args) throws Exception {
     File f = new File("D:"+File.separator+"WebStormFile");
       getFileList(f);
}

 5.字節流InputStream/OutputStream

 

字節輸入輸出流:InputStream、OutputSteam(下圖紅色長方形框內),紅色橢圓框內是其典型實現(FileInputSteam、FileOutStream)

  

1、字節輸入流:InputStream

public abstract class InputStream
  extends Object
  implements Closeable

 

這個抽象類是表示輸入字節流的所有類的超類。

  方法摘要:

  

  下面我們用 字節輸出流 InputStream 的典型實現 FileInputStream 來介紹:

//1、創建目標對象,輸入流表示那個文件的數據保存到程序中。不寫盤符,默認該文件是在該項目的根目錄下
            //a.txt 保存的文件內容為:AAaBCDEF
        File target = new File("io"+File.separator+"a.txt");
        //2、創建輸入流對象
        InputStream in = new FileInputStream(target);
        //3、具體的 IO 操作(讀取 a.txt 文件中的數據到程序中)
            /**
             * 注意:讀取文件中的數據,讀到最后沒有數據時,返回-1
             *  int read():讀取一個字節,返回讀取的字節
             *  int read(byte[] b):讀取多個字節,並保存到數組 b 中,從數組 b 的索引為 0 的位置開始存儲,返回讀取了幾個字節
             *  int read(byte[] b,int off,int len):讀取多個字節,並存儲到數組 b 中,從數組b 的索引為 0 的位置開始,長度為len個字節
             */
        //int read():讀取一個字節,返回讀取的字節
        int data1 = in.read();//獲取 a.txt 文件中的數據的第一個字節
        System.out.println((char)data1); //A
        //int read(byte[] b):讀取多個字節保存到數組b 中
        byte[] buffer  = new byte[10];
        in.read(buffer);//獲取 a.txt 文件中的前10 個字節,並存儲到 buffer 數組中
        System.out.println(Arrays.toString(buffer)); //[65, 97, 66, 67, 68, 69, 70, 0, 0, 0]
        System.out.println(new String(buffer)); //AaBCDEF[][][]
         
        //int read(byte[] b,int off,int len):讀取多個字節,並存儲到數組 b 中,從索引 off 開始到 len
        in.read(buffer, 0, 3);
        System.out.println(Arrays.toString(buffer)); //[65, 97, 66, 0, 0, 0, 0, 0, 0, 0]
        System.out.println(new String(buffer)); //AaB[][][][][][][]
        //4、關閉流資源
        in.close();
 

 2、字節輸出流:OutputStream

public abstract class OutputStream 
                extends Object
            implements Closeable, Flushable

這個抽象類是表示字節輸出流的所有類的超類。繼承自InputStream  的流都是向程序中輸入數據的,且數據單位為字節(8bit)。 輸出流接收輸出字節並將其發送到某個接收器。

  方法摘要:

  

 

  下面我們用 字節輸出流 OutputStream 的典型實現 FileOutputStream 來介紹:

//1、創建目標對象,輸出流表示把數據保存到哪個文件。不寫盤符,默認該文件是在該項目的根目錄下
        File target = new File("io"+File.separator+"a.txt");
        //2、創建文件的字節輸出流對象,第二個參數是 Boolean 類型,true 表示后面寫入的文件追加到數據后面,false 表示覆蓋
        OutputStream out = new FileOutputStream(target,true);
        //3、具體的 IO 操作(將數據寫入到文件 a.txt 中)
            /**
             * void write(int b):把一個字節寫入到文件中
             * void write(byte[] b):把數組b 中的所有字節寫入到文件中
             * void write(byte[] b,int off,int len):把數組b 中的從 off 索引開始的 len 個字節寫入到文件中
             */
        out.write(65); //將 A 寫入到文件中
        out.write("Aa".getBytes()); //將 Aa 寫入到文件中
        out.write("ABCDEFG".getBytes(), 1, 5); //將 BCDEF 寫入到文件中
        //經過上面的操作,a.txt 文件中數據為 AAaBCDEF
         
        //4、關閉流資源
        out.close();
        System.out.println(target.getAbsolutePath());

 

3、用字節流完成文件的復制

/**
         * 將 a.txt 文件 復制到 b.txt 中
         */
        //1、創建源和目標
        File srcFile = new File("io"+File.separator+"a.txt");
        File descFile = new File("io"+File.separator+"b.txt");
        //2、創建輸入輸出流對象
        InputStream in = new FileInputStream(srcFile);
        OutputStream out = new FileOutputStream(descFile);
        //3、讀取和寫入操作
        byte[] buffer = new byte[10];//創建一個容量為 10 的字節數組,存儲已經讀取的數據
        int len = -1;//表示已經讀取了多少個字節,如果是 -1,表示已經讀取到文件的末尾
        while((len=in.read(buffer))!=-1){
            //打印讀取的數據
            System.out.println(new String(buffer,0,len));
            //將 buffer 數組中從 0 開始,長度為 len 的數據讀取到 b.txt 文件中
            out.write(buffer, 0, len);
        }
        //4、關閉流資源
        out.close();
        in.close();

6.字符流Reader/Writer

字節輸入輸出流:Reader、Writer(下圖紅色長方形框內),紅色橢圓框內是其典型實現(FileReader、FileWriter)

  

 

①、為什么要使用字符流?

  因為使用字節流操作漢字或特殊符號語言的時候容易亂碼,因為漢字不止一個字節,為了解決這個問題,建議使用字符流。

②、什么情況下使用字符流?

  一般可以用記事本打開的文件,我們可以看到內容不亂碼的。就是文本文件,可以使用字符流。而操作二進制文件(比如圖片、音頻、視頻)必須使用字節流

 1、字符輸出流:FileWriter

public abstract class Writer
  extends Object
  implements Appendable, Closeable, Flushable

用於寫入字符流的抽象類

  方法摘要:

  

  下面我們用 字符輸出流 Writer  的典型實現 FileWriter 來介紹這個類的用法:

//1、創建源
        File srcFile = new File("io"+File.separator+"a.txt");
        //2、創建字符輸出流對象
        Writer out = new FileWriter(srcFile);
        //3、具體的 IO 操作
            /***
             * void write(int c):向外寫出一個字符
             * void write(char[] buffer):向外寫出多個字符 buffer
             * void write(char[] buffer,int off,int len):把 buffer 數組中從索引 off 開始到 len個長度的數據寫出去
             * void write(String str):向外寫出一個字符串
             */
        //void write(int c):向外寫出一個字符
        out.write(65);//將 A 寫入 a.txt 文件中
        //void write(char[] buffer):向外寫出多個字符 buffer
        out.write("Aa帥鍋".toCharArray());//將 Aa帥鍋 寫入 a.txt 文件中
        //void write(char[] buffer,int off,int len)
        out.write("Aa帥鍋".toCharArray(),0,2);//將 Aa 寫入a.txt文件中
        //void write(String str):向外寫出一個字符串
        out.write("Aa帥鍋");//將 Aa帥鍋 寫入 a.txt 文件中
         
        //4、關閉流資源
        /***
         * 注意如果這里有一個 緩沖的概念,如果寫入文件的數據沒有達到緩沖的數組長度,那么數據是不會寫入到文件中的
         * 解決辦法:手動刷新緩沖區 flush()
         * 或者直接調用 close() 方法,這個方法會默認刷新緩沖區
         */
        out.flush();
        out.close();

 2、字符輸入流:Reader

public abstract class Reader
  extends Object
  implements Readable, Closeable

用於讀取字符流的抽象類。

  方法摘要:

  

  下面我們用 字符輸入流 Reader  的典型實現 FileReader 來介紹這個類的用法:

//1、創建源
        File srcFile = new File("io"+File.separator+"a.txt");
        //2、創建字符輸出流對象
        Reader in = new FileReader(srcFile);
        //3、具體的 IO 操作
            /***
             * int read():每次讀取一個字符,讀到最后返回 -1
             * int read(char[] buffer):將字符讀進字符數組,返回結果為讀取的字符數
             * int read(char[] buffer,int off,int len):將讀取的字符存儲進字符數組 buffer,返回結果為讀取的字符數,從索引 off 開始,長度為 len
             *
             */
        //int read():每次讀取一個字符,讀到最后返回 -1
        int len = -1;//定義當前讀取字符的數量
        while((len = in.read())!=-1){
            //打印 a.txt 文件中所有內容
            System.out.print((char)len);
        }
         
        //int read(char[] buffer):將字符讀進字符數組
        char[] buffer = new char[10]; //每次讀取 10 個字符
        while((len=in.read(buffer))!=-1){
            System.out.println(new String(buffer,0,len));
        }
         
        //int read(char[] buffer,int off,int len)
        while((len=in.read(buffer,0,10))!=-1){
            System.out.println(new String(buffer,0,len));
        }
        //4、關閉流資源
        in.close();

 3、用字符流完成文件的復制

/**
         * 將 a.txt 文件 復制到 b.txt 中
         */
        //1、創建源和目標
        File srcFile = new File("io"+File.separator+"a.txt");
        File descFile = new File("io"+File.separator+"b.txt");
        //2、創建字符輸入輸出流對象
        Reader in = new FileReader(srcFile);
        Writer out = new FileWriter(descFile);
        //3、讀取和寫入操作
        char[] buffer = new char[10];//創建一個容量為 10 的字符數組,存儲已經讀取的數據
        int len = -1;//表示已經讀取了多少個字節,如果是 -1,表示已經讀取到文件的末尾
        while((len=in.read(buffer))!=-1){
            out.write(buffer, 0, len);
        }
         
        //4、關閉流資源
        out.close();
        in.close();

7.包裝流(包含緩沖流,轉換流對象流等等)

  ①、包裝流隱藏了底層節點流的差異,並對外提供了更方便的輸入\輸出功能,讓我們只關心這個高級流的操作

  ②、使用包裝流包裝了節點流,程序直接操作包裝流,而底層還是節點流和IO設備操作

  ③、關閉包裝流的時候,只需要關閉包裝流即可

1、緩沖流

  

  緩沖流:是一個包裝流,目的是緩存作用,加快讀取和寫入數據的速度。

  字節緩沖流:BufferedInputStream、BufferedOutputStream

  字符緩沖流:BufferedReader、BufferedWriter

案情回放:我們在將字符輸入輸出流、字節輸入輸出流的時候,讀取操作,通常都會定義一個字節或字符數組,將讀取/寫入的數據先存放到這個數組里面,然后在取數組里面的數據。這比我們一個一個的讀取/寫入數據要快很多,而這也就是緩沖流的由來。只不過緩沖流里面定義了一個 數組用來存儲我們讀取/寫入的數據,當內部定義的數組滿了(注意:我們操作的時候外部還是會定義一個小的數組,小數組放入到內部數組中),就會進行下一步操作。 

      

下面是沒有用緩沖流的操作:   

//1、創建目標對象,輸入流表示那個文件的數據保存到程序中。不寫盤符,默認該文件是在該項目的根目錄下
            //a.txt 保存的文件內容為:AAaBCDEF
        File target = new File("io"+File.separator+"a.txt");
        //2、創建輸入流對象
        InputStream in = new FileInputStream(target);
        //3、具體的 IO 操作(讀取 a.txt 文件中的數據到程序中)
            /**
             * 注意:讀取文件中的數據,讀到最后沒有數據時,返回-1
             *  int read():讀取一個字節,返回讀取的字節
             *  int read(byte[] b):讀取多個字節,並保存到數組 b 中,從數組 b 的索引為 0 的位置開始存儲,返回讀取了幾個字節
             *  int read(byte[] b,int off,int len):讀取多個字節,並存儲到數組 b 中,從數組b 的索引為 0 的位置開始,長度為len個字節
             */
        //int read():讀取一個字節,返回讀取的字節
        int data1 = in.read();//獲取 a.txt 文件中的數據的第一個字節
        System.out.println((char)data1); //A
        //int read(byte[] b):讀取多個字節保存到數組b 中
        byte[] buffer  = new byte[10];//這里我們定義了一個 長度為 10 的字節數組,用來存儲讀取的數據
        in.read(buffer);//獲取 a.txt 文件中的前10 個字節,並存儲到 buffer 數組中
        System.out.println(Arrays.toString(buffer)); //[65, 97, 66, 67, 68, 69, 70, 0, 0, 0]
        System.out.println(new String(buffer)); //AaBCDEF[][][]
         
        //int read(byte[] b,int off,int len):讀取多個字節,並存儲到數組 b 中,從索引 off 開始到 len
        in.read(buffer, 0, 3);
        System.out.println(Arrays.toString(buffer)); //[65, 97, 66, 0, 0, 0, 0, 0, 0, 0]
        System.out.println(new String(buffer)); //AaB[][][][][][][]
        //4、關閉流資源
        in.close();

  我們查看 緩沖流的 JDK 底層源碼,可以看到,程序中定義了這樣的 緩存數組,大小為 8192

  BufferedInputStream:

        

 

 

  BufferedOutputStream:

      

//字節緩沖輸入流
        BufferedInputStream bis = new BufferedInputStream(
                new FileInputStream("io"+File.separator+"a.txt"));
        //定義一個字節數組,用來存儲數據
        byte[] buffer = new byte[1024];
        int len = -1;//定義一個整數,表示讀取的字節數
        while((len=bis.read(buffer))!=-1){
            System.out.println(new String(buffer,0,len));
        }
        //關閉流資源
        bis.close();<br><br>
         
        //字節緩沖輸出流
        BufferedOutputStream bos = new BufferedOutputStream(
                new FileOutputStream("io"+File.separator+"a.txt"));
        bos.write("ABCD".getBytes());
        bos.close();
//字符緩沖輸入流
        BufferedReader br = new BufferedReader(
                new FileReader("io"+File.separator+"a.txt"));
        char[] buffer = new char[10];
        int len = -1;
        while((len=br.read(buffer))!=-1){
            System.out.println(new String(buffer,0,len));
        }
        br.close();
         
        //字符緩沖輸出流
        BufferedWriter bw = new BufferedWriter(
                new FileWriter("io"+File.separator+"a.txt"));
        bw.write("ABCD");
        bw.close();

2、轉換流:把字節流轉換為字符流

  InputStreamReader:把字節輸入流轉換為字符輸入流

  OutputStreamWriter:把字節輸出流轉換為字符輸出流

   

 

 用轉換流進行文件的復制:

/**
         * 將 a.txt 文件 復制到 b.txt 中
         */
        //1、創建源和目標
        File srcFile = new File("io"+File.separator+"a.txt");
        File descFile = new File("io"+File.separator+"b.txt");
        //2、創建字節輸入輸出流對象
        InputStream in = new FileInputStream(srcFile);
        OutputStream out = new FileOutputStream(descFile);
        //3、創建轉換輸入輸出對象
        Reader rd = new InputStreamReader(in);
        Writer wt = new OutputStreamWriter(out);
        //3、讀取和寫入操作
        char[] buffer = new char[10];//創建一個容量為 10 的字符數組,存儲已經讀取的數據
        int len = -1;//表示已經讀取了多少個字符,如果是 -1,表示已經讀取到文件的末尾
        while((len=rd.read(buffer))!=-1){
            wt.write(buffer, 0, len);
        }
        //4、關閉流資源
        rd.close();
        wt.close();

轉換流和子類區別

發現有如下繼承關系:

OutputStreamWriter:

|--FileWriter:

InputStreamReader:

|--FileReader;

 

父類和子類的功能有什么區別呢?

OutputStreamWriter和InputStreamReader是字符和字節的橋梁:也可以稱之為字符轉換流。字符轉換流原理:字節流+編碼表。

FileWriter和FileReader:作為子類,僅作為操作字符文件的便捷類存在。當操作的字符文件,使用的是默認編碼表時可以不用父類,而直接用子類就完成操作了,簡化了代碼。

InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));//默認字符集。

InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"GBK");//指定GBK字符集。

FileReader fr = new FileReader("a.txt");

這三句代碼的功能是一樣的,其中第三句最為便捷。

注意:一旦要指定其他編碼時,絕對不能用子類,必須使用字符轉換流。什么時候用子類呢?

條件:

1、操作的是文件。2、使用默認編碼。

總結:

字節--->字符 : 看不懂的--->看的懂的。  需要讀。輸入流。 InputStreamReader

字符--->字節 : 看的懂的--->看不懂的。  需要寫。輸出流。 OutputStreamWriter

3、內存流(數組流):

  把數據先臨時存在數組中,也就是內存中。所以關閉 內存流是無效的,關閉后還是可以調用這個類的方法。底層源碼的 close()是一個空方法

        

 

  ①、字節內存流:ByteArrayOutputStream 、ByteArrayInputStream

//字節數組輸出流:程序---》內存
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        //將數據寫入到內存中
        bos.write("ABCD".getBytes());
        //創建一個新分配的字節數組。 其大小是此輸出流的當前大小,緩沖區的有效內容已被復制到其中。
        byte[] temp = bos.toByteArray();
        System.out.println(new String(temp,0,temp.length));
         
        byte[] buffer = new byte[10];
        ///字節數組輸入流:內存---》程序
        ByteArrayInputStream bis = new ByteArrayInputStream(temp);
        int len = -1;
        while((len=bis.read(buffer))!=-1){
            System.out.println(new String(buffer,0,len));
        }
         
        //這里不寫也沒事,因為源碼中的 close()是一個空的方法體
        bos.close();
        bis.close();

②、字符內存流:CharArrayReader、CharArrayWriter

//字符數組輸出流
        CharArrayWriter caw = new CharArrayWriter();
        caw.write("ABCD");
        //返回內存數據的副本
        char[] temp = caw.toCharArray();
        System.out.println(new String(temp));
         
        //字符數組輸入流
        CharArrayReader car = new CharArrayReader(temp);
        char[] buffer = new char[10];
        int len = -1;
        while((len=car.read(buffer))!=-1){
            System.out.println(new String(buffer,0,len));
        }

③、字符串流:StringReader,StringWriter(把數據臨時存儲到字符串中)

//字符串輸出流,底層采用 StringBuffer 進行拼接
        StringWriter sw = new StringWriter();
        sw.write("ABCD");
        sw.write("帥鍋");
        System.out.println(sw.toString());//ABCD帥鍋
 
        //字符串輸入流
        StringReader sr = new StringReader(sw.toString());
        char[] buffer = new char[10];
        int len = -1;
        while((len=sr.read(buffer))!=-1){
            System.out.println(new String(buffer,0,len));//ABCD帥鍋
        }

4、合並流:把多個輸入流合並為一個流,也叫順序流,因為在讀取的時候是先讀第一個,讀完了在讀下面一個流。

 

//定義字節輸入合並流
        SequenceInputStream seinput = new SequenceInputStream(
                new FileInputStream("io/a.txt"), new FileInputStream("io/b.txt"));
        byte[] buffer = new byte[10];
        int len = -1;
        while((len=seinput.read(buffer))!=-1){
            System.out.println(new String(buffer,0,len));
        }
         
        seinput.close();

8.序列化與反序列化(對象流)

1、什么是序列化與反序列化?

  序列化:指把堆內存中的 Java 對象數據,通過某種方式把對象存儲到磁盤文件中或者傳遞給其他網絡節點(在網絡上傳輸)。這個過程稱為序列化。通俗來說就是將數據結構或對象轉換成二進制串的過程

  反序列化:把磁盤文件中的對象數據或者把網絡節點上的對象數據,恢復成Java對象模型的過程。也就是將在序列化過程中所生成的二進制串轉換成數據結構或者對象的過程

 

2、為什么要做序列化?

  ①、在分布式系統中,此時需要把對象在網絡上傳輸,就得把對象數據轉換為二進制形式,需要共享的數據的 JavaBean 對象,都得做序列化。

  ②、服務器鈍化:如果服務器發現某些對象好久沒活動了,那么服務器就會把這些內存中的對象持久化在本地磁盤文件中(Java對象轉換為二進制文件);如果服務器發現某些對象需要活動時,先去內存中尋找,找不到再去磁盤文件中反序列化我們的對象數據,恢復成 Java 對象。這樣能節省服務器內存。

 

3、Java 怎么進行序列化?

  ①、需要做序列化的對象的類,必須實現序列化接口:Java.lang.Serializable 接口(這是一個標志接口,沒有任何抽象方法),Java 中大多數類都實現了該接口,比如:String,Integer

  ②、底層會判斷,如果當前對象是 Serializable 的實例,才允許做序列化,Java對象 instanceof Serializable 來判斷。

  ③、在 Java 中使用對象流來完成序列化和反序列化

    ObjectOutputStream:通過 writeObject()方法做序列化操作

    ObjectInputStream:通過 readObject() 方法做反序列化操作

    

 

 

 第一步:創建一個 JavaBean 對象

public class Person implements Serializable{
    private String name;
    private int age;
     
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
}  

第二步:使用 ObjectOutputStream 對象實現序列化

//在根目錄下新建一個 io 的文件夾
        OutputStream op = new FileOutputStream("io"+File.separator+"a.txt");
        ObjectOutputStream ops = new ObjectOutputStream(op);
        ops.writeObject(new Person("vae",1));
         
        ops.close();

我們打開 a.txt 文件,發現里面的內容亂碼,注意這不需要我們來看懂,這是二進制文件,計算機能讀懂就行了。

錯誤一:如果新建的 Person 對象沒有實現 Serializable 接口,那么上面的操作會報錯:

    

第三步:使用ObjectInputStream 對象實現反序列化

  反序列化的對象必須要提供該對象的字節碼文件.class

InputStream in = new FileInputStream("io"+File.separator+"a.txt");
        ObjectInputStream os = new ObjectInputStream(in);
        byte[] buffer = new byte[10];
        int len = -1;
        Person p = (Person) os.readObject();
        System.out.println(p);  //Person [name=vae, age=1]
        os.close();

問題1:如果某些數據不需要做序列化,比如密碼,比如上面的年齡?

解決辦法:在字段面前加上 transient

private String name;//需要序列化
    transient private int age;//不需要序列化

那么我們在反序列化的時候,打印出來的就是Person [name=vae, age=0],整型數據默認值為 0 

 

問題2:序列化版本問題,在完成序列化操作后,由於項目的升級或修改,可能我們會對序列化對象進行修改,比如增加某個字段,那么我們在進行反序列化就會報錯:

 

 

解決辦法:在 JavaBean 對象中增加一個 serialVersionUID 字段,用來固定這個版本,無論我們怎么修改,版本都是一致的,就能進行反序列化了

    
private static final long serialVersionUID = 8656128222714547171L;

 


9.隨機訪問文件流

 


字節流與字符流的區別

 https://blog.csdn.net/cynhafa/article/details/6882061


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM