首頁 > 軟體

java管道piped輸入流與輸出流應用場景案例分析

2022-02-21 13:06:00

前言

PipedInputStream 和 PipedOutputStream 設計用來解決跨執行緒的位元組資料傳輸。它們總是成對出現的,而在使用上,也只能 工作在兩個不同的執行緒上,在一個執行緒裡使用管道輸入和輸出流可能會造成死鎖。網上有很多介紹這兩個存在於 io 包下的 api。卻幾乎 找不到一個寫 PipedInputStream 的使用場景的,所以本文結合實際業務,來聊一聊 PipedInputStream 的應用。

原理簡介

我們知道,輸出流寫資料,輸入流讀資料,PipedInputStream 和 PipedOutputStream 也一樣,在 PipedOutputStream 的內部有一個 PipedInputStream 型別的 sink屬性,用來接收 PipedOutputStream 寫入的位元組資料。

而在 PipedInputStream 內部,定義了一個預設為 1024 大小的位元組陣列 buffer,作為資料傳輸的緩衝區。這樣一來,就變成了 PipedOutputStream 往 buffer 裡寫資料,當寫滿了 buffer 時,便使用 notifyAll() 喚醒讀資料的執行緒可以讀資料了,然後阻塞 1s 後繼續嘗試寫資料。

PipedInputStream 從 buffer 裡讀資料,當資料讀完 buffer 為空時,便 notifyAll() 喚醒寫的執行緒可以寫資料了,然後阻塞 1s 後繼續嘗試讀資料。

PipedOutputStream 端資料寫完後,呼叫 close() 方法,會標記 PipedInputStream 裡的 closedByWriter=true。此時,從 buffer 讀取資料,會返回 -1。標識了資料讀完到達了流的末尾了。

使用場景概述

public static void main(String[] args) {
        try (PipedOutputStream out = new PipedOutputStream();
             PipedInputStream in = new PipedInputStream(out)) {
            new Thread(() -> {
                try {
                    out.write("hello kl".getBytes(StandardCharsets.UTF_8));
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
            int receive;
            while ((receive = in.read()) != -1) {
                System.err.print((char) receive);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

上面程式碼演示了,在一個執行緒裡寫資料,然後在 main 執行緒讀資料的場景,完成了跨執行緒的資料傳輸。寫到這裡,都挺乾巴巴的,很多人看了後肯定也不知道它到底能幹啥,有啥作用,繼續往下看。

實際應用

簡單的理解了原理後,寫了一個簡單的演示 demo,但是 demo 不能說明啥問題,那從一個執行緒傳輸位元組到另一個執行緒到底有啥用呢?博主,簡單的的總結下:通過 java 應用生成檔案,然後需要將檔案上傳到雲端的場景,都可以用管道流。相同的業務場景,在沒了解管道流之前,都是先將檔案寫入到本地磁碟,然後從檔案磁碟讀出來上傳到雲盤。瞭解這個後,可以腦補出很多的業務場景了(真實業務場景,都是博主遇到過的),比如:

案例一:EXCEL 檔案匯出功能

之前有一個檔案匯出的功能,但是因為,匯出的檔案比較大,匯出下載完的時間非常長,所以,設計成了,頁面點選匯出後,後臺觸發匯出任務,然後將mysql 中的資料根據匯出條件查詢出來,生成 Excel檔案,然後將檔案上傳到 oss,最後像觸發匯出任務的人的釘釘發一個下載檔案的連結。之前的做法,正如上面所言,先將檔案寫到本地,然後從本地目錄讀出來上傳到 oss,下面演示下管道流一步到位的方式:

public static void main(String[] args) {
        try (PipedOutputStream out = new PipedOutputStream();
             PipedInputStream in = new PipedInputStream(out)) {
            new Thread(() -> {
                Listdatabase = new LinkedList<>();
                try {
                    //檔案生成
                    ExcelUtils.getInstance().exportObjects2Excel(database,out);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
            //檔案上傳
            ossClient.putObject("test","test.xlsx",in);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

案例二:XML 檔案資料傳輸

此類需求常見於和銀行以及金融機構對接時,要求上報一些 xml 格式的資料,給到指定的 ftp、或是 oss 的某個目錄下,用於對賬。其實從檔案上傳的場景來說,和上面的案例一是一樣。也是我總結的那樣,在記憶體裡生成檔案,然後上傳到雲端,虛擬碼如下:

public static void main(String[] args) {
        try (PipedOutputStream out = new PipedOutputStream();
             PipedInputStream in = new PipedInputStream(out)) {
            new Thread(() -> {
                Listdatabase = new LinkedList<>();
                try(GZIPOutputStream gzipOut = new GZIPOutputStream(out)) {
                    Marshaller marshaller = JAXBContext.newInstance(Object.class).createMarshaller();
                    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
                    marshaller.marshal(database,gzipOut);
                } catch (IOException | JAXBException e) {
                    e.printStackTrace();
                }
            }).start();
            //檔案上傳
            ossClient.putObject("test","test.xml.gz",in);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

總體來說,和案例一沒啥區別,只是案例二多了一步壓縮操作,最終上傳的檔案是一個 gzip 的壓縮包,壓縮包內是 xml 檔案。用這種方式可以大大減少檔案的體積、提升上傳的速度

結語

PipedInputStream 和 PipedOutputStream 設計用來解決跨執行緒的位元組資料傳輸。在實際業務需求中,當需要在記憶體中生成檔案然後上傳到雲端時,請記得使用管道流

以上就是java管道piped輸入流與輸出流應用場景案例分析的詳細內容,更多關於java管道piped輸入流與輸出流應用的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com