天天看點

08 - JavaSE之IO流

IO流

JAVA流式輸入輸出原理:可以想象成一根管道怼到檔案上,另一端是我們程式,然後流的輸入輸出都是按照程式本身作為第一人稱說明的。比如 input,對于我們程式來說就是有資料輸入我們程式,output就是我們程式輸出資料到檔案等。對象不能搞錯了,否則就南轅北轍了。

  • 通過不同的角度對流的輸入輸出功能進行分類:
  1. 按資料流的方向分為:輸入流和輸出流
  2. 按處理資料機關不同分為:位元組流和字元流(2個位元組)
  3. 按功能不同分為:節點流和處理流

輸入流和輸出流

  • JAVA JDK 所提供的所有流類型位于包 java.io 内部,分别繼承自下面四個抽象流類型:
    位元組流             字元流              

    輸入流 InputStream Reader

    輸出流 OutputStream Writer

節點流和處理流

  • 節點流為可以從一個特定的資料源(節點)讀寫資料(如:檔案,記憶體)。

節點流可以簡單的了解為:一根管道直接怼到檔案上,進行資料的讀寫。

  • 處理流是連接配接在已存在的節點流或處理流上的,通過讀資料進行處理(過濾等)為程式提供更加強大的讀寫功能。

處理流可以簡單的了解為:套在節點流管道的管子,可以對流過節點流的資料進行處理,過濾等操作。

InputStream 抽象類

  • 繼承自 InputStream的流都是用于向程式中輸入資料,且資料的機關為位元組。
  • 繼承自 InputStream的類有如下等:(加粗為節點流,未加粗為處理流)

FileInputStream

PipedInputStream

FilterInputStream

ByteArrayInputStream

SequenceInputStream

StringBufferInputStream

ObjectInputStream

  • InputStream的基本方法

    int read() throws IOException

    int read(byte[] buffer) throws IOException

    int read(byte[] buffer, int offset, int length) throws IOException

    void close() throws IOException

OutputStream 抽象類

  • 繼承自 OutputStream的流都是用于從程式中輸出資料,且資料的機關為位元組。
  • 繼承自 OutputStream的類有如下等:(加粗為節點流,未加粗為處理流)

FileOutputStream

PipedOutputStream

FilterOutputStream

ByteArrayOutputStream

ObjectOutputStream

  • OutputStream 基本方法

    void write(int b) throws IOException

    void write(byte[] b) throws IOException

    void write(byte[] b, int off, int len) throws IOException

    void flush() throws IOException // 在關閉輸出流之前使用,将輸出緩沖區的資料寫到目的地

Reader

  • 繼承自 Reader 的流都是用于程式從外部讀入資料,且資料的機關是字元(2個位元組)。
  • 如下為繼承自Reader的流。(加粗為節點流,未加粗為處理流)

BufferedReader

CharArrayReader

InputStreamReader

FilterReader

PipedReader

StringReader

  • Reader的基本方法 - 略

Writer

  • 繼承自 Writer的流都是用于程式向外部寫入資料,且資料的機關是字元(2個位元組)。
  • 如下為繼承自Writer的流。(加粗為節點流,未加粗為處理流)

BufferedWriter

CharArrayWriter

OutputStreamWriter

FilterWriter

PipedWriter

StringWriter

  • Writer的基本方法 - 略

節點流類型

類型 位元組流 字元流

File(檔案) FileInputStream / FileOutputStream FileReader / FileWriter

Memory Array ByteArrayInputStream / ByteArrayOutputStream CharArrayReader / CharArrayWriter

Memory String -- StringReader / StringWriter

Pipe(管道) PipedInputStream / PipedOutputStream PipedReader / PipedWriter

舉例1:FileInputStream

