Java——I/O流(File類、節點流、緩衝流、轉換流、物件流)

2020-08-12 20:41:36

1. File類的使用

1.1 概述

  • java.io.File類:檔案和檔案目錄路徑的抽象表示形式,與平臺無關
  • File 能新建、刪除、重新命名檔案和目錄,但 File 不能存取檔案內容本身。
    如果需要存取檔案內容本身,則需要使用 輸入/輸出流。
  • 想要在Java程式中表示一個真實存在的檔案或目錄,那麼必須有一個File對
    象,但是Java程式中的一個File物件,可能沒有一個真實存在的檔案或目錄。
  • File物件可以作爲參數傳遞給流的構造器

建立File類的範例

在这里插入图片描述
在这里插入图片描述

//單元測試中的相對路徑,相對於當前module
//main方法中的相對路徑,相對於當前工程
File file = new File("hello.txt");

//絕對路徑
File file1 = new File("d:\\JAVA_WORK\\info");
File file2 = new File("d:" + File.separator + "example" +
 					   File.separator + "info.txt");
File file3 = new File("d:/JAVA_WORK");
File file1 = new File("D:\\JAVA_WORK","info");
File file2 = new File(file1,"hello.txt");

1.2 使用

在这里插入图片描述
其中,重新命名方法public boolean renameTo(File dest),若file1.renameTo(file2),要想保證返回 true,需要 file1 在硬碟中是存在的,且 file2 不能在硬碟中存在。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. I/O流

2.1 概述

  • Java程式中,對於數據的輸入/輸出操作以「流(stream)」 的
    方式進行。
  • java.io包下提供了各種「流」類和介面,用以獲取不同種類的
    數據,並通過標準的方法輸入或輸出數據。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

2.2 節點流(檔案流)

2.2.1 字元流

