自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一篇帶你徹底讀懂 IO 流技術(shù)!

存儲 存儲架構(gòu)
本文闡述的內(nèi)容較多,整合了很多有用的信息,從 Java 基本的 I/O 類庫結(jié)構(gòu)開始說起,主要介紹了 IO 的傳輸格式和傳輸方式,包括字節(jié)流和字符流接口相關(guān)的分類介紹,以及磁盤 I/O 和網(wǎng)絡(luò) I/O 的基本工作方式。

一、摘要

說到 IO,相信大家都不陌生,英文全稱:Input/Output,即輸入/輸出,通常指數(shù)據(jù)在內(nèi)部存儲器和外部存儲器或其他周邊設(shè)備之間的輸入和輸出。

比如我們常用的SD卡、U盤、移動硬盤等等存儲文件的硬件設(shè)備,當我們將其插入電腦的 usb 硬件接口時,我們就可以從電腦中讀取設(shè)備中的信息或者寫入信息,這個過程就涉及到 I/O 的操作。

當然,涉及 I/O 的操作,也不僅僅局限于硬件設(shè)備的讀寫,還有網(wǎng)絡(luò)數(shù)據(jù)的傳輸。比如,我們在電腦上用瀏覽器搜索互聯(lián)網(wǎng)上的信息,這個信息的過程也涉及到 I/O 的操作。

無論是從磁盤中讀寫文件,還是在網(wǎng)絡(luò)中傳輸數(shù)據(jù),可以說 I/O 主要為處理人機交互、機與機交互中獲取和交換信息提供的一套解決方案。

在 Java 的 IO 體系中,類將近有 80 個,位于java.io包下,初步看起來感覺非常復(fù)雜,但是經(jīng)過一番梳理之后,你會發(fā)現(xiàn)還是有規(guī)律可循的。

從傳輸數(shù)據(jù)的格式角度看,可以大致分為兩組:

  • 基于字節(jié)操作的 I/O 接口:InputStream 和 OutputStream
  • 基于字符操作的 I/O 接口:Reader 和 Writer

從傳輸數(shù)據(jù)的方式角度看,也可以大致分為兩組:

  • 基于磁盤操作的 I/O 接口:File
  • 基于網(wǎng)絡(luò)操作的 I/O 接口:Socket

雖然 Socket 類并不在java.io包下,但是我們?nèi)匀话阉鼈儎澐衷谝黄?,因?I/O 的核心問題,要么是數(shù)據(jù)格式影響 I/O 操作,要么是傳輸方式影響 I/O 操作,也就是將什么樣的數(shù)據(jù)寫到什么地方的問題。

I/O 只是人與機器或者機器與機器交互的手段,除了在它們能夠完成這個交互功能外,我們關(guān)注的就是如何提高它的運行效率,而數(shù)據(jù)格式和傳輸方式是影響效率最關(guān)鍵的因素。

下面我們基于這兩點,來展開分析!

二、傳輸格式的分類

從傳輸格式角度看,可以分兩類:字節(jié)流和字符流。

  • 基于字節(jié)的輸入和輸出操作接口分別是:InputStream 和 OutputStream
  • 基于字符的輸入和輸出操作接口分別是:Reader 和 Writer 。

圖片

2.1、字節(jié)流接口

字節(jié)流,是 I/O 流中最底層的流,能處理任何類型的數(shù)據(jù)傳輸,比如文字、圖片、視頻、文件等。

2.1.1、基于字節(jié)輸入流的接口

打開 JDK 源碼,整理之后,InputStream 輸入流接口的類繼承層次如下圖所示:

圖片

這些輸入流類,根據(jù)角色不同,還可以進行分類,分為:節(jié)點流和處理流。

  • 節(jié)點流:指的是向指定的設(shè)備,比如磁盤、網(wǎng)絡(luò),進行讀/寫數(shù)據(jù),也被稱為底層流,直接和數(shù)據(jù)源相接
  • 處理流:指的是在已存在的節(jié)點流或者處理流基礎(chǔ)上,包裝一些更加方便操作 io 流的功能,比如壓縮、序列化、緩沖操作等,也被稱為包裝流

輸入流類,根據(jù)角色的劃分類別如下:

圖片

OutputStream 輸出流的類層次結(jié)構(gòu)也是類似。

