1. 程式人生 > 實用技巧 >Java學習之檔案io流篇

Java學習之檔案io流篇

Java學習之檔案io流篇

0x00 前言

在平時的一些開發中,普遍都會讓指令碼執行的時候,去儲存一些指令碼執行結果的資料,例如開發一個爬蟲,這時候我們爬取下來的內容,就需要儲存到本地,那麼這時候就會用到一些操作檔案的類。

0x01 File 類

File類主要用於檔案和目錄建立、查詢、刪除等操作的。

先來檢視他的構造方法

public File(String pathname) :通過將給定的路徑名字串轉換為抽象路徑名來建立新的 File例項。
public File(String parent, String child) :從父路徑名字串和子路徑名字串建立新的 File例項。
public File(File parent, String child) :從父抽象路徑名和子路徑名字串建立新的 File例項。

常用方法:

public String getAbsolutePath() :返回此File的絕對路徑名字串。
public String getPath() :將此File轉換為路徑名字串。
public String getName() :返回由此File表示的檔案或目錄的名稱。
public long length() :返回由此File表示的檔案的長度。

程式碼例項:

public static void main(String[] args) {
        String Pathname = "a.txt";
        File abc = new File(Pathname);
        System.out.println(abc.getAbsolutePath());
        String Pathname1 = "a\\a.txt";
        File abc1 = new File(Pathname1);
        System.out.println(abc1.getAbsolutePath());
        System.out.println(abc1.getPath());
        System.out.println(abc1.length());
    }

判斷方法:

public boolean exists() :此File表示的檔案或目錄是否實際存在。
public boolean isDirectory() :此File表示的是否為目錄。
public boolean isFile() :此File表示的是否為檔案。
    public static void main(String[] args) {
        String Pathname = "a.txt";
        File abc = new File(Pathname);
        boolean a = abc.exists();
        System.out.println(a);
        System.out.println(abc.isFile());
        System.out.println(abc.isDirectory());
    }

增刪功能方法

public boolean createNewFile() :當前僅當具有該名稱的檔案尚不存在時,建立一個新的空檔案。
public boolean delete() :刪除由此File表示的檔案或目錄。
public boolean mkdir() :建立由此File表示的目錄。
public boolean mkdirs() :建立由此File表示的目錄,包括任何必需但不存在的父目錄。
public static void main(String[] args) throws IOException {
        String Pathname = "a.txt";
        File abc = new File(Pathname);
        boolean file = abc.createNewFile();
        System.out.println(file+"\n"+abc.getAbsolutePath());


       File abc1 = new File("abc");
       abc1.mkdir();

       File abc2 = new File("abc\\abc");
       abc2.mkdirs();
    }

如果是對目錄進行刪除,刪除的目錄必須為空才能進行刪除。

目錄遍歷

在file裡面給我們提供了,可以直接獲取file目錄下面所有子檔案或目錄。

public String[] list() :返回一個String陣列,表示該File目錄中的所有子檔案或目錄。
public File[] listFiles() :返回一個File陣列,表示該File目錄中的所有的子檔案或目錄。

程式碼例項:

public static void main(String[] args) throws IOException {
        String Pathname = "../";
        File abc = new File(Pathname);
        String[] name = abc.list();
        for (String s : name) {
            System.out.println(s);
        }
    }

使用list方法獲取所有檔案,然後使用增強for迴圈進行遍歷。

0x02 IO流概述

IO流概述

java裡面的io流指的是對一些檔案內容做一個輸入輸出的作用。也就是input和output,對檔案進行讀取和輸入資料的操作。

input:把資料從其他裝置上讀取到記憶體的流

output:把資料從記憶體寫出到其他裝置的流。

位元組流

在計算機裡面所有的文字資料包括圖片、文字、視訊這些在儲存的時候,都是以二進位制的形式進行村粗的。位元組流是以一個位元組為單位,讀寫資料的流。

字元流

以一個字元為單位,讀寫資料的流

0x03 位元組流輸出流

OutputStream抽象類是位元組輸入流的超類,他定義了幾個共性的方法。

public void close() :關閉此輸出流並釋放與此流相關聯的任何系統資源。
public void flush() :重新整理此輸出流並強制任何緩衝的輸出位元組被寫出。
public void write(byte[] b) :將 b.length位元組從指定的位元組陣列寫入此輸出流。
public void write(byte[] b, int off, int len) :從指定的位元組陣列寫入 len位元組,從偏移量 off開始輸
出到此輸出流。
public abstract void write(int b) :將指定的位元組輸出流。

在操作完成後,必須使用close方法將資源釋放。

FileOutputStream 類

FileOutputStream類是檔案輸出流,用於將資料些出到檔案當中。

檢視構造方法:

public FileOutputStream(File file) :建立檔案輸出流以寫入由指定的 File物件表示的檔案。
public FileOutputStream(String name) : 建立檔案輸出流以指定的名稱寫入檔案。

如果建立一個io流的物件,必須傳入檔案的路徑,,如果沒有該檔案就會建立該檔案,如果有就會清空原本有的資料。

程式碼:

public static void main(String[] args) throws IOException {
        File file = new File("a.txt");
        FileOutputStream FileoutDate = new FileOutputStream(file);
        FileoutDate.write(97);
        FileoutDate.close();
    }

這裡寫進入一個97,但是開啟檔案會發現寫進入的變成了a,這是因為我們這裡是以位元組進行寫入的,,而97的ascii轉換為字元後,就是a這個字元。

這裡還可以指定寫出的資料長度。

public class FileOutput {
    public static void main(String[] args) throws IOException {
        File file = new File("a.txt");
        FileOutputStream FileoutDate = new FileOutputStream(file);
        byte[] b = "abce3".getBytes();
        FileoutDate.write(b,2,2);
        FileoutDate.close();
    }
    }

這裡要先獲取字元的位元組型別資料,使用write寫入,從第二位開始索引,寫入2個位元組。

在程式開發中,有些資料可能沒法一次執行獲取所有結果,這時候我們如果以上面的方式來迴圈寫入執行結果的話,每次迴圈就都會被清空一次,只獲得最後一次的執行結果。
那麼這時候我們就可以使用到追加,把它追加進入,而不是直接覆蓋重寫。

public FileOutputStream(File file, boolean append) : 建立檔案輸出流以寫入由指定的 File物件表示的
檔案。
public FileOutputStream(String name, boolean append) : 建立檔案輸出流以指定的名稱寫入檔案。

程式碼:

 public static void main(String[] args) throws IOException {
        File file = new File("a.txt");
        FileOutputStream FileoutDate = new FileOutputStream(file,true);
        byte[] b = "abce3".getBytes();
        FileoutDate.write(b,2,2);
        FileoutDate.close();
    }

這幾行程式碼和前面的相同,只是在FileOutputStream 構造方法裡面傳入一個ture,表示使用追加模式,該模式預設為false。

0x04 位元組輸入流

InputStream抽象類是位元組輸入流的超類。可以讀取位元組資料到記憶體中。

共性方法:

public void close() :關閉此輸入流並釋放與此流相關聯的任何系統資源。
public abstract int read() : 從輸入流讀取資料的下一個位元組。
public int read(byte[] b) : 從輸入流中讀取一些位元組數,並將它們儲存到位元組陣列 b中 。

FileInputStream 類

FileInputStream是檔案輸入流,從檔案中讀取位元組到記憶體中。

構造方法:

FileInputStream(File file) : 通過開啟與實際檔案的連線來建立一個 FileInputStream ,該檔案由檔案系
統中的 File物件 file命名。
FileInputStream(String name) : 通過開啟與實際檔案的連線來建立一個 FileInputStream ,該檔案由檔案
系統中的路徑名 name命名。

程式碼:

 public static void main(String[] args) throws IOException {
        File file = new File("a.txt");
        FileInputStream fileinputdata = new FileInputStream(file);
        int read = fileinputdata.read();
        System.out.println((char) read);
        read = fileinputdata.read();
        System.out.println((char) read);
        read = fileinputdata.read();
        System.out.println(read);
        fileinputdata.close();
    }

使用read方法讀取完後,地址會往後推一位,知道讀取到沒有,會返回-1。

以上的方法都是讀取單個位元組,我們可以定義一個位元組型別的數值,然後讓他每次讀取我們指定的長度。

程式碼:

    public static void main(String[] args) throws IOException {
        File file = new File("a.txt");
        FileInputStream fileinputdata = new FileInputStream(file);
        int len;
        byte[] b = new byte[2];
        while ((len=fileinputdata.read(b))!=-1){
            System.out.println(new String(b,0,len));
        }
        fileinputdata.close();
    }

這裡定義了b變數用來接收每次讀取的資料產的長度,然後定義一個len變數,接收每次讀取的資料,這裡可以直接把賦值放在迴圈條件裡面,如果賦值的變數不等於-1,就一直迴圈,知道讀取到-1,停止迴圈,前面說到如果沒有資料讀取會輸出返回一個-1,結束迴圈。

0x05 字元流

在位元組讀寫的時候,一些中文字元讀寫可能會顯示亂碼。因為一箇中文字元可能佔用多個位元組。所以在一些讀寫的是字元資料的話,可以使用字元流來處理該資料。

字元輸入流

Reader抽象類是表示用於讀取字元流的超類,可以讀取字元資訊到記憶體中。

