首頁>技術>

IO異常處理

JDK7之前在處理IO異常時需要手動釋放資源

try(	建立流物件,如果有多個使用分號隔開){}catch(異常型別 變數名){	異常處理}

FileInputStream和FileOutputStream都已經實現了Autocloseable介面

Properties檔案處理

Properties檔案是日常開發中經常使用的配置檔案,例如springboot應用就有application.properties檔案。

Java對Properties檔案處理提供了java.util.Properties類來處理。

Properties

Properties繼承自java.util.HashTable,使用鍵值對儲存資料,可以當成Map集合使用。

 /**     * Properties當做Map使用     */    @Test    public void testPropertyAsMap() {        //建立一個Properties物件        Properties properties = new Properties();        //鍵和值的型別是java.lang.Object型別        properties.put("姓名", "tony");        properties.put("年齡", 28);        log.info(properties);    }

程式執行結果

 /**     * Properties儲存資料     */    @Test    public void testPropertySetGet() {        Properties properties = new Properties();        //等價於Map的put()方法        //設定屬性         properties.setProperty("server.port", "8091");        properties.setProperty("spring.application.name", "order-api");        //獲取所有的屬性        Set<String> stringPropertyNames = properties.stringPropertyNames();        for (String stringPropertyName : stringPropertyNames) {            //根據屬性獲取值            String value = (String) properties.get(stringPropertyName);            log.info("key :{} value:{}", stringPropertyName, value);        }    }

程式執行結果

不過通常是用於結合流來載入配置檔案(字尾名是.properties的檔案),也就是讀取配置檔案的資料到記憶體中,Properties中鍵值對的型別只能是String型別。

讀取檔案之前首先在src/main/resources目錄下準備一個application.properties檔案

然後可以使用Properties物件的load()方法讀取Properties檔案

偶爾也會往配置檔案中寫入資料,寫資料時可以使用Properties物件的store()方法

緩衝流緩衝流概述

緩衝流可以實現高效的讀寫,是對InputStream,OutputStream,Reader,Writer提供了緩衝的功能。其基本原理是在建立流物件時,會建立一個內建的預設大小的緩衝陣列,透過換緩衝區讀寫,減少系統IO次數,從而提高讀寫效率。

位元組緩衝流

位元組緩衝流主要包含位元組緩衝輸入流(BufferedInputStream)和位元組緩衝輸出流(BufferedOutputStream)java.io.BufferedInputStream體系結構

java.io.BufferedOutputStream體系結構

BufferedInputStream使用InputStream的引數建立物件BufferedOutputStream使用OutputStream的引數建立物件

    /**     * BufferedInputStream物件的建立     */    @Test    public void testBufferedInputStreamConstructor() {        File file = new File("/Users/liuguanglei/Documents/test/test.txt");        try (                InputStream inputStream = new FileInputStream(file);                BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);        ) {        } catch (IOException ex) {                ex.printStackTrace();        }    }

BufferedInputStream和BufferedOutputStream相比InputStream和OutputStream在IO效能方面有很大的提升。

這裡我們以複製大話Java效能最佳化這本書為例,該書的大小是122.8M,來對比它們之間的效能差異。

首先使用InputStream和OutputStream逐位元組複製大話Java效能最佳化

  /**     * 普通位元組流讀寫一個位元組實現複製檔案     */    @Test    public void testInputStreamOutputStreamFileCopyV1() {        long beginTime = System.currentTimeMillis();        File sourceFile = new File("/Users/liuguanglei/Documents/book/大話JAVA效能最佳化.pdf");        File targetFile = new File("/Users/liuguanglei/Documents/book/大話JAVA效能最佳化v1.pdf");        try (                InputStream inputStream = new FileInputStream(sourceFile);                OutputStream outputStream = new FileOutputStream(targetFile);        ) {            int length = 0;            while ((length = inputStream.read()) != -1) {                outputStream.write(length);            }        } catch (IOException ex) {            ex.printStackTrace();        }        long endTime = System.currentTimeMillis();        long time = endTime - beginTime;        log.info("【 普通位元組流讀寫一個位元組實現複製檔案】大話JAVA效能最佳化.pdf複製耗時的時間是{}毫秒", time);    }

程式執行結果

大話Java效能最佳化v1

當使用InputStream和OutputStream複製大話JAVA效能最佳化.pdf時耗時557263毫秒

然後使用BufferedInputStream和BufferedOutputStream逐位元組複製大話Java效能最佳化

 /**     * 緩衝位元組流讀寫一個位元組複製檔案     */    @Test    public void testBufferedInputStreamOutputStreamFileCopyV2() {        long beginTime = System.currentTimeMillis();        File sourceFile = new File("/Users/liuguanglei/Documents/book/大話JAVA效能最佳化.pdf");        File targetFile = new File("/Users/liuguanglei/Documents/book/大話JAVA效能最佳化v2.pdf");        try (                InputStream inputStream = new FileInputStream(sourceFile);                OutputStream outputStream = new FileOutputStream(targetFile);                BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);                BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);        ) {            int length = 0;            while ((length = bufferedInputStream.read()) != -1) {                bufferedOutputStream.write(length);            }        } catch (IOException ex) {            ex.printStackTrace();        }        long endTime = System.currentTimeMillis();        long time = endTime - beginTime;        log.info("【緩衝位元組流讀寫一個位元組複製檔案】大話JAVA效能最佳化.pdf複製耗時的時間是{}毫秒", time);    }