2.1.2、基于字節(jié)輸出流的接口

OutputStream 輸入流接口的類繼承層次如下圖所示:

圖片

字節(jié)輸出流類,根據(jù)角色的劃分類別如下:

圖片

這里就不詳細的介紹各個子類的使用方法,有興趣的朋友可以查看 JDK 的 API 說明文檔,筆者也會在后期的系列文章會進行詳細的介紹。

這里只是重點想說一下,無論是輸入還是輸出,操作數(shù)據(jù)的方式可以組合使用,各個處理流的類并不是只操作固定的節(jié)點流,比如如下輸出方式:

//將文件輸出流包裝到序列化輸出流中,再將序列化輸出流包裝到緩沖中
OutputStream out = new BufferedOutputStream(new ObjectOutputStream(new FileOutputStream(new File("fileName")));

另外,輸出流最終寫到什么地方必須要指定,要么是寫到硬盤中,要么是寫到網(wǎng)絡(luò)中,從圖中可以發(fā)現(xiàn),寫網(wǎng)絡(luò)實際上也是寫文件,只不過寫到網(wǎng)絡(luò)中,需要經(jīng)過底層操作系統(tǒng)將數(shù)據(jù)發(fā)送到其他指定的計算機中,而不是寫入到本地硬盤中。

2.2、字符流接口

不管是磁盤還是網(wǎng)絡(luò)傳輸,最小的存儲單元都是字節(jié),而不是字符,所以 I/O 操作的都是字節(jié)而不是字符。

那為什么要有操作字符的 I/O 接口呢?

這是因為我們的程序中通常操作的數(shù)據(jù)都是以字符形式,為了程序操作更方便而提供一個直接寫字符的 I/O 接口,僅此而已!

除此之外,使用字節(jié)流操控文字時不是很方便,容易亂碼,由此誕生了不同的字符集以及對應(yīng)的字符編碼規(guī)則!

由于全世界的文字博大精深,不同的字符集,占用的字節(jié)位數(shù)不同,以中文為例,在GBK編碼規(guī)則中,一個中文使用二個字節(jié)存儲;而在UTF-8編碼規(guī)則中,一個中文使用三個字節(jié)存儲,如果寫入和讀取的編碼規(guī)則不一樣,讀取的字節(jié)數(shù)很容易裂開,導(dǎo)致出現(xiàn)亂碼。

比如以下案例:

public static void main(String[] args) throws Exception {
    byte[] bytes = "學(xué)習(xí)Java語言".getBytes("ISO8859-1");
    File file = new File("encoding.txt");
    OutputStream out = new FileOutputStream(file);
    out.write(bytes);
    out.close();
}

文件的內(nèi)容如下:

??Java??

為了更方便地處理中文這些字符,計算機就推出了字符編碼規(guī)則。

實現(xiàn)原理:字節(jié)流 + 編碼表。

  • 當寫入一段文字時,會使用指定的字符集,將該 String 編碼為一系列字節(jié),將結(jié)果存儲到新的字節(jié)數(shù)組中,進行傳輸
  • 當讀取一段文字時,通過指定的字符集,解碼指定的字節(jié)數(shù)組來構(gòu)造新的 String,從而解決文字亂碼的問題。
2.2.1、基于字符輸入流的接口

Reader 輸入流接口的類繼承層次如下圖所示:

圖片

同樣的,字符輸入流類,根據(jù)角色的劃分類別如下:

圖片

2.2.2、基于字符輸出流的接口

Writer 輸出流的類繼承層次如下圖所示:

圖片

字符輸出流類,根據(jù)角色的劃分類別如下:

圖片

2.3、字節(jié)與字符的轉(zhuǎn)化

剛剛我們說到,不管是磁盤還是網(wǎng)絡(luò)傳輸,最小的存儲單元都是字節(jié),而不是字符,設(shè)計字符的原因是為了程序更方便的操作文本。

那么怎么將字符轉(zhuǎn)化成字節(jié)或者將字節(jié)轉(zhuǎn)化成字符呢?

其中,InputStreamReader和OutputStreamWriter就是轉(zhuǎn)化橋梁。

2.3.1、輸入流轉(zhuǎn)換方案

輸入流字符解碼相關(guān)類結(jié)構(gòu)的轉(zhuǎn)化過程如下圖所示:

圖片

從圖上可以看到,InputStreamReader類是字節(jié)到字符的轉(zhuǎn)化橋梁, 其中StreamDecoder指的是一個解碼操作類,Charset指的是字符集。

InputStream到Reader的過程需要指定編碼字符集,否則將采用操作系統(tǒng)默認字符集,很可能會出現(xiàn)亂碼問題,StreamDecoder則是完成字節(jié)到字符的解碼的實現(xiàn)類。

案例如下:

File file = new File("encoding.txt");
FileInputStream inputStream =new FileInputStream(file);
//字節(jié)輸入流轉(zhuǎn)為字符輸入流
InputStreamReader streamReader =new InputStreamReader(inputStream, Charset.forName("UTF-8"));
2.3.2、輸出流轉(zhuǎn)換方案

輸出流轉(zhuǎn)化過程也是類似,如下圖所示:

圖片

通過OutputStreamWriter類完成字符到字節(jié)的編碼過程,由StreamEncoder 完成編碼過程。

案例如下:

File file = new File("output.txt");
FileOutputStream outputStream =new FileOutputStream(file);
//字符輸出流轉(zhuǎn)字節(jié)輸出流
OutputStreamWriter streamWriter =new OutputStreamWriter(outputStream, Charset.forName("UTF-8"));

三、傳輸方式的分類

上文我們介紹了數(shù)據(jù)的傳輸格式,可以通過字節(jié)流和字符流接口來完成數(shù)據(jù)的傳輸,至于數(shù)據(jù)寫到何處,主要取決于數(shù)據(jù)的傳輸方式。

從傳輸方式角度看,可以分兩類:磁盤和網(wǎng)絡(luò)。

  • 基于磁盤操作的操作接口是:File
  • 基于網(wǎng)絡(luò)操作的操作接口是:Socket

3.1、文件接口

我們知道數(shù)據(jù)在磁盤的唯一最小描述就是文件,也就是說上層應(yīng)用程序只能通過文件來操作磁盤上的數(shù)據(jù),文件也是操作系統(tǒng)和磁盤驅(qū)動器交互的一個最小單元。

圖片

在 Java I/O 體系中,**File類是唯一代表磁盤文件本身的對象**。

File 類定義了一些與平臺無關(guān)的方法來操作文件,包括檢查一個文件是否存在、創(chuàng)建、刪除文件、重命名文件、判斷文件的讀寫權(quán)限是否存在、設(shè)置和查詢文件的最近修改時間等等操作。

值得注意的是 Java 中通常的 File 并不代表一個真實存在的文件對象,當你通過指定一個路徑描述符時,它就會返回一個代表這個路徑相關(guān)聯(lián)的一個虛擬對象,這個可能是一個真實存在的文件或者是一個包含多個文件的目錄。

例如,讀取一個文件內(nèi)容,程序如下:

public static void main(String[] args) throws Exception {
    StringBuilder str = new StringBuilder();
    char[] buf = new char[1024];

    // 讀取文件的內(nèi)容
    FileReader f = new FileReader("input.txt");
    while(f.read(buf)>0){
        str.append(buf);
    }
    str.toString();
}

以上面的程序為例,從硬盤中讀取一段文本字符,操作流程如下圖:

圖片

當我們傳入一個指定的文件名來創(chuàng)建File對象,通過FileReader來讀取文件內(nèi)容時,會自動創(chuàng)建一個FileInputStream對象來讀取文件內(nèi)容,也就是我們上文中所說的字節(jié)流來讀取文件。

緊接著,會創(chuàng)建一個FileDescriptor的對象,其實這個對象就是真正代表一個存在的文件對象的描述。

由于我們需要讀取的是字符格式,所以需要StreamDecoder類通過解碼方法decode,將字節(jié)轉(zhuǎn)字符,至于如何從磁盤驅(qū)動器上讀取一段數(shù)據(jù),由操作系統(tǒng)幫我們完成。

3.2、網(wǎng)絡(luò)接口

繼續(xù)來說說數(shù)據(jù)傳輸?shù)牧硪环N處理方式:網(wǎng)絡(luò)通信。

