Java如何進行讀取文件(1)—IO流

一、FileInputStream、FileoutputStream

(一)FileInputStream

(1)read()方法

(a)不加改造的read()方法

        FileInputStream fis = null;
        try {
//             fis = new FileInputStream("C:\\Users\\趙曉東\\Desktop\\javaIO\\FileInputStream.txt");
//             /*相對路徑一定是從當前所在的位置作爲起點開始找*/
            /*IDEA默認的當前路徑是哪裏?*/
         fis = new FileInputStream("abc.txt");
           int readDate=  fis.read();
            System.out.println(readDate);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fis !=null){
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

我的abc文件中寫的是abc,讀取的結果爲:

97

可見它只能讀取一個,並且最後一定要在finally裏面寫一個關閉的方法。避免浪費資源。fis.close();l

(b)加上while循環的read()方法

如果我們想讓它一直讀取怎麼辦呢?這時候就要用到了循環。

        FileInputStream fis = null;
        try {
            fis = new FileInputStream("abc.txt");
            int readCount = 0;
            while (readCount !=-1){

                 readCount =fis.read();
                 if(readCount ==-1){
                     break;
                 }
                System.out.println(readCount);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fis!=null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

解釋:當它讀到最後會返回一個-1代表讀完了。

(c)讀取多個的read(byte[] b)

  • int
  • read(byte[] b)
  • 從該輸入流讀取最多 b.length個字節的數據爲字節數組。

 這樣可以一次最多讀取b.length個字節,減少了硬盤和內存的交互,提高程序的執行效率,往byte[]數組當中讀。

首先,我在zxd文件中寫了abcdef,int readCount表示返回的是讀取的數量。

        FileInputStream fis = null;
        try {
            fis = new FileInputStream("zxd");
            /*第一次讀*/
            byte[] bytes = new byte[4];
           int readCount = fis.read(bytes);
            System.out.println(readCount);
           /*第二次讀*/
            readCount = fis.read(bytes);
            System.out.println(readCount);
            /*第三次讀*/
            readCount = fis.read(bytes);
            System.out.println(readCount);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fis!=null){
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

讀取的結果

4
2
-1

並且在第二次讀的時候,會把前面ab會覆蓋了。下面我們用圖來模擬一下。

第一次讀的時候,會把abcdef中的abcd讀入到數組裏面。

第二次讀的時候,ef會把前面ab覆蓋了。 

(2)available()

這個表示返回流當中剩餘的沒有讀到的字節數量

    • int available()

      返回從此輸入流中可以讀取(或跳過)的剩餘字節數的估計值,而不會被下一次調用此輸入流的方法阻塞。

 

            FileInputStream fis = null;
            fis = new FileInputStream("zxd");
            int allNumber=fis.available();
            System.out.println(allNumber);
6

可見available返回的是文件的大小。正好可以通過它,傳入到數組中然後這樣就可以直接讀取了。

            FileInputStream fis = null;
            fis = new FileInputStream("zxd");
            int allNumber=fis.available();
            System.out.println(allNumber);
            byte [] bytes = new byte[allNumber];
            int number =fis.read(bytes);
            System.out.println(new String(bytes));
6
abcdef

這樣就可以通過avaliable方法獲得文件大小,然後再用數組進行聲明,直接用read方法進行讀取,就不會浪費,也不會因爲空間太小了。

(3)skip跳過幾個字節不讀取

  • long
  • skip(long n)
  • 跳過並從輸入流中丟棄 n字節的數據。

 

            FileInputStream fis = null;
            fis = new FileInputStream("zxd");
            fis.skip(3);
            System.out.println(fis.read());

跳過3個正好讀取的是d,也就是100了。

100

(二)FileoutputStream

(1)Wirte()方法進行寫

    • void write(byte[] b)

      b.length個字節從指定的字節數組寫入此文件輸出流。

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

      len字節從位於偏移量 off的指定字節數組寫入此文件輸出流。

也可以全寫進去,也可以只寫一部分。

      FileOutputStream fot = null;
        try {
            fot = new FileOutputStream("myfile");
            byte [] bytes = {97,98,99,100};
            fot.write(bytes);
            /*一定要進行刷新*/
            fot.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fot.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

 這時候,我們會看見idea裏面多了一個myfile文件,裏面還有abcd

如果沒有myfile文件會新建,如果有則直接輸入。輸入的時候會把源文件情況,再輸入。所以謹慎使用。

下面這種方式是以追加的方式在末尾寫入,不會情況源文件內容。

 這時候我們會發現myfile文件中追加了abcd

 將字符串轉換成數組的形式進行追加

 

三、FileReader、FileWriter

 文件字符輸入流,只能讀取普通文本,讀取文本內容時,比較方便,快捷。這次就變成了char[]數組了。他們兩個的方法和FileInputStream和FinleoutputStream一樣。

四、帶有緩衝區的字符流(這時候就不需要char byte數組了,自帶有緩衝區)

(一)BufferedReader

帶有緩衝區的字符輸入流。使用這個流的時候,不需要自定義char數組,或者說不需要自定義byte數組。自帶緩衝。

從構造方法可以看出,如果要實例一個BufferedReader,裏面需要傳入一個流。

        FileReader reader = new FileReader("zxd");
        /**/
        BufferedReader br = new BufferedReader(reader);

        /*關閉流*/
        br.close();

當一個流的構造方法中,需要一個流的時候,被傳進來的流叫做節點流。外部負責包裝的流,叫做:包裝流。還有一個名字叫做:處理流。

從源代碼可以看出,當我們關閉流的時候,只需要關閉最後一個即可。

 bufferedReader.readLine()能讀一行。但是不帶換行符。

 (a)如果字節流想傳進入怎麼辦?

這時候需要通過InputStreamReader

 這時候報錯,的原因是字節流不能作爲BufferedReader的參數,所以這時候需要InputStreamReader

(1) readline()

        FileReader reader = new FileReader("zxd");
        /**/
        BufferedReader br = new BufferedReader(reader);
        String s1 =br.readLine();
        System.out.println(s1);
        /*關閉流*/
        br.close();
abcdef

readline()讀取直接讀取一行。

接下來,我們可以對它進行循環。

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

但是readline()不帶換行符。

(2)當我們想往BufferedReader中傳入字節流可以嗎?(FileInputStream)

可以看出,是不可以的。 

這個時候需要使用轉換流了。(將字節流轉換成字符流)

java.io.InputStreamReader
java.io.outputStreamWriter

通過 構造方法可以看出InputStreamReader裏面可以傳入InputStream的。

 這個時候就不報錯了。

(二)BufferedWriter

        FileWriter f1 = new FileWriter("zjy");
        BufferedWriter br = new BufferedWriter(f1);
        /*進行文件的寫工作*/
        br.write("hahah");
        /*刷新*/
        br.flush();
        /*關閉包裝流/
         */
        br.close();

當然,你想追加,可以構造方法哪裏寫true。同樣,如果想用字節流(Inputoutstream)也是需要轉換流的。(outputStreamReader)

五、PrintStream標準的字節輸出流(默認輸出到控制檯)

我們平時輸出的:

System.out.println("Hello World");

其實,就是這個字節輸出流

PrintStream ps = System.out
ps.println("Hello World");

當然它不需要關閉close().

那麼可以輸出改變方向嗎?

            /*指向一個日誌文件*/
        PrintStream out = null;
        try {
            out = new PrintStream(new FileOutputStream("log.txt",true));
            /*改變輸出方向*/
            System.setOut(out);
            /*日期當前時間*/
            Date nowTime = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
            String strTime = sdf.format(nowTime);

            System.out.println(strTime +":"+msg);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
public class LogTest {
    public static void main(String[] args) {
        /*測試工具類*/
        Logger.log("調用gc,啓動垃圾回收");
        Logger.log("用戶嘗試進行登錄");
    }
}

可以,以上就是日誌工具的實現原理。

六、對象專屬的流ObjectInputStream、ObjectOutputStream

(1)序列化ObjectOutputStream

對象的序列化(Serialize)和反序列化(DeSerialize)

將內存中的Java對象放到硬盤文件上叫序列化,是因爲內存中的Java對象切割成一塊一塊的並且編號,放到硬盤文件上。

 如果從硬盤文件恢復到內存中,那麼叫反序列化。

一個是拆分一個是組裝。 

如果需要序列化就是需要ObjectOutputStream了。如果反序列化就是需要ObjectInputStream了。

下面用個例子說明一下:

首先寫一個Student類

public class Student {
    private  int no;
    private  String name;

    public Student(int no, String name) {
        this.no = no;
        this.name = name;
    }

    public Student(int no) {
        this.no = no;
    }

    public Student(String name) {
        this.name = name;
    }

    public Student() {
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
    public static void main(String[] args) throws  Exception{
        /*進行序列化例子*/
        Student s1 = new Student(1,"趙曉東");
        /*通過流去給一個文件students爲文件的名字*/
        FileOutputStream f1 = new FileOutputStream("Students");
        /*調用ObjectOutputStream*/
        ObjectOutputStream o1 = new ObjectOutputStream(f1);
        /*調用方法進行序列化*/
        o1.writeObject(s1);
        /*刷新*/
        o1.flush();
        /*關閉流*/
        o1.close();
    }
}
Exception in thread "main" java.io.NotSerializableException: com.javase.IO.Student
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.javase.IO.ObjectOutputStreamTest.main(ObjectOutputStreamTest.java:16)

這時候我們看見會報錯。這個異常的意思是Student不支持序列化。然後我們讓student去實現Serializable

這時候會有Students文件,並且是亂碼的形式

 

所以參與序列化和反序列化的對象必須實現Serializable接口 

這個接口當中什麼都沒有,那麼它起到什麼作用呢?起到了標識的作用,JVM看見了它會特殊待遇。JAVA中有兩種接口,一種是普通接口,另一種是標誌接口,標識接口是給JVM虛擬機看的。

JVM看到Serializable會自動生成一個序列化版本號。

(2)反序列化ObjectInputStream

    public static void main(String[] args) throws  Exception{
        /*創建流讀取文件*/
        FileInputStream f1 = new FileInputStream("Students");
        /*進行反序列化*/
        ObjectInputStream o1 = new ObjectInputStream(f1);
        /*調用方法*/
        Object ob =o1.readObject();
        System.out.println(ob);
        o1.close();

    }
}

(3)能不能一次序列化多個對象

可以,可以將序列化放到集合當中,序列化集合。

User類

public class User implements Serializable {
    private  int id;
    private  String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public User() {
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

序列化

    public static void main(String[] args)  throws  Exception{
        /*創建集合*/
        List l1 = new ArrayList<>();
        l1.add(new User(1,"哈哈哈"));
        l1.add(new User(2,"呵呵呵"));
        l1.add(new User(3,"嘿嘿嘿"));
//        通過流去讀取
        FileOutputStream f1 = new FileOutputStream("Users");
//        序列化
        ObjectOutputStream o1 = new ObjectOutputStream(f1);
//        調用序列化方法
        o1.writeObject(l1);
        o1.flush();
        o1.close();

    }

結果

(4)transient表示不參加序列化操作

private transient String name;

(5)關於序列化版本號

Java虛擬機看到Serializable接口之後,會自動生成了一個序列化版本號,這裏沒有手動手寫處理,java虛擬機會默認提供這個序列化版本號。

java語言中採用什麼機制來區分類的?

第一:首先通過類名進行區分,如果類名不一樣,肯定不是一個類。

第二:如果類名一樣,再怎麼進行類的區別?靠序列化版本號進行區分。

這種自動生成序列化版本號有什麼缺陷?

一旦自動生成的序列化版本號缺點是:一旦代碼確定之後,不能進行後續的修改,因爲只要修改,必然會重新編譯,此時會生成全新的序列化版本號,這個時候java虛擬機會認爲這是一個全新的類。

最終結論:

凡是一個類實現了Serializable接口,建議給該類提供一個固定不變的序列化版本號,這樣,以後這個類即使代碼修改了,但是版本號不變,java虛擬機會認爲是同一個類。

所以建議將序列化版本號手動的寫出來,不建議自動生成。

private static final long serialVersionUID=1L;

 總結:記得在一級的時候,不知道序列化是幹什麼的,不知道怎麼去讀取學生的文件的,現在終於是明白了。另外我發現流在讀取的時候和集合的遍歷很相似。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章