程式執行結果

大話JAVA效能最佳化v2

而使用了BufferedInputStream和OutputStream複製大話JAVA效能最佳化.pdf複製耗時的時間是2272毫秒

最後使用BufferedInputStream和BufferedOutputStream複製,每次讀取8192個位元組

/**     * 緩衝流每次讀取8192位元組實現檔案複製     */    @Test    public void testBufferedInputStreamOutputStreamFileCopyV3() {        long beginTime = System.currentTimeMillis();        File sourceFile = new File("/Users/liuguanglei/Documents/book/大話JAVA效能最佳化.pdf");        File targetFile = new File("/Users/liuguanglei/Documents/book/大話JAVA效能最佳化v3.pdf");        try (                InputStream inputStream = new FileInputStream(sourceFile);                OutputStream outputStream = new FileOutputStream(targetFile);                BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);                BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);        ) {            int length = 0;            byte[] buffer = new byte[8192];            while ((length = bufferedInputStream.read(buffer)) != -1) {                bufferedOutputStream.write(buffer, 0, length);            }        } catch (IOException ex) {            ex.printStackTrace();        }        long endTime = System.currentTimeMillis();        long time = endTime - beginTime;        log.info("【緩衝流每次讀取8192位元組實現檔案複製】大話JAVA效能最佳化.pdf複製耗時的時間是{}毫秒", time);    }

程式執行結果

程式執行結果

使用緩衝流每次讀取8192位元組實現檔案複製大話JAVA效能最佳化.pdf複製耗時的時間是227毫秒,因此是效率最快的方法。

所有的測試結果基於macBookPro 2019 15寸

字元緩衝流

字元緩衝包含字元緩衝輸入流(BufferedReader)和字元緩衝輸出流(BufferedWriter)java.io.BufferedReader的體系結構

java.io.BufferedWriter的體系結構

