1. 程式人生 > 實用技巧 >Java I/O系統

Java I/O系統

File類

在學習那些真正用於在流中讀寫資料的類之前,我們先學習FIle類,它可以幫助我們處理檔案目錄的問題。
FIle物件既可以代表一個特定檔案的名稱,又可以代表一個目錄下的資料夾的名稱。

目錄列表器

如果你想檢視某個資料夾下有什麼,有兩種方法來使用File物件。
1.如果你想檢視目標資料夾下的所有檔案。呼叫list()它將返回一個字元陣列,其中儲存著目標資料夾下的所有檔案。
2.如果目標資料夾中檔案數量過多,你想篩選具有某些相同特徵的檔案,就要用到"目錄過濾器"。呼叫list​(FilenameFilter filter)它同樣會返回一個字元陣列,其中儲存組目標資料夾下過濾後的所有檔案。

public class DirList {
    public static void main(String[] args) {
       File path = new File("D:\\idea\\workspace\\project18");

       /*
            方法一:
            呼叫無參list()返回字元陣列,用asList將字元陣列轉換成List然後輸出
        */
       List<String> list = Arrays.asList(path.list());
        for (String s : list)
            System.out.println(s);

        /*
           方法二:
           呼叫有參list(),過濾出字尾是.iml的檔案,存在字元陣列中。
           用asList將字元陣列轉換成List然後輸出
         */
       String[] str =path.list(new FilenameFilter() {
           @Override
           public boolean accept(File file, String s) {
               return s.endsWith("iml");
           }
       });
        List<String> list1 = Arrays.asList(str);
        System.out.println();
        for (String s : list1)
            System.out.println(s);
    }
}

目錄實用工具

File物件比檔名更有用,因為它包含更多資訊。
1.呼叫無參listFiles(),返回一個File[].
2.呼叫有參listFiles​(FilenameFilter filter),返回過濾後的File[]。


public class Directory {

    static void func(File file){
        File[] fs = file.listFiles();
        for(File f:fs){
            if(f.isDirectory())	//若是目錄,則遞迴列印該目錄下的檔案
                func(f);
            if(f.isFile())		//若是檔案,直接列印
                System.out.println(f);
        }
    }

    static void func(File file, String regex){
        File[] fs = file.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File file, String name) {
                return Pattern.compile(regex).matcher(new File(name).getName()).matches();
            }
        });
        for(File f:fs){
            if(f.isDirectory())	//若是目錄,則遞迴列印該目錄下的檔案
                func(f);
            if(f.isFile())		//若是檔案,直接列印
                System.out.println(f);
        }
    }

    public static void main(String[] args) {
        File path = new File("D:\\idea\\workspace\\project11");
        /*
            方法一:
            呼叫無參listFiles(),返回一個File[]
         */
        Directory.func(path);

        System.out.println();
        System.out.println();
        System.out.println();
        System.out.println();

        /*
            方法二:
            呼叫有參listFiles​(FilenameFilter filter),返回過濾後的File[],

         */
        Directory.func(path, "out");
    }
}

目錄的檢查及建立

File類不僅可以代表存在的檔案或目錄,也可以建立新的目錄或尚不存在的整個目錄路徑。還可以檢視檔案的特性(大小,最後的修改日期,讀寫許可權),以及檢查File物件是一個檔案還是一個目錄。並且可以刪除檔案。

public class MakeDirectories {

    private static void fileData(File f) {
        System.out.println(
                "Absolute path: " + f.getAbsolutePath() +//檔案的絕對路徑
                        "\n Can read: " + f.canRead() + //是否可讀
                        "\n Can write: " + f.canWrite() +//是否可寫
                        "\n  getName:  "  +  f.getName()  +//檔名
                        "\n getParent: " + f.getParent() +//上一級檔名
                        "\n getPath: " + f.getPath() +//相對路徑
                        "\n length: " + f.length() +//檔案大小
                        "\n lastModified: " + f.lastModified());//最後修改的時間
        if(f.isFile())//判斷是否是檔案
            System.out.println("It's a file");
        else if(f.isDirectory())//判斷是否是目錄
            System.out.println("It's a directory");
    }
    public static void main(String[] args) {
        File file = new File("D:\\idea\\workspace\\project18");
        File file1 = new File("D:\\idea\\workspace\\project18\\project18.iml");

        MakeDirectories.fileData(file);
        MakeDirectories.fileData(file1);
    }
}