3.2.1、Socket 簡介

在 Java 網(wǎng)絡(luò)體系中,Socket是描述計算機之間完成相互通信一種抽象定義。

光從描述看可能很難理解,打個比方,可以把Socket比作為兩個城市之間的交通工具,有了它,就可以在城市之間來回穿梭了;并且,交通工具有多種,每種交通工具也有相應(yīng)的交通規(guī)則。

Socket 也一樣,也有多種,大部分情況下我們使用的都是基于 TCP/IP 的流套接字,它是一種穩(wěn)定的通信協(xié)議。

比較典型的基于 Socket 通信的應(yīng)用程序場景,如下圖:

圖片

主機 A 的應(yīng)用程序要想和主機 B 的應(yīng)用程序通信,必須通過 Socket 建立連接,而建立 Socket 連接必須需要底層 TCP/IP 協(xié)議來建立 TCP 連接。

3.2.2、建立通信鏈路

我們知道網(wǎng)絡(luò)層使用的 IP 協(xié)議可以幫助我們根據(jù) IP 地址來找到目標主機,但是一臺主機上可能運行著多個應(yīng)用程序,如何才能與指定的應(yīng)用程序通信呢?

這個時候需要通過 TCP 或 UPD 協(xié)議,也就是指定對應(yīng)的端口號。