① 將當前Module下的 hello.txt 檔案內容讀入程式中,並輸出到控制檯

  • read()的理解:返回讀入的一個字元。如果達到檔案末尾,返回-1
  • 異常的處理:爲了保證流資源一定可以執行關閉操作。需要使用try-catch-finally處理
  • 讀入的檔案一定要存在,否則就會報FileNotFoundException
	@Test
    public void testFileReader() throws Exception{
    	FileReader fr = null;
        try {
            //1.範例化File類的物件,指明要操作的檔案
            File file = new File("hello.txt");
            //2.提供具體的流
            fr = new FileReader(file);

            //3.數據的讀入
            int data;
            while((data = fr.read()) != -1){
                System.out.print((char)data);
            }
            
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.流的關閉操作
            try {
                if(fr != null) fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
    }

//對read()操作升級:使用read的過載方法
    @Test
    public void testFileReader1()  {
        FileReader fr = null;
        try {
            //1.File類的範例化
            File file = new File("hello.txt");

            //2.FileReader流的範例化
            fr = new FileReader(file);

            //3.讀入的操作
            //read(char[] cbuf)
            //返回每次讀入cbuf陣列中的字元的個數。如果達到檔案末尾,返回-1
            char[] cbuf = new char[5];
            int len;
            while((len = fr.read(cbuf)) != -1){
                //方式一:
                //錯誤的寫法
//                for(int i = 0;i < cbuf.length;i++){
//                    System.out.print(cbuf[i]);
//                }
                //正確的寫法
//                for(int i = 0;i < len;i++){
//                    System.out.print(cbuf[i]);
//                }
                //方式二:
                //錯誤的寫法,對應着方式一的錯誤的寫法
//                String str = new String(cbuf);
//                System.out.print(str);
                //正確的寫法
                String str = new String(cbuf,0,len);
                System.out.print(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fr != null){
                //4.資源的關閉
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }

③ 從記憶體中寫出數據到硬碟的檔案裡

  • 輸出操作,對應的File可以不存在的。並不會報異常
  • File 對應的硬碟中的檔案如果不存在,在輸出的過程中,會自動建立此檔案。
  • File對應的硬碟中的檔案如果存在
    • 如果流使用的構造器是:FileWriter(file,false) / FileWriter(file),則對原有檔案的覆蓋
    • 如果流使用的構造器是:FileWriter(file,true),則不會對原有檔案覆蓋,而是在原有檔案基礎上追加內容
	@Test
    public void testFileWriter() {
        FileWriter fw = null;
        try {
            //1.提供File類的物件,指明寫出到的檔案
            File file = new File("hello1.txt");

            //2.提供FileWriter的物件,用於數據的寫出
            fw = new FileWriter(file,false);

            //3.寫出的操作
            fw.write("I have a dream!\n");
            fw.write("you need to have a dream!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.流資源的關閉
            if(fw != null){
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

2.2.2 位元組流

	/*
    實現對圖片的複製操作
     */
	@Test
    public void testFileInputstream(){
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            File srcFile = new File("test.png");
            File destFile = new File("test1.png");

            fileInputStream = new FileInputStream(srcFile);
            fileOutputStream = new FileOutputStream(destFile);

            byte[] b = new byte[10];
            int len;
            while ((len=fileInputStream.read(b))!=-1){
                fileOutputStream.write(b,0,len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if(fileInputStream!=null) fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(fileOutputStream!=null) fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

2.3 緩衝流(處理流的一種)

2.3.1 位元組流

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

作用:提供流的讀取、寫入的速度;原理:內部提供了一個緩衝區

	/*
    實現非文字檔案的複製
     */
    @Test
    public void BufferedStreamTest() throws FileNotFoundException {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            //1.造檔案
            File srcFile = new File("愛情與友情.jpg");
            File destFile = new File("愛情與友情3.jpg");
            
            //2.造流
            //2.1 造節點流
            FileInputStream fis = new FileInputStream((srcFile));
            FileOutputStream fos = new FileOutputStream(destFile);
            //2.2 造緩衝流
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);

            //3.複製的細節:讀取、寫入
            byte[] buffer = new byte[1024];
            int len;
            while((len = bis.read(buffer)) != -1){
                bos.write(buffer,0,len);
                //重新整理緩衝區
                //bos.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.資源關閉
            //要求:先關閉外層的流,再關閉內層的流
            //說明:關閉外層流的同時,內層流也會自動的進行關閉。
            //所以關於內層流的關閉,可省略
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bis != null){
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

2.3.2 字元流

	@Test
    public void testBufferedReaderBufferedWriter(){
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            //建立檔案和相應的流
            br = new BufferedReader(new FileReader(new File("dbcp.txt")));
            bw = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));

            //讀寫操作
            //方式一:使用char[]陣列
//            char[] cbuf = new char[1024];
//            int len;
//            while((len = br.read(cbuf)) != -1){
//                bw.write(cbuf,0,len);
//    //            bw.flush();
//            }

            //方式二:使用String
            String data;
            while((data = br.readLine()) != null){
                //換行方法一:
                //寫出的data中不包含換行符
                //bw.write(data + "\n");
                
                //換行方法二:
                bw.write(data);
                //提供換行的操作
                bw.newLine();

            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //關閉資源
            if(bw != null){

                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(br != null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }

2.3.3 總結

抽象基礎類別 節點流(或檔案流) 緩衝流(處理流的一種)
InputStream FileInputStream——read(byte[] buffer) BufferedInputStream——read(byte[] buffer)
OutputStream FileOutputStream——write(byte[] buffer,0,len) BufferedOutputStream write(byte[] buffer,0,len) / flush()
Reader FileReader ——read(char[] cbuf) BufferedReader——read(char[] cbuf) / readLine()
Writer FileWriter——write(char[] cbuf,0,len) BufferedWriter——write(char[] cbuf,0,len) / flush()

2.4 轉換流(處理流的一種)

  • 轉換流提供了在位元組流和字元流之間的轉換
  • Java API提供了兩個轉換流(屬於字元流):
    • InputStreamReader:將一個位元組的輸入流 轉換爲字元的輸入流
    • OutputStreamWriter:將一個字元的輸出流 轉換爲位元組的輸出流
	/*
    InputStreamReader的使用,實現位元組的輸入流到字元的輸入流的轉換
    */
    @Test
    public void InputStreamReaderTest(){
        InputStreamReader inputStreamReader = null;
        OutputStreamWriter outputStreamWriter = null;
        try {
            //參數2指明瞭字元集
            //具體使用哪個字元集,取決於檔案hello.txt儲存時使用的字元集
            inputStreamReader = new InputStreamReader(new FileInputStream("hello.txt"), "UTF-8");
            outputStreamWriter = new OutputStreamWriter(new FileOutputStream("hello_gbk.txt"),"GBK");
            char[] cbuf = new char[1024];
            int len;

            while ((len = inputStreamReader.read(cbuf))!=-1){
                outputStreamWriter.write(cbuf,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(inputStreamReader!=null) inputStreamReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(outputStreamWriter!=null) outputStreamWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.5 標準輸入、輸出流

  • System.inSystem.out分別代表了系統標準的輸入和輸出裝置
  • 預設輸入裝置是: 鍵盤, 輸出裝置是:顯示器
  • System.in的型別是InputStream
  • System.out的型別是PrintStream,其是OutputStream的子類
  • 重定向:通過System類的setInsetOut方法對預設裝置進行改變。
    • public static void setIn(InputStream in)
    • public static void setOut(PrintStream out)

FilterOutputStream 的子類

例 題
從鍵盤輸入字串,要求將讀取到的整行字串轉成大寫輸出。然後繼續
進行輸入操作,直至當輸入「e」或者「exit」時,退出程式:

public static void main(String[] args) {

        BufferedReader br = null;
        try {
            InputStreamReader isr = new InputStreamReader(System.in);
            br = new BufferedReader(isr);

            while (true) {
                System.out.println("請輸入字串:");
                String data = br.readLine();
                if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)) {
                    System.out.println("程式結束");
                    break;
                }
                String upperCase = data.toUpperCase();
                System.out.println(upperCase);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

2.6 列印流

實現將 基本數據型別 的數據格式轉化爲 字串 輸出

在这里插入图片描述

@Test
    public void test2() {
        
        PrintStream ps = null;
        try {
            FileOutputStream fos = new FileOutputStream(new File("D:\\IO\\text.txt"));
            // 建立列印輸出流,設定爲自動重新整理模式(寫入換行符或位元組 '\n' 時都會重新整理輸出緩衝區)
            ps = new PrintStream(fos, true);
            if (ps != null) {// 把標準輸出流(控制檯輸出)改成檔案
                System.setOut(ps);
            }

            for (int i = 0; i <= 255; i++) { // 輸出ASCII字元
                System.out.print((char) i);
                if (i % 50 == 0) { // 每50個數據一行
                    System.out.println(); // 換行
                }
            }

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

2.7 數據流

在这里插入图片描述

	//練習:將記憶體中的字串、基本數據型別的變數寫出到檔案中。
    @Test
    public void test3() throws IOException {
		DataOutputStream dos = null;
        try {
        	dos = new DataOutputStream(new FileOutputStream("data.txt"));
            dos.writeUTF("Tom");
        	dos.flush();//重新整理操作,將記憶體中的數據寫入檔案
        	dos.writeInt(23);
        	dos.flush();
        	dos.writeBoolean(true);
        	dos.flush();
        
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (dos!= null) {
                dos.close();
            }
        }
    }
    
    /*
    將檔案中儲存的基本數據型別變數和字串讀取到記憶體中,儲存在變數中。
    注意點:讀取不同類型的數據的順序要與當初寫入檔案時,儲存的數據的順序一致!
     */
    @Test
    public void test4() throws IOException {
      
		DataInputStream dis = null;
        try {
        	dis = new DataInputStream(new FileInputStream("data.txt"));
        	String name = dis.readUTF();
        	int age = dis.readInt();
        	boolean isMale = dis.readBoolean();
        	
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (dis!= null) {
                dis.close();
            }
        }
    }

2.8 物件流

2.8.1 物件序列化

① 物件序列化機制 機製允許把記憶體中的Java物件轉換成平臺無關的二進制流,從
而允許把這種二進制流持久地儲存在磁碟上,或通過網路將這種二進制流傳
輸到另一個網路節點。 (當其它程式獲取了這種二進制流,就可以恢復成原
來的Java物件)
② 序列化優點:可將任何實現了Serializable介面的物件轉化爲位元組數據,
使其在儲存和傳輸時可被還原
③ 序列化是 RMI(Remote Method Invoke – 遠端方法呼叫)過程的參數和返
回值都必須實現的機制 機製,而 RMI 是 JavaEE 的基礎。因此序列化機制 機製是
JavaEE 平臺的基礎
④ 如果需要讓某個物件支援序列化機制 機製,則必須讓 物件所屬的類及其屬性 是可
序列化的(預設基本數據型別已是可序列化),爲了讓某個類是可序列化的,該類须實現如下兩個介面之一,否則會拋NotSerializableException異常:

  • Serializable
  • Externalizable

⑤ 凡是實現Serializable介面的類都有一個表示序列化版本識別符號的靜態變數:

  • private static final long serialVersionUID;
  • serialVersionUID用來表明類的不同版本間的相容性。 簡言之,其目的是以序列化物件進行版本控制,有關各版本反序列化時是否相容。
  • 如果類沒有顯示定義次靜態常數,它的值是Java執行時環境根據類的內部細節自動生成的。 若對類做了修改, serialVersionUID可能發生變化,故建議顯式宣告。

⑥ 簡單來說, Java的序列化機制 機製是通過在執行時判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時, JVM會把傳來的位元組流中的
serialVersionUID與本地相應實體類的serialVersionUID進行比較,如果相同就認爲是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異
常。 (InvalidCastException)

2.8.2

ObjectInputStreamOjbectOutputSteam

  • 用於儲存和讀取基本數據型別數據或物件的處理流。它的強大之處就是可
    以把Java中的物件寫入到數據源中,也能把物件從數據源中還原回來(持久化)。

序列化: 用ObjectOutputStream類儲存基本型別數據或物件的機制 機製(寫出)
反序列化: 用ObjectInputStream類讀取基本型別數據或物件的機制 機製(寫入)

注:ObjectOutputStreamObjectInputStream不能序列化statictransient修飾的成員變數。

	/*
    序列化過程:將記憶體中的java物件儲存到磁碟中或通過網路傳輸出去
    使用ObjectOutputStream實現
     */
    @Test
    public void testObjectOutputStream(){
        ObjectOutputStream oos = null;

        try {
            //1.
            oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
            //2.
            oos.writeObject(new String("我愛北京天安門"));
            oos.flush();//重新整理操作
            
			//	Person類已實現Serializable介面
			//且宣告瞭serialVersionUID
            oos.writeObject(new Person("王銘",23));
            oos.flush();

            oos.writeObject(new Person("張學良",23,1001,new Account(5000)));
            oos.flush();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(oos != null){
                //3.
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

    }

    /*
    反序列化:將磁碟檔案中的物件還原爲記憶體中的一個java物件
    使用ObjectInputStream來實現
     */
    @Test
    public void testObjectInputStream(){
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("object.dat"));

            Object obj = ois.readObject();
            String str = (String) obj;

            Person p = (Person) ois.readObject();
            Person p1 = (Person) ois.readObject();

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

            }
        }
    }

2.9 隨機存取檔案流

RandomAccessFile 宣告在 java.io 包下,但直接繼承於java.lang.Object類。 並且它實現了DataInputDataOutput這兩個介面,即這個類既作爲輸出流,也可作爲輸入流。