輸入和輸出

Java類庫中的I/O類分為兩部分輸入和輸出。
輸入:

InputStream 型別

InputStream 的作用是表示那些從不同資料來源產生輸入的類。這些資料來源包括:
(1) 位元組陣列
(2) String 物件
(3) 檔案
(4) “管道”,它的工作原理與現實生活中的管道類似:將一些東西置入一端,它們在另一端出來。
(5) 一個由其他種類的流組成的序列,以便我們可以將它們收集併合併到一個流內。
(6) 其他資料來源,如 Internet 連線等。
每一種資料來源都有相應的InputStream子類。除此以外, FilterInputStream 也屬於 InputStream 的一種型別,用它可為“裝飾器”類提供一個基礎類,以便將屬性或者有用的介面同輸入流連線到一起。

功能 構造器引數如何使用
ByteArrayInputStream 允許將記憶體中的緩衝區作為 InputStream 使用 緩衝區,位元組將從中取出。作為一個數據源使用。通過將其同一個 FilterInputStream 物件連線,可提供一個有用的介面
StringBufferInputStream 將String 轉換成 InputStream 字串,底層實現實際使用StringBuffer。作為一個數據源使用。通過將其同一個 FilterInputStream 物件連線,可提供一個有用的介面
FileInputStream 用於從檔案讀取資訊 代表檔名的一個 String,或者一個 File 或 FileDescriptor 物件 作為一個數據源使用。通過將其同一個 FilterInputStream 物件連線,可提供一個有用的介面
PipedInputStream 產生用於寫入相關 PipedOutputStream 的資料 實現"管道化"概念 PipedOutputStream 作為多執行緒中的資料來源:將其與 FilterInputStream 物件連線,可提供一個有用的介面
SequenceInputStream 將兩個或更多的 InputStream 物件轉換成單個 InputStream 使用 兩個InputStream 物件或者一個容納InputStream 物件的容器 Enumeratio。 作為一個數據源使用。通過將其同一個FilterInputStream 物件連線,可提供一個有用的介面
FilterInputStream 抽象類,作為“裝飾器”的介面。其中“裝飾器”為其他的InputStream類提供有用功能

輸出:

OutputStream的型別

該類別的類決定了往何處輸出:
(1) 位元組陣列
(2) 檔案
(3) 管道
除此以外, FilterOutputStream 為“破壞器”類提供了一個基礎類,它將屬性或者有用的介面同輸出流連線起來。

功能 如何使用
ByteArrayOutputStream 在記憶體中建立緩衝區。所有送往“流”的資料都要放置在此緩衝區 用於指定資料的目的地:將其同FilterOutputStream 物件相連以提供有用介面
FileOutputStream 將資訊寫入檔案 用於指定資料的目的地:將其同FilterOutputStream 物件相連以提供有用介面
PipedOutputStream 任何寫入其中的資訊都會自動作為相關的PipedInputStream的輸出。實現“管道化”的概念 用於指定資料的目的地:將其同FilterOutputStream 物件相連以提供有用介面
FilterOutputStream 抽象類,作為“裝飾器”的介面。其中“裝飾器”為其他的OutputStream類提供有用功能

新增屬性和有用的介面

通過FilterInputStream從InputStream讀取資料

功能 如何使用
DataInputStream 與DataOutputStream搭配使用,可以從流中讀取基本資料型別 包含用於讀取基本資料型別的全部介面
BufferedInputStream 使用它可以防止每次讀取時都得進行實際寫操作。代表“使用緩衝區” 本質上不提供介面,只不過是像程序中新增緩衝區所必需的。與介面物件搭配

FilterInputStream能夠完成兩件完全不同的事情。其中DataInputStream允許我們讀取基本資料型別。
其他FilterInputStream類則是在內部修改InputStream的行為方式:是否緩衝等等。