import java.io.*;
public class Test {
    public static void main(String[] args) {
        int b = 0;
        long num = 0;
        FileInputStream ln = null;

        try {
             ln = new FileInputStream("I:/Java/Demo/test.txt");
        } catch (FileNotFoundException e) {
            System.out.println("檔案不存在!");
            System.exit(-1);
        }

        try {
            while ((b=ln.read()) != -1) {
                System.out.print((char)b);
                num++;
            }
            ln.close();
            System.out.println();
            System.out.println("共讀出:" + num + "個位元組。");
        } catch (IOException e) {
            System.out.println("檔案讀取失敗!");
            System.exit(-1);
        }
    }
}           

舉例2:FileOutputStream

import java.io.*;
public class Test {
    public static void main(String[] args) {
        int b = 0;
        FileInputStream in = null;
        FileOutputStream out = null;

        try {
            in = new FileInputStream("I:/Java/Demo/test.txt");
            out = new FileOutputStream("I:/Java/Demo/newtest.txt");

            while((b=in.read()) != -1) {
                out.write(b);
            }
        } catch (FileNotFoundException e) {
            System.out.println("檔案不存在!");
            System.exit(-1);

        } catch (IOException e) {
            System.out.println("檔案複制失敗!");
            System.exit(-2);
        }
        System.out.println("檔案複制成功!");
    }
}           

處理流類型

處理類型 位元組流 字元流

Buffering BufferedInputStream/BufferedOutputStream BufferedReader/BufferedWriter

Filtering FilterInputStream/FilterOutputStream FilterReader/FilterWriter

Converting between bytes and character -- InputStreamReader/OutputStreamWriter

Object Serialization ObjectInputStream/ObjectOutputStream --

Data conversion DataInputStream/DataOutputStream --

Counting LineNumberInputStream LineNumberReader

Peeking ahead PushbackInputStream PushbackReader

Printing PrintStream PrintWriter

  • 緩沖流(以Buffered開頭)
  1. 緩沖流套接在相應的節點流上,對讀寫的資料提供了緩沖的功能,提高了讀寫的效率,同時增加了一些新的方法。

    BufferedReader / BufferedWriter / BufferedInputStream / BufferedOutputStream

  2. 緩沖流支援其父類的 mark 和 reset 方法。
  3. BufferedReader 提供了 readLine 方法用于讀取一行字元串(以\r或者\n分割)
  4. BufferedWriter 提供了 newLine 用于寫入一個行分隔符。
  5. 對于輸出的緩沖流,寫出的資料會先在記憶體中緩存,使用 flush 方法将會使記憶體中的資料立刻寫出。

舉例1:

import java.io.*;
public class Test {
    public static void main(String[] args) {
        int b = 0;
        FileInputStream in = null;
        BufferedInputStream bin = null;

        try {
            in = new FileInputStream("I:/Java/Demo/test.txt");
            bin = new BufferedInputStream(in);

            bin.mark(100);

            for(int i=0; (i<=10)&&(b=bin.read()) != -1; i++) {
                System.out.print((char)b);
            }

            bin.reset();
            System.out.println();

            for(int i=0; (i<=10)&&(b=bin.read()) != -1; i++) {
                System.out.print((char)b);
            }

        } catch (FileNotFoundException e) {
            System.out.println("檔案不存在!");
            System.exit(-1);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}           

舉例2:

import java.io.*;
public class Test {
    public static void main(String[] args) {
        String s = null;
        try {
            BufferedWriter bw = new BufferedWriter(new FileWriter("I:/Java/Demo/test.txt"));
            BufferedReader br = new BufferedReader(new FileReader("I:/Java/Demo/test.txt"));

            for(int i=0; i<100; i++) {
                s = Double.toString(Math.random());
                bw.write(s);
                bw.newLine();
            }

            bw.flush();

            while((s=br.readLine()) != null) {
                System.out.println(s);
            }

            bw.close();
            br.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
//0.7365309652822098
//0.577186775602007
//0.11736466966949166
//0.2998096440959087
//0.23859539950503672
//...           
  • 轉換流
  1. InputStreamReader 和 OutputStreamWriter 用于位元組資料到字元資料之間的轉換。
  2. InputStreamReader 需要和 InputStream 套接。
  3. OutputStreamWriter 需要和 OutputStream 套接。
  4. 轉換流在構造時可以指定其編碼結合,例如: InputStream is = new InputStreamReader(System.in, "ISO8859_1")
import java.io.*;
public class Test {
    public static void main(String[] args) {
        try {
            OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("I:/Java/Demo/test.txt"));
            osw.write("1234567890");
            System.out.println(osw.getEncoding());
            osw.flush();
            osw.close();

            osw = new OutputStreamWriter(new FileOutputStream("I:/Java/Demo/test.txt", true), "ISO8859_1");
            osw.write("abcdefghijklmn");
            System.out.println(osw.getEncoding());
            osw.flush();
            osw.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}           
import java.io.*;
public class Test {
    public static void main(String[] args) {
        try {
            String s = null;
            InputStreamReader isr = new InputStreamReader(System.in);
            BufferedReader br = new BufferedReader(isr);

            while((s = br.readLine()) != null) {
                if(s.equalsIgnoreCase("exit")) {
                    break;
                }
                System.out.println(s);
            }
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}           
  • 資料流
  1. DataInputStream 和 DataOutputStream 分别繼承自 InputStream 和 OutputStream,它屬于處理流,需要分别套接在 InputStream 和 OutputStream 類型的節點流上。
  2. DataInputStream 和 DataOutputStream 提供了可以存取與機器無關的 Java 原始類型資料(如:int,double 等)的方法。
  3. DataInputStream 和 DataOutputStream 的構造方法為:

    DataInputStream (InputStream in)

    DataOutputStream (OutputStream out)

資料流的作用?

比如,我們如何存儲一個很大的數到檔案?我們之前的方法是将其轉換成字元串存儲,可不可以直接存儲呢?資料流可以。它提供了很多方法用于存儲基礎資料類型的資料,參看 API 文檔。

import java.io.*;
public class Test {
    public static void main(String[] args) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 在記憶體自動生成一個 Byte[] 數組
        DataOutputStream dos = new DataOutputStream(baos);

        try {
            dos.writeDouble(Math.random());
            dos.writeBoolean(true);

            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            System.out.println(bais.available());

            DataInputStream dis = new DataInputStream(bais);

            System.out.println(dis.readDouble());
            System.out.println(dis.readBoolean());

            dos.close();
            dis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}           

問題:我們寫一個 double,寫一個boolean寫到哪裡了呢?

其實,在我們 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 的時候,就在記憶體中開辟了一塊空間,用來将程式要寫入的資料寫到這片記憶體中。

還有,在讀資料的時候,由于這片記憶體的配置設定是按照隊列的方式配置設定的,是以先寫進去的資料要先讀出來,如果如上面程式那樣,先讀boolean的話,就會讀取到double 8個位元組的第一個位元組上,切記!

  • Print 流(列印流)
  1. PrintWriter 和 PrintStream 都屬于輸出流,分别針對與字元和位元組。
  2. PrintWriter 和 PrintStream 提供了重載的 print println 方法用于多種資料類型的輸出。
  3. PrintWriter 和 PrintStream 的輸出操作不會抛出異常,使用者通過檢測錯誤狀态擷取錯誤資訊。
  4. PrintWriter 和 PrintStream 有自動 flush 功能。

    PrintWriter(PrintWriter out)

    PrintWriter(Writer out, boolean autoFlush)

    PrintWriter(OutputStream out)

    PrintWriter(OutputStream out, boolean autoFlush)

    PrintStream(OutputStream out)

    PrintStream(OutputStream out, boolean autoFlush)

import java.io.*;
public class Test {
    public static void main(String[] args) {
        PrintStream ps = null;

        try {
            FileOutputStream fos = new FileOutputStream("I:\\Java\\Demo\\test.txt");

            ps = new PrintStream(fos);

            if(null != ps) {
                System.setOut(ps);
            }

            for(int i=0; i<60000; i++) {
                System.out.print((char)i + " ");

                if(i%50 == 0) {
                    System.out.println();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}           

将輸出列印語句重定向到 test.txt 檔案中。

import java.io.*;

public class Test {
    public static void main(String[] args) {
        String filename = args[0];

        if (filename != null) {
            list(filename, System.out);
        }
    }

    public static void list(String f, PrintStream fs) {
        try {
            String s = null;
            BufferedReader br = new BufferedReader(new FileReader(f));

            while ((s = br.readLine()) != null) {
                fs.println(s);
            }
            br.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}           

将 args[0] 所寫的檔案名對應的檔案列印到終端。

舉例3:

import java.io.*;
import java.util.*;

public class Test {
    public static void main(String[] args) {
        String s = null;

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

        try {
            FileWriter fw = new FileWriter("I:/Java/Demo/log.txt", true);
            PrintWriter log = new PrintWriter(fw);

            while((s=br.readLine()) != null) {
                if(s.equalsIgnoreCase("exit")) {
                    break;
                }
                System.out.println(s);
                log.println("------");
                log.println(s);
                log.flush(); // 可以省略

            }
            log.println("--- " + new Date() + " ---");
            log.flush(); // 可以省略
            log.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}           

将從終端輸入的内容作為 log 寫到 log.txt 檔案裡面。

  • Object 流

Object 流的作用:當我們存儲一個元素的時候,(比如在畫圖軟體上畫了一個圓)我們的圓有很多屬性,原點位置,半徑大小,顔色,粗細等,既然每一個屬性都需要存儲,而且這些屬性都在一個 Object 對象裡面,那麼我們為什麼不直接寫整個 Object呢?(這叫序列化。)

注意:當你 new 一個 Object 的時候,不單單隻有你的屬性,還有一些比如 Object的版本号,this,super 等。

舉例:

import java.io.*;
public class Test {
    public static void main(String[] args) {
        TmpClass tm = new TmpClass();
        tm.a  =10;
        tm.d = false;

        try {
            FileOutputStream fos = new FileOutputStream("I:/Java/Demo/text.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);

            oos.writeObject(tm);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("I:/Java/Demo/text.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
            TmpClass t = (TmpClass) ois.readObject();
            System.out.println(t.a + " " + t.b + " " + t.c + " " + t.d);
        } catch (ClassNotFoundException c) {
            System.out.println("讀取檔案失敗");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class TmpClass implements Serializable {
    int a = 1;
    int b = 2;
    double c = 3.0;
    boolean d = true;
}           
  • Serializable 接口
  1. 可以被序列化的,如果想把一個類的對象,寫到硬碟或者網絡,想把這個對象序列化成一個位元組流的話,必須要實作這個接口。
  2. 這個接口沒有任何方法,是一個空接口,是一個标記性接口,它隻是給編譯器看的,編譯器看到後就知道這個類的對象時可以被序列化的,是可以整個寫入檔案的。
  • tansient關鍵字
  1. 使用方法,修飾成員變量。

    class TmpClass implements Serializable {

    int a = 1;

    int b = 2;

    tansient double c = 3.0;

    boolean d = true;

    }

  2. transient 表示透明的意思,就是說在一個類的對象序列化的時候不予考慮,就是将這個類的對象寫入檔案的時候,不寫這個被transient 修飾的成員變量,那麼我們再讀出來的時候就是預設值(如上,c 讀出來的時候是 0.0)
  • externalizable 接口
  1. 可以了解為個性化的 Serializable 接口,externalizable 接口是繼承 Serializable 接口得到的,相當于自己控制序列化的過程。(了解就好)

總結

  • 本章複習方法:把所有的名字,關鍵字寫到一張紙上,每天看看想想自己,給朕看半個時辰。—— 康熙