ch19_IO_stream

小兵学习笔记發表於2024-09-13
  • 第19章 IO流
    • 檔案
    • 常用的檔案操作
      • 建立檔案物件相關構造器和方法
      • 獲取檔案的相關資訊
      • 目錄的操作和檔案刪除
    • IO 流原理及流的分類
      • Java IO 流原理
      • 流的分類
    • IO 流體系圖-常用的類
      • IO 流體系圖
      • 檔案 VS 流
      • FileInputStream 介紹
      • FileInputStream 應用例項
      • FileOutputStream 介紹
      • FileOutputStream 應用例項
      • FileOutputStream 應用例項2
      • FileReader 和FileWriter 介紹
      • FileReader 相關方法
      • FileWriter 相關方法
      • FileReader 和FileWriter 應用案例
    • 節點流和處理流
      • 基本介紹
      • 節點流和處理流一覽圖
      • 節點流和處理流的區別和聯絡
      • 處理流的功能
      • 處理流 BufferedReader 和BufferedWriter
        • 應用案例
      • 處理流BufferedInputStream 和BufferedOutputStream
        • 應用案例
      • 物件流ObjectInputStream 和ObjectOutputStream
      • 物件流介紹
        • 應用案例
      • 標準輸入輸出流
        • 介紹
        • 應用案例1
        • 應用案例2
      • 轉換流InputStreamReader 和OutputStreamWriter
        • 應用案例
      • 列印流PrintStream 和PrintWriter
    • Properties 類
      • 基本介紹
      • 應用案例
    • 本章作業

第19章 IO流

檔案

檔案,對我們並不陌生,檔案是儲存資料的地方。檔案在程式中是以流的形式來操作的。

流:資料在資料來源(檔案)和程式(記憶體)之間經歷的路徑

輸入流:資料從資料來源(檔案)到程式(記憶體)的路徑

輸出流:資料從程式(記憶體)到資料來源(檔案)的路徑

常用的檔案操作

建立檔案物件相關構造器和方法

  • new File(String pathname) //根據路徑構建一個File物件
  • new File(File parent, String child) //根據父目錄檔案+子路徑構建new
  • File(String parent, String child) //根據父目錄+子路徑構建
package com.hspedu.file;

import org.junit.jupiter.api.Test;

import java.io.*;

/**
 * 演示建立檔案
 */
public class FileCreate {
    public static void main(String[] args) {

    }

