Java IO流詳解

參考轉載地址:http://www.javashuo.com/article/p-trkmdkht-u.htmlhtml

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

       http://www.javashuo.com/article/p-uwwfyuxx-ho.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對應是內存,可能不太準確可是便於我本身的理解,有問題請指點)。

流的分類:

1、根據流向分爲輸入流和輸出流:

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

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

 

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

  綜合起來:

   

 

2、根據傳輸數據單位分爲字節流和字符流

  

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

1) 字節流:數據流中最小的數據單元是字節 
2)  字符流:數據流中最小的數據單元是字符, Java中的字符是Unicode編碼,一個字符佔用兩個字節( 不管中文仍是英文都是兩個字節)。

3、根據功能分爲節點流和包裝流

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

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

      一個流對象通過其餘流的屢次包裝,稱爲流的連接。

 注意:一個IO流能夠便是輸入流又是字節流又或是以其餘方式分類的流類型,是不衝突的。好比FileInputStream,它既是輸入流又是字節流仍是文件節點流。

4、一些特別的的流類型

  轉換流:轉換流只有字節流轉換爲字符流,由於字符流使用起來更方便,咱們只會向更方便使用的方向轉化。如:InputStreamReader與OutputStreamWriter。

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

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

 操做 IO 流的模板:

  ①、建立源或目標對象

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

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

 

  ②、建立 IO 流對象

    輸入:建立輸入流對象

    輸出:建立輸出流對象

 

  ③、具體的 IO 操做

 

  ④、關閉資源

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

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

 

 

注意:一、程序中打開的文件 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 類只能操做文件的屬性,文件的內容是不能操做的。

 

一、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
就是指「\」

二、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

三、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

四、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)

  

一、字節輸入流:InputStream

public abstract class InputStream
  extends Object
  implements Closeable

 

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

  方法摘要:

  

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

//一、建立目標對象,輸入流表示那個文件的數據保存到程序中。不寫盤符,默認該文件是在該項目的根目錄下
            //a.txt 保存的文件內容爲:AAaBCDEF
        File target = new File("io"+File.separator+"a.txt");
        //二、建立輸入流對象
        InputStream in = new FileInputStream(target);
        //三、具體的 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[][][][][][][]
        //四、關閉流資源
        in.close();
 

 二、字節輸出流:OutputStream

public abstract class OutputStream 
                extends Object
            implements Closeable, Flushable

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

  方法摘要:

  

 

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

//一、建立目標對象,輸出流表示把數據保存到哪一個文件。不寫盤符,默認該文件是在該項目的根目錄下
        File target = new File("io"+File.separator+"a.txt");
        //二、建立文件的字節輸出流對象,第二個參數是 Boolean 類型,true 表示後面寫入的文件追加到數據後面,false 表示覆蓋
        OutputStream out = new FileOutputStream(target,true);
        //三、具體的 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
         
        //四、關閉流資源
        out.close();
        System.out.println(target.getAbsolutePath());

 

三、用字節流完成文件的複製

/**
         * 將 a.txt 文件 複製到 b.txt 中
         */
        //一、建立源和目標
        File srcFile = new File("io"+File.separator+"a.txt");
        File descFile = new File("io"+File.separator+"b.txt");
        //二、建立輸入輸出流對象
        InputStream in = new FileInputStream(srcFile);
        OutputStream out = new FileOutputStream(descFile);
        //三、讀取和寫入操做
        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);
        }
        //四、關閉流資源
        out.close();
        in.close();

6.字符流Reader/Writer

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

  

 

①、爲何要使用字符流?

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

②、什麼狀況下使用字符流?

  通常能夠用記事本打開的文件,咱們能夠看到內容不亂碼的。就是文本文件,可使用字符流。而操做二進制文件(好比圖片、音頻、視頻)必須使用字節流

 一、字符輸出流:FileWriter

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

用於寫入字符流的抽象類

  方法摘要:

  

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