通過FilterOutputStream向OutputStream寫入

功能 如何使用
DataOutputStream 與DataOutputStream搭配使用,可以向流中寫入基本資料型別 包含用於讀取基本資料型別的全部介面
PrintStream 用於產生格式化輸出,其中DataOutputStream處理資料的儲存,PrintStream處理顯示 可以用布林值指示是否在每次換行時清空緩衝區。
BufferedOutputStream 使用它可以防止每次傳送資料時都得進行實際寫操作。代表“使用緩衝區”,可以用flush()清空緩衝區 本質上不提供介面,只不過是像程序中新增緩衝區所必需的。與介面物件搭配

Reader和Writer

InputStream和OutputStream是面向位元組的I/O功能,Reader和Writer面向的是字元的I/O功能。
需要注意的是:有時我們必須把來自於“位元組”層次結構中的類和“字元”層次結構中的類結合起來使用。為了實現這個目的。要用到“介面卡”類。
InputStreamReader可以把InputStream轉換成Reader。OutputStreamWriter可以把OutputStream轉換成Writer。
設計Reader和RWriter的繼承層次結構主要是為了國際化。

更改流的行為

對於InputStream和OutputStream來說,我們用FilterInputStream和FilterOutputStream的裝飾器子類來修改“流”以滿足特殊需求。Reader和Writer的類繼承層次繼續沿用相同的思想(但不完全相同)

過濾器:Java 1.0 類 相應的 Java 1.1 類
FilterInputStream FilterReader
FilterOutputStream FilterWriter
BufferedInputStream BufferedReader
BufferedOutputStream BufferedWriter
DataInputStream 使用DataInputStream(除了當需要使用readLine()以外,這時應該使用BufferedReader )
PrintStream PrintWriter

注意:當我們需要使用readLine(),應該使用BufferedReader。不應該使用DataInputStream,這會遭到編譯器的強烈反對。

自我獨立的類:RandomAccessFile

此類不是InputStream和OutputStream繼承層次結構中的一部分,它擁有和別的I/O型別本質不同的行為:我們可以在檔案內向前或向後移動。
只有RandomAccessFile支援搜尋方法,並且只適用於檔案。

I/O流的典型用法

緩衝輸入檔案


public class BufferedInputFile {
    public static String read(String filename) throws IOException {
        BufferedReader in = new BufferedReader(new FileReader(filename));
        String s;
        StringBuilder sb = new StringBuilder();
        while((s = in.readLine())!=null){
            sb.append(s);
        }
        in.close();
        return sb.toString();
    }
    public static void main(String[] args) throws IOException {
        System.out.println(BufferedInputFile.read("E:\\outlook\\2.txt"));
    }
}

從記憶體輸入

public class MemoryInput {
    public static void main(String[] args) throws IOException {
        StringReader in = new StringReader(BufferedInputFile.read("E:\\outlook\\2.txt"));
        int c;
        while((c = in.read())!= -1){
            System.out.print((char)c);
        }
    }
}

read()讀到檔案末尾返回-1,由此來判斷檔案是否讀完。

格式化的記憶體輸入

public class FormattedMemoryInput {
    public static void main(String[] args) throws IOException {
        DataInputStream in = new DataInputStream(new ByteArrayInputStream(read("E:\\outlook\\2.txt").getBytes()));
        while(true){
            System.out.println((char)in.readByte());
        }
    }
}

readByte():如果此輸入流已到達檔案末尾,會丟擲EOFException異常。

基本的檔案輸出

public class BasicFileOutput {
    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new FileReader("E:\\outlook\\1.txt"));//建立輸入流連結原始檔
        BufferedWriter out = new BufferedWriter(new FileWriter("E:\\outlook\\2.txt"));//建立輸出流連結目標檔案
        String s;
        while ((s = in.readLine())!=null)//將原始檔中的資料寫入記憶體並存入字串s
        {
            System.out.println(s);
            out.write(s);//將s中的資料寫入目標檔案
        }
        out.close(); 
        in.close();
    }

儲存和恢復資料