通過 IP + 端口號,就可以創(chuàng)建一個代表唯一一個主機上的一個應(yīng)用程序的通信鏈路了,創(chuàng)建后的通信鏈路我們稱它為 Socket 實例。

以 TCP 協(xié)議為例,為了準確無誤地把數(shù)據(jù)送達目標處,TCP 協(xié)議采用了三次握手策略,如下圖:

圖片

其中,SYN 全稱為 Synchronize Sequence Numbers,表示同步序列編號,是 TCP/IP 建立連接時使用的握手信號。

ACK 全稱為 Acknowledge character,即確認字符,表示發(fā)來的數(shù)據(jù)已確認接收無誤。

在客戶機和客戶機之間建立正常的 TCP 網(wǎng)絡(luò)連接時,發(fā)送端首先發(fā)出一個 SYN 消息,接收端使用 SYN + ACK 應(yīng)答表示接收到了這個消息,最后發(fā)送端再以 ACK 消息響應(yīng)。

整體流程如下:

  • 發(fā)送端 –(發(fā)送帶有 SYN 標志的數(shù)據(jù)包 )–> 接受端(第一次握手);
  • 接受端 –(發(fā)送帶有 SYN + ACK 標志的數(shù)據(jù)包)–> 發(fā)送端(第二次握手);
  • 發(fā)送端 –(發(fā)送帶有 ACK 標志的數(shù)據(jù)包) –> 接受端(第三次握手);

完成三次握手之后,發(fā)送端和接收端之間建立起可靠的 TCP 連接,客戶端應(yīng)用程序與服務(wù)器應(yīng)用程序就可以開始傳送數(shù)據(jù)了。

3.2.3、傳輸數(shù)據(jù)

當客戶端要與服務(wù)端通信時,客戶端首先要創(chuàng)建一個 Socket 實例,也就是指定目標服務(wù)器的 IP 和端口。

默認操作系統(tǒng)將為這個 Socket 實例分配一個沒有被使用的本地端口號,并創(chuàng)建一個包含本地、遠程地址和端口號的套接字數(shù)據(jù)結(jié)構(gòu),這個數(shù)據(jù)結(jié)構(gòu)將一直保存在系統(tǒng)中直到這個連接關(guān)閉。

  • 客戶端簡單示例
public static void main(String[] args) throws IOException {
    //通過IP和端口與服務(wù)端建立連接
    Socket socket =new Socket("127.0.0.1",8080);
    //將字符流轉(zhuǎn)化成字節(jié)流,并輸出
    BufferedWriter bufferedWriter =new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
    String str="Hello,我是客戶端!";
    bufferedWriter.write(str);
    bufferedWriter.flush();
    bufferedWriter.close();
}
  • 服務(wù)端簡單示例
public static void main(String[] args) throws Exception {
    //初始化服務(wù)端socket并且綁定 8080 端口
    ServerSocket serverSocket = new ServerSocket(8080);
    //循環(huán)監(jiān)聽所有連接的客戶端請求
    while (true){
        try {
            //等待客戶端的連接
            Socket socket = serverSocket.accept();
            //將字節(jié)流轉(zhuǎn)化成字符流,讀取客戶端輸入的內(nèi)容
            BufferedReader bufferedReader =new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //讀取一行數(shù)據(jù)
            String str = bufferedReader.readLine();
            //輸出打印
            System.out.println("服務(wù)端收到客戶端發(fā)送的信息:" + str);
        } catch (Exception e) {
        }
    }
}

