1. 程式人生 > 實用技巧 >Java(25)IO流和File類

Java(25)IO流和File類

IO流+File類

File類

講IO流之前先來講以下File類。Java的標準庫Java.io提供了File類來操作檔案和目錄。操作可以有:新建、刪除、重新命名等,但是不能訪問檔案本身的內容,如果想要訪問,需要使用IO流。

新建File物件:

package day01;

import java.io.File;

class Battery{
    public static void main(String[] args) {
        File a=new File("C:\\Users\\97464\\Desktop\\test\\File1.txt");

        File b=new File("C:\\Users\\97464\\Desktop","test\\File1.txt");

        /*
        1.上面兩種方式建立的FIle物件是一個意思,File類的不同構造方法導致可以有不同的引數。
        2.構造File物件的時候,既可以傳入絕對路徑,也可以傳入相對路徑。
        3.注意Windows平臺使用\作為路徑分隔符,在Java字串中需要用\\表示一個\。Linux平臺使用/作為路徑分隔符。傳入相對路徑時,相對路徑前面加上當前目錄就是絕對路徑。
        4.   .代表當前目錄..代表上級目錄
         */
    }
}

獲取檔案或者目錄的路徑和名字等操作

package day01;

import java.io.File;
import java.io.IOException;

class Battery{
    public static void main(String[] args){
        File tt=new File("F:\\test\\test\\..\\hello.txt");
        //獲取檔案或者目錄的路徑:
        System.out.println(tt.getPath());  //返回構造方法傳入的路徑
        System.out.println(tt.getAbsolutePath());  //返回絕對路徑
        try {
            System.out.println(tt.getCanonicalPath());  
            //返回規範路徑(C:\\Users\\..----->C:\\)
        }catch (IOException e){
            e.printStackTrace();
        }
        //獲取檔案或者目錄的名字:
        System.out.println(tt.getName());

        //返回一個用當前檔案的絕對路徑構建的File物件;
        File gg=tt.getAbsoluteFile();
        System.out.println(gg.getName());

        //返回檔案或目錄的父級目錄:
        System.out.println(tt.getParent());

        //重新命名檔案或目錄:
        tt.renameTo(new File("F:\\test\\test\\..\\hello2.txt"));
    }
}
/*執行結果為:
F:\test\test\..\hello.txt
F:\test\test\..\hello.txt
F:\test\hello.txt
hello.txt
hello.txt
F:\test\test\..
*/

/*----------------補充:--------------------------------
上面的getCanonicalPath()方法,如果去檢視它的原始碼可以發現它丟擲了IOException異常,如果想要使用它,需要在呼叫的時候try...catch捕獲異常,或者由main()繼續丟擲異常,交給JVM處理。
*/

檔案檢測

package day01;

import java.io.File;

class Battery{
    public static void main(String[] args){
        File a=new File("F:\\test\\hello2.txt");
        //判斷檔案或者目錄是否存在:
        System.out.println(a.exists());

        //判斷檔案是否可讀或可寫:
        System.out.println(a.canRead());
        System.out.println(a.canWrite());

        //判斷當前File物件是不是檔案或者目錄:
        System.out.println(a.isFile());
        System.out.println(a.isDirectory());
    }
}
/*執行結果為:
true
true
true
true
false
*/

獲取檔案大小和檔案的最後修改時間

package day01;

import java.io.File;

class Battery{
    public static void main(String[] args){
        File a=new File("F:\\test\\hello2.txt");
        System.out.println(a.length()); //返回檔案的大小,以位元組為單位
        System.out.println(a.lastModified());  //返回最後修改時間,是一個毫秒數
    }
}
/*執行結果為:
11
1580719765617
*/

新建和刪除檔案或目錄

package day01;

import java.io.File;
import java.io.IOException;