public class StoringAndRecoveringData {
    public static void main(String[] args) throws IOException {
        DataOutputStream out = new DataOutputStream(
                new BufferedOutputStream(new FileOutputStream("E:\\outlook\\1.txt")));//連結目標檔案
        out.writeDouble(3.1415926);
        out.writeUTF("xxgbl");
        out.close();
        DataInputStream in = new DataInputStream(
                new BufferedInputStream(new FileInputStream("E:\\outlook\\1.txt")));
        System.out.println(in.readDouble());
        System.out.println(in.readUTF());
    }
}

如果我們使用DataOutputStream 寫入資料,Java保證我們可以使用DataInputStream準確的讀取資料。

讀寫隨機訪問檔案

public class UsingRandomAccessFile {

    static void display() throws IOException {
        RandomAccessFile rf = new RandomAccessFile("E:\\outlook\\1.txt", "r");
        for(int i=0; i<8; i++)
            System.out.println("Value"+ i+":  "+ rf.readDouble());
        rf.close();
    }
    public static void main(String[] args) throws IOException {
        RandomAccessFile out = new RandomAccessFile("E:\\outlook\\1.txt", "rw");
        for(int i=0; i<8; i++)
            out.writeDouble(i*1.414);
        display();
        out.close();
        System.out.println();
        out = new RandomAccessFile("E:\\outlook\\1.txt", "rw");
        out.seek(5*8);
        out.writeDouble(47.00001);
        display();
        out.close();
    }
}

在使用RandomAccessFile時,必須知道檔案排版,此類具有讀取基本型別和UTF-8字串的各種具體方法。

管道流

PipedInputStream,PipedOutputStream,PipedReader,PipedWriter用於任務之間的通訊。它的價值在多執行緒中有所體現。

標準I/O

System.in:是一個InputStream物件
System.out:是一個PrintStream 物件
System.err:是一個PrintStream 物件

壓縮

壓縮類 功能
CheckedInputStream 一種輸入流,它還維護正在讀取的資料的校驗和。 然後可以使用校驗和來驗證輸入資料的完整性。
CheckedOutputStream 輸出流,它還維護正在寫入的資料的校驗和。 然後可以使用校驗和來驗證輸出資料的完整性。
DeflaterOutputStream 壓縮類的基類
ZIPOutputStream DeflaterOutputStream的子類,用於將資料壓縮成Zip格式
GZIPOutputStream DeflaterOutputStream的子類,用於將資料壓縮成GZIP格式
InflaterInputStream 解壓縮的基類
ZipInputStream InflaterInputStream的子類,用於解壓Zip格式的資料
GZIPInputStream InflaterInputStream的子類,用於解壓GZIP格式的資料

使用GZIP進行簡單壓縮

public class GZIPcompress {
    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(
                new InputStreamReader(new FileInputStream("E:\\outlook\\1.txt")));//連結原始檔
        BufferedOutputStream out = new BufferedOutputStream(new GZIPOutputStream(
                new FileOutputStream("E:\\outlook\\2.gz")));//連結目標檔案
        int c;
        while((c = in.read())!= -1)//讀取原始檔
            out.write(c);//向目標檔案寫入資料。
        out.close();
        in.close();
    }
}
執行上述程式碼,會生成一個2.gz的壓縮包。解壓同理:讀取壓縮檔案,寫入目標檔案中即可。

Zip進行多檔案儲存

public class ZipCompress {
    public static void main(String[] args) throws IOException {
            
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("E:\\outlook\\test.zip"));//建立壓縮檔案
        BufferedOutputStream out = new BufferedOutputStream(zos);//輸出流連結壓縮檔案

        BufferedReader in1 = new BufferedReader(new InputStreamReader(
                new FileInputStream("E:\\outlook\\1.txt")));//輸入流連結原始檔

        zos.putNextEntry(new ZipEntry("1.txt"));//Zip壓縮每新增一個壓縮檔案,都必須呼叫putNextEntry()

        int i = 0;
        while((i=in1.read())!=-1){
            out.write(i);//讀取原始檔資料並寫入壓縮檔案
        }
        in1.close();
        out.flush();//強制寫出快取中的資料。清空快取。
        BufferedReader in2 = new BufferedReader(new InputStreamReader(
                new FileInputStream("E:\\outlook\\2.txt")));