BufferedReader和BufferedWriter分別是基於Reader和Writer建立物件

 /**     * BufferedReader和BufferedWriter物件的建立     */    @Test    public void testBufferedReaderBufferedWriter() {        String sourceFileName = "buffered_file_read.txt";        String targetFileName = "buffered_file_write.txt";        File sourceFile = new File("/Users/liuguanglei/Documents/test", sourceFileName);        //檔案不存在        if (!sourceFile.exists()) {            try {                //建立檔案                sourceFile.createNewFile();            } catch (IOException e) {                e.printStackTrace();            }        }        File targetFile = new File("/Users/liuguanglei/Documents/test", targetFileName);        try (                Reader reader = new FileReader(sourceFile);                Writer writer = new FileWriter(targetFile);                BufferedReader bufferedReader = new BufferedReader(reader);                BufferedWriter bufferedWriter = new BufferedWriter(writer);        ) {        } catch (IOException ex) {            ex.printStackTrace();        }    }

字元緩衝流相對於Writer和Reader提供了換行讀寫資料的方法其中BufferedWriter提供了newLine()方法用於寫完資料後換行

 /**     * 換行寫資料     */    @Test    public void testBufferedWriteNewLine() {        String targetFileName = "buffered_file_write.txt";        File targetFile = new File("/Users/liuguanglei/Documents/test", targetFileName);        if (!targetFile.exists()) {            try {                targetFile.createNewFile();            } catch (IOException e) {                e.printStackTrace();            }        }        try (                Writer writer = new FileWriter(targetFile);                BufferedWriter bufferedWriter = new BufferedWriter(writer);        ) {            bufferedWriter.write("跟光磊學Java開發");            //寫資料後換行            bufferedWriter.newLine();            bufferedWriter.write("跟光磊學前端開發");            //寫資料後換行            bufferedWriter.newLine();            bufferedWriter.write("跟光磊學大資料開發");            //寫資料後換行            bufferedWriter.newLine();            bufferedWriter.write("跟光磊學Linux運維");        } catch (IOException ex) {            ex.printStackTrace();        }    }

程式執行結果

其中BufferedReader提供了readLine()方法用於讀取一行資料

/**     * BufferedReader讀取行資料     * @see BufferedReader#readLine()      */    @Test    public void testBufferedReadNewLine() {        String fileName = "buffered_file_write.txt";        File targetFile = new File("/Users/liuguanglei/Documents/test", fileName);        if (!targetFile.exists()) {            try {                targetFile.createNewFile();            } catch (IOException e) {                e.printStackTrace();            }        }        try (                Reader reader = new FileReader(targetFile);                BufferedReader bufferedReader = new BufferedReader(reader);        ) {            //讀取第一行資料            String line = null;            while ((line = bufferedReader.readLine()) != null) {                log.info(line);            }        } catch (IOException ex) {            ex.printStackTrace();        }    }

程式執行結果

BufferedReader和BufferedWriter案例:實現檔案內容排序

首先準備測試檔案subject.txt

然後使用BufferedReader讀取檔案內容到List集合中,並使用Collections.sort()對集合的內容進行排序。最後使用BufferedWriter寫入檔案

     /**     * 檔案內容排序     */    @Test    public void testFileContentSort() {        try (                BufferedReader bufferedReader = new BufferedReader(new FileReader("/Users/liuguanglei/Documents/test/subject.txt"));        ) {            String content = null;            List<String> stringList = new ArrayList<>();            while ((content = bufferedReader.readLine()) != null) {                stringList.add(content);            }            //對集合的元素排序            Collections.sort(stringList);            //建立帶緩衝的字元輸出流            BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("/Users/liuguanglei/Documents/test/subject.txt"));            //將排序之後的字串寫入到subject.txt檔案中            for (String sortContent : stringList) {                bufferedWriter.write(sortContent);                bufferedWriter.newLine();            }            //關閉流            bufferedWriter.close();        } catch (IOException ex) {            ex.printStackTrace();        }    }

程式執行結果

程式執行結果

轉換流字符集和字元編碼

計算機底層儲存資料都是二進位制儲存,而平常看到的字元、圖片、音訊、影片都是由特定的應用軟體將二進位制的資料轉換後的結果。

按照某種規則將資料儲存到計算機中,稱為編碼。按照某種規則將計算機中儲存的二進位制資料解析出來,稱為解碼。

日常開發中出現亂碼的原因是編碼和解碼的規則不一致導致的。

規則就是字元編碼(Character Encoding),字元編碼就是字元與二進位制數之間的對應規則。常用的字元編碼有GBK編碼,UTF-8編碼。GBK編碼是針對中文的。

UTF-8包含了全世界絕大多數國家的字元和二進位制之間的對應規則,因此是最常用的編碼。

而字符集(Charset)也叫編碼表,是一個系統支援所有的字元集合,包含國家的文字、標點符號、圖形符號以及數字等等。計算機要準確的儲存和識別各種字符集符號,需要進行字元編碼,一套字符集至少要有一套字元編碼,常見的字符集有ASCII字符集、GBK字符集、Unicode字符集。

ASCII字符集對應ASCII編碼,ASCII字符集支援歐洲常用字元GBK字符集對應GBK編碼,GBK字符集支援最常用中文碼錶,同時支援繁體漢字和日韓漢字Unicode字符集對應UTF-8編碼、UTF-16編碼、UTF-32編碼,最常用的是UTF-8編碼,可以表示Unicode標準中的任何字元,也是電子郵件、網頁、以及其他儲存或者傳送文字應用中優先採用的編碼,網際網路工程工作小組要求所有網際網路協議必須支援UTF-8編碼,所以後期開發Web應用,也會使用UTF-8編碼,而UTF-8編碼針對不同的文字儲存時佔據不同的空間。其中ASCII字元的內容儲存時佔1個位元組、拉丁文字元佔據2個位元組,中文儲存時佔據3個位元組。其他極少數使用的Unicode輔助字元佔據四個位元組。

由此可見,當指定了編碼,它對應的字符集也就指定了,因此編碼才是開發人員關心的。

macOS下的文字預設是UTF-8編碼

轉換流的使用

在不使用轉換流之前,某些Windows的版本使用的是GBK編碼,而在IDEA中使用的編碼預設是UTF-8。如果使用字元流讀取含有中文的GBK編碼的檔案,就會出現亂碼。因為儲存的編碼是GBK,而讀取的編碼是UTF-8,此時出現了亂碼。

/**     * 讀取gbk.txt     */    @Test    public void testFileReadGBK() {        try (                Reader reader = new FileReader("/Users/liuguanglei/Documents/test/gbk.txt");        ) {            int content = 0;            while ((content = reader.read()) != -1) {                log.info((char) content);            }        } catch (IOException ex) {            ex.printStackTrace();        }    }

程式執行結果

由於位元組流、字元流都存在上述問題,因此Java提供了轉換流: java.io.InputStreamReader和java.io.OutputStreamWriter,轉換流在讀寫資料的基礎上提供了轉換編碼的功能。java.io.InputStreamReader繼承了Reader,它是位元組流轉換為字元流的橋樑,用於讀取資料。

java.io.InputStreamReader體系結構

InputStreamReader在讀取資料前需要建立該類的物件,可以使用平臺預設的編碼,也可以指定編碼。

/**     * InputStreamReader物件的建立     *     * @see InputStreamReader#InputStreamReader(InputStream) 預設編碼     * @see InputStreamReader#InputStreamReader(InputStream, Charset) 指定編碼     */    @Test    public void testInputStreamReaderConstructor() {        try (                InputStream inputStream = new FileInputStream("/Users/liuguanglei/Documents/test/gbk.txt");                //指定編碼為GBK建立InputStreamReader物件                InputStreamReader inputStreamReaderGBK = new InputStreamReader(inputStream, "gbk");                //預設編碼建立InputStreamReader物件                InputStreamReader inputStreamReaderDefaultEncoding = new InputStreamReader(inputStream);        ) {        } catch (IOException ex) {            ex.printStackTrace();        }    }

之前使用FileReader讀取gbk.txt檔案亂碼,這裡就可以使用InputStreamReader來解決

 /**     * InputStreamReader物件的建立     *     * @see InputStreamReader#InputStreamReader(InputStream, Charset) 指定編碼     */    @Test    public void testInputStreamReaderGBK() {        try (                InputStream inputStream = new FileInputStream("/Users/liuguanglei/Documents/test/gbk.txt");                //指定編碼為GBK建立InputStreamReader物件                InputStreamReader inputStreamReaderGBK = new InputStreamReader(inputStream, "gbk");        ) {            int content = 0;            while ((content = inputStreamReaderGBK.read()) != -1) {                log.info((char) content);            }        } catch (IOException ex) {            ex.printStackTrace();        }    }

程式執行結果

java.io.OutputStreamWriter可以指定編碼寫檔案,該類繼承自Writer,因此表示字元輸出流,用於寫資料。

java.io.OutputStreamWriter體系結構

OutputStreamWirter透過OuptStream建立物件,可以使用預設編碼和指定編碼兩種方式

 /**     * InputStreamReader物件的建立     */    @Test    public void testInputStreamReaderConstructor() {        try (                OutputStream outputStream = new FileOutputStream("/Users/liuguanglei/Documents/test/utf-8.txt");                //指定編碼為GBK建立OutputStreamWriter物件                OutputStreamWriter outputStreamWriterUTF8 = new OutputStreamWriter(outputStream, "utf-8");                //預設編碼建立OutputStreamWriter物件                OutputStreamWriter outputStreamWriterDefaultEncoding = new OutputStreamWriter(outputStream);        ) {        } catch (IOException ex) {            ex.printStackTrace();        }    }

OutputStreamWirter物件建立完畢後就可以使用wirte方法寫資料

 /**     * InputStreamReader物件的建立     */    @Test    public void testInputStreamReaderConstructor() {        try (                OutputStream outputStream = new FileOutputStream("/Users/liuguanglei/Documents/test/utf-8.txt");                //指定編碼為GBK建立OutputStreamWriter物件                OutputStreamWriter outputStreamWriterUTF8 = new OutputStreamWriter(outputStream, "utf-8");                //預設編碼建立OutputStreamWriter物件                OutputStreamWriter outputStreamWriterDefaultEncoding = new OutputStreamWriter(outputStream);        ) {        } catch (IOException ex) {            ex.printStackTrace();        }    }

程式執行結果

使用UTF-8編碼和使用GBK編碼的中文內容佔據的記憶體大小是不一樣的

使用InputStreamReader和OutputStreamWriter實現檔案編碼轉換

 /**     * 將GBK編碼的gbk.txt轉換為UTF-8編碼     */    @Test    public void testFileEncodingGBK2UTF8() {        try (                InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("/Users/liuguanglei/Documents/test/gbk.txt"), "gbk");        ) {            int readContent = 0;            List<Integer> contentList = new ArrayList<>();            while ((readContent = inputStreamReader.read()) != -1) {                contentList.add(readContent);            }            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("/Users/liuguanglei/Documents/test/gbk.txt"), "utf-8");            for (Integer content : contentList) {                outputStreamWriter.write(content);            }            outputStreamWriter.close();        } catch (IOException ex) {            ex.printStackTrace();        }    }

程式執行結果

序列化流

序列化流能夠持久化物件

Java提供了一種物件序列化的機制:用一個位元組序列表示一個物件,該位元組序列包含該物件的資料、物件的型別以及物件中儲存的屬性資訊。位元組序列寫出到檔案後,相當於檔案中持久儲存了一個物件資訊。反之,該位元組序列也可以從檔案中讀取回來,重構物件,對它進行反序列化,物件的資料、物件的型別和物件中儲存的資料都可以在記憶體中建立物件。

序列化 就是物件轉換為位元組,Java提供了java.io.ObjectOutputStream來完成物件的序列化java.io.ObjectOutputStream體系結構

在序列化物件前該物件所屬的類必須實現java.io.Serializable介面,表示該類的物件可以被序列化

使用ObjectOuputStream的writeObejct()方法可以將物件序列化到指定的檔案中

序列化Employee物件到employee.txt檔案,該檔案不能使用文字編輯器開啟,但是可以反序列化讀取物件內容。

在序列化時還需要注意,如果序列化物件所屬的類中包含其他自定義的類,其他自定義的類也需要實現序列化介面,否則會觸發java.io.NotSerializableException異常。

而Employee之所以能實現序列化,除了自身實現了序列化介面以外,其包含的成員變數也實現了序列化介面。例如String,Integer都實現了序列化的介面。

如果物件的某個屬性不想被序列化,就得把該屬性標明為瞬態的,即使用transient關鍵字修飾即可。

反序列化:位元組轉換為物件,Java提供了java.io.ObjectInputStream來完成物件的反序列化,只需要呼叫該物件的readObject()即可

 /**     * 物件反序列化:位元組轉換為物件     */    @Test    public void testObjectInputStream() {        try (                //建立反序列化物件關聯源資料檔案路徑                ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("/Users/liuguanglei/Documents/test/employee.txt"));        ) {            //反序列化            Object object = objectInputStream.readObject();            if (null != object && object instanceof Employee) {                Employee employee = (Employee) object;                log.info(employee);            }        } catch (IOException | ClassNotFoundException ex) {            ex.printStackTrace();        }    }

程式執行結果

employeeNo使用transient關鍵字標註不允許被序列化後,反序列化時不會看到該屬性值。

反序列化時,對於JVM可以被反序列化的物件,它必須能夠找到class檔案的類,如果找不到該類的class檔案,則會丟擲一個ClassNotFoundExcepiton異常。另外當JVM反序列化物件時,能夠找到class檔案,但是class檔案在序列化後被修改了(例如修改屬性),那麼反序列化的操作也會失敗,丟擲一個InvildClassException,發生這個異常可能有如下的原因

該類的序列號版本與從流中讀取的類描述符的版本不一致,可以在類中定義一個序列版本號的靜態常量解決該問題。該類包含未知資料型別該類沒有可以訪問的無參構造方法

序列化的型別除了自定義的類以外,還可以序列化集合

列印流

第一個Java程式就使用了列印流(java.io.PrintStream),PrintStream繼承OutputStream,所以本質是一個位元組輸出流,可以列印所有的資料。java.io.PrintStream體系結構

PrintStream可以透過傳遞檔案路徑來建立物件,然後呼叫該物件的println()方法列印資料並換行,如果不換行呼叫print()方法接即可。

/**     * PrintStream可以列印任意型別的資料     *     * @see java.io.PrintStream#print(int)     * @see java.io.PrintStream#print(long)     * @see java.io.PrintStream#print(double)     */    @Test    public void testPrintObject() {        try (                //建立列印流物件,關聯列印內容到data.txt檔案                PrintStream printStream = new PrintStream("/Users/liuguanglei/Documents/test/data.txt");        ) {            //列印資料並換行            printStream.println(10);            printStream.println(10.0);            printStream.println("中國人");            printStream.println(Arrays.toString(new int[]{1,2,3,4,5,6,7,7}));            //列印資料不換行            printStream.print("跟");            printStream.print("光");            printStream.print("磊");            printStream.print("學");            printStream.print("Java");        } catch (IOException ex) {            ex.printStackTrace();        }    }

程式執行結果

而當年學習的第一個HelloWorld程式時的System.out.println()預設列印輸出到控制檯,可以呼叫System.setOut()方法改變資料的流向

  /**     * System.out預設輸出到終端     * 透過System.setOut(PrintStream printStream)改變資料的流向     *     * @see System#setOut(PrintStream)     */    @Test    public void testModifySystemOut2File() {        try (                PrintStream printStream = new PrintStream(new FileOutputStream("/Users/liuguanglei/Documents/test/data.txt"));        ) {            System.setOut(printStream);            System.out.println("跟光磊學前端開發");            System.out.println("跟光磊學Linux運維");        } catch (IOException ex) {        }    }

程式執行結果

裝飾者設計模式

裝飾者設計模式指的是在不改變原類,不使用繼承的基礎上,動態的擴充套件一個物件的功能。

實現裝飾者設計模式的步驟

裝飾類和被裝飾類必須實現相同的介面在裝飾類中必須接收被裝飾類的引用在裝飾類中對需要擴充套件的方法進行擴充套件在裝飾類中對不需要擴充套件的方法呼叫被裝飾類的同名方法

裝飾者模式類圖

/** * 統一資料訪問介面 * @param <T> */interface BaseDao<T> {    /**     * 增加資料     *     * @param element     * @return     */    int add(T element);    /**     * 刪除資料     *     * @param element     * @return     */    int delete(T element);    /**獲取資料     * @param element     * @return     */    T get(T element);}

然後定義一個預設實現類DefaultBaseDao,預設都是操作資料庫

@Log4j2class DefaultBaseDao<T> implements BaseDao<T> {    @Override    public int add(T element) {        if (element != null) {            log.info("資料入庫成功");            return 1;        }        return 0;    }    @Override    public int delete(T element) {        if (null != element) {            log.info("刪除資料{}成功", element);            return 1;        }        return 0;    }    @Override    public T get(T element) {        log.info("從資料庫獲取{}成功", element);        return element;    }}

然後定義一個增強實現類CachedBaseDao,主要是增強BaseDao的查詢資料方法,首先從快取中查詢,如果不存在,則再查詢資料庫

@Log4j2class CachedBaseDao<T> implements BaseDao<T> {    private BaseDao<T> baseDao;    CachedBaseDao(BaseDao baseDao) {        this.baseDao = baseDao;    }    @Override    public int add(T element) {        return baseDao.add(element);    }    //不需要增強的方法    @Override    public int delete(T element) {        return baseDao.delete(element);    }    /**     * 增強查詢方法 首先從快取中查,如果沒有則從資料庫中查     *     * @param element     * @return     */    @Override    public T get(T element) {        if (null != element) {            T result = getFormCached(element);            if (null != result) {                log.info("從快取中獲取資料成功");                return result;            } else {                log.info("從資料庫獲取資料成功");                return baseDao.get(element);            }        } else {            throw new IllegalArgumentException("引數非法");        }    }    /**     * 模擬從快取中獲取資料     *     * @param element     * @param <T>     * @return     */    private <T> T getFormCached(T element) {        if (null != element) {            return element;        }        return null;    }}

裝飾者設計模式測試用例

commons-io

commons-io是Apache開源組織提供有關IO操作的類庫。可以提高IO功能的開發效率,目前(2020/12/28)最新的版本是2.8.0,需要依賴Java8。

在使用commons-io之前,需要將commons-io的坐依賴新增到java-core-jdk-api模組的pom.xml檔案中

<dependency>            <groupId>commons-io</groupId>            <artifactId>commons-io</artifactId>			 <version>2.8.0</version>        </dependency>

commons-io依賴

commons-io提供了一個工具類 org.apache.commons.io.IOUtils封裝了大量IO讀寫操作的程式碼,其中有兩個常用的方法

public static int copy(final InputStream input, final OutputStream output) throws IOException 把input輸入流中的內容複製到output輸出流中,返回複製位元組的個數,適合2GB以下的檔案複製public static long copyLarge(final InputStream input, final OutputStream output) 把input輸入流中的內容複製到output輸出流中,返回複製位元組的個數,適合2GB以上的檔案複製
 /**     * 單個檔案複製     * @see IOUtils#copy(InputStream, OutputStream)      */    @Test    public void testFileCopy() {        try (                //建立檔案輸入流                InputStream inputStream = new FileInputStream("/Users/liuguanglei/Documents/book/深入理解JVM&G1GC.pdf");                //建立檔案輸出流                OutputStream outputStream = new FileOutputStream("/Users/liuguanglei/Documents/book/深入理解JVM&G1GCV1.pdf");        ) {            //適合2GB以下            IOUtils.copy(inputStream, outputStream);        } catch (IOException ex) {            ex.printStackTrace();        }    }

程式執行結果

commons-io還提供了一個工具類 org.apache.commons.io.FileUtils封裝了一些對檔案操作的方法

public static void copyDirectoryToDirectory(final File sourceDir, final File destinationDir) throws IOException 複製sourceDir資料夾到targetDir資料夾
  /**     * 資料夾遞迴複製     * @see FileUtils#copyDirectoryToDirectory(File, File)      */    @Test    public void testCopyDirectoryToDirectory() {        File srcFile = new File("/Users/liuguanglei/Documents/test");        File targetFile = new File("/Users/liuguanglei/Desktop");        try {            //複製原目錄到指定的目錄            //將/Users/liuguanglei/Documents/test資料夾複製到桌面            FileUtils.copyDirectoryToDirectory(srcFile, targetFile);        } catch (IOException exception) {            exception.printStackTrace();        }    }

程式執行結果

public static void copyFileToDirectory(final File srcFile, final File destDir) throws IOException 複製指定的檔案到destDir目錄下
    /**     * 檔案複製     * @see FileUtils#copyFileToDirectory(File, File)      */    @Test    public void testCopyFileToDirectory() {        File srcFile = new File("/Users/liuguanglei/Documents/book/深入理解JVM&G1GCV1.pdf");        File targetFile = new File("/Users/liuguanglei/Documents/深入理解JVM&G1GCV1.pdf");        try {            //複製檔案到指定的目錄            FileUtils.copyFileToDirectory(srcFile, targetFile);        } catch (IOException exception) {            exception.printStackTrace();        }    }

程式執行結果

13
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 網工必備學習內容——深度理解TCP、UDP協議