我們先啟動服務(wù)端程序,再運行客戶端,服務(wù)端收到客戶端發(fā)送的信息,打印結(jié)果如下:

服務(wù)端收到客戶端發(fā)送的信息:Hello,我是客戶端!

注意,客戶端只有與服務(wù)端建立三次握手成功之后,才會發(fā)送數(shù)據(jù),而 TCP/IP 握手過程,底層操作系統(tǒng)已經(jīng)幫我們實現(xiàn)了!

當連接已經(jīng)建立成功,服務(wù)端和客戶端都會擁有一個Socket實例,每個Socket實例都有一個InputStream和OutputStream,正如我們前面所說的,網(wǎng)絡(luò) I/O 都是以字節(jié)流傳輸?shù)?,Socket正是通過這兩個對象來交換數(shù)據(jù)。

當Socket對象創(chuàng)建時,操作系統(tǒng)同時將會為InputStream和OutputStream分別分配一定大小的緩沖區(qū),數(shù)據(jù)的寫入和讀取都是通過這個緩存區(qū)完成的。

發(fā)送端將數(shù)據(jù)寫到OutputStream對應(yīng)的SendQ隊列中,當隊列填滿時,數(shù)據(jù)將被發(fā)送到另一端InputStream的RecvQ隊列中,如果這時RecvQ已經(jīng)滿了,那么OutputStream的write方法將會阻塞直到RecvQ隊列有足夠的空間容納SendQ發(fā)送的數(shù)據(jù)。

值得特別注意的是,緩存區(qū)的大小以及寫入端的速度和讀取端的速度非常影響這個連接的數(shù)據(jù)傳輸效率,由于可能會發(fā)生阻塞,所以網(wǎng)絡(luò) I/O 和磁盤 I/O 在數(shù)據(jù)的寫入和讀取還要有一個協(xié)調(diào)的過程,如果兩邊同時傳送數(shù)據(jù),可能會產(chǎn)生死鎖的問題。

如何提高網(wǎng)絡(luò) IO 傳輸效率、保證數(shù)據(jù)傳輸?shù)目煽?,這個我們后面單獨開篇進行講解。

四、小結(jié)

本文闡述的內(nèi)容較多,整合了很多有用的信息,從 Java 基本的 I/O 類庫結(jié)構(gòu)開始說起,主要介紹了 IO 的傳輸格式和傳輸方式,包括字節(jié)流和字符流接口相關(guān)的分類介紹,以及磁盤 I/O 和網(wǎng)絡(luò) I/O 的基本工作方式。

五、參考

1、https://developer.ibm.com/zh/articles/j-lo-javaio/

責任編輯:武曉燕 來源: Java極客技術(shù)
相關(guān)推薦

2023-02-28 23:04:15

2018-04-09 16:35:10

數(shù)據(jù)庫MySQLInnoDB

2021-05-09 09:06:24

Python批處理命令

2015-10-22 14:32:44

微服務(wù)PaaS應(yīng)用開發(fā)

2019-11-14 05:22:41

Javascript語言this

2021-07-03 08:04:10

io_uringNode.js異步IO

2021-05-20 06:57:16

RabbitMQ開源消息

2023-04-20 08:00:00

ES搜索引擎MySQL

2020-10-23 07:56:04

Java中的IO流

2023-05-05 06:39:52

Java工廠設(shè)計模式

2021-06-16 08:28:25

unary 方法函數(shù)技術(shù)

2022-03-11 10:21:30

IO系統(tǒng)日志

2022-03-10 08:31:51

REST接口規(guī)范設(shè)計Restful架構(gòu)

2021-05-18 05:40:27

kubebuilderwebhook進階

2025-01-17 07:00:00

2022-02-24 07:56:42

開發(fā)Viteesbuild

2021-05-12 06:18:19

KubeBuilderOperatork8s

2021-05-17 05:51:31

KubeBuilderOperator測試

2022-07-07 12:01:43

ATTCALDERA框架

2024-06-25 08:18:55

點贊
收藏

51CTO技術(shù)棧公眾號