        zos.putNextEntry(new ZipEntry("2.txt"));

        int c = 0;
        while((c=in2.read())!=-1){
            out.write(c);
        }
        in2.close();
        out.close();
    }

}

上述程式碼將1.txt,2.txt壓縮成一個test.zip壓縮包。
zip解壓有兩種方式:

        //解壓
        FileInputStream f = new FileInputStream("E:\\outlook\\test.zip");
        ZipInputStream zipInputStream = new ZipInputStream(f);
        BufferedReader in = new BufferedReader(new InputStreamReader(zipInputStream));

        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("E:\\outlook\\test1.txt")));
        /*
            方法一:
            為了能夠解壓zip,ZipInputStream提供了getNextEntry(),返回一個ZipEntry物件,此物件儲存了壓縮檔案裡的所有檔案。
         */
      /*  int i =0;
        while(zipInputStream.getNextEntry()!=null){
            while((i=in.read())!=-1){
                out.write(i);
            }
        }
        out.flush();
        out.close();
        in.close();*/

        /*
            方法二:
            利用ZipFile物件讀取檔案
         */
        ZipFile zf = new ZipFile("E:\\outlook\\test.zip");
        Enumeration entries =zf.entries();
        int i = 0;
        while(entries.hasMoreElements()){
            ZipEntry zipEntry = (ZipEntry) entries.nextElement();
            while(zipInputStream.getNextEntry()!=null){
                while((i=in.read())!=-1){
                    out.write(i);
                }
            }
        }
        out.flush();
        out.close();
        in.close();

    }

物件序列化

Java的物件序列化將那些實現Serializable介面的物件轉換成一個位元組序列,並能將位元組序列恢復成原來的物件。此介面沒有方法沒有欄位,只是一個標識功能。
使用物件序列化的原因:
1.支援Java的遠端方法呼叫(RMI),它使存活在其他計算機上的物件使用起來就像使存活在本機一樣。
2.使用JavaBean。使用一個Bean時,一般情況下是在設計階段對它的狀態資訊進行配置。這種狀態資訊必須儲存下來,並在程式啟動時進行後期恢復,這種具體工作是由物件序列化完成的。

要序列化一個物件很簡單,首先建立OutputStream物件,然後將其封裝在一個ObjectOutputStream物件內。這時呼叫writeObject​()即可將物件例項化。

      ObjectOutputStream out = new ObjectOutputStream(new OutputStream());
      out.writeObject​()  

反序列化同理:

      ObjectInputStream in= new ObjectInputStream(new InputStream());
      in.readObject​()  

序列化的控制

預設的序列化機制並不難操作,然而,如果有特殊需求呢?例如:出於安全考慮,你不希望物件的某一部分被序列化;或者一個物件被還原後,子物件需要重建,從而不必將子物件序列化。
為此,你可以通過實現Externalizable介面來對序列化過程進行控制,此介面繼承了Serializable介面,同時新增了writeExternal​()和readExternal()方法。這兩個方法會在序列化和反序列化過程中自動呼叫,以便執行一些特殊操作。

transient(瞬時)關鍵字

當一個類中的某個欄位是敏感資訊(例如:密碼),我們不希望將其序列化。因為一經序列化,人們可以通過其他方式來訪問到它。
解決這個問題:
方法一:
1.將類實現為Externalizable。
2.將不希望被自動序列化的欄位用transient修飾。例如 private transient String password;
方法二:
1.實現Serializable介面。
2.新增writeObject(),readObject()方法。這樣一旦序列化或反序列化,就會自動呼叫這兩個方法。
這兩個方法必須有準確的方法特徵簽名:

     private void writeObject(java.io.ObjectOutputStream out)
     throws IOException
     private void readObject(java.io.ObjectInputStream in)
     throws IOException, ClassNotFoundException;

XML

物件序列化的一個重要限制是:這只是Java的解決方案,只有Java程式才能反序列化這種物件。
一種更具互操作性的解決方案是將資料轉換成XML格式。這使得資料能被跨平臺和語言使用。