class Battery{
    public static void main(String[] args){
        File a=new File("F:\\test\\hello.txt");
        if(!a.exists()){ //判斷檔案是否不存在
            try{
                a.createNewFile();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
        a.delete(); //刪除檔案

        File b=new File("F:\\test2");
        b.mkdir(); //建立單層目錄

        File c=new File("F:\\test3\\kobe\\number24");
        c.mkdirs();//建立多層目錄
    }
}

遍歷目錄下的檔案和子目錄

package day01;

import java.io.File;

class Battery{
    public static void main(String[] args){
        File d=new File("F:\\test3");
        System.out.println(d.list().getClass());
        //可以看到d.list()返回的是資料型別是class [Ljava.lang.String;
        //[:表示返回的型別是陣列,L:表示陣列元素是一個物件例項 
        // Java.land.String表示物件是String型別
        //下面進行迴圈輸出目錄下的檔案或者子目錄:
        String []strArr=d.list();
        for (String i:strArr){
            System.out.println(i);
        }

        File e=new File("F:\\test3");
        File [] arrFile=e.listFiles(); 
        //e.listFiles()返回test3目錄下的檔案或子目錄的file物件
        for (File i: arrFile){
            System.out.println(i);
        }
    }
}
/*執行結果為:
class [Ljava.lang.String;
kobe
F:\test3\kobe
*/

上面遍歷檔案和子目錄是單層遍歷,如果想要進行多層遍歷(子目錄的子目錄也會被遍歷),可以進行遞迴遍歷。

  • 案例:
package day01;

import java.io.File;

class Rabbit{
    public void fds(File a){
        if(a.isFile()){
            System.out.println(a.getName());
        }else {
            System.out.println(a.getName());
            File []fileArr=a.listFiles();
            for (File i:fileArr){
                fds(i); //遞迴
            }
        }
    }
}
class RunClass{
    public static void main(String[] args) {
        Rabbit test=new Rabbit();
        File tf=new File("F:\\test3");
        test.fds(tf);
    }
}

IO流

IO概念:

IO是指Input/Output,即輸入和輸出,以記憶體為中心:

  • Input指從外部讀入資料到記憶體。----例如把檔案從磁碟讀取到記憶體,從網路讀取資料到記憶體等
  • Output指把資料從記憶體輸出到外部。----例如把資料從記憶體寫入檔案,把資料從記憶體輸出到網路等

為什麼要以記憶體為中心?

------因為資料被讀取到記憶體中才能被處理,程式碼是在記憶體中執行的,資料也必須記憶體。

IO流概念:

IO流是一種順序讀寫資料的模式,它的特點是單向流動。

------例如: 想要把一張圖片放入一個資料夾,不能整張直接塞進去,而是需要把圖片轉化為一個數據集(例如二進位制),把這些資料一點一點傳到資料夾,這個資料的傳遞類似於自來水在水管中的流動,稱為IO流。

同步和非同步

同步IO是指,讀寫IO時程式碼必須等待資料返回後才繼續執行後續程式碼,它的優點是程式碼編寫簡單,缺點是CPU執行效率低。

而非同步IO是指,讀寫IO時僅發出請求,然後立刻執行後續程式碼,它的優點是CPU執行效率高,缺點是程式碼編寫複雜。

Java標準庫的包java.io提供了所有的同步IO,而java.nio則是非同步IO。

下面我們即將討論的InputStreamOutputStreamReaderWriter都是同步IO的抽象類的具體實現類

流的分類

按操作資料單位不同分為:位元組流(8 bit),字元流(16 bit)

按資料流的流向不同分為:輸入流,輸出流

按流的角色不同分為:節點流,處理流

抽象基類 位元組流 字元流
輸入流 InputStream Reader
輸出流 OutputStream Writer

Java的IO流共有40多個相關類,實際上都是從上面的四種抽象基類派生的。

位元組流

IO流以byte(位元組)為最小單位,稱為位元組流。位元組流非常通用,不僅可以用來操作字元型文件,還可以操作任何地其它型別檔案(圖片、壓縮包等),因為位元組流本身使用地就是二進位制。

╔════════════╗
║   Memory   ║ 記憶體
╚════════════╝
       ▲
       │0x48
       │0x65
       │0x6c
       │0x6c
       │0x6f
       │0x21
 ╔═══════════╗
 ║ Hard Disk ║ 硬碟
 ╚═══════════╝
 //上面,記憶體從硬碟(磁碟)讀入了6個位元組的資料,是按順序讀入的,是輸入位元組流。
 //反過來就是輸出位元組流:
 ╔════════════╗
 ║   Memory   ║ 記憶體
 ╚════════════╝
       │0x21
       │0x6f
       │0x6c
       │0x6c
       │0x65
       │0x48
       ▼
 ╔═══════════╗
 ║ Hard Disk ║ 硬碟
 ╚═══════════╝
InputStream

InputStream是Java標準庫提供的最基本的輸入位元組流。位於Java.io這個包裡。InputStream是一個抽象類,是所有輸入流的父類,這個抽象類定義的最重要的方法是int read()。原始碼如下:

public abstract int read() throws IOException;

構建InputStream物件:

import java.io.IOException;
import java.io.InputStream;

class MainClass{
    public static void main(String[] args) {
        InputStream is=new InputStream() {
            @Override  //可以看到新建InputStream輸入流物件必須重寫抽象方法int read()
            public int read() throws IOException {
                return 0;
            }
        };
    }
}

read()這個方法會讀取輸入流的下一個位元組,並返回位元組表示的int(0-255)ascii碼),讀到末尾就會返回-1

read()方法還可以傳遞引數——read(byte [] b),作用是一次讀取一個位元組陣列,把輸入流讀取到的內容存放到這個位元組陣列中,並返回存放到陣列的內容的位元組數,同樣是到末尾就返回-1byte陣列是作為一個緩衝區,每次存入陣列大小為byte.length的資料,存入的資料是一個int

FileInputStream

FileInputStreamInputStream的一個子類,用來從檔案中讀取資料。FileInputStream類的構造方法有:

  • FileInputStream(File file): 傳遞一個檔案的File類物件
  • FileInputStream(String name): 傳遞一個String型別的檔案路徑

案例1: int read()的使用

F:\\test\\hello.txt的檔案內容:
abclove
package day01;

import java.io.FileInputStream;
import java.io.IOException;

class MainClass{
    public static void main(String[] args) throws IOException {  //所有與IO操作相關的程式碼都必須正確處理IOException
        FileInputStream fis=new FileInputStream("F:\\test\\hello.txt");  //建立流物件
        for(;;){  //無限迴圈
            int n = fis.read();  //反覆呼叫read()方法,直到n=-1
            if (n==-1){
                break;
            }
            System.out.print(n+":"); //列印read()返回的byte值
            System.out.println((char)n);
        }
        fis.close(); //關閉流,釋放對應的底層資源
    }
}
/*執行結果為
97:a
98:b
99:c
108:l
111:o
118:v
101:e
 */
//從執行結果可知,read()返回的單位元組整數值,是對應字元的acsii碼。通過(char)就可以轉換為對應字元。

案例2:int read(byte [] b)的使用(緩衝)

package day01;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

class MainClass{
    public static void main(String[] args) throws IOException {
        InputStream cup=new FileInputStream("F:\\test\\hello.txt");
        byte [] b=new byte[3];  
        //b陣列是作為一個緩衝區,在下面的迴圈中,每迴圈一次,b就會更新一次,輸入流把讀取到的內容放到b中
        int len=0; //初始化len為0
        while((len=cup.read(b))!=-1){  //len代表存放到陣列b的資料的位元組數
            System.out.print(len+":");
            System.out.println(new String(b,0,len)); //用法看下面的補充
        }
        cup.close();
    }
}
/*執行結果為:
3:abc
3:lov
1:e
 */
/*-------------------------補充:----------------------------------------
String類的構造方法public String(byte bytes[], int offset, int length)的用法:
	作用:將位元組陣列的某部分轉為字串
	引數:byte bytes[]表示要被轉的陣列
		int offset表示偏移量,即從陣列的第幾個位置開始轉為字串,
		int length表示總共要轉化的位元組數

read()方法是阻塞的(blocking)。讀取IO流相比執行普通程式碼,速度要慢很多。

package day01;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

class MainClass{
    public static void main(String[] args) throws IOException {
      InputStream a=new FileInputStream("F:\\test\\hello.txt");
        System.out.println("kobe");
        int b=a.read();  // 必須等待read()方法返回才能執行下一行程式碼
        System.out.println("gigi");
    }
}
OutputStream

OutputStream是Java標準庫提供的最基本的輸出流。OutputStream和InputStream一樣,也是抽象類,是所有輸出流的父類。抽象類定義的一個重要的方法是void write(int b),原始碼如下:

public abstract void write(int b) throws IOException;

write()還有過載方法:

  • write(byte b[]):用於一次性寫入多個位元組
  • write(byte b[], int off, int len) :將陣列boff位置開始,把長度為len位元組的資料寫入到檔案中

InputStream一樣,OutputStreamwrite()方法也是阻塞的。

FileOutputStream

FileOutputStreamOutputStream的子類。FileOutputStream類的構造方法有:

  • FileOutputStream(File file): 傳遞一個檔案的File類物件
  • FileOutputStream(String name): 傳遞一個String型別的檔案路徑

案例1:一次寫入一個位元組

package day01;

import java.io.*;

class MainClass{
    public static void main(String[] args) throws IOException {
        OutputStream ops=new FileOutputStream("F:\\test\\outPut.txt");
        ops.write(97); //往檔案寫入97ascii碼代表的字元a
        ops.write(98); //往檔案寫入98ascii碼代表的字元b
        ops.write(99); //往檔案寫入99ascii碼代表的字元c
        ops.flush();
        ops.close();
    }
}
//執行結果是:往F:\\test\\outPut.txt這個檔案寫入“abc",如果這個檔案不存在,則會新建檔案。

從案例1可以看到,OutputStream還提供了一個flush()方法,它的目的是將緩衝區的內容真正輸出到目的地。

為什麼要有flush()?因為向磁碟、網路寫入資料的時候,出於效率的考慮,作業系統並不是輸出一個位元組就立刻寫入到檔案或者傳送到網路,而是把輸出的位元組先放到記憶體的一個緩衝區裡(本質上就是一個byte[]陣列),等到緩衝區寫滿了,再一次性寫入檔案或者網路。對於很多IO裝置來說,一次寫一個位元組和一次寫1000個位元組,花費的時間幾乎是完全一樣的,所以OutputStream有個flush()方法,能強制把緩衝區內容輸出。

通常情況下,我們不需要呼叫這個flush()方法,因為緩衝區寫滿了OutputStream會自動呼叫它,並且,在呼叫close()方法關閉OutputStream之前,也會自動呼叫flush()方法。

但是,在某些情況下,我們必須手動呼叫flush()方法。舉個例子:

小明正在開發一款線上聊天軟體,當用戶輸入一句話後,就通過OutputStreamwrite()方法寫入網路流。小明測試的時候發現,傳送方輸入後,接收方根本收不到任何資訊,怎麼肥四?

原因就在於寫入網路流是先寫入記憶體緩衝區,等緩衝區滿了才會一次性發送到網路。如果緩衝區大小是4K,則傳送方要敲幾千個字元後,作業系統才會把緩衝區的內容傳送出去,這個時候,接收方會一次性收到大量訊息。

解決辦法就是每輸入一句話後,立刻呼叫flush(),不管當前緩衝區是否已滿,強迫作業系統把緩衝區的內容立刻傳送出去。

實際上,InputStream也有緩衝區。例如,從FileInputStream讀取一個位元組時,作業系統往往會一次性讀取若干位元組到緩衝區,並維護一個指標指向未讀的緩衝區。然後,每次我們呼叫int read()讀取下一個位元組時,可以直接返回緩衝區的下一個位元組,避免每次讀一個位元組都導致IO操作。當緩衝區全部讀完後繼續呼叫read(),則會觸發作業系統的下一次讀取並再次填滿緩衝區。

案例2:一次性寫入若干位元組

package day01;

import java.io.*;

/**
 * 一次性寫入若干位元組,通過void write(byte [])來實現
 */
class MainClass{
    public static void main(String[] args) throws IOException {
        OutputStream ops=new FileOutputStream("F:\\test\\outPut.txt");
        ops.write("hello Krystal".getBytes("utf-8") );
        ops.flush();
        ops.close();
    }
}
/*----------------------補充:------------------------------------------
 String.getBytes(String decode)方法會根據指定的decode編碼返回某字串在該編碼下的byte陣列
*/

案例3:複製檔案

package day01;

import java.io.*;

class MainClass{
    public static void main(String[] args) throws IOException {
        Stero st=new Stero();
        st.fun1("F:\\test\\outPut.txt","F:\\test\\outPut2.txt");
        //把outPut.txt複製為outPut2.txt
    }
}
class Stero{
    void fun1(String intputPath,String outputPath) throws IOException {
        InputStream ips=new FileInputStream(intputPath);
        OutputStream ops=new FileOutputStream(outputPath);
        byte[]a=new byte[3];
        int len;
        while((len=ips.read(a)) != -1){
            ops.write(a,0,len);
        }
        ops.close(); //先關閉輸出流
        ips.close(); //後關閉輸入流
        //最早開的最晚關
    }
}

正確關閉流

上面的流的案例,存在一個潛在的問題,如果讀取或者寫入過程中,發生了IO錯誤,流就沒法及時關閉,資源也無法釋放。 Java7引入了新的try(resource)語法,只需要編寫try語句,就可讓編譯器自動為我們關閉資源。

案例:自動正確地關閉流

class Cable{
    public static void main(String[] args) throws IOException{
        //把新建流物件地語句放在try()裡面即可
        try(FileInputStream sf=new FileInputStream("F:\\test\\hello.txt")){
            int n;
            while((n=sf.read())!=-1){
                System.out.println((char)n);
            }
        }
    }
}

字元流

如果我們需要讀寫的是字元(不是圖片什麼的),並且字元不全是單位元組表示的ASCII字元,那麼按照char來讀寫更方便,這種流稱為字元流。字元流傳輸的最小資料單位是char,Java提供了ReaderWriter兩個基類來操作字元流。

ReaderWriter本質上是一個能自動編解碼的InputStreamOutputStream

使用Reader,資料來源雖然是位元組,但我們讀入的資料都是char型別的字元,原因是Reader內部把讀入的byte做了解碼,轉換成了char。使用InputStream,我們讀入的資料和原始二進位制資料一模一樣,是byte[]陣列,但是我們可以自己把二進位制byte[]陣列按照某種編碼轉換為字串。究竟使用Reader還是InputStream,要取決於具體的使用場景。如果資料來源不是文字,就只能使用InputStream,如果資料來源是文字,使用Reader更方便一些WriterOutputStream是類似的。

Reader

Reader是Java的IO庫提供的另一個輸入流介面。和InputStream的區別是,InputStream是一個位元組流,即以byte為單位讀取,而Reader是一個字元流,即以char為單位讀取,Reader是所有字元輸入流的父類。Reader類的主要方法是int read(),原始碼是:

public int read() throws IOException;
FileReader

FileReaderReader的子類,FileReader的用法和FileInputStream的用法極其相似。

案例1:

//hello.txt的內容如下(注意編碼格式要為utf-8,要不然會出錯,另存為就可以改編碼格式)
武漢,加油。
China,add oil. 
package day01;

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

class RunClas{
    public static void main(String[] args) {
        Water w=new Water();
        try {
            w.fun1("F:\\test\\hello.txt");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
class Water{
    public void fun1(String rPath) throws IOException {
        try(Reader fr=new FileReader(rPath)){
            char[] a=new char[4]; //建立用於緩衝的字元陣列
            int len;
            while((len=fr.read(a))!=-1){
                System.out.println(new String(a,0,len));
            }
        }
    }
}
Writer

Reader是帶編碼轉換器的InputStream,它把byte轉換為char,而Writer就是帶編碼轉換器的OutputStream,它把char轉換為byte並輸出。

Writer是所有字元輸出流的超類,它提供的方法主要有:

  • 寫入一個字元(0~65535)void write(int c)
  • 寫入字元陣列的所有字元:void write(char[] c)
  • 寫入String表示的所有字元:void write(String s)
FileWriter

FileWriterWriter的一個子類。FileWriter的用法和FileOutputStream的用法很相似。

案例1:使用void write(String s)方法

package day01;

import java.io.*;

class RunClas {
    public static void main(String[] args) {
        Desk d=new Desk();
        try {
            d.funF("F:\\test\\FileWriterOutput.txt","Hello,krystal,i'm missing you.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class Desk {
    public void funF(String oPath, String content) throws IOException {
        try (Writer a = new FileWriter(oPath)) {
            a.write(content);
            a.flush();
            a.close();
        }
    }
}

案例2:用字元流進行復制檔案

package day01;

import java.io.*;

class RunClas {
    public static void main(String[] args) throws IOException {
        Desk d = new Desk();
        d.funF("F:\\test\\hello.txt", "F:\\test\\hello3.txt");
    }
}

class Desk {
    public void funF(String iPath, String oPath) throws IOException {
        Reader ra = new FileReader(iPath);
        Writer wa = new FileWriter(oPath);
        char[] charArr = new char[100];
        int len;
        while ((len = ra.read(charArr)) != -1) {
            wa.write(charArr);
        }
        wa.flush();
        wa.close();
    }
}

流對檔案的操作注意事項:

  • 在寫入一個檔案時,目錄下的同名檔案會被覆蓋
  • 在讀取一個檔案時,必須保證改檔案是存在的,否則報異常

緩衝流

為了提高 資料讀寫的速度,Java提供了帶緩衝功能的流類,在使用這些流類時,會建立一個內部緩衝區陣列。(基於記憶體的)。

BufferedInputStream-->FileInputStream
BufferedOutputStream-->FileOutputStream
BufferedReader-->FileReader
BufferedWriter-->FileWriter

緩衝流先把資料緩衝到記憶體裡,然後在記憶體中做io操作,基於記憶體的io操作比基於硬碟的操作快7500倍。

案例1:BufferedInputStream的使用
package day01;

import java.io.*;

class ExaF{
    public static void main(String[] args) throws IOException {
        //建立File流物件:
        FileInputStream a=new FileInputStream("F:\\test\\hello.txt");
        //建立緩衝流物件:
        BufferedInputStream b=new BufferedInputStream(a); //需要傳入FIle流作為引數
        byte[] bArr=new byte[100];
        int len;
        while((len=b.read(bArr))!=-1){
            System.out.println(new String(bArr,0,len));
        }
    }
}
案例2:BufferedOutputStream的使用
package day01;

import java.io.*;

class ExaF{
    public static void main(String[] args) throws IOException {
        FileOutputStream a=new FileOutputStream("F:\\test\\BOutput.txt");
        BufferedOutputStream b=new BufferedOutputStream(a); 
        b.write("I love you,krystal".getBytes());
        b.flush();
        b.close();
    }
}
案例3:使用位元組流+緩衝流實現檔案複製
package day01;

import java.io.*;

class ExaF{
    public static void main(String[] args) throws IOException {
        //建立File流物件:
        FileInputStream ia=new FileInputStream("F:\\test\\hello.txt");
        FileOutputStream oa=new FileOutputStream("F:\\test\\Chello.txt");

        //建立緩衝流物件:
        BufferedInputStream ib=new BufferedInputStream(ia);
        BufferedOutputStream ob=new BufferedOutputStream(oa); 

        byte[] bArr=new byte[100];
        int len;
        while((len=ib.read(bArr))!=-1){
            ob.write(bArr);
        }
        ob.flush();
        ob.close();
        ib.close();
        oa.close();
        ia.close();
    }
}
案例4:BufferedReader的使用
package day01;

import java.io.*;

class ExaB{
    public static void main(String[] args) throws IOException {
        FileReader a=new FileReader("F:\\test\\hello.txt");
        BufferedReader b=new BufferedReader(a);
        char []cArr=new char[100];
        int len;
        while((len=b.read(cArr))!=-1){
            System.out.println(new String(cArr,0,len));
        }
        b.close();
        a.close();
    }
}
案例5:BufferedWriter的使用
package day01;

import java.io.*;

class ExaB{
    public static void main(String[] args) throws IOException {
        FileWriter a=new FileWriter("F:\\test\\brout.txt");
        BufferedWriter b=new BufferedWriter(a);
        b.write("Hello,krystal!");
        b.flush();
        b.close();
        a.close();
    }
}
案例6:使用字元流+緩衝流實現檔案的複製
package day01;

import java.io.*;

class ExaB{
    public static void main(String[] args) throws IOException {
        BufferedReader br=new BufferedReader(new FileReader("F:\\test\\hello.txt"));
        BufferedWriter bw=new BufferedWriter(new FileWriter("F:\\test\\em.txt"));
        char [] cArr=new char[100];
        int len;
        while((len=br.read(cArr))!=-1){
            bw.write(cArr);
        }
        bw.flush();
        bw.close();
        br.close();
    }
}

轉換流

轉換流是指InputStreamReaderOutputStreamWriter,上面講了,流的資料都是字元時,轉成字元流更高效,那麼轉換流就是用來將位元組流轉換成字元流的,並且可以指定字符集的編碼解碼格式。

案例1:轉換位元組輸入流為字元輸入流

//"F:\\test\\aa.txt"檔案的編碼為GBK,檔案內容如下:
中國我愛你。
I love you,China.
package day01;

import java.io.*;

class Bear{
    public static void main(String[] args) throws IOException {
        //建立檔案位元組輸入流物件
        FileInputStream fis=new FileInputStream("F:\\test\\aa.txt");
        //建立轉換流物件
        InputStreamReader isr=new InputStreamReader(fis,"utf-8");//指定字符集編碼
        char []cArr=new char[10];
        int len;
        while((len=isr.read(cArr))!=-1){
            System.out.println(new String(cArr,0,len));
        }
        isr.close();
        fis.close();
    }
}
/*執行結果為:
�й��Ұ��㡣

I love you
,China.
*/
/*出現了亂碼,是因為程式碼中指定的字符集編碼與讀取的檔案的資料的編碼格式不一致,
指定編碼的一行程式碼改成:InputStreamReader isr=new InputStreamReader(fis,"GBK");即可避免亂碼*/

案例2:轉換位元組輸出流為字元輸出流

package day01;

import java.io.*;

class Bear{
    public static void main(String[] args) throws IOException {
        FileOutputStream fos=new FileOutputStream("F:\\test\\ors.txt");
        OutputStreamWriter osw=new OutputStreamWriter(fos,"utf-8");
        osw.write("中國,我愛你");
        osw.flush();
        osw.close();
        fos.close();
    }
}

標準輸入輸出流

System.outSystem.in是系統標準的輸入和輸出裝置(鍵盤和顯示器)

System.in的型別是InputStream

System.out的型別是PrintStream。

  • 案例1:建立一個接受鍵盤輸入的標準輸入流
package day01;

import java.io.*;

class Bear{
    public static void main(String[] args) throws IOException {
        RedPap rd=new RedPap();
        rd.fun();
    }
}
class RedPap{
    public void fun() throws IOException {
        //建立接受鍵盤輸入的輸入流
        InputStreamReader isr=new InputStreamReader(System.in);
        //把輸入流放到緩衝流裡:
        BufferedReader bfr=new BufferedReader(isr);
        //建立臨時接受資料的字串:
        String str="";
        while((str=bfr.readLine())!=null){  //readLine()是緩衝輸入字元流提供的按行讀取終端資料的方法,每一次呼叫會以字串形式返回一行內容,讀取完會返回null
            System.out.println(str);
        }
    }
}
//執行結果如下圖:

案例2:將控制檯的輸入內容輸出到檔案krystal.txt中,控制檯出現”over“時結束輸出。

package day01;

import java.io.*;

class Bear{
    public static void main(String[] args) throws IOException {
        Krystal cf=new Krystal();
        cf.fun("F:\\test\\Krystal.txt");
    }
}
class Krystal{
    public void fun(String kPath) throws IOException {
        BufferedReader bfr=new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bfw=new BufferedWriter(new FileWriter(kPath));
        String str="";
        while((str=bfr.readLine())!=null){
            if(str.equals("over")){
                break;
            }
            bfw.write(str);
        }
        bfw.close();
        bfr.close();
    }
}

序列化與反序列化

首先思考兩個問題:

1、如果想把一個類的例項化物件存到電腦的硬碟上,要怎麼做?

  硬碟儲存的基礎是二進位制,需要把物件轉化為一個二進位制的位元組流,然後把流儲存到硬碟上。如果要用存到硬盤裡的物件,又得把流轉化為物件再使用。

2、如果想把一個物件通過網路傳到另一臺機器上,要怎麼做?

  網路通訊的基礎也是二進位制,需要把物件轉化為二進位制的資料流,然後通過網路傳輸流。接收者如果想要使用接收的物件得先把物件的流轉為物件。

因為上面兩類問題的存在,產生了物件的輸入與輸出流。

序列化(Serialize):序列化是指把一個Java物件變成二進位制內容,本質上就是一個byte[]陣列,用ObjectOutputStream類將一個物件寫入IO流中。

反序列化(Deserialize):用ObjectInputStream類從IO流中恢復物件。

注意事項:

  • 序列化和反序列化針對的是例項化物件的各種屬性,不包括類的屬性。
  • 一個Java物件要能序列化,必須實現一個特殊的java.io.Serializable介面
  • 實現Serializable介面的類的物件的是可序列化的
  • Serializable介面沒有定義任何方法,它是一個空介面。
  • 我們把這樣的空介面稱為“標記介面”(Marker Interface),實現了標記介面的類僅僅是給自身貼了個“標記”,並沒有增加任何方法。

案例1:

package day01;

import java.io.*;

class AbleObj implements Serializable {
    String name;
    int age;
    String school;
    double weigth;
}
class ObjOut{  //序列化類
    public void funO(String oosPath) throws IOException {
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(oosPath)); //定義物件輸出流
        AbleObj ao=new AbleObj();
        ao.name="krystal";
        ao.age=20;
        ao.school="SongSanHu";
        oos.writeObject(ao);
        oos.flush();
        oos.close();
    }
}
class ObjIn{  //反序列化類
    public void funI(String oisPath) throws IOException, ClassNotFoundException {
        ObjectInputStream ois =new ObjectInputStream(new FileInputStream(oisPath));
        Object a=ois.readObject();
        AbleObj b=(AbleObj)a; //強制轉換為AbleObj型別
        /*物件的序列化和反序列化使用的類要嚴格一致,序列化是什麼類反序列化就用什麼類,
        包名、類名、類結構等等所有都要一致。*/
        System.out.println(b.name);
        System.out.println(b.school);
        ois.close()
    }
}
public class Test01{  //執行類
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjOut tt=new ObjOut();
        tt.funO("F:\\test\\KrystalInfo.txt");
        ObjIn kk=new ObjIn();
        kk.funI("F:\\test\\KrystalInfo.txt");
    }
}
/*執行結果為:
krystal
SongSanHu
 */
安全性

因為Java的序列化機制可以導致一個例項能直接從byte[]陣列建立,而不經過構造方法,因此,它存在一定的安全隱患。一個精心構造的byte[]陣列被反序列化後可以執行特定的Java程式碼,從而導致嚴重的安全漏洞。

實際上,Java本身提供的基於物件的序列化和反序列化機制既存在安全性問題,也存在相容性問題。更好的序列化方法是通過JSON這樣的通用資料結構來實現,只輸出基本型別(包括String)的內容,而不儲存任何與程式碼相關的資訊。

隨機存取流

RandomAccessFile類支援"隨機訪問"的方式,即程式可以直接跳到檔案的任意地方進行讀寫操作RandomAccessFile物件包含一個記錄指標,用來標記當前讀寫開始位置,通過void seek(long pos)方法可以將記錄指標定位到pos位置。

案例1:隨機訪問檔案(任意位置讀取)

//F:\\test\\AccessTest.txt檔案的內容為:
123456789I love you,krystal. Where are you?
package day01;

import java.io.FileInputStream;
import java.io.RandomAccessFile;

public class Test01{
    public static void main(String[] args) throws Exception {
        RandomIn ii=new RandomIn();
        ii.Rfi("F:\\test\\AccessTest.txt");
    }
}
class RandomIn{
    public void Rfi(String path1) throws Exception{
        RandomAccessFile raf=new RandomAccessFile(path1,"rw");
        /*引數2是mode模式:
        "r":以只讀模式開啟檔案
        "rw":開啟以便讀取和寫入(常用)
        "rwd":開啟以便讀取和寫入,同步檔案內容的更新
        "rws":開啟以便讀取和寫入,同步檔案內容和元資料的更新
        */
        raf.seek(5);  //設定讀取檔案內容的起點
        byte [] bArr=new byte[100];
        int len;
        while((len=raf.read(bArr))!=-1){
            System.out.println(new String(bArr,0,len));
        }
        raf.close();
    }
}
/*執行結果為:
6789I love you,krystal. Where are you?
*/

案例2:隨機寫入

//F:\\test\\AccessTest.txt檔案的內容為:
123456789I love you,krystal. Where are you?
package day01;

import java.io.IOException;
import java.io.RandomAccessFile;

public class Test01{
    public static void main(String[] args) throws Exception {
        RandomOut oo=new RandomOut();
        oo.rof("F:\\test\\AccessTest.txt");
    }
}
class RandomOut{
    public void rof(String path2) throws IOException {
        RandomAccessFile rdaf=new RandomAccessFile(path2,"rw");
        rdaf.seek(0); 
        rdaf.write("Hi,this is Jimmy! ".getBytes());
        rdaf.seek(rdaf.length());  //這個相當於給檔案末尾追加內容
        rdaf.write(" I miss you so much!".getBytes());
        rdaf.close();
    }
}
/*執行完成後,F:\\test\\AccessTest.txt檔案的內容變化如下:
123456789I love you,krystal. Where are you?
Hi,this is Jimmy! u,krystal. Where are you? I miss you so much!
可以看到,在開頭或者中間寫入內容時,會覆蓋掉等長度的原內容。
*/