//一、建立源
        File srcFile = new File("io"+File.separator+"a.txt");
        //二、建立字符輸出流對象
        Writer out = new FileWriter(srcFile);
        //三、具體的 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 文件中
         
        //四、關閉流資源
        /***
         * 注意若是這裏有一個 緩衝的概念,若是寫入文件的數據沒有達到緩衝的數組長度,那麼數據是不會寫入到文件中的
         * 解決辦法:手動刷新緩衝區 flush()
         * 或者直接調用 close() 方法,這個方法會默認刷新緩衝區
         */
        out.flush();
        out.close();

 二、字符輸入流:Reader

public abstract class Reader
  extends Object
  implements Readable, Closeable

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

  方法摘要:

  

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

//一、建立源
        File srcFile = new File("io"+File.separator+"a.txt");
        //二、建立字符輸出流對象
        Reader in = new FileReader(srcFile);
        //三、具體的 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));
        }
        //四、關閉流資源
        in.close();

 三、用字符流完成文件的複製

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

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

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

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

  ③、關閉包裝流的時候,只須要關閉包裝流便可

一、緩衝流

  

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

  字節緩衝流:BufferedInputStream、BufferedOutputStream

  字符緩衝流:BufferedReader、BufferedWriter

案情回放:咱們在將字符輸入輸出流、字節輸入輸出流的時候,讀取操做,一般都會定義一個字節或字符數組,將讀取/寫入的數據先存放到這個數組裏面,而後在取數組裏面的數據。這比咱們一個一個的讀取/寫入數據要快不少,而這也就是緩衝流的由來。只不過緩衝流裏面定義了一個 數組用來存儲咱們讀取/寫入的數據,當內部定義的數組滿了(注意:咱們操做的時候外部仍是會定義一個小的數組,小數組放入到內部數組中),就會進行下一步操做。 

      

下面是沒有用緩衝流的操做:   

//一、建立目標對象,輸入流表示那個文件的數據保存到程序中。不寫盤符,默認該文件是在該項目的根目錄下
            //a.txt 保存的文件內容爲:AAaBCDEF
        File target = new File("io"+File.separator+"a.txt");
        //二、建立輸入流對象
        InputStream in = new FileInputStream(target);
        //三、具體的 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[][][][][][][]
        //四、關閉流資源
        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();

二、轉換流:把字節流轉換爲字符流

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

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

   

 

 用轉換流進行文件的複製:

/**
         * 將 a.txt 文件 複製到 b.txt 中
         */
        //一、建立源和目標
        File srcFile = new File("io"+File.separator+"a.txt");
        File descFile = new File("io"+File.separator+"b.txt");
        //二、建立字節輸入輸出流對象
        InputStream in = new FileInputStream(srcFile);
        OutputStream out = new FileOutputStream(descFile);
        //三、建立轉換輸入輸出對象
        Reader rd = new InputStreamReader(in);
        Writer wt = new OutputStreamWriter(out);
        //三、讀取和寫入操做
        char[] buffer = new char[10];//建立一個容量爲 10 的字符數組,存儲已經讀取的數據
        int len = -1;//表示已經讀取了多少個字符,若是是 -1,表示已經讀取到文件的末尾
        while((len=rd.read(buffer))!=-1){
            wt.write(buffer, 0, len);
        }
        //四、關閉流資源
        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");

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

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

條件:

一、操做的是文件。二、使用默認編碼。

總結:

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

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

三、內存流(數組流):

  把數據先臨時存在數組中,也就是內存中。因此關閉 內存流是無效的,關閉後仍是能夠調用這個類的方法。底層源碼的 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帥鍋
        }

四、合併流:把多個輸入流合併爲一個流,也叫順序流,由於在讀取的時候是先讀第一個,讀完了在讀下面一個流。

 

//定義字節輸入合併流
        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.序列化與反序列化(對象流)

一、什麼是序列化與反序列化?

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

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

 

二、爲何要作序列化?

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

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

 

三、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
相關文章
相關標籤/搜索