    //方式1 new File(String pathname)
    @Test
    public void create01() {
        String filePath = "e:\\news1.txt";
        File file = new File(filePath);

        try {
            file.createNewFile();
            System.out.println("檔案建立成功");
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    //方式2 new File(File parent,String child) //根據父目錄檔案+子路徑構建
    //e:\\news2.txt
    @Test
    public  void create02() {
        File parentFile = new File("e:\\");
        String fileName = "news2.txt";
        //這裡的file物件,在java程式中,只是一個物件
        //只有執行了createNewFile 方法,才會真正的,在磁碟建立該檔案
        File file = new File(parentFile, fileName);

        try {
            file.createNewFile();
            System.out.println("建立成功~");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //方式3 new File(String parent,String child) //根據父目錄+子路徑構建
    @Test
    public void create03() {
        //String parentPath = "e:\\";
        String parentPath = "e:\\";
        String fileName = "news4.txt";
        File file = new File(parentPath, fileName);

        try {
            file.createNewFile();
            System.out.println("建立成功~");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //下面四個都是抽象類
    //InputStream
    //OutputStream
    //Reader //字元輸入流
    //Writer  //字元輸出流
}

獲取檔案的相關資訊

getNamegetAbsolutePathgetParentlengthexistsisFileisDirectory

package com.hspedu.file;

import org.junit.jupiter.api.Test;

import java.io.File;

public class FileInformation {
    public static void main(String[] args) {

    }

    // 獲取檔案的資訊
    @Test
    public void info() {
        // 先建立檔案物件
        File file = new File("e:\\news1.txt");

        // 呼叫相應的方法,得到對應資訊
        System.out.println("檔名字=" + file.getName());
        // getName、getAbsolutePath、getParent、length、exists、isFile、isDirectory
        System.out.println("檔案絕對路徑=" + file.getAbsolutePath());
        System.out.println("檔案父級目錄=" + file.getParent());
        System.out.println("檔案大小(位元組)=" + file.length());
        System.out.println("檔案是否存在=" + file.exists());//T
        System.out.println("是不是一個檔案=" + file.isFile());//T
        System.out.println("是不是一個目錄=" + file.isDirectory());//F
    }
}

目錄的操作和檔案刪除

mkdir建立一級目錄、mkdirs建立多級目錄、delete刪除空目錄或檔案

應用案例演示

  1. 判斷 d:\\news1.txt是否存在,如果存在就刪除
  2. 判斷 D:\\ldemo02是否存在,存在就刪除,否則提示不存在.
  3. 判斷 D:\\demo\\a\\b\\c目錄是否存在,如果存在就提示已經存在,否則就建立
package com.hspedu.file;

import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;

public class Directory_ {
    public static void main(String[] args) {

        //
    }

    //判斷 d:\\news1.txt 是否存在,如果存在就刪除
    @Test
    public void m1() {

        String filePath = "e:\\news1.txt";
        File file = new File(filePath);
        if (file.exists()) {
            if (file.delete()) {
                System.out.println(filePath + "刪除成功");
            } else {
                System.out.println(filePath + "刪除失敗");
            }
        } else {
            System.out.println("該檔案不存在...");
        }

    }

    //判斷 D:\\demo02 是否存在,存在就刪除,否則提示不存在
    //這裡我們需要體會到,在java程式設計中,目錄也被當做檔案
    @Test
    public void m2() {

        String filePath = "D:\\demo02";
        File file = new File(filePath);
        if (file.exists()) {
            if (file.delete()) {
                System.out.println(filePath + "刪除成功");
            } else {
                System.out.println(filePath + "刪除失敗");
            }
        } else {
            System.out.println("該目錄不存在...");
        }

    }

    //判斷 D:\\demo\\a\\b\\c 目錄是否存在,如果存在就提示已經存在,否則就建立
    @Test
    public void m3() {

        String directoryPath = "D:\\demo\\a\\b\\c";
        File file = new File(directoryPath);
        if (file.exists()) {
            System.out.println(directoryPath + "存在..");
        } else {
            if (file.mkdirs()) { //建立一級目錄使用mkdir() ,建立多級目錄使用mkdirs()
                System.out.println(directoryPath + "建立成功..");
            } else {
                System.out.println(directoryPath + "建立失敗...");
            }
        }


    }
}

IO 流原理及流的分類

Java IO 流原理

  1. I/O 是 Input/Output 的縮寫,I/O技術是非常實用的技術,用於處理資料傳輸。
    如讀/寫檔案,網路通訊等。

  2. Java程式中,對於資料的輸入/輸出操作以”流(stream)”的方式進行。

  3. java.io包下提供了各種“流”類和介面,用以獲取不同種類的資料,並透過方法輸入或輸出資料

  4. 輸入input:讀取外部資料(磁碟、光碟等儲存裝置的資料)到程式(記憶體)中。

  5. 輸出output:將程式(記憶體)資料輸出到磁碟、光碟等儲存裝置中

流的分類

  • 按運算元據單位不同分為:位元組流(8 bit)(二進位制檔案例如聲音影片word等可以無損操作),字元流(按字元)(文字檔案)。
  • 按資料流的流向不同分為:輸入流,輸出流。
  • 按流的角色的不同分為:節點流,處理流 / 包裝流。

  1. Java的IO流共涉及40多個類,實際上非常規則,都是從如上4個抽象基類派生的。
  2. 由這四個類派生出來的子類名稱都是以其父類名作為子類名字尾。

IO 流體系圖-常用的類

IO 流體系圖

檔案 VS 流

FileInputStream 介紹

FileInputStream 應用例項

請使用FileInputStream 讀取hello.txt 檔案,並將檔案內容顯示到控制檯.

當然,注意一個漢字通常是由3個bytes構成的,而read只會讀取一個byte的資料,因此如果用read則會亂碼,建議文字檔案用字元流處理。

package com.hspedu.inputstream_;

import org.junit.jupiter.api.Test;

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

/**
 * 演示FileInputStream的使用(位元組輸入流 檔案--> 程式)
 */
public class FileInputStream_ {
    public static void main(String[] args) {

    }

    /**
     * 演示讀取檔案...
     * 單個位元組的讀取,效率比較低
     * -> 使用 read(byte[] b)
     */
    @Test
    public void readFile01() {
        String filePath = "e:\\hello.txt";
        int readData = 0;
        FileInputStream fileInputStream = null;
        try {
            //建立 FileInputStream 物件,用於讀取 檔案
            fileInputStream = new FileInputStream(filePath);
            //從該輸入流讀取一個位元組的資料。 如果沒有輸入可用,此方法將阻止。
            //如果返回-1 , 表示讀取完畢
            while ((readData = fileInputStream.read()) != -1) {
                System.out.print((char)readData);//轉成char顯示
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //關閉檔案流,釋放資源.
            try {
                fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 使用 read(byte[] b) 讀取檔案,提高效率
     */
    @Test
    public void readFile02() {
        String filePath = "e:\\hello.txt";
        //位元組陣列
        byte[] buf = new byte[8]; //一次讀取8個位元組.
        int readLen = 0;
        FileInputStream fileInputStream = null;
        try {
            //建立 FileInputStream 物件,用於讀取 檔案
            fileInputStream = new FileInputStream(filePath);
            //從該輸入流讀取最多b.length位元組的資料到位元組陣列。 此方法將阻塞,直到某些輸入可用。
            //如果返回-1 , 表示讀取完畢
            //如果讀取正常, 返回實際讀取的位元組數
            while ((readLen = fileInputStream.read(buf)) != -1) {
                System.out.print(new String(buf, 0, readLen));//顯示
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //關閉檔案流,釋放資源.
            try {
                fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

FileOutputStream 介紹

FileOutputStream 應用例項

請使用FileOutputStream 在a.txt 檔案,中寫入“hello,world”. 如果檔案不存在,會建立檔案(注意:前提是目錄已經存在.)

package com.hspedu.outputstream_;

import org.junit.jupiter.api.Test;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStream01 {
    public static void main(String[] args) {

    }

    /**
     * 演示使用 FileOutputStream 將資料寫到檔案中,
     * 如果該檔案不存在,則建立該檔案
     */
    @Test
    public void writeFile() {

        //建立 FileOutputStream物件
        String filePath = "e:\\a.txt";
        FileOutputStream fileOutputStream = null;
        try {
            //得到 FileOutputStream物件 物件
            //老師說明
            //1. new FileOutputStream(filePath) 建立方式,當寫入內容是,會覆蓋原來的內容
            //2. new FileOutputStream(filePath, true) 建立方式,當寫入內容是,是追加到檔案後面
            fileOutputStream = new FileOutputStream(filePath, true);
            //寫入一個位元組
            //fileOutputStream.write('H');//
            //寫入字串
            String str = "hello,world!";
            //str.getBytes() 可以把 字串-> 位元組陣列
            //fileOutputStream.write(str.getBytes());
            /*
            write(byte[] b, int off, int len) 將 len位元組從位於偏移量 off的指定位元組陣列寫入此檔案輸出流
             */
            fileOutputStream.write(str.getBytes(), 0, 3);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

FileOutputStream 應用例項2

程式設計完成圖片/音樂的複製.

package com.hspedu.outputstream_;

import com.hspedu.inputstream_.FileInputStream_;

import java.io.*;

public class FileCopy {
    public static void main(String[] args) {
        //完成 檔案複製,將 e:\\Koala.jpg 複製 c:\\
        //思路分析
        //1. 建立檔案的輸入流 , 將檔案讀入到程式
        //2. 建立檔案的輸出流, 將讀取到的檔案資料,寫入到指定的檔案.
        String srcFilePath = "e:\\Koala.jpg";
        String destFilePath = "e:\\Koala3.jpg";
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;

        try {

            fileInputStream = new FileInputStream(srcFilePath);
            fileOutputStream = new FileOutputStream(destFilePath);
            //定義一個位元組陣列,提高讀取效果
            byte[] buf = new byte[1024];
            int readLen = 0;
            while ((readLen = fileInputStream.read(buf)) != -1) {
                //讀取到後,就寫入到檔案 透過 fileOutputStream
                //即,是一邊讀,一邊寫
                fileOutputStream.write(buf, 0, readLen);//一定要使用這個方法
                // 不能用 fileOutputStream.write(buf) 因為最後有可能讀不夠而出錯
            }
            System.out.println("複製ok");


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //關閉輸入流和輸出流,釋放資源
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

FileReader 和FileWriter 介紹

FileReader 相關方法

  1. new FileReader(File/String)
  2. read: 每次讀取單個字元,返回該字元,如果到檔案末尾返回-1
  3. read(charD): 批次讀取多個字元到陣列,返回讀取到的字元數,如果到檔案未尾返回-1

相關API:

  1. new String(charD):將char[]轉換成String
  2. new String(charl,off,len):將char[]的指定部分轉換成String

FileWriter 相關方法

  1. new FileWriter(File/String):覆蓋模式,相當於流的指標在首端
  2. new FileWriter(File/String,true):追加模式,相當於流的指標在尾端
  3. write(int):寫入單個字元
  4. write(char):寫入指定陣列
  5. write(charl.off,.len):寫入指定陣列的指定部分6) write (string):寫入整個字串
  6. write(string,off,len):寫入字串的指定部分

相關APl: String類: toCharArray:將String轉換成char[]

注意: FileWriter使用後,必須要關閉(close)或重新整理(flush),否則寫入不到指定的檔案!因為僅僅到了Java程式記憶體中。

FileReader 和FileWriter 應用案例

  1. 使用FileReader 從story.txt 讀取內容,並顯示
package com.hspedu.reader_;

import org.junit.jupiter.api.Test;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class FileReader_ {
    public static void main(String[] args) {


    }

    /**
     * 單個字元讀取檔案
     */
    @Test
    public void readFile01() {
        String filePath = "e:\\story.txt";
        FileReader fileReader = null;
        int data = 0;
        //1. 建立FileReader物件
        try {
            fileReader = new FileReader(filePath);
            //迴圈讀取 使用read, 單個字元讀取
            while ((data = fileReader.read()) != -1) {
                System.out.print((char) data);
            }

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

    /**
     * 字元陣列讀取檔案
     */
    @Test
    public void readFile02() {
        System.out.println("~~~readFile02 ~~~");
        String filePath = "e:\\story.txt";
        FileReader fileReader = null;

        int readLen = 0;
        char[] buf = new char[8];
        //1. 建立FileReader物件
        try {
            fileReader = new FileReader(filePath);
            //迴圈讀取 使用read(buf), 返回的是實際讀取到的字元數
            //如果返回-1, 說明到檔案結束
            while ((readLen = fileReader.read(buf)) != -1) {
                System.out.print(new String(buf, 0, readLen));
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileReader != null) {
                    fileReader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
  1. 使用FileWriter 將“風雨之後,定見彩虹” 寫入到note.txt 檔案中, 注意細節.
package com.hspedu.writer_;

import java.io.FileWriter;
import java.io.IOException;

public class FileWriter_ {
    public static void main(String[] args) {

        String filePath = "e:\\note.txt";
        //建立FileWriter物件
        FileWriter fileWriter = null;
        char[] chars = {'a', 'b', 'c'};
        try {
            fileWriter = new FileWriter(filePath);//預設是覆蓋寫入
//            3) write(int):寫入單個字元
            fileWriter.write('H');
//            4) write(char[]):寫入指定陣列
            fileWriter.write(chars);
//            5) write(char[],off,len):寫入指定陣列的指定部分
            fileWriter.write("教育".toCharArray(), 0, 3);
//            6) write(string):寫入整個字串
            fileWriter.write(" 你好北京~");
            fileWriter.write("風雨之後,定見彩虹");
//            7) write(string,off,len):寫入字串的指定部分
            fileWriter.write("上海天津", 0, 2); // 輸出上海,因為從offset0開始,寫入兩個字元
            //在資料量大的情況下,可以使用迴圈操作.


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

            // 對應FileWriter , 一定要關閉流,或者flush才能真正的把資料寫入到檔案
            // 看原始碼就知道原因.
            /*
                看看程式碼
                private void writeBytes() throws IOException {
        this.bb.flip();
        int var1 = this.bb.limit();
        int var2 = this.bb.position();

        assert var2 <= var1;

        int var3 = var2 <= var1 ? var1 - var2 : 0;
        if (var3 > 0) {
            if (this.ch != null) {
                assert this.ch.write(this.bb) == var3 : var3;
            } else {
                this.out.write(this.bb.array(), this.bb.arrayOffset() + var2, var3);
            }
        }

        this.bb.clear();
    }
             */
            try {
                //fileWriter.flush();
                //關閉檔案流,等價於 flush() + 關閉
                fileWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

        System.out.println("程式結束...");
    }
}

節點流和處理流

基本介紹

  1. 節點流可以從一個特定的資料來源讀寫資料,如FileReader、FileWriter

    image-20230504222403139

  2. 處理流(也叫包裝流)是“連線”在已存在的流(節點流或處理流)之上,為程式提供更為強大的讀寫功能,也更加靈活,如BufferedReader、BufferedWriter

節點流和處理流一覽圖

節點流和處理流的區別和聯絡

  1. 節點流是底層流/低階流,直接跟資料來源相接。
  2. 處理流(包裝流)包裝節點流,既可以消除不同節點流的實現差異,也可以提供更方
    便的方法來完成輸入輸出。
  3. 處理流(也叫包裝流)對節點流進行包裝,使用了修飾器設計模式,不會直接與資料
    源相連[模擬修飾器設計模式]
package com.hspedu;

public class StringReader_ extends Reader_ {
    public void readString() {
        System.out.println("讀取字串..");
    }
}
package com.hspedu;

public class FileReader_ extends Reader_ {
        public void readFile() {
        System.out.println("對檔案進行讀取...");
    }
}
package com.hspedu;

public abstract class Reader_ { //抽象類
    public void readFile() {
    }
    public void readString() {
    }

    //在Reader_ 抽象類,使用read方法統一管理.
    //後面在呼叫時,利於物件動態繫結機制, 繫結到對應的實現子類即可.
    //public abstract void read();
}
package com.hspedu;

/**
 * 做成處理流/包裝流
 */
public class BufferedReader_ extends Reader_{

    private Reader_ reader_; //屬性是 Reader_型別

    //接收Reader_ 子類物件
    public BufferedReader_(Reader_ reader_) {
        this.reader_ = reader_;
    }

    public void readFile() { //封裝一層
        reader_.readFile();
    }

    //讓方法更加靈活, 多次讀取檔案, 或者加緩衝byte[] ....
    public void readFiles(int num) {
        for(int i = 0; i < num; i++) {
            reader_.readFile();
        }
    }

    //擴充套件 readString, 批次處理字串資料
    public void readStrings(int num) {
        for(int i = 0; i <num; i++) {
            reader_.readString();
        }
    }

}
package com.hspedu;

import java.io.*;

public class Test_ {
    public static void main(String[] args) {


        BufferedReader_ bufferedReader_ = new BufferedReader_(new FileReader_());
        bufferedReader_.readFiles(10);
        //bufferedReader_.readFile();
        //Serializable
        //Externalizable
        //ObjectInputStream
        //ObjectOutputStream
        //這次希望透過 BufferedReader_ 多次讀取字串
        BufferedReader_ bufferedReader_2 = new BufferedReader_(new StringReader_());
        bufferedReader_2.readStrings(5);
    }
}

處理流的功能

  1. 效能的提高:主要以增加緩衝的方式來提高輸入輸出的效率。
  2. 操作的便捷:處理流可能提供了一系列便捷的方法來一次輸入輸出大批次的資料,使用更加靈活方便。

處理流 BufferedReader 和BufferedWriter

BufferedReader 和 BufferedWriter屬於字元流,是按照字元來讀取資料的

關閉時處理流,只需要關閉外層流即可(因為真正工作的是內層流,關閉時只需要關閉外層處理流,會自動關閉內層流)【原始碼】

應用案例

  1. 使用BufferedReader讀取文字檔案,並顯示在控制檯
package com.hspedu.reader_;

import java.io.BufferedReader;
import java.io.FileReader;

/**
 * 演示bufferedReader 使用
 */
public class BufferedReader_ {
    public static void main(String[] args) throws Exception {

        String filePath = "e:\\a.java";
        //建立bufferedReader
        BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
        //讀取
        String line; //按行讀取, 效率高
        //說明
        //1. bufferedReader.readLine() 是按行讀取檔案
        //2. 當返回null 時,表示檔案讀取完畢
        while ((line = bufferedReader.readLine()) != null) {
            System.out.println(line);
        }

        //關閉流, 這裡注意,只需要關閉 BufferedReader ,因為底層會自動的去關閉 節點流
        //FileReader。
        /*
            public void close() throws IOException {
                synchronized (lock) {
                    if (in == null)
                        return;
                    try {
                        in.close();//in 就是我們傳入的 new FileReader(filePath), 關閉了.
                    } finally {
                        in = null;
                        cb = null;
                    }
                }
            }

         */
        bufferedReader.close();

    }
}
  1. 使用BufferedWriter將” hello,教育”,寫入到檔案中
package com.hspedu.writer_;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

/**
 * 演示BufferedWriter的使用
 */
public class BufferedWriter_ {
    public static void main(String[] args) throws IOException {
        String filePath = "e:\\ok.txt";
        //建立BufferedWriter
        //說明:
        //1. new FileWriter(filePath, true) 表示以追加的方式寫入
        //2. new FileWriter(filePath) , 表示以覆蓋的方式寫入
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));
        bufferedWriter.write("hello, 教育!");
        bufferedWriter.newLine();//插入一個和系統相關的換行
        bufferedWriter.write("hello2, 教育!");
        bufferedWriter.newLine();
        bufferedWriter.write("hello3, 教育!");
        bufferedWriter.newLine();
        //說明:關閉外層流即可 , 傳入的 new FileWriter(filePath) ,會在底層關閉
        bufferedWriter.close();

    }
}
  1. 綜合使用BufferedReader和 BufferedWriter完成文字檔案複製,注意檔案編碼。
package com.hspedu.writer_;

import java.io.*;

public class BufferedCopy_ {

    public static void main(String[] args) {


        //1. BufferedReader 和 BufferedWriter 是按照字元操作
        //2. 不要去操作 二進位制檔案[聲音,影片,doc, pdf ], 可能造成檔案損壞
        //BufferedInputStream
        //BufferedOutputStream
        String srcFilePath = "e:\\a.java";
        String destFilePath = "e:\\a2.java";
//        String srcFilePath = "e:\\0245_零基礎學Java_引出this.avi";
//        String destFilePath = "e:\\a2.avi";
        BufferedReader br = null;
        BufferedWriter bw = null;
        String line;
        try {
            br = new BufferedReader(new FileReader(srcFilePath));
            bw = new BufferedWriter(new FileWriter(destFilePath));

            //說明: readLine 讀取一行內容,但是沒有換行
            while ((line = br.readLine()) != null) {
                //每讀取一行,就寫入
                bw.write(line);
                //插入一個換行
                bw.newLine();
            }
            System.out.println("複製完畢...");

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //關閉流
            try {
                if(br != null) {
                    br.close();
                }
                if(bw != null) {
                    bw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }
}

處理流BufferedInputStream 和BufferedOutputStream

BufferedInputStream是位元組流在建立BufferedInputStream時,會建立一個內部緩衝區陣列。

BufferedOutputStream是位元組流, 實現緩衝的輸出流, 可以將多個位元組寫入底層輸出流中,而不必對每次位元組寫入呼叫底層系統。

應用案例

要求: 程式設計完成圖片/音樂的複製(要求使用Buffered..流).

package com.hspedu.outputstream_;

import java.io.*;

/**
 * 演示使用BufferedOutputStream 和 BufferedInputStream使用
 * 使用他們,可以完成二進位制檔案複製.
 * 思考:位元組流可以操作二進位制檔案,可以操作文字檔案嗎?當然可以
 */
public class BufferedCopy02 {
    public static void main(String[] args) {

//        String srcFilePath = "e:\\Koala.jpg";
//        String destFilePath = "e:\\hsp.jpg";
//        String srcFilePath = "e:\\0245_零基礎學Java_引出this.avi";
//        String destFilePath = "e:\\hsp.avi";
        String srcFilePath = "e:\\a.java";
        String destFilePath = "e:\\a3.java";

        //建立BufferedOutputStream物件BufferedInputStream物件
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            //因為 FileInputStream  是 InputStream 子類
            bis = new BufferedInputStream(new FileInputStream(srcFilePath));
            bos = new BufferedOutputStream(new FileOutputStream(destFilePath));

            //迴圈的讀取檔案,並寫入到 destFilePath
            byte[] buff = new byte[1024];
            int readLen = 0;
            //當返回 -1 時,就表示檔案讀取完畢
            while ((readLen = bis.read(buff)) != -1) {
                bos.write(buff, 0, readLen);
            }

            System.out.println("檔案複製完畢~~~");

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

            //關閉流 , 關閉外層的處理流即可,底層會去關閉節點流
            try {
                if(bis != null) {
                    bis.close();
                }
                if(bos != null) {
                    bos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}

物件流ObjectInputStream 和ObjectOutputStream

看一個需求:不僅需要儲存值,還需要儲存資料型別。

  1. 將int num = 100這個 int資料儲存到檔案中,注意不是100 數字,而是int 100,並且,能夠從檔案中直接恢復int 100

  2. 將Dog dog = new Dog(“小黃”,3)這個 dog物件儲存到檔案中,並且能夠從檔案恢復.

  3. 上面的要求,就是能夠將基本資料型別或者物件進行序列化和反序列化操作

序列化和反序列化

  1. 序列化就是在儲存資料時,儲存資料的值和資料型別
  2. 反序列化就是在恢復資料時,恢復資料的值和資料型別
  3. 需要讓某個物件支援序列化機制,則必須讓其類是可序列化的,為了讓某個類是可序列化的,該類必須實現如下兩個介面之一:
    • Serializable //這是一個標記介面,沒有方法
    • Externalizable //該介面有方法需要實現,因此我們一般實現上面的介面

物件流介紹

功能:提供了對基本型別或物件型別的序列化和反序列化的方法

  • ObjectOutputStream 提供序列化功能
  • ObjectInputStream 提供反序列化功能

應用案例

  1. 使用ObjectOutputStream序列化基本資料型別和一個Dog物件(name, age),並
    儲存到data.dat檔案中。
package com.hspedu.outputstream_;

import java.io.Serializable;

// 如果需要序列化某個類的物件,實現 Serializable
public class Dog implements Serializable {
    private String name;
    private int age;
    // 序列化物件時,預設將裡面所有屬性都進行序列化,但除了static或transient修飾的成員
    private static String nation;
    private transient String color;
    // 序列化物件時,要求裡面屬性的型別也需要實現序列化介面
    private Master master = new Master();

    //serialVersionUID 序列化的版本號,可以提高相容性
    private static final long serialVersionUID = 1L;

    public Dog(String name, int age, String nation, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
        this.nation = nation;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}' + nation + " " +master;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
package com.hspedu.outputstream_;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * 演示ObjectOutputStream的使用, 完成資料的序列化
 */
public class ObjectOutStream_ {
    public static void main(String[] args) throws Exception {
        //序列化後,儲存的檔案格式,不是存文字,而是按照他的格式來儲存
        String filePath = "e:\\data.dat";

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));

        //序列化資料到 e:\data.dat
        oos.writeInt(100);// int -> Integer (Integer 實現了 Serializable)
        oos.writeBoolean(true);// boolean -> Boolean (實現了 Serializable)
        oos.writeChar('a');// char -> Character (實現了 Serializable)
        oos.writeDouble(9.5);// double -> Double (實現了 Serializable)
        oos.writeUTF("教育");//String
        //儲存一個dog物件
        oos.writeObject(new Dog("旺財", 10, "日本", "白色"));
        oos.close();
        System.out.println("資料儲存完畢(序列化形式)");
    }
}
  1. 使用ObjectlnputStream 讀取data.dat 並反序列化恢復資料
package com.hspedu.inputstream_;



import com.hspedu.outputstream_.Dog;

import java.io.*;

public class ObjectInputStream_ {
    public static void main(String[] args) throws IOException, ClassNotFoundException {

        //指定反序列化的檔案
        String filePath = "e:\\data.dat";

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));

        //讀取
        //1. 讀取(反序列化)的順序需要和你儲存資料(序列化)的順序一致,因為儲存按順序儲存什麼樣的型別,讀取的時候也就要按照相應的型別。
        //2. 否則會出現異常

        System.out.println(ois.readInt());
        System.out.println(ois.readBoolean());

        System.out.println(ois.readChar());
        System.out.println(ois.readDouble());
        System.out.println(ois.readUTF());


        //dog 的編譯型別是 Object , dog 的執行型別是 Dog
        Object dog = ois.readObject();
        System.out.println("執行型別=" + dog.getClass());
        System.out.println("dog資訊=" + dog);//底層 Object -> Dog

        //這裡是特別重要的細節:

        //1. 如果我們希望呼叫Dog的方法, 需要向下轉型
        //2. 需要我們將Dog類的定義,放在到可以引用的位置
        Dog dog2 = (Dog)dog;
        System.out.println(dog2.getName()); //旺財..

        //關閉流, 關閉外層流即可,底層會關閉 FileInputStream 流
        ois.close();
    }
}
package com.hspedu.outputstream_;

import java.io.Serializable;

// 如果需要序列化某個類的物件,實現 Serializable
public class Dog implements Serializable {
    private String name;
    private int age;
    // 序列化物件時,預設將裡面所有屬性都進行序列化,但除了static或transient修飾的成員
    private static String nation;
    private transient String color;
    // 序列化物件時,要求裡面屬性的型別也需要實現序列化介面
    private Master master = new Master();

    //serialVersionUID 序列化的版本號,可以提高相容性
    private static final long serialVersionUID = 1L;

    public Dog(String name, int age, String nation, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
        this.nation = nation;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}' + nation + " " +master;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

注意事項和細節說明

  1. 讀寫順序要一致。

  2. 要求序列化或反序列化物件,需要實現Serializable

  3. 序列化的類中建議新增SerialVersionUID,為了提高版本的相容性。當加入新屬性時,序列化和反序列化會認為是原來的修改版,而不會認為是一個全新的類。

    private static final long serialVersionUID = 1L;
    
  4. 序列化物件時,預設將裡面所有屬性都進行序列化,但除了static或transient修飾的成員。也就是序列化並不儲存statictransient修飾的資訊。

  5. 序列化物件時,要求裡面屬性的型別也需要實現序列化介面

  6. 序列化具備可繼承性,也就是如果某類已經實現了序列化,則它的所有子類也已經預設實現了序列化。

標準輸入輸出流

介紹

應用案例1

傳統方法System.out.println("");是使用out 物件將資料輸出到顯示器

應用案例2

傳統的方法, Scanner是從標準輸入鍵盤接收資料

package com.hspedu.standard;

import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Scanner;

public class InputAndOutput {
    public static void main(String[] args) {
        //System 類的 public final static InputStream in = null;
        // System.in 編譯型別   InputStream
        // System.in 執行型別   BufferedInputStream
        // 表示的是標準輸入 鍵盤
        System.out.println(System.in.getClass());

        //1. System.out public final static PrintStream out = null;
        //2. 編譯型別 PrintStream
        //3. 執行型別 PrintStream
        //4. 表示標準輸出 顯示器
        System.out.println(System.out.getClass());

        System.out.println("hello, 教育");

        Scanner scanner = new Scanner(System.in);
        System.out.println("輸入內容");
        String next = scanner.next();
        System.out.println("next=" + next);
    }
}

轉換流InputStreamReader 和OutputStreamWriter

檔案亂碼問題,引出學習轉換流必要性。

可以這麼理解:相當於把InputStream轉為Reader,把OutputStream轉為Writer。

  1. InputStreamReader:Reader的子類,可以將InputStream(位元組流)包裝成(轉換)Reader(字元流)
  2. OutputStreamWriter:Writer的子類,實現將OutputStream(位元組流)
    包裝成Writer(字元流)
  3. 當處理純文字資料時,如果使用字元流效率更高,並且可以有效解決中文
    問題,所以建議將位元組流轉換成字元流。
  4. 可以在使用時指定編碼格式(比如utf-8, gbk , gb2312,ISO8859-1等)

應用案例

1.程式設計將位元組流FilelnputStream包裝成(轉換成)字元流InputStreamReader,對
檔案進行讀取(按照utf-8/gbk格式),進而包裝成 BufferedReader

package com.hspedu.transformation;

import java.io.*;

/**
 * 演示使用 InputStreamReader 轉換流解決中文亂碼問題
 * 將位元組流 FileInputStream 轉成字元流  InputStreamReader, 指定編碼 gbk/utf-8
 */
public class InputStreamReader_ {
    public static void main(String[] args) throws IOException {

        String filePath = "e:\\a.txt";
        //解讀
        //1. 把 FileInputStream 轉成 InputStreamReader
        //2. 指定編碼 gbk
        //InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
        //3. 把 InputStreamReader 傳入 BufferedReader
        //BufferedReader br = new BufferedReader(isr);

        //將2 和 3 合在一起
        BufferedReader br = new BufferedReader(new InputStreamReader(
                                                    new FileInputStream(filePath), "gbk"));

        //4. 讀取
        String s = br.readLine();
        System.out.println("讀取內容=" + s);
        //5. 關閉外層流
        br.close();
    }
}
  1. 程式設計將位元組流 FileOutputStream包裝成(轉換成)字元流OutputStreamWriter,對檔案進行寫入(按照gbk格式,可以指定其他,比如utf-8)
package com.hspedu.transformation;

import java.io.*;

/**
 * 演示 OutputStreamWriter 使用
 * 把FileOutputStream 位元組流,轉成字元流 OutputStreamWriter
 * 指定處理的編碼 gbk/utf-8/utf8
 */
public class OutputStreamWriter_ {
    public static void main(String[] args) throws IOException {
        String filePath = "e:\\hsp.txt";
        String charSet = "utf-8";
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), charSet);
        osw.write("hi, 教育");
        osw.close();
        System.out.println("按照 " + charSet + " 儲存檔案成功~");
    }
}

列印流PrintStream 和PrintWriter

列印流只有輸出流,沒有輸入流

package com.hspedu.printstream;

import java.io.IOException;
import java.io.PrintStream;

/**
 * 演示PrintStream (位元組列印流/輸出流)
 */
public class PrintStream_ {
    public static void main(String[] args) throws IOException {

        PrintStream out = System.out;
        //在預設情況下,PrintStream 輸出資料的位置是 標準輸出,即顯示器
        /*
             public void print(String s) {
                if (s == null) {
                    s = "null";
                }
                write(s); //!!!
            }

         */
        out.print("john, hello");
        // 因為print底層使用的是write , 所以我們可以直接呼叫write進行列印/輸出
        out.write("你好".getBytes());
        out.close();

        //我們可以去修改列印流輸出的位置/裝置
        //1. 輸出修改成到 "e:\\f1.txt"
        //2. "hello, 教育~" 就會輸出到 e:\f1.txt
        //3. 原始碼:
        // public static void setOut(PrintStream out) {
        //        checkIO();
        //        setOut0(out); // native 方法,修改了out
        //   }
        System.setOut(new PrintStream("e:\\f1.txt"));
        System.out.println("hello, 教育~");
    }
}
package com.hspedu.transformation;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 演示 PrintWriter 使用方式
 */
public class PrintWriter_ {
    public static void main(String[] args) throws IOException {

        //PrintWriter printWriter = new PrintWriter(System.out); // 輸出到顯示器
        PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\f2.txt"));
        printWriter.print("hi, 北京你好~~~~");
        printWriter.close();//flush + 關閉流, 才會將資料寫入到檔案..

    }
}

Properties 類

看一個需求

如下一個配置檔案mysql.properties

  • ip=192.168.0.13
  • user=root
  • pwd=12345

請問程式設計讀取ip. user 和pwd的值是多少

分析

  1. 傳統的方法
  2. 使用Properties類可以方便實現

package com.hspedu.properties_;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

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


        //讀取mysql.properties 檔案,並得到ip, user 和 pwd
        BufferedReader br = new BufferedReader(new FileReader("src\\mysql.properties"));
        String line = "";
        while ((line = br.readLine()) != null) { //迴圈讀取
            String[] split = line.split("=");
            //如果我們要求指定的ip值
            if("ip".equals(split[0])) {
                System.out.println(split[0] + "值是: " + split[1]);
            }
        }
        br.close();
    }
}

基本介紹

  1. 專門用於讀寫配置檔案的集合類

    配置檔案的格式:

    鍵=值

    鍵=值

  2. 注意:鍵值對不需要有空格,值不需要用引號一起來。預設型別是String

  1. Properties的常見方法
  • load:載入配置檔案的鍵值對到Properties物件
  • list:將資料顯示到指定裝置
  • getProperty(key):根據鍵獲取值
  • setProperty(key,value):設定鍵值對到Properties物件
  • store:將Properties中的鍵值對儲存到配置檔案,在idea中,儲存資訊到配置檔案,如果含有中文,會儲存為unicode碼
    http://tool.chinaz.com/tools/unicode.aspx unicode碼查詢工具

應用案例

1.使用Properties類完成對mysql.properties的讀取,看老師程式碼演示

package com.hspedu.properties_;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;

public class Properties02 {
    public static void main(String[] args) throws IOException {
        //使用Properties 類來讀取mysql.properties 檔案

        //1. 建立Properties 物件
        Properties properties = new Properties();
        //2. 載入指定配置檔案
        properties.load(new FileReader("src\\mysql.properties"));
        //3. 把k-v顯示控制檯
        properties.list(System.out);
        //4. 根據key 獲取對應的值
        String user = properties.getProperty("user");
        String pwd = properties.getProperty("pwd");
        System.out.println("使用者名稱=" + user);
        System.out.println("密碼是=" + pwd);
    }
}

2.使用Properties類新增key-val 到新檔案mysql2.properties中

3.使用Properties類完成對 mysq12.properties 的讀取,並修改某個key-val

package com.hspedu.properties_;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;

public class Properties03 {
    public static void main(String[] args) throws IOException {
        //使用Properties 類來建立 配置檔案, 修改配置檔案內容

        Properties properties = new Properties();
        //建立
        //1. 如果該檔案沒有key 就是建立!!
        //2. 如果該檔案有key ,就是修改!!
        /*
            Properties 父類是 Hashtable , 底層就是Hashtable 核心方法
            public synchronized V put(K key, V value) {
                // Make sure the value is not null
                if (value == null) {
                    throw new NullPointerException();
                }

                // Makes sure the key is not already in the hashtable.
                Entry<?,?> tab[] = table;
                int hash = key.hashCode();
                int index = (hash & 0x7FFFFFFF) % tab.length;
                @SuppressWarnings("unchecked")
                Entry<K,V> entry = (Entry<K,V>)tab[index];
                for(; entry != null ; entry = entry.next) {
                    if ((entry.hash == hash) && entry.key.equals(key)) {
                        V old = entry.value;
                        entry.value = value;//如果key 存在,就替換
                        return old;
                    }
                }

                addEntry(hash, key, value, index);//如果是新k, 就addEntry
                return null;
            }

         */
        properties.setProperty("charset", "utf8");
        properties.setProperty("user", "湯姆");//注意儲存時,是中文的 unicode碼值
        properties.setProperty("pwd", "888888");

        //將k-v 儲存檔案中即可
        properties.store(new FileOutputStream("src\\mysql2.properties"), null);
        System.out.println("儲存配置檔案成功~");

    }
}

本章作業

1.程式設計題

(1)在判斷e盤下是否有資料夾mytemp ,如果沒有就建立mytemp

(2)在e:llmytemp目錄下,建立檔案hello.txt

(3)如果hello.txt已經存在,提示該檔案已經存在,就不要再重複建立了

(4)並且在hello.txt檔案中,寫入hello,world~

package com.hspedu.homework;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class Homework01 {
    public static void main(String[] args) throws IOException {
        /**
         *(1) 在判斷 e 盤下是否有資料夾mytemp ,如果沒有就建立mytemp
         *(2) 在e:\\mytemp 目錄下, 建立檔案 hello.txt
         *(3) 如果hello.txt 已經存在,提示該檔案已經存在,就不要再重複建立了
         *(4) 並且在hello.txt 檔案中,寫入 hello,world~
         */

        String directoryPath = "e:\\mytemp";
        File file = new File(directoryPath);
        if(!file.exists()) {
            //建立
            if(file.mkdirs()) {
                System.out.println("建立 " + directoryPath + " 建立成功" );
            }else {
                System.out.println("建立 " + directoryPath + " 建立失敗");
            }
        }

        String filePath  = directoryPath + "\\hello.txt";// e:\mytemp\hello.txt
        file = new File(filePath);
        if(!file.exists()) {
            //建立檔案
            if(file.createNewFile()) {
                System.out.println(filePath + " 建立成功~");

                //如果檔案存在,我們就使用BufferedWriter 字元輸入流寫入內容
                BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file));
                bufferedWriter.write("hello, world~~ 教育");
                bufferedWriter.close();

            } else {
                System.out.println(filePath + " 建立失敗~");
            }
        } else {
            //如果檔案已經存在,給出提示資訊
            System.out.println(filePath + " 已經存在,不在重複建立...");
        }
    }
}

2.程式設計題
要求:使用BufferedReader讀取一個文字檔案,為每行加上行號,再連同內容一併輸出到螢幕上。

//如果把檔案的編碼改成了 gbk,出現中文亂碼,大家思考如何解決

//1.預設是按照utf-8處理,開始沒有亂碼

//2.提示:使用我們的轉換流,將FilelnputStream -> InputStreamReader[可以指定編碼]- >BufferedReader ...

package com.hspedu.homework;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Homework02 {
    public static void main(String[] args) {
        /**
         * 要求:  使用BufferedReader讀取一個文字檔案,為每行加上行號,
         * 再連同內容一併輸出到螢幕上。
         */
        String filePath = "e:\\a.txt";
        BufferedReader br = null;
        String line = "";
        int lineNum = 0;
        try {
            br = new BufferedReader(new FileReader(filePath));
            while ((line = br.readLine()) != null) { //迴圈讀取
                System.out.println(++lineNum + line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {

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

3.程式設計題
(1)要編寫一個dog.properties

  • name=tom
  • age=5
  • color=red

(2)編寫Dog 類(name,age,color)建立一個dog物件,讀取dog.properties用相應的內容完成屬性初始化,並輸出

(3)將建立的Dog物件,序列化到檔案dog.dat 檔案

package com.hspedu.homework;

import org.junit.jupiter.api.Test;

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

public class Homework03 {
    public static void main(String[] args) throws IOException {
        /**
         * (1) 要編寫一個dog.properties   name=tom age=5 color=red
         * (2) 編寫 Dog 類(name,age,color)  建立一個dog物件,讀取dog.properties 用相應的內容完成屬性初始化, 並輸出
         * (3) 將建立的Dog 物件 ,序列化到 檔案 e:\\dog.dat 檔案
         */
        String filePath = "src\\dog.properties";
        Properties properties = new Properties();
        properties.load(new FileReader(filePath));
        String name = properties.get("name") + ""; //Object -> String
        int age = Integer.parseInt(properties.get("age") + "");// Object -> int
        String color = properties.get("color") + "";//Object -> String

        Dog dog = new Dog(name, age, color);
        System.out.println("===dog物件資訊====");
        System.out.println(dog);

        //將建立的Dog 物件 ,序列化到 檔案 dog.dat 檔案
        String serFilePath = "e:\\dog.dat";
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(serFilePath));
        oos.writeObject(dog);

        //關閉流
        oos.close();
        System.out.println("dog物件,序列化完成...");
    }

    //在編寫一個方法,反序列化dog
    @Test
    public void m1() throws IOException, ClassNotFoundException {
        String serFilePath = "e:\\dog.dat";
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(serFilePath));
        Dog dog = (Dog)ois.readObject();

        System.out.println("===反序列化後 dog====");
        System.out.println(dog);

        ois.close();

    }
}

class Dog implements  Serializable{
    private String name;
    private int age;
    private String color;

    public Dog(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}';
    }
}