共性方法:

public void close() :關閉此流並釋放與此流相關聯的任何系統資源。
public int read() : 從輸入流讀取一個字元。
public int read(char[] cbuf) : 從輸入流中讀取一些字元,並將它們儲存到字元陣列 cbuf中 。

FileReader 類

構造方法:

FileReader(File file) : 建立一個新的 FileReader ,給定要讀取的File物件。
FileReader(String fileName) : 建立一個新的 FileReader ,給定要讀取的檔案的名稱。

建立一個流物件,必須傳入一個檔案路徑。

構造方法定義程式碼:

 public static void main(String[] args) throws IOException {
    File file = new File("a.txt");
        FileReader filereader = new FileReader(file);
    }
或者:
public static void main(String[] args) throws IOException {
        
        FileReader filereader = new FileReader("a.txt");
    }

讀取單個字元資料:

public static void main(String[] args) throws IOException {

        FileReader filereader = new FileReader("a.txt");
        int b;
        while ((b = filereader.read())!=-1){
            System.out.println((char)b);
        }
    }

讀取多個字元資料:

 public static void main(String[] args) throws IOException {

        FileReader filereader = new FileReader("a.txt");
        int b;
        char[] buf = new char[2];
        while ((b = filereader.read(buf))!=-1){
            System.out.println(new String(buf));
        }
    }

字元輸出流

Writer抽象類是字元輸出流的超累,將知道的字元資訊寫出到指定地方。

共性方法:

void write(int c) 寫入單個字元。
void write(char[] cbuf) 寫入字元陣列。
abstract void write(char[] cbuf, int off, int len) 寫入字元陣列的某一部分,off陣列的開始索引,len
寫的字元個數。
void write(String str) 寫入字串。
void write(String str, int off, int len) 寫入字串的某一部分,off字串的開始索引,len寫的字元個
數。
void flush() 重新整理該流的緩衝。
void close() 關閉此流,但要先重新整理它。

FileWriter 類

FileWriter類是寫出字元到檔案中的一個類,,構造時候使用預設的字元編碼和預設的位元組緩衝區。

構造方法:

FileWriter(File file) : 建立一個新的 FileWriter,給定要讀取的File物件。
FileWriter(String fileName) : 建立一個新的 FileWriter,給定要讀取的檔案的名稱。

寫出資料程式碼:

public static void main(String[] args) throws IOException {

        FileWriter filewriter = new FileWriter("a.txt");
        filewriter.write("二狗");
        filewriter.write(97);
        filewriter.write(156);
        filewriter.flush();
        filewriter.close();
    }

這裡的程式碼,如果不寫flush方法,也不寫close是沒法寫入資料的。
來看看這2個方法的區別:

flush :重新整理緩衝區,流物件可以繼續使用。
close :先重新整理緩衝區,然後通知系統釋放資源。流物件不可以再被使用了。

0x06 快取流

位元組流和字元流每次讀寫都會訪問硬碟,當讀寫頻率增加其訪問效率不高
而使用快取流讀取時會將大量資料先讀取到快取中,以後每次讀取先訪問快取,直到快取讀取完畢再到硬碟讀取,快取流寫入資料也是一樣,先將資料寫入到快取區,直到快取區達到一定的量,才把這些資料一起寫道硬碟中去,這樣減少了IO操作。

位元組緩衝流: BufferedInputStream , BufferedOutputStream
字元緩衝流: BufferedReader , BufferedWriter

位元組快取輸出流

構造方法:

public BufferedInputStream(InputStream in) :建立一個 新的緩衝輸入流。
public BufferedOutputStream(OutputStream out) : 建立一個新的緩衝輸出流。

程式碼:

    public static void main(String[] args) throws IOException {

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("A.txt"));

        int b ;
        while ((b = bis.read())!=-1){
            bos.write(b);
        }
    }

上面的程式碼使用了位元組快取流的方式來進行了一個檔案的複製貼上,如果是使用位元組輸出流的方式是很慢的。而使用位元組快取流的方式讀寫的速度會很快。

如果我們想讓讀寫更快的話,可以定義一個每次讀寫的長度,讓他每次讀寫固定到的長度。

    public static void main(String[] args) throws IOException {

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("A.txt"));

        int b ;
        byte[] bytes = new byte[8*1024];
        while ((b = bis.read(bytes))!=-1){
            bos.write(bytes,0,b);
        }
    }

每次讀寫8*1024個位元組,減少檔案讀寫的時間。

字元快取流

構造方法:

public BufferedReader(Reader in) :建立一個 新的緩衝輸入流。
public BufferedWriter(Writer out) : 建立一個新的緩衝輸出流。

程式碼:

public static void main(String[] args) throws IOException {
        BufferedReader bis = new BufferedReader(new FileReader("a.txt"));
            String b = null ;
            while ((b = bis.readLine())!=null){
                System.out.println(b);
            }
            bis.close();
    }

使用readline讀取當行資料。

0x07 序列化與反序列化

概述

java提供了一種物件序列化的機制,用一個位元組序列表示一個物件,該位元組包含物件的資料、物件的型別、物件的儲存屬性。位元組序列寫出到檔案後,相當於可以持久報錯了一個物件資訊,這過程叫做序列化。

而反過來,將儲存在檔案的位元組序列從檔案中讀取出來,重構物件,重新用來建立物件,這步驟叫做反序列化。

總結:簡單來講就是將一個物件,寫入檔案當中,而反序列化就是將寫入檔案的物件,讀取出來。

ObjectOutputStream 類

ObjectOutputStream 類,將Java物件的原始資料型別寫出到檔案,實現物件的持久儲存。也就是對物件進行序列化的一個類。

構造方法:

public ObjectOutputStream(OutputStream out)

注意事項:

1.使用該類必須實現Serializable 介面,Serializable是一個標記介面,不實現此介面的類將不會時任何狀態序列化或者是放序列化,會丟擲NotSerializableException的異常。

2.該類的所有的屬性必須時可以序列化的,如果有一個屬性不需要序列化的,測該屬性必須標明時瞬態的,使用transient修飾符。

程式碼:

建立一個類:

public class Method implements Serializable {
    public String name;
    public String address;
    public transient int age;
    public void method(){
        System.out.println(name +address);
    }

}

main方法:

public static void main(String[] args) {
        Method e = new Method();
        e.name = "zhangsan";
        e.address = "beiqinglu";
        e.age = 20;

        try {
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("a.txt"));
            out.writeObject(e);
            out.close();
            System.out.println("ok");

        } catch (IOException ioException) {
            ioException.printStackTrace();
        }finally {
        }
    }

檢視a.txt檔案可以看到顯示的字元都是亂碼,這是因為將物件序列化了,寫進入的是一個物件,而不是一些字元。

ObjectInputStream類

ObjectInputStream反序列化流,將之前使用ObjectOutputStream序列化的原始資料恢復為物件

構造方法:

public ObjectInputStream(InputStream in) : 建立一個指定InputStream的ObjectInputStream。

反序列化方法1

如果我們需要對序列化的物件,進行反序列化,將他從檔案讀取出來還原成物件那麼我們這裡呼叫ObjectOutputStream的readobject方法,讀取一個物件。

public final Object readObject () : 讀取一個物件。

程式碼:

 public static void main(String[] args) {
        Method e = null;
        try {
            FileInputStream fis = new FileInputStream("a.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
             e = (Method) ois.readObject();
            ois.close();
            fis.close();
        } catch (IOException | ClassNotFoundException ioException) {
            ioException.printStackTrace();
        }
        System.out.println("name="+e.name);
        System.out.println("address ="+e.address);
        System.out.println("age="+e.age);
    }

反序列化方法2

當jvm方序列化物件的時候,能找到class屬性,,但是class檔案在序列化之後修改了,那麼反序列化操作肯定是失敗的,會丟擲InvalidClassException。

丟擲InvalidClassException的一次原因有3種:

1.該類的序列版本號與從流中讀取的類描述符的版本號不匹配

2.該類包含未知資料型別

3.該類沒有可訪問的無引數構造方法

Serializable 介面給需要序列化的類,提供了一個序列版本號。 serialVersionUID 該版本號的目的在於驗證序
列化的物件和對應類是否版本匹配。

public class Employee implements java.io.Serializable {
// 加入序列版本號
private static final long serialVersionUID = 1L;
public String name;
public String address;
// 新增新的屬性 ,重新編譯, 可以反序列化,該屬性賦為預設值.
public int eid;
public void addressCheck() {
System.out.println("Address check : " + name + " ‐‐ " + address);
}
}

0x08 總結


File類:用於建立刪除檔案與資料夾。

FileoutputStream:位元組輸出流,用於將位元組寫出到檔案中。

FileinputStream:位元組輸出流,用於讀取檔案中的位元組。

FileReader類是表示用於讀取字元流,可以讀取字元資訊到記憶體中。

FileWriter類是寫出字元到檔案中的一個類,,構造時候使用預設的字元編碼和預設的位元組緩衝區。

位元組緩衝流: BufferedInputStream , BufferedOutputStream

字元緩衝流: BufferedReader , BufferedWriter

0x09 結尾

這篇文章的內容比較多,在後面也寫了個簡單的小總結,這篇也寫了我2天左右。後面去深入研究一下歷史爆出的一些反序列化漏洞。