第9章 - Java IO

第9章 - Java IO

作者:vwFisher
時間:2019-09-04
GitHub代碼:https://github.com/vwFisher/JavaBasicGuide

目錄



1 IO模型介紹

1.1 Linux的網(wǎng)絡IO模型

Linux內(nèi)核將所有外部設備都可以看成一個文件操作(那么對外部設備的操作都可以看成對文件進行操作)

對一個文件的讀寫假勿,都通過調(diào)用內(nèi)核提供的系統(tǒng)調(diào)用衣屏。

內(nèi)核給我們返回一個 file descriptor(fd, 文件描述符)妄帘。而對一個 socket 的讀寫也會有相應的描述符骤星,稱為 socketfd (socket描述符)碧聪,描述符就是一個數(shù)字恤磷,指向內(nèi)核中一個結構體(文件路徑, 數(shù)據(jù)區(qū)等一些屬性)败明。

先對一些方法進行講解:

一隘马、recvfrom:經(jīng) Socket 接收數(shù)據(jù), 函數(shù)原型如下:

ssize_t recvfrom(
    int sockfd, // 標識一個已連接套接口的描述字
    void *buf, // 接收數(shù)據(jù)緩沖區(qū)
    size_t len, // 緩沖區(qū)長度
    unsigned int flags,  // 調(diào)用操作方式(一個或多個標志組合)
    struct sockaddr *from, // [可選]指針, 指向裝有源地址的緩沖區(qū)
    socket_t *fromlen // [可選]指針, 指向from緩沖區(qū)長度值
); 

二、select:同步阻塞方式妻顶。幾乎所有 unix酸员、linux 都支持的一種 多路IO 方式, 通過 select 函數(shù)發(fā)出 IO 請求后, 線程阻塞, 一直到數(shù)據(jù)準備完畢, 然后才能把數(shù)據(jù)從核心空間拷貝到用戶空間

三蜒车、poll:poll 對 select 的使用方法進行了一些改進, 突破了最大文件數(shù)的限制, 同時使用更加方便一些

四、epoll:是為了解決 select/poll 的性能問題沸呐〈纪酰基本思路如下:

  1. 專門的內(nèi)核線程來不停地掃描 fd列表
  2. 有結果后呢燥,把結果放到 fd 相關的鏈表中
  3. 用戶線程只需要定期從該 fd 對應的鏈表中讀取事件就可以了
  4. 為了節(jié)省把數(shù)據(jù)從核心空間拷貝到用戶空間的消耗崭添,采用了 mmap 方式,允許程序在用戶空間直接訪問數(shù)據(jù)所在的內(nèi)核空間叛氨,不需要把數(shù)據(jù) copy一份

服務器端編程經(jīng)常需要構造高性能的 IO模型呼渣,常見的 IO模型 有四種,Unix 提供了 五種 IO模型

一寞埠、同步阻塞IO (Blocking IO)

傳統(tǒng)的 IO模型屁置,Java 中老的 BIO 便是這種模式。在接到事件(數(shù)據(jù)到達仁连、數(shù)據(jù)拷貝完成等)前程序需阻塞等待蓝角。

優(yōu)點:編碼簡單。

缺點:效率低饭冬,處理程序阻塞會導致 cpu利用率 很低

二使鹅、同步非阻塞IO (Non-blocking IO)

默認創(chuàng)建的 socket 都是阻塞的,非阻塞IO 要求 socket 被設置為 NONBLOCK昌抠。

在未接到事件時處理程序一直主動輪詢患朱,這樣處理程序無需阻塞,可以在輪詢間歇去干別的炊苫,但是輪詢會造成重復請求裁厅,同樣浪費資源。以前 Java 中實現(xiàn)的的 偽異步(偽AIO) 模式就是采用這種思想

三侨艾、IO多路復用 (IO Multiplexing) 異步阻塞IO

經(jīng)典的 Reactor 設計模式执虹,Java 中的 Selector 和 Linux 中的 epoll 都是這種模型。

增加了對 socket 的事件監(jiān)聽器(selector)唠梨,從而把處理程序和對應的 socket事件 解耦袋励,所用的 socket連接 都注冊在 監(jiān)聽器,在等待階段只有監(jiān)聽器會阻塞姻成,處理線程從監(jiān)聽器獲取事件對 socket連接處理 即可插龄,而且一個處理線程可以對應多個連接(前兩種一般都是一個 socket 連接起一個線程,這就是為什么叫復用)科展,優(yōu)點是節(jié)省資源均牢,由于處理程序能夠被多個連接復用,因此少數(shù)的線程就能處理大量連接才睹。缺點同樣因為復用徘跪,如果是大量費時處理的連接(如大量連接上傳大文件)甘邀,很容易造成線程占滿而導致新連接失敗

四、信號驅(qū)動IO模型

在數(shù)據(jù)準別階段無需阻塞垮庐,只需向系統(tǒng)注冊一個信號松邪,在數(shù)據(jù)準備好后,系統(tǒng)會響應該信號哨查。該模型依賴于系統(tǒng)實現(xiàn)逗抑,而且信號通信使用比較麻煩,因此 Java 中未有對應實現(xiàn)

五寒亥、異步IO (Asynchronous IO) 異步非阻塞IO

經(jīng)典的 Proactor 設計模式邮府,與 信號驅(qū)動IO 很類似,而且在數(shù)據(jù)拷貝階段(指數(shù)據(jù)從系統(tǒng)緩沖區(qū)拷貝至程序自己的緩沖區(qū)溉奕,其他模型改階段程序都需要阻塞等待)同樣可以異步處理.

優(yōu)點:效率很高褂傀;

缺點:依賴系統(tǒng)底層實現(xiàn)。JDK1.7 之后在 concurrent 包中提供

1.1.1 BIO - 同步阻塞I/O模型

BIO-同步阻塞IO模型.png

同步阻塞IO 模型是最常用的 IO模型, 用戶線程在內(nèi)核進行 IO操作 時被阻塞, 默認所有文件操作都是阻塞的.

以 Socket(套接字) 為例來講解此模型. 在進程空間中調(diào)用 recvfrom, 其系統(tǒng)調(diào)用直到數(shù)據(jù)報到達且被拷貝到應用進程的緩沖區(qū)或者發(fā)生錯誤才返回, 期間一直在等待. 我們就說進程從調(diào)用 recvfrom 開始到它返回的整段時間內(nèi)是被阻塞的.

用戶線程通過系統(tǒng)調(diào)用 read 發(fā)起 IO 讀操作加勤,由用戶空間轉(zhuǎn)到內(nèi)核空間仙辟。內(nèi)核等到數(shù)據(jù)包到達后,然后將接收的數(shù)據(jù)拷貝到用戶空間鳄梅,完成 read 操作叠国。所以用戶需要等待 read 將 socket 中的數(shù)據(jù)讀取到 buffer 后,才能繼續(xù)處理數(shù)據(jù)卫枝。整個過程用戶線程都是被阻塞的煎饼。則阻塞中,CPU 的利用效率就很低了

用戶線程使用 同步阻塞IO模型 的偽代碼描述為:

{
    read(socket, buffer); // 讀取 socket 數(shù)據(jù)到 buffer
    process(buffer);  // 處理 buffer 數(shù)據(jù)
}

1.1.2 N-BIO - 同步非阻塞IO模型

NBIO-同步非阻塞IO模型.png

同步非阻塞IO 是在 同步阻塞IO 的基礎上, 將 socket 設置為 NONBLOCK. 這樣做用戶線程可以在發(fā)起 IO請求 后可以立即返回校赤。recvfrom 從應用層到內(nèi)核的時候吆玖,如果緩沖區(qū)沒有數(shù)據(jù)的話,就直接返回一個 EWOULDBLOCK 錯誤马篮,一般都對非阻塞IO模型 進行輪詢檢查這個狀態(tài)沾乘,看內(nèi)核是不是有數(shù)據(jù)到來

由于 socket 是非阻塞的方式,用戶線程需要不斷read浑测,嘗試讀取 socket 數(shù)據(jù)翅阵,直到讀取成功,才能繼續(xù)處理迁央。

優(yōu)點:調(diào)用 read 立即返回掷匠,如果讀取失敗,可以做其他事

缺點:需要不斷輪詢請求岖圈,直至成功才能讀取數(shù)據(jù)繼續(xù)處理讹语,會消耗大量 CPU資源

用戶線程使用 同步非阻塞IO模型 的偽代碼描述為:

{
    while(read(socket, buffer) != SUCCESS); // 不斷循環(huán) 讀取 socket 數(shù)據(jù)到 buffer
    process(buffer);  // 處理 buffer 數(shù)據(jù)
}

1.1.3 IO多路復用模型

IO多路復用模型.png

建立在內(nèi)核提供的多路分離函數(shù) select 基礎之上的,避免同步非阻塞IO模型中輪詢等待的問題蜂科。

Linux 提供 select/poll顽决,進程通過將一個或多個 fd 傳遞給 select 或 poll 系統(tǒng)調(diào)用短条,阻塞在 select。由 select/poll 幫我們偵測 fd 是否就緒才菠。但 select/poll 是順序掃描 fd 是否就緒, 且支持的 fd 數(shù)量有限.

用戶線程茸时,先將需要進行 IO操作 的 socket 添加到 select 中,然后阻塞等待 select 系統(tǒng)調(diào)用返回赋访。當數(shù)據(jù)到達時可都, socket 被激活,select 函數(shù)返回进每。用戶線程正式發(fā)起 read 請求汹粤,讀取數(shù)據(jù)并繼續(xù)執(zhí)行

優(yōu)點:可以在一個線程內(nèi)同時處理多個 Socket 的 IO請求命斧,用戶可以注冊多個 Socket田晚,然后不斷調(diào)用 select 讀取被激活的 Socket」幔可以提高 CPU 利用率

缺點:使用 select 函數(shù)進行 IO請求 與 BIO 沒什么區(qū)別贤徒,而且還多了監(jiān)視 Socket 等操作。效率更差

其中 while 循環(huán)前將 socket 添加到 select 監(jiān)視中, 然后在 while 內(nèi)一直調(diào)用 select 獲取被激活的 socket, 一旦 socket 可讀, 便調(diào)用 read函數(shù) 將 socket 中的數(shù)據(jù)讀取出來.

用戶線程使用select函數(shù)的偽代碼描述為:

{
    select(socket);
    while(1) {
        sockets = select();
        for(socket in sockets) {
            if(can_read(socket)) {
                read(socket, buffer);
                process(buffer);
            }
        }
    }
}

1.1.3.1 Reactor模型

Reactor設計模式

IO多路復用模型 使用了 Reactor 設計模式實現(xiàn)了這一機制汇四。Linux 還提供了一個 epoll系統(tǒng) 調(diào)用接奈,epoll 是基于事件驅(qū)動方式,而不是順序掃描通孽,當有 fd 就緒時序宦,立即回調(diào)函數(shù) rollback.

通過 Reactor 方式,用戶線程可以將輪詢IO操作狀態(tài)的工作統(tǒng)一交給 handle_events 事件循環(huán)進行處理. 用戶線程注冊事件處理器之后可以繼續(xù)執(zhí)行做其他的工作(異步)背苦。

Reactor 負責調(diào)用內(nèi)核的 select 函數(shù)檢查 socket 狀態(tài)互捌。當有 socket 被激活時,則通知相應的用戶線程(或執(zhí)行用戶線程的回調(diào)函數(shù))行剂,執(zhí)行 handle_event 進行數(shù)據(jù)讀取秕噪、處理的工作。由于 select 函數(shù)是阻塞的厚宰,因此 多路IO復用模型也 被稱為 異步阻塞IO模型腌巾。

注意:這里的所說的阻塞是指 select 函數(shù)執(zhí)行時線程被阻塞,而不是指 socket. 一般在使用 IO多路復用模型 時铲觉, socket 都是設置為 NONBLOCK 的澈蝙,不過這并不會產(chǎn)生影響,因為用戶發(fā)起 IO請求 時撵幽,數(shù)據(jù)已經(jīng)到達了灯荧,用戶線程一定不會被阻塞

IO多路復用 是最常使用的 IO模型, 但是其異步程度還不夠 徹底, 因為它使用了會阻塞線程的 select 系統(tǒng)調(diào)用. 因此 IO多路復用 只能稱為 異步阻塞IO, 而非真正的 異步IO

一、EventHandler:表示IO事件處理器, 它擁有IO文件句柄Handle, 以及對Handle的操作

  1. get_handler():獲取IO文件句柄
  2. handle_evnet():處理讀/寫事件

二并齐、Concrete:繼承EventHandler漏麦。對事件處理器的行為進行定制

三客税、Reactor:用于管理EventHandler,主要是注冊撕贞、刪除更耻、和處理

  1. register_event():注冊事件處理器
  2. remove_event():移除事件處理器
  3. handle_events():進行讀取、處理工作捏膨。主要是調(diào)用Synchronous Event Demutiplexer的select()

四秧均、Synchronous Event Demutiplexer:同步事件多路分離器,一般是內(nèi)核

select():只要某個文件句柄被激活(可讀/寫等)号涯,select就返回(阻塞)目胡,Reactor 的 handle_events 就會調(diào)用與文件句柄關聯(lián)的事件處理器的 handle_event 進行相關操作.

用戶線程使用 IO多路復用模型 的偽代碼描述為:

void UserEventHandler::handle_event() {
    if(can_read(socket)) {
        read(socket, buffer);
        process(buffer);
    }
}
{
    Reactor.register(new UserEventHandler(socket));
}

用戶需要重寫 EventHandler 的 handle_event 函數(shù)進行讀取數(shù)據(jù)、處理數(shù)據(jù)的工作, 用戶線程只需要將自己的 EventHandler 注冊到 Reactor 即可. Reactor 中 handle_events 事件循環(huán)的偽代碼大致如下

Reactor::handle_events() {
    while(1) {
        sockets = select();
        for(socket in sockets) {
            get_event_handler(socket).handle_event();
        }
    }
}

1.1.4 信號驅(qū)動IO模型

首先開啟套接口 信號驅(qū)動IO功能链快,并通過系統(tǒng)調(diào)用 sigaction 執(zhí)行一個信號處理函數(shù)(此系統(tǒng)調(diào)用立即返回誉己,進程繼續(xù)工作,它是非阻塞的)域蜗。當數(shù)據(jù)準備就緒時巨双,就為該進程生成一個 SIGIO 信號。隨即可以在信號處理程序中調(diào)用 recvfrom 來讀數(shù)據(jù)霉祸,并通知循環(huán)函數(shù)處理數(shù)據(jù)筑累。如圖

信號驅(qū)動IO模型.png

1.1.5 異步IO模型

異步IO模型.png

告知內(nèi)核啟動某個操作,并讓內(nèi)核在某個操作完成后(包括將數(shù)據(jù)從內(nèi)核拷貝到用戶自己的緩沖區(qū))通知我們

這種模型與信號驅(qū)動模型的主要區(qū)別是:

  1. IO復用:事件循環(huán)將文件句柄狀態(tài)事件給用戶線程丝蹭,由用戶處理慢宗。
  2. 信號驅(qū)動IO:由內(nèi)核通知我們何時可以啟動一個IO操作
  3. 異步IO:由內(nèi)核通知我們IO操作何時完成

"真正"的異步IO需要操作系統(tǒng)更強的支持。在IO多路復用模型中奔穿,事件循環(huán)將文件句柄的狀態(tài)事件通知給用戶線程镜沽,由用戶線程自行讀取數(shù)據(jù)、處理數(shù)據(jù)巫橄。而在異步IO模型中淘邻,當用戶線程收到通知時,數(shù)據(jù)已經(jīng)被內(nèi)核讀取完畢湘换,并放在了用戶線程指定的緩沖區(qū)內(nèi)宾舅,內(nèi)核在IO完成后通知用戶線程直接使用即可

異步IO模型使用了Proactor設計模式實現(xiàn)了這一機制

Proactor設計模式.png

Proactor模式 和 Reactor模式 在結構上比較相似。在用戶線程(Client)使用方式差別很大彩倚。

在 Reactor模式 中筹我,用戶線程向 Reactor 對象注冊感興趣的事件監(jiān)聽,然后事件觸發(fā)時調(diào)用事件處理函數(shù).

在 Proactor模式 中帆离,用戶線程將 AsynchronousOperation(讀/寫等)蔬蕊、Proactor 以及操作完成時的 CompletionHandler 注冊到 AsynchronousOperationProcessor。AsynchronousOperationProcessor 使用 Facade 模式提供了一組 異步操作API(讀/寫等) 供用戶使用, 當用戶線程調(diào)用 異步API 后哥谷,便繼續(xù)執(zhí)行自己的任務岸夯。 AsynchronousOperationProcessor 會開啟獨立的內(nèi)核線程執(zhí)行異步操作麻献,實現(xiàn)真正的異步。當 異步IO操作 完成時猜扮, AsynchronousOperationProcessor 將用戶線程 與 AsynchronousOperation 一起注冊的 Proactor 和 CompletionHandler 取出勉吻,然后將 CompletionHandler 與 IO操作 的結果數(shù)據(jù)一起轉(zhuǎn)發(fā)給 Proactor,Proactor 負責回調(diào)每一個異步操作的事件完成處理函數(shù) handle_event旅赢。雖然 Proactor 模式中每個異步操作都可以綁定一個 Proactor 對象, 但是一般在操作系統(tǒng)中, Proactor 被實現(xiàn)為 Singleton模式, 以便于集中化分發(fā)操作完成事件

異步IO模型中齿桃,用戶線程直接使用內(nèi)核提供的 異步IO API 發(fā)起 read 請求,且發(fā)起后立即返回煮盼,繼續(xù)執(zhí)行用戶線程代碼短纵。 不過此時用戶線程已經(jīng)將調(diào)用的 AsynchronousOperation 和 CompletionHandler 注冊到內(nèi)核,然后操作系統(tǒng)開啟獨立的內(nèi)核線程去處理 IO操作僵控。當 read 請求的數(shù)據(jù)到達時香到,由內(nèi)核負責讀取 socket 中的數(shù)據(jù),并寫入用戶指定的緩沖區(qū)中喉祭。最后內(nèi)核將 read 的數(shù)據(jù)和用戶線程注冊的 CompletionHandler 分發(fā)給內(nèi)部 Proactor养渴,Proactor 將IO完成的信息通知給用戶線程(一般通過調(diào)用用戶線程注冊的完成事件處理函數(shù)),完成異步IO

用戶線程使用 異步IO模型 的偽代碼描述為:

void UserCompletionHandler::handle_event(buffer) {
    process(buffer);
}
{
    aio_read(socket, new UserCompletionHandler);
}

用戶需要重寫 CompletionHandler 的 handle_event 函數(shù)進行處理數(shù)據(jù)的工作泛烙,參數(shù) buffer 表示 Proactor 已經(jīng)準備好的數(shù)據(jù),用戶線程直接調(diào)用內(nèi)核提供的 異步IO API翘紊,并將重寫的 CompletionHandler 注冊即可

相比于 IO多路復用模型爽雄,異步IO 并不十分常用, 不少高性能并發(fā)服務程序使用 IO多路復用模型 + 多線程任務 處理的架構基本可以滿足需求衬廷。況且目前操作系統(tǒng)對 異步IO 的支持并非特別完善,更多的是采用 IO多路復用模型 模擬 異步IO 的方式(IO事件觸發(fā)時不直接通知用戶線程,而是將數(shù)據(jù)讀寫完畢后放到用戶指定的緩沖區(qū)中)对粪。


1.2 epoll - IO復用技術

Java NIO 的核心類庫 多路復用器 Selector 就是基于 epoll 的多路復用技術實現(xiàn)在IO編程過程中。

當需要處理多個請求時, 可以利用 多線程 或 IO多路復用技術 進行處理. I/O多路復用技術 通過把多個 I/O 的阻塞復用同一個 select 阻塞上, 從而使得系統(tǒng)在單線程的情況下可以同時處理多個客戶端請求. 與傳統(tǒng)的多線程/多進程模型比, I/O多路復用 的最大是系統(tǒng)開銷小, 系統(tǒng)不需要建立新的進程或線程, 也不需要維護這些線程和進程的運行, 降低了系統(tǒng)的維護工作量, 節(jié)省了系統(tǒng)資源. IO多路復用 的主要應用場景:

  1. 服務器需要同時處理多個處于監(jiān)聽狀態(tài)和多個連接狀態(tài)的套接字
  2. 服務器需要處理多種網(wǎng)絡協(xié)議的套接字

目前支持 I/O多路復用 的系統(tǒng)調(diào)用有 select冲泥、pselect氧猬、poll、epoll柳琢。

epoll 是為了克服 select/poll 的缺點出現(xiàn)的绍妨,具體如下

一、支持一個進程打開的socket描述符(FD)不受限制(僅受限于操作系統(tǒng)的最大文件句柄數(shù))

select:一個進程打開的FD有一定限制柬脸,由 FD_SETSIZE(默認2048) 設置他去。對于那些需要支持的上萬TCP連接數(shù)目的大型服務器來說顯然太少了

  1. 可以選擇修改這個宏,重新編譯內(nèi)核倒堕。但會帶來網(wǎng)絡效率的下降.
  2. 可以選多進程的解決方案灾测。雖然 Linux 上面創(chuàng)建進程的代價比較小,但仍舊是不可忽視的垦巴。其次進程間的數(shù)據(jù)交換非常麻煩媳搪,Java 由于沒有共享內(nèi)存铭段,需要通過 Socket 通信或其他方式進行數(shù)據(jù)同步,帶來了額外的性能損耗, 增加了程序復雜的, 所以也不是一種完美的解決方案.

epoll:支持FD上限是最大可以打開文件的數(shù)目秦爆,這個數(shù)字一般遠大于2048稠项。一般在1GB內(nèi)存的機器上大約是10萬左右, 具體數(shù)目可以 cat/proc/sys/fs/file- max 察看,通常情況下這個數(shù)目和系統(tǒng)內(nèi)存關系很大

二鲜结、I/O效率不會隨著FD數(shù)目增加而線性下降

select/poll:當擁有很大的 Socket 集合展运,由于網(wǎng)絡延時,任一時間只有部分的 Socket 是"活躍"的, 但是 select/poll 每次調(diào)用都會線性掃描全部的集合精刷,導致效率呈現(xiàn)線性下降拗胜。

epoll:它只對"活躍"的 Socket 進行操作。在內(nèi)核實現(xiàn)中, epoll 是根據(jù)每個 fd 的 callback 函數(shù)實現(xiàn)的怒允。只有活躍的 Socket 才會主動調(diào)用 callback 函數(shù), 其他idle狀態(tài)Socket則不會. 在這點上, epoll實現(xiàn)了一個"偽"AIO"埂软。

三、使用mmap加速內(nèi)核與用戶控件的消息傳遞

select纫事、poll勘畔、epoll:都需要內(nèi)核把FD消息通知給用戶空間,如何避免不必要的內(nèi)存拷貝就很重要丽惶,epoll是通過內(nèi)核與用戶空間mmap同一塊內(nèi)存實現(xiàn)的

四炫七、epoll的API更加簡單

創(chuàng)建epoll描述符、添加監(jiān)聽事件钾唬、阻塞等待所監(jiān)聽的事件發(fā)生万哪、關閉epoll描述符.

當然用來克服select/poll缺點的方法不只有epoll(epoll是Linux的實現(xiàn)方案)。

  1. freeBSD下有kqueue
  2. Solaris下由dev/poll




2. Java IO概述

Java I/O操作類在包java.io下抡秆,大概可以分為如下4組:

  1. 基于 字節(jié) 操作的I/O操作:InputStream和OutputStream
  2. 基于 字符 操作的I/O操作:Writer和Reader
  3. 基于 磁盤 操作的I/O操作:File
  4. 基于 網(wǎng)絡 操作的I/O操作:Socket

前2組主要是傳輸數(shù)據(jù)的數(shù)據(jù)格式奕巍,后兩組主要是傳輸數(shù)據(jù)的方式。

I/O的核心問題在于儒士,影響I/O操作因素的止,要么是數(shù)據(jù)格式,要么是傳輸數(shù)據(jù)方式

2.1 流概述

流是一組有序的數(shù)據(jù)序列着撩,I/O(Input/Output)流提供了一條通道程序诅福,可以通過這條通道把源中的字節(jié)序列送到目的地。

  1. IO流用來處理設備之間的數(shù)據(jù)傳輸睹酌。Java用于操作流的對象都在IO包中
  2. Java對數(shù)據(jù)的操作是通過流的方式
  3. 流按 操作數(shù)據(jù) 分為兩種, 字節(jié)流(InputStream权谁、OutputStream)、字符流(Reader憋沿、Writer)
  4. 流按 流向 分為: 輸入流(InputStream旺芽、Reader)、輸出流(OutputStream、Writer)
  5. 按照 流的角色 分為:節(jié)點流采章、處理流运嗜。

輸入:外部媒介(文件、網(wǎng)絡悯舟、壓縮包担租、其他數(shù)據(jù)源) -> InputStream數(shù)據(jù) -> 計算機,

輸出:計算機 -> OutputStream數(shù)據(jù) -> 外部媒介(文件抵怎、網(wǎng)絡奋救、壓縮包、其他數(shù)據(jù)源)

在Java IO庫雖然龐大反惕,單在實際應用中尝艘,一般都是使用它們的子類,大致Java提供的各子類用途匯總:

  1. 文件訪問
  2. 網(wǎng)絡訪問
  3. 內(nèi)存緩存訪問
  4. 線程內(nèi)部通信(管道)
  5. 緩沖
  6. 過濾
  7. 解析
  8. 讀寫文本 (Reader/Writer)
  9. 讀寫對象姿染、基本類型數(shù)據(jù)

按操作方式分類結構圖

JavaIO按操作方式分類結構圖.png

按操作對象分類結構圖

JavaIO按操作對象分類結構圖.png

2.2 基于字節(jié)的IO操作接口

只支持8位字節(jié)(1byte)流, 不能很好控制16位Unicode字符

基于字節(jié)IO操作接口輸入和輸出分別是InputStream和OutputStream

2.2.1 InputStream - 輸入字節(jié)流

InputStream類是字節(jié)輸入流的抽象類, 是所有字節(jié)輸入流的抽象父類

InputStream類常用的方法 (標記阻塞: 在某個字符可用背亥、發(fā)生 I/O 錯誤或者已到達流的末尾前,方法一直阻塞)

public abstract int read() throws IOException
    阻塞悬赏,從輸入流中讀取數(shù)據(jù)的下一個字節(jié). 返回0~255范圍內(nèi)的int字節(jié)值. 如果因為已經(jīng)到達流末尾而沒有可用的字節(jié), 則返回值-1

public read(byte[] b) throws IOException
    阻塞狡汉,從輸入流中讀入一定長度的字節(jié), 并以整數(shù)形式返回字節(jié)數(shù)

public int read(byte b[], int off, int len) throws IOException
    阻塞,在off偏移位置開始, 從輸入流中讀入長度len的字節(jié), 并以整數(shù)形式返回字節(jié)數(shù)

public long skip(long n) throws IOException
    跳過輸入流上的n個字節(jié)并返回實際跳過的字節(jié)數(shù)

public int available() throws IOException
    不阻塞闽颇,從輸入流讀取/跳過的估計字節(jié)數(shù)盾戴;如果到達輸入流末尾,則返回 0

public void close() throws IOException
    關閉此輸入流并釋放與該流關聯(lián)的所有系統(tǒng)資源

public synchronized void mark(int readlimit) throws IOException
    在輸入流的當前位置放置一個標記, readlimit參數(shù)告知此輸入流在標記位置失效之前允許讀取的字節(jié)數(shù)

public synchronized void reset() throws IOException
    將輸入流指針返回到當前所做的標記處

public boolean markSupported() throws IOException
    如果當前支持的n個字節(jié)并返回實際跳過的字節(jié)數(shù)

[注:不是所有InputStream類的子類都支持所有方法, 如skip(), mark(), reset()等, 這些方法只對某些子類有用]

InputStream:是所有的輸入字節(jié)流的抽象父類
    |-- ByteArrayInputStream:介質(zhì)類进萄,Byte數(shù)組
    |-- FileInputStream:介質(zhì)類捻脖,本地文件讀取數(shù)據(jù)
    |-- ObjectInputStream
    |-- PipedInputStream:是從與其他線程的管道中讀取數(shù)據(jù)
    |-- SequenceInputStream
    |-- StringBufferInputStream:已過時,介質(zhì)類中鼠,底層使用StringBuffer。推薦使用StringReader
    |-- FilterInputStream:裝飾類(裝飾器模式主角)
        |-- BufferInputStream
        |-- DataInputStream
        |-- LineNumberInputStream
        |-- PushbackInputStream

類 介紹

ByteArrayInputStream
    功能:將內(nèi)存中的Byte數(shù)組適配為一個InputStream
    構造:從內(nèi)存中的Byte數(shù)組創(chuàng)建該對象(2種方法)
    使用:一般作為數(shù)據(jù)源, 會使用其他裝飾流提供額外的功能, 一般都建議加個緩沖功能

FileInputStream
    功能:最基本的文件輸入流. 主要用于從文件中讀取信息  構造:通過一個代表文件路徑的 String沿癞、File對象或者 FileDescriptor對象創(chuàng)建
    使用:一般作為數(shù)據(jù)源, 同樣會使用其它裝飾器提供額外的功能
    
PipedInputStream
    功能:讀取從對應PipedOutputStream寫入的數(shù)據(jù). 在流中實現(xiàn)了管道的概念
    構造:利用對應的PipedOutputStream創(chuàng)建
    使用:在多線程程序中作為數(shù)據(jù)源, 同樣會使用其它裝飾器提供額外的功能
    
ObjectInputStream
    功能:對象輸入流

SequenceInputStream
    功能:將2個或者多個InputStream 對象轉(zhuǎn)變?yōu)橐粋€InputStream    構造:使用兩個InputStream(或子類)對象創(chuàng)建該對象
    使用:一般作為數(shù)據(jù)源, 同樣會使用其它裝飾器提供額外的功能
    
    
裝飾援雇、輸入字節(jié)流FilterInputStream:給其它被裝飾對象提供額外功能的抽象類

DataInputStream
    功能:一般與DataOutputStream配對使用,完成基本數(shù)據(jù)類型的讀寫
    構造:利用一個InputStream構造
    使用:提供了大量的讀取基本數(shù)據(jù)類新的讀取方法
    
BufferedInputStream
    功能:阻止每次讀取一個字節(jié)都會頻繁操作IO. 將字節(jié)讀取一個緩存區(qū),從緩存區(qū)讀取    構造:利用一個InputStream椎扬、或者帶上一個自定義的緩存區(qū)的大小構造
    使用:使用InputStream的方法讀取, 多一個緩存的功能. 設計模式中透明裝飾器的應用
    
LineNumberInputStream
    功能:跟蹤輸入流中的行號
    構造:利用一個InputStream構造
    使用:增加getLineNumber()和 setLineNumber(int)方法得到和設置行號
    
PushbackInputStream
    功能:可以在讀取最后一個byte 后將其放回到緩存中  構造:利用一個InputStream構造
    使用:僅僅會在設計compiler的scanner時會用到這個類, 在我們的java語言的編譯器中使用它. 很多程序員可能一輩子都不需要

2.2.2 OutputStream - 輸出字節(jié)流

OutputStream類是字節(jié)輸出流的抽象類, 是所有字節(jié)輸出流的抽象父類惫搏。常用的方法

public abstract void write(int b) throws IOException
    將指定的字節(jié)寫入此輸出流
public void write(byte b[]) throws IOException
    將指定byte數(shù)組寫入此輸出流
public void write(byte b[], int off, int len) throws IOException
    將指定byte數(shù)組中從偏移量off開始的len個字節(jié)寫入此輸出流
public void flush() throws IOException
    徹底完成輸出并清空緩存區(qū)
public void close() throws IOException
    關閉輸出流
OutputStream:是所有的輸出字節(jié)流的父類
    |-- ByteArrayOutputStream:介質(zhì)類,Byte數(shù)組
    |-- FileOutputStream:介質(zhì)類蚕涤,本地文件讀取數(shù)據(jù)
    |-- ObjectOutputStream
    |-- PipedOutputStream:向與其它線程共用的管道中寫入數(shù)據(jù)
    |-- FilterOutputStream:裝飾類(裝飾器模式主角)
        |-- BufferOutputStream
        |-- DataOutputStream
        |-- PrintStream

類 介紹

ByteArreaOutputStream
    功能:在內(nèi)存中創(chuàng)建一個buffer, 所有寫入此流中的數(shù)據(jù)都被放入到此buffer中  構造:無參或者使用一個可選的初始化buffer的大小的參數(shù)構造
    使用:將其和FilterOutputStream套接得到額外的功能, 建議首先和BufferedOutputStream套接實現(xiàn)緩沖功能. 通過toByteArray方法可以得到流中的數(shù)據(jù). 不通過裝飾器的用法
    
FileOutputStream
    功能:將信息寫入文件中 
    構造:使用代表文件路徑的String筐赔、File對象或者 FileDescriptor對象創(chuàng)建. 還可以加一個代表寫入的方式是否為append的標記
    使用:一般將其和FilterOutputStream套接得到額外的功能
    
PipedOutputStream
    功能:任何寫入此對象的信息都被放入對應PipedInputStream 對象的緩存中, 從而完成線程的通信, 實現(xiàn)了"管道"的概念.具體在后面詳細講解 
    構造:利用PipedInputStream構造, 在多線程程序中數(shù)據(jù)的目的地的
    使用:一般將其和FilterOutputStream套接得到額外的功能
    
ObjectOutputStream
    功能:對象輸出流

裝飾輸出字節(jié)流FilterOutputStream:實現(xiàn)裝飾器功能的抽象類, 為其它OutputStream對象增加額外的功能

DataOutputStream
    功能:通常和DataInputStream配合使用, 使用它可以寫入基本數(shù)據(jù)類新    構造:使用OutputStream構造
    使用:包含大量的寫入基本數(shù)據(jù)類型的方法
    
PrintStream
    功能:產(chǎn)生具有格式的輸出信息
    構造:使用OutputStream和一個可選的表示緩存是否在每次換行時是否flush的標記構造. 還提供很多和文件相關的構造方法
    使用:一般是一個終極("final")的包裝器, 很多時候我們都使用它
    
BufferedOutputStream
    功能:使用它可以避免頻繁地向IO寫入數(shù)據(jù), 數(shù)據(jù)一般都寫入一個緩存區(qū), 在調(diào)用flush方法后會清空緩存、一次完成數(shù)據(jù)的寫入  
    構造:從一個OutputStream或者和一個代表緩存區(qū)大小的可選參數(shù)構造
    使用:提供和其它OutputStream一致的接口, 只是內(nèi)部提供一個緩存的功能

2.2.3 字節(jié)流輸入與輸出的對應

InputStream:                                         OutputStream
  |-- ByteArrayInputStream              ByteArrayOutputStream --|
  |-- FileInputStream                        FileOutputStream --|
  |-- ObjectInputStream ---[ both use ]--- ObjectOutputStream --|
  |-- PipedInputStream  ---[ both use ]---  PipedOutputStream --|
  |-- FilterInputStream                    FilterOutputStream --|
    |-- BufferInputStream                  BufferOutputStream --|
    |-- DataInputStream ---[ both use ]---   DataOutputStream --|
    |-- LineNumberInputStream                                   |
    |-- PushbackInputStream                                     |
  |-- SequenceInputStream                                       |
  |-- StringBufferInputStream                                   |
                                                  PrintStream --|

平行代表對應關系揖铜,both use 代表需要搭配使用

而沒有對應關系的流

1.LineNumberInputStream

主要完成從流中讀取數(shù)據(jù)時茴丰,會得到相應的行號。至于什么時候分行、在哪里分行是由該類主動確定的, 并不是在原始中有這樣一個行號贿肩。

在輸出部分沒有對應的部分, 我們完全可以自己建立一個LineNumberOutputStream, 在最初寫入時會有一個基準的行號, 以后每次遇到換行時會在下一行添加一個行號.

2.PushbackInputStream

查看最后一個字節(jié), 不滿意就放入緩沖區(qū). 主要用在編譯器的語法峦椰、詞法分析部分. 輸出部分的BufferedOutputStream幾乎實現(xiàn)相近的功能

3.StringBufferInputStream

已經(jīng)被Deprecated(過時), 本身就不應該出現(xiàn)在InputStream部分, 主要因為String應該屬于字符流的范圍. 已經(jīng)被廢棄了, 當然輸出部分也沒有必要需要它了! 還允許它存在只是為了保持版本的向下兼容而已.

4.SequenceInputStream

可以認為是一個工具類, 將兩個或者多個輸入流當成一個輸入流依次讀取. 完全可以從IO包中去除, 還完全不影響IO包的結構, 卻讓其更"純潔"--純潔的Decorator(裝飾)模式

5.PrintStream

可以認為是一個輔助工具. 主要可以向其他輸出流, 或者FileInputStream寫入數(shù)據(jù), 本身內(nèi)部實現(xiàn)還是帶緩沖的. 本質(zhì)上是對其它流的綜合運用的一個工具而已. System.out和Systm.out就是PrintStream的實例

需要搭配使用

1.ObjectInputStream/ObjectOutputStream

要求寫/讀對象的次序要保持一致, 否則輕則不能得到正確的數(shù)據(jù), 重則拋出異常(一般會如此)

2.DataInputStream/DataOutputStream

主要是要求寫/讀數(shù)據(jù)的次序要保持一致, 否則輕則不能得到正確的數(shù)據(jù), 重則拋出異常(一般會如此);
3.PipedInputStream/PipedOutputStream

在創(chuàng)建時一般就一起創(chuàng)建, 調(diào)用它們的讀寫方法時會檢查對方是否存在, 或者關閉汰规!需要雙方管道都存在才能交互

2.2.4 ByteArray 流

(basic.io.bytestream.byteArray.ByteArrayStreamDemo)

2.2.4.1 ByteArrayInputStream

構造函數(shù):使用buf作為其緩沖區(qū)數(shù)組

ByteArrayInputStream(byte buf[])     
// pos=offset, count = Math.min(offset + length, buf.length)
ByteArrayInputStream(byte buf[], int offset, int length)

內(nèi)部維護屬性

protected byte buf[]:流的創(chuàng)建者提供的byte數(shù)組
protected int pos:要從輸入流中讀取的下一個字符的索引
protected int mark:流中手動標記的位置汤功,用于reset(),默認為0
protected int count:buf最后一個有效字符的索引+1的索引值

方法

synchronized int read():讀取下一個數(shù)據(jù)字節(jié)
synchronized int read(byte b[], int off, int len):將最多l(xiāng)en個數(shù)據(jù)字節(jié)從此輸入流讀入byte 數(shù)組
synchronized long skip(long n):跳過n個輸入字節(jié)
synchronized int available():返回剩余字節(jié)數(shù)
boolean markSupported():測試此 InputStream 是否支持 mark/reset
void mark(int readAheadLimit):設置流中的當前標記位置溜哮,即mark
synchronized void reset():將緩沖區(qū)的位置重置為標記位置滔金,回滾到mark
void close():關閉流無效, 關閉后仍可被調(diào)用

字節(jié)數(shù)組流, 用于操作字節(jié)數(shù)組, 它的內(nèi)部緩沖區(qū)就是一個字節(jié)數(shù)組

2.2.4.2 ByteArrayOutputStream

構造函數(shù):創(chuàng)建一個新的byte輸出流(默認size=32)

ByteArrayOutputStream()
ByteArrayOutputStream(int size)

內(nèi)部維護屬性

protected byte buf[]:   存儲數(shù)據(jù)的緩沖區(qū)
protected int count:緩沖區(qū)中的有效字節(jié)數(shù)

擴容機制:當write超過設定的size。默認進行2倍擴容茂嗓,最大數(shù)量 <= Integer.MAX_VALUE

方法

synchronized void write(int b):將指定的字節(jié)寫入此byte數(shù)組輸出流
synchronized void write(byte b[], int off, int len):將指定byte數(shù)組中從偏移量off開始的len個字節(jié)寫入此byte數(shù)組輸出流
synchronized void writeTo(OutputStream out):將此byte數(shù)組輸出流的全部內(nèi)容寫入到指定的輸出流參數(shù)中, 這與使用out.write(buf, 0, count)調(diào)用該輸出流的write方法效果一樣
synchronized void reset():將此byte數(shù)組輸出流的count字段重置為零, 從而丟棄輸出流中目前已累積的所有輸出
synchronized byte toByteArray()[]:創(chuàng)建一個新分配的byte數(shù)組
synchronized int size():返回緩沖區(qū)的當前大小
synchronized String toString():使用平臺默認的字符集, 通過解碼字節(jié)將緩沖區(qū)內(nèi)容轉(zhuǎn)換為字符串
synchronized String toString(String charsetName):使用指定的charsetName, 通過解碼字節(jié)將緩沖區(qū)內(nèi)容轉(zhuǎn)換為字符串
void close():   關閉流無效, 關閉后仍可被調(diào)用
synchronized String toString(int hibyte):已過時, 此方法無法將字節(jié)正確轉(zhuǎn)換為字符

2.2.5 文件I/O流(FileInputStream與FileOutputStream類)

(basic.io.bytestream.file.FileStreamDemo)

2.2.5.1 FileInputStream

構造函數(shù):文件名/文件對象/文件描述對象餐茵,最終都是new一個FileDescriptor,然后調(diào)用open()
    public FileInputStream(String name) throws FileNotFoundException
    public FileInputStream(File file) throws FileNotFoundException
    public FileInputStream(FileDescriptor fdObj)
內(nèi)部維護屬性:
    private final FileDescriptor fd;
    private final String path;
    private FileChannel  channel = null;
    private final Object closeLock = new Object();
    private volatile boolean closed = false;
方法:
    public int read() throws IOException
    public int read(byte b[]) throws IOException
    public int read(byte b[], int off, int len) throws IOException
    public long skip(long n) throws IOException
    public int available() throws IOException
    public void close() throws IOException 
    public final FileDescriptor getFD() throws IOException
    public FileChannel getChannel()
    protected void finalize() throws IOException
本地方法:
    private native void open0(String name) throws FileNotFoundException
    private void open(String name) throws FileNotFoundException
    private native int read0() throws IOException
    private native int readBytes(byte b[], int off, int len) throws IOException
    private native long skip0(long n) throws IOException;
    private native int available0() throws IOException
    private static native void initIDs():設置類中(也就是FileInputStream)的屬性的內(nèi)存地址偏移量在抛,便于在必要時操作內(nèi)存給它賦值
    private native void close0() throws IOException

2.2.5.2 FileOutputStream

構造函數(shù):
    public FileOutputStream(String name) throws FileNotFoundException
    public FileOutputStream(String name, boolean append) throws FileNotFoundException
    public FileOutputStream(File file) throws FileNotFoundException
    public FileOutputStream(File file, boolean append) throws FileNotFoundException
    public FileOutputStream(FileDescriptor fdObj)
內(nèi)部維護屬性:
    private final FileDescriptor fd;
    private final boolean append;
    private FileChannel channel;
    private final String path;
    private final Object closeLock = new Object();
    private volatile boolean closed = false;
方法:
    public void write(int b) throws IOException
    public void write(byte b[]) throws IOException
    public void write(byte b[], int off, int len) throws IOException
    public void close() throws IOException 
    public final FileDescriptor getFD()  throws IOException
    public FileChannel getChannel()
    protected void finalize() throws IOException
    private static native void initIDs();
    private void open(String name, boolean append) throws FileNotFoundException
本地方法:
    private native void write(int b, boolean append) throws IOException;
    private native void open0(String name, boolean append) throws FileNotFoundException;
    private native void writeBytes(byte b[], int off, int len, boolean append) throws IOException;
    private native void close0() throws IOException;

2.2.6 操作對象

(basic.io.bytestream.objects.ObjectStreamTest)

ObjectInputStream與ObjectOutputStream钟病,被操作的對象需要實現(xiàn)Serializable(標記接口)

職責:從序列化文檔中讀取并解析出一個類。這個序列化文檔是由 ObjectOutputStream 寫入的刚梭。這一功能允許我們持久化一個內(nèi)存對象肠阱,這便是序列化。Java IO 庫規(guī)定了序列化的存儲規(guī)范朴读,從上層到下層是屹徘,對象,塊存儲衅金,塊內(nèi)元素(塊數(shù)據(jù)類型噪伊,塊長度,數(shù)據(jù))

實例:

/** Serializable:用于給被序列化的類加入ID號, 用于判斷類和對象是否是同一個版本. */
class Box implements Serializable {
    private int width;   
    private int height; 
    private String name;   
    public Box(String name, int width, int height) {
        this.name = name;
        this.width = width;
        this.height = height;
    }
    @Override
    public String toString() {
        return "["+name+": ("+width+", "+height+") ]";
    }
}

對象序列化

    private static void testWrite() {   
        try {
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(TMP_FILE));
            out.writeBoolean(true);
            out.writeByte((byte)65);
            out.writeChar('a');
            out.writeInt(20131015);
            out.writeFloat(3.14F);
            out.writeDouble(1.414D);
            // 寫入HashMap對象
            HashMap map = new HashMap();
            map.put("one", "red");
            map.put("two", "green");
            map.put("three", "blue");
            out.writeObject(map);
            // 寫入自定義的Box對象氮唯,Box實現(xiàn)了Serializable接口(對象序列化, 被序列化的對象必須實現(xiàn)Serializable接口)
            Box box = new Box("desk", 80, 48);
            out.writeObject(box);

            out.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

對象反序列化

    private static void testRead() {
        try {
            ObjectInputStream in = new ObjectInputStream(new FileInputStream(TMP_FILE));
            System.out.printf("boolean:%b\n" , in.readBoolean());
            System.out.printf("byte:%d\n" , (in.readByte()&0xff));
            System.out.printf("char:%c\n" , in.readChar());
            System.out.printf("int:%d\n" , in.readInt());
            System.out.printf("float:%f\n" , in.readFloat());
            System.out.printf("double:%f\n" , in.readDouble());
            // 讀取HashMap對象
            HashMap map = (HashMap) in.readObject();
            Iterator iter = map.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry entry = (Map.Entry)iter.next();
                System.out.printf("%-6s -- %s\n" , entry.getKey(), entry.getValue());
            }
            // 讀取Box對象鉴吹,Box實現(xiàn)了Serializable接口
            Box box = (Box) in.readObject();
            System.out.println("box: " + box);

            in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

2.2.7 管道流

(basic.io.bytestream.pipe.PipedStreamDemo)

PipedInputStream和PipedOutputStream, 輸入輸出可以直接進行連接, 通過結合線程使用

2.2.8 裝飾類流(FilterInputStream/FilterOutputStream)

2.2.8.1 緩存輸入/輸出流

(basic.io.bytestream.filter.buffered.BufferedStreamDemo)

BufferedInputStream與BufferedOutputStream類。緩存可以說是I/O的一種性能優(yōu)化, 緩存流為I/O流增加了內(nèi)存緩存區(qū), 有了緩存區(qū), 使得在流上執(zhí)行skip(), mark()和reset()方法都稱為了可能惩琉。

1).BufferedInputStream:可以對任意的InputStream類進行帶緩存區(qū)的包裝以達到性能的優(yōu)化.

構造函數(shù):傳入InputStream豆励,構建帶有size(默認32)個字節(jié)的緩存流,最優(yōu)的緩存區(qū)大小瞒渠,取決于操作系統(tǒng)良蒸,可用內(nèi)存空間及及其配置

    BufferedInputStream(InputStream in)
    BufferedInputStream(InputStream in, int size)

BufferedInputStream讀取文件過程:文件 -> InputStream -> BufferedInputStream --> 目的地

2).BufferedOutputStream:輸出信息和向OutputStream輸入信息完全一樣, 只不過BufferedOutputStream有一個flush()方法用來將緩存區(qū)的數(shù)據(jù)強制輸出完

構造函數(shù):傳入OutputStream,構建帶有size(默認32)個字節(jié)的緩存流伍玖,最優(yōu)的緩存區(qū)大小嫩痰,取決于操作系統(tǒng),可用內(nèi)存空間及及其配置

    BufferedOutputStream(OutputStream out)
    BufferedOutputStream(OutputStream out, int size)

說明:flush()方法就是用于即使緩存區(qū)沒有滿的情況下, 也將緩存區(qū)的內(nèi)容強制寫入到外設, 習慣上稱這個過程為刷新, flush方法只對使用緩存區(qū)的OutputStream類的子類有效, 當調(diào)用close()方法時, 系統(tǒng)在關閉流之前, 也會將緩存區(qū)中信息刷新到磁盤文件中

2.2.8.2 數(shù)據(jù)流

(basic.io.bytestream.filter.data.DataStreamDemo)

數(shù)據(jù)輸入流/輸出流(DataInputStream/DataOutputStream類), 允許應用程序以及其無關的方式從底層輸入流中讀取基本Java數(shù)據(jù)類型, 也就是說, 當讀取一個數(shù)據(jù)時, 不必關心這個數(shù)值應當是什么字節(jié)

1.DataInputStream類與DataOutputStream類的構造方法如下:

DataInputStream(InputStream in): 使用指定的基礎InpuStream創(chuàng)建一個DataInputStream
DataOutputStream(OutputStream out):創(chuàng)建一個新的數(shù)據(jù)輸出流, 將數(shù)據(jù)寫入指定基礎輸出流

2.DataOutputStream類提供如下3種寫入字符串的方法

writeBytes(String s)
writeChars(String s)
writeUTF(String s)

由于Java中的字符是Unicode編碼, 是雙字節(jié)的, writeBytes只是將字符串中的每一個字符的低字節(jié)內(nèi)容寫入目標設備中. 而writeChars將字符串中的每一個字符的兩個字節(jié)的內(nèi)容都寫到目標設備中, writeUTF將字符串按照UTF編碼后的字節(jié)長度寫入目標設備, 然后才是每一個字節(jié)的UTF編碼

DataInputStream類只提供了一個readUTF()方法返回字符串. 這是因為要在一個連續(xù)的字節(jié)流讀取一個字符串, 如果沒有特殊的標記作為一個字符串的結尾, 并且事先也不知道這個字符串的長度, 也就無法知道讀取到什么位置才是這個字符串的結束

DataOutputStream類中只有writeUTF()方法向目標設備寫入字符串的長度, 所以只能準確地讀回寫入字符串

2.2.9 序列流SequenceInputStream

對多個流進行合并:1).文件切割 2).文件合并 3).文件切割合并+配置文件

2.2.10 ZIP壓縮輸入/輸出流

使用ZIP壓縮管理文件(ZIP archive), 是一種典型的文件壓縮格式, 可以節(jié)省存儲空間. 關于ZIP壓縮的I/O實現(xiàn), 在Java的內(nèi)置類中, 提供了非常好用的相關類, 所以實際的實現(xiàn)方式非常容易

Java實現(xiàn)了I/O數(shù)據(jù)流與網(wǎng)絡數(shù)據(jù)流的單一接口, 因此數(shù)據(jù)的壓縮窍箍、網(wǎng)絡傳輸和解壓縮的實現(xiàn)比較容易.

ZipEntry類產(chǎn)生的對象, 是用來代表一個ZIP壓縮文件內(nèi)的進入點(entry), ZipInputStream類用來讀取ZIP壓縮格式的文件, 所支持的包括已壓縮及未壓縮的進入點(entry), ZipOutputStream類用來寫出ZIP壓縮格式的文件, 而且所支持的包括已壓縮及未壓縮的進入點(entry)

2.2.10.1 壓縮文件

利用ZipInputStream類對象串纺,可將文件壓縮為.zip文件丽旅。

ZipOutputStream類常用的方法

ZipOutputStream(OutputStream out)   構造函數(shù)
putNextEntry(ZipEntry e)    開始寫一個新的ZipEntry, 并且將流內(nèi)的位置移至此entry所指數(shù)據(jù)的開頭
write(byte[] b, int off, int len)   將字節(jié)數(shù)組寫入當前ZIP條目數(shù)據(jù)
finish()    完成寫入ZIP輸出流的內(nèi)容, 無序關閉它所配合的OutputStream
setComment(String comment)  可設置此ZIP文件的注釋文字

2.2.10.2 解壓縮ZIP文件

ZipInputStream類可讀取ZIP壓縮格式的文件,包括對已壓縮和未壓縮條目的支持(entry)造垛。

ZipInputStream類常用的方法

ZipInputStream(InputStream in)  構造函數(shù)
read(byte[] b, int off, int len)    讀取目標b數(shù)組off偏移量的長度, 長度是len字節(jié)
available() 判斷是否已讀完目前entry所指定的數(shù)據(jù), 已讀完返回0, 否則返回1
closeEntry()    關閉目前ZIP條目并定位流以讀取下一個條目
skip(long n)    跳過當前ZIP條目中指定的字節(jié)數(shù)
getNextEntry()  讀取下一個ZipEntry, 并將流內(nèi)的位置移至該entry所指數(shù)據(jù)的開頭
createZipEntry(String name) 以指定的name參數(shù)新建一個ZipEntry對象

2.3 基于字符的IO操作接口

2.3.1 Reader - 輸入字符流

Java中的字符是Unicode編碼(雙字節(jié)). InputStream是用來處理字節(jié)的, 在處理字符文本時不是很方便.

Java為字符文本的輸入提供Reader魔招,但Reader類并不是InputStream類的替換者。直是在處理字符串時簡化了編程. Reader類是字符輸入流的抽象類, 所有字符輸入流的實現(xiàn)都是它的子類

Reader類常用的方法 (標記阻塞: 在某個字符可用五辽、發(fā)生 I/O 錯誤或者已到達流的末尾前办斑,方法一直阻塞)

public int read(java.nio.CharBuffer target) throws IOException  將字符讀入指定的字符緩沖區(qū)。緩沖區(qū)可照原樣用作字符的存儲庫:所做的唯一改變是 put 操作的結果杆逗。不對緩沖區(qū)執(zhí)行翻轉(zhuǎn)或重繞操作
public int read() throws IOException    阻塞乡翅,讀取單個字符, 范圍在 0 到 65535 之間 (0x00-0xffff), 如果位于緩沖區(qū)末端, 則返回-1
public int read(char cbuf[]) throws IOException 阻塞,將字符讀入數(shù)組
abstract public int read(char cbuf[], int off, int len) throws IOException  阻塞罪郊,將字符讀入數(shù)組的某一部分 [cbuf:目標緩沖區(qū), off:開始存儲字符處的偏移量, len:讀取的最多字符數(shù)]
public long skip(long n) throws IOException 阻塞蠕蚜,跳過字符
public boolean ready() throws IOException   判斷是否準備讀取此流
public boolean markSupported()  判斷此流是否支持mark()操作, 模式實現(xiàn)始終返回false, 子類應該重寫此方法
public void mark(int readAheadLimit) throws IOException 標記流中的當前位置。對reset()的后續(xù)調(diào)用將嘗試將該流重新定位到此點悔橄。如果該流支持mark()操作靶累。
public void reset() throws IOException  配合mark(),reset回到mark標記的位置癣疟。如果該流支持reset()操作
abstract public void close() throws IOException 關閉該流并釋放與之關聯(lián)的所有資源挣柬。在關閉該流后,再調(diào)用 read()睛挚、ready()邪蛔、mark()、reset() 或 skip() 將拋出 IOException扎狱。關閉以前關閉的流無效
Reader:所有的輸入字符流的抽象父類
    |-- BufferedReader:很明顯就是一個裝飾器, 它和其子類負責裝飾其它Reader對象
        |-- LineNumberReader
    |-- CharArrayReader:基本的介質(zhì)流侧到,將Char數(shù)組中讀取數(shù)據(jù)
    |-- InputStreamReader:一個連接字節(jié)流和字符流的橋梁, 它將字節(jié)流轉(zhuǎn)變?yōu)樽址?        |-- FileReader:源碼中明顯使用了將FileInputStream轉(zhuǎn)變?yōu)镽eader的方法
    |-- PipedReader:是從與其它線程共用的管道中讀取數(shù)據(jù)
    |-- StringReader:基本的介質(zhì)流,將String中讀取數(shù)據(jù)
    |-- FilterReader:所有自定義具體裝飾流的父類
        |-- PushbackReader:對Reader對象進行裝飾, 會增加一個行號
    Reader中各個類的用途和使用方法基本和InputStream中的類使用一致

2.3.2 Writer - 輸出字符流

Writer類是字符輸出流的抽象類, 所有字符輸出類的實現(xiàn)都是它的子類

Writer類常用的方法(標記阻塞: 在某個字符可用淤击、發(fā)生 I/O 錯誤或者已到達流的末尾前匠抗,方法一直阻塞)

public void write(int c) throws IOException 寫入單個字符. 要寫入的字符包含在給定整數(shù)值的16個低位中, 16 高位被忽略, 用于支持高效單字符輸出的子類應重寫此方法
public void write(char cbuf[]) throws IOException   寫入字符數(shù)組
abstract public void write(char cbuf[], int off, int len) throws IOException    寫入字符數(shù)組的某一部分
[cbuf: 目標數(shù)組, off:偏移量, len:寫入長度]
public void write(String str) throws IOException    寫入字符串
public void write(String str, int off, int len) throws IOException  寫入字符串的某一部分
public Writer append(CharSequence csq) throws IOException   將指定字符序列添加到此 writer
public Writer append(CharSequence csq, int start, int end) throws IOException   將指定字符序列添加到此 writer
public Writer append(char c) throws IOException 將指定字符添加到此 writer
abstract public void flush() throws IOException 徹底完成輸出并清空緩存區(qū)
abstract public void close() throws IOException 關閉此流,但要先刷新它
Writer:所有的輸出字符流的抽象父類
    |-- BufferedWriter:是一個裝飾器為Writer提供緩沖功能
    |-- CharArrayWriter:基本的介質(zhì)流污抬,將Char數(shù)組中寫入數(shù)據(jù)
    |-- OutputStreamWriter:OutputStream到Writer轉(zhuǎn)換的橋梁, 它將字節(jié)流轉(zhuǎn)變?yōu)樽址?        |-- FileWriter:源碼中明顯使用了將FileOutStream轉(zhuǎn)變?yōu)閃riter的方法
    |-- PipedWriter:是從與其它線程共用的管道中寫入數(shù)據(jù)
    |-- PrintWriter:和PrintStream極其類似戈咳,功能和使用也非常相似
    |-- StringWriter:基本的介質(zhì)流,將String中寫入數(shù)據(jù)
    |-- FilterWriter:所有自定義具體裝飾流的父類
    Writer中各個類的用途和使用方法基本和InputStream中的類使用一致.后面會有Writer與InputStream的對應關系

2.3.3 字符流的輸入與輸出的對應

Reader                                         Writer
  |-- BufferedReader              BufferedWriter --|
    |-- LineNumberReader                           |
  |-- CharArrayReader             CharArrayWriter--|
  |-- InputStreamReader        OutputStreamWriter--|
    |-- FileReader                  FileWriter --|
  |-- PipedReader ---[ both use ]--- PipedWriter --|
  |-- StringReader                  StringWriter --|
  |-- FilterReader                  FilterWriter --|
    |-- PushbackReader                             |
                                     PrintWriter --|

2.3.4 FileReader與FileWriter類

FileInputStream和FileOutputStream只體用了對字節(jié)或字節(jié)數(shù)組的讀寫方法, 由于漢子在文件中占用兩個字節(jié), 如果使用字節(jié)流, 讀取不好可能會出現(xiàn)亂碼現(xiàn)象.

對于讀取字符流, 采用字符流FileReader和FileWriter避免亂碼的情況

FileReader對應FileInputStream壕吹,F(xiàn)ileWriter對應FileOutputStream

2.3.5 打印流

PrintWriter(純文本)與PrintStream(二進制文件, 如圖片、音樂删铃、視頻等)可以直接操作輸入流和文件

  1. 提供了打印方法可以對多重數(shù)據(jù)類型值進行打印. 并保持數(shù)據(jù)的表示形式
  2. 它不拋IOException

構造函數(shù)

  1. 字符串路徑
  2. File對象
  3. 字節(jié)輸出流
  4. 字符輸出流

2.3.6 BufferedReader與BufferedWriter類

(basic.io.charstream.buffered.BufferedDemo)

BufferedReader類與BufferedWriter類分別繼承Reader類與Writer類, 這兩個類具有內(nèi)部緩存機制, 并可以為單位進行輸入/輸出耳贬,BudderedReader類讀取文件的過程:

字符數(shù)據(jù) -> BufferedWriter -> OutputStreamWriter -> OutputStream -> 文件
BufferedReader類常用方法:

read(): 讀取單個字符
readLine(): 讀取一個文本行, 并將返回為字符串; 若無數(shù)據(jù)可讀, 則返回null
write(String s, int off, int len): 寫入字符串的某一部分
flush(): 刷新該流的緩存
newLine(): 寫入一個行分隔符

在使用BufferedWriter類的Write()方法時, 數(shù)據(jù)并沒有立刻被寫入至輸出流中, 而是首先先進入緩存區(qū)中, 如果想立刻將緩存區(qū)中的數(shù)據(jù)寫入輸出流中, 要調(diào)用flush()

自定義BufferReader:

(basic.io.charstream.buffered.CustomBufferedReader)

自定義的讀取緩沖區(qū). 其實就是模擬一個BufferedReader

分析: 緩沖區(qū)無非就是封裝了一個數(shù)組, 并對外提供了更多的方法對數(shù)組進行訪問.

緩沖的原理: 其實就是從源中獲取一批數(shù)據(jù)裝進緩沖區(qū)中, 再從緩沖區(qū)中不斷的取出一個一個數(shù)據(jù), 在此次取完后, 再從源中繼續(xù)取一批數(shù)據(jù)進緩沖區(qū), 當源中的數(shù)據(jù)取光時, 用-1作為結束標記

2.4 字節(jié)流和字符流

2.4.1 輸入的對應

InputStream                                      Reader
  |-- ByteArrayInputStream         CharArrayReader --|
  |-- PipedInputStream                 PipedReader --|
  |-- ObjectInputStream                              |
  |                               InputStreamReader--| 
  |-- FileInputStream                 FileReader --|
  |-- FilterInputStream               FilterReader --|
    |-- PushbackInputStream       PushbackReader --|
    |-- BufferedInputStream         BufferedReader --|
    |-- LineNumberInputStream   LineNumberReader --|
    |-- DataInputStream                              |
  |                                   StringReader --|
  |-- SequenceInputStream
  |-- StringBufferInputStream 

注意:即使對應, 它們的繼承關系也是不太對應的

2.4.2 輸出的對應

OutputStream                                   Writer
  |-- ByteArrayOutputStream      CharArrayWriter --|
  |-- ObjectOutputStream                           | 
  |                           OutputStreamWriter --|
  |-- FileOutputStream               FileWrite --|
  |-- PipedOutputStream              PipedWriter --|
  |-- FilterOutputStream            FilterWriter --|
    |-- BufferedOutputStream      BufferedWriter --|
    |-- PrintStream                  PrintWriter --|
    |-- DataOutputStream                           |
    |                               StringWriter --|

2.4.3 各個類所負責的媒介

| | Byte Based | Character Based |
Input Output Input Output

InputStream OutputStream Reader Writer
Basic InputStream OutputStream Reader </br> InputStreamReader Writer
OutputStramWriter
Arrays ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
Files FileInputStream RandomAccessFile FileOutputStream RandomAccessFile FileReader FileWriter
Pipes PipedInputStream PipedOutputStream PipedReader PipedWriter
Buffering BufferedInputStream BufferOutputStream BufferedReader BufferedWriter
Fitering FilterInputStream FilterOutputStream FilterReader FilterWriter
Parsing PushbackInputStream StreamTokenizer PushbackReader LineNumberReader
Strings StringReader StringWriter
Data DataInputStream DataOutputStream
Data-Formatted PrintStream PrintWriter
Objects ObjectInputStream ObjectOutputStream
Utilities SequenceInputStream

2.4.4 轉(zhuǎn)換流(字節(jié)流->字符流)(TransStreamDemo)

Java的I/O中存在輸入、輸出的對應和字節(jié)流和字符流的對應猎唁,它們之間的轉(zhuǎn)化橋梁

  1. InputStreamReader:字節(jié)到字符的橋梁咒劲、解碼
  2. OutputStreamWriter:字符到字節(jié)的橋梁、編碼
public class ConvertDemo {
    public static void main(String[] args) throws IOException  {
        ConvertDemo demo = new ConvertDemo();
        demo.byte2char();
    }

    /** 字節(jié)流 -> 字符流  */
    public void byte2char() throws IOException  {
        // 源頭,可以是System.in腐魂,可以是文件帐偎,可以是固定byte
//      InputStream inputStream = System.in;
        InputStream inputStream = new ByteArrayInputStream("Hello World".getBytes());
        // 將字節(jié)轉(zhuǎn)成字符的橋梁, 裝換流
        InputStreamReader reader = new InputStreamReader(inputStream);
//        InputStreamReader reader = new InputStreamReader(inputStream, "UTF-8");
        // int ch = isr.read();
        // System.out.println((char)ch);

        BufferedReader buffer = new BufferedReader(reader);
        // 目標,可以是System.out蛔屹,可以是文件削樊,可以是其他
        OutputStream out = System.out;
        OutputStreamWriter writer = new OutputStreamWriter(out);
        BufferedWriter bufferedWriter = new BufferedWriter(writer);

        String line = null;
        while ((line = buffer.readLine()) != null) {
            if ("over".equals(line))
                break;
            // System.out.println(line.toUpperCase());
            // osw.write(line.toUpperCase()+"\r\n");
            // osw.flush();
            bufferedWriter.write(line.toUpperCase());
            bufferedWriter.newLine();
            bufferedWriter.flush();
        } 
    }
}

2.4.5 流的操作規(guī)律

流的操作規(guī)律: 明確4點

1.明確源和目的(匯) 
    源: InputStream(字節(jié)輸入流)  Reader(字符輸入流)
    匯: OutPutStream(字節(jié)輸出流)  Writer(字符輸出流)
2.明確數(shù)據(jù)是否純文本數(shù)據(jù)(這步可以明確需求中具體要使用哪個體系)
    源: 是純文本:Reader    否:InputStream
    匯: 是純文本:Writer    否:OutputStream
3.明確具體的設備
    硬盤:File     鍵盤:System.in        內(nèi)存:數(shù)組       網(wǎng)絡:Socket流
4.是否需要其他額外功能
    1.是否需要高效(緩沖區(qū)),需要就加上Buffer 
    2.轉(zhuǎn)換流, 什么時候使用轉(zhuǎn)換流呢兔毒?
        1).源或目的對應的設備是字節(jié)流, 但操作的卻是文本數(shù)據(jù), 使用轉(zhuǎn)換作為橋梁. 提高對文本操作的便捷. 
        2).一旦操作文本涉及到具體的指定編碼表時, 必須使用轉(zhuǎn)換流 . 

2.5 File類

(basic.io.FileDemo)

File類是io包中唯一代表磁盤文件本身的對象. File類定義了一些與平臺無關的方法, 可以通過調(diào)用File類種的方法, 實現(xiàn)創(chuàng)建, 刪除, 重命名文件等.

數(shù)據(jù)流可以將數(shù)據(jù)寫入到文件中, 而文件也是數(shù)據(jù)流最常用的數(shù)據(jù)媒體

[注: 對于Microsoft Windows平臺, 包含盤符的路徑名前綴由驅(qū)動器號和一個":"組成, 如果路徑名是絕對路徑名, 還可能后跟"\"]

構造函數(shù):文件名/文件對象/文件描述對象漫贞,最終都是new一個FileDescriptor,然后調(diào)用open()

    public File(String pathname)
    public File(String parent, String child)
    public File(File parent, String child)
    public File(URI uri)
    private File(String pathname, int prefixLength)
    private File(String child, File parent)

內(nèi)部維護屬性:

    private static final FileSystem fs = DefaultFileSystem.getFileSystem():對應JDK獲取文件系統(tǒng)
    public static final char separatorChar = fs.getSeparator():路徑分隔符(Unix:'/',Windows:'\\')
    public static final String separator = "" + separatorChar
    public static final char pathSeparatorChar = fs.getPathSeparator():path分割符(Unix:':', Windows:';')
    public static final String pathSeparator = "" + pathSeparatorChar
    private final String path
    private static enum PathStatus { INVALID, CHECKED }
    private transient PathStatus status = null
    private final transient int prefixLength
    int getPrefixLength():絕對路徑的前綴長度
路徑方法:
    String getParent()  獲取上一級的目錄相對路徑
    String getPath()    獲取文件的目錄相對路徑
    String getAbsolutePath()    獲取文件的絕對路徑
    String getCanonicalPath()   返回此抽象路徑名的規(guī)范路徑名字符串
判斷方法:
    boolean isAbsolute()    是否為絕對路徑名
    boolean canRead()   是否可讀
    boolean canWrite()  是否可寫
    boolean exists()    是否存在
    boolean isDirectory()   是否是目錄
    boolean isFile()    是否是文件
    boolean isHidden()  是否是隱藏文件
    boolean canExecute()    是否可執(zhí)行
    boolean equals(Object obj)  與文件對象比較是否相同
獲取方法:
    long length()   文件的長度
    String getName()    文件或目錄名稱
    long lastModified() 最后修改時間
    long getTotalSpace()    指定的分區(qū)大小
    long getFreeSpace() 指定的分區(qū)中未分配的字節(jié)數(shù)
    long getUsableSpace()   指定的分區(qū)上可用于此虛擬機的字節(jié)數(shù)
    int compareTo(File pathname)    按字母順序比較兩個抽象路徑名
    int hashCode()  哈希碼
    String toString()   路徑名字符串
    File getParentFile()    父目錄的抽象路徑名育叁;如果此路徑名沒有指定父目錄迅脐,則返回 null
    File getAbsoluteFile()  抽象路徑名的絕對路徑名形式
    File getCanonicalFile() 抽象路徑名的規(guī)范形式
    URL toURL() 過時, 
    URI toURI() 構造一個表示此抽象路徑名的 file: URI
    String[] list() 此抽象路徑名表示的目錄中的文件和目錄
    String[] list(FilenameFilter filter)    此抽象路徑名表示的目錄中滿足指定過濾器的文件和目錄
    File[] listFiles()  同上,返回File對象列表
    File[] listFiles(FilenameFilter filter) 同上
    File[] listFiles(FileFilter filter) 同上
    static File[] listRoots()   列出可用的文件系統(tǒng)根
    Path toPath()   
操作方法:
    boolean mkdir() 指定的目錄
    boolean mkdirs()    指定的目錄,包括所有必需但不存在的父目錄
    boolean createNewFile() 創(chuàng)建文件
    boolean renameTo(File dest) 重命名
    boolean delete()    刪除文件
    boolean setLastModified(long time)  設置最后修改時間
    boolean setReadOnly()   設置只讀
    boolean setWritable(boolean writable, boolean ownerOnly)    設置寫狀態(tài)
    boolean setWritable(boolean writable)   設置寫狀態(tài)
    boolean setReadable(boolean readable, boolean ownerOnly)    設置讀狀態(tài)
    boolean setReadable(boolean readable)   設置讀狀態(tài)
    boolean setExecutable(boolean executable, boolean ownerOnly)    設置執(zhí)行狀態(tài)
    boolean setExecutable(boolean executable)   設置執(zhí)行狀態(tài)
    void deleteOnExit() 在虛擬機終止時豪嗽,請求刪除此抽象路徑名表示的文件或目錄
    synchronized void writeObject(java.io.ObjectOutputStream s) 
    synchronized void readObject(java.io.ObjectInputStream s)   
靜態(tài)方法:
    static File createTempFile(String prefix, String suffix, File directory)    在默認臨時文件目錄中創(chuàng)建一個空文件谴蔑,使用給定前綴和后綴生成其名稱
    static File createTempFile(String prefix, String suffix)    在指定目錄中創(chuàng)建一個新的空文件,使用給定的前綴和后綴字符串生成其名稱

2.6 磁盤IO工作機制

讀取和寫入文件龟梦,IO操作都調(diào)用操作系統(tǒng)提供的接口隐锭。而只要是系統(tǒng)調(diào)用就涉及到內(nèi)核空間地址和用戶空間地址切換的問題,這是操作系統(tǒng)為了保護系統(tǒng)本身的運行安全而將內(nèi)核程序運行使用內(nèi)存空間和用戶程序運行的內(nèi)存空間隔離造成的变秦。保證了內(nèi)核程序運行的安全性成榜,但也就存在,從內(nèi)核空間向用戶空間復制的問題蹦玫。

這里就會涉及到幾個概念:

  1. 物理磁盤:如硬盤
  2. 內(nèi)核地址空間:內(nèi)核使用的地址空間
  3. 內(nèi)核地址空間的高速頁緩存:為了減小IO響應時間赎婚,對磁盤讀取的文件進行緩存,當讀取同一個地址的空間數(shù)據(jù)樱溉,直接從緩存讀取
  4. 用戶地址空間:用戶線程(程序)操作的空間挣输,與內(nèi)核空間隔離
  5. 用戶地址空間的應用緩存

應用程序訪問文件的幾種訪問文件方式:

1).標準訪問文件方式
    read():物理磁盤 -> 內(nèi)存地址空間(高速頁緩存) -> 用戶地址空間(應用緩存) -> read()
    write():write() -> 內(nèi)存地址空間(高速頁緩存) -> 用戶地址空間(應用緩存) -> 物理磁盤
2).直接IO的方式
    read():物理磁盤 -> 用戶地址空間(應用緩存) -> read()
    write():write() -> 用戶地址空間(應用緩存) -> 物理磁盤
3).同步訪問文件方式:就是數(shù)據(jù)讀取和寫入都是同步操作的
4).異步訪問文件方式
    當訪問數(shù)據(jù)的線程發(fā)出請求后,線程會接著去處理其他事情福贞,而不是阻塞等待撩嚼,當請求的數(shù)據(jù)返回后繼續(xù)處理下面的操作。提高應用程序的效率挖帘,而不會改變訪問文件的效率
5).內(nèi)存映射方式
    read():物理磁盤 -> 內(nèi)存地址空間(高速頁緩存) -- 地址映射 --> 用戶地址空間(應用緩存) -> read()
    write():write() -> 內(nèi)存地址空間(高速頁緩存) -- 地址映射 --> 用戶地址空間(應用緩存) -> 物理磁盤
    操作系統(tǒng)將內(nèi)存中的某一塊區(qū)域與磁盤中的文件關聯(lián)起來完丽,當要訪問內(nèi)存中一段數(shù)據(jù)時,轉(zhuǎn)換為訪問文件的某一段數(shù)據(jù)拇舀。減少數(shù)據(jù)從內(nèi)核空間緩存到用戶空間緩存的數(shù)據(jù)復制操作逻族,因為這兩個空間的數(shù)據(jù)時共享的。

2.6.1 Java訪問磁盤文件

數(shù)據(jù)在磁盤中的唯一最小描述就是文件骄崩,簡單的說聘鳞,上層應用程序只能通過文件來操作磁盤上的數(shù)據(jù)薄辅。文件也是操作系統(tǒng)和磁盤驅(qū)動器交互的最小單元。

在Java中抠璃,F(xiàn)ile類并不代表一個真實存在的文件對象站楚,他只是代表這個路徑的一個虛擬對象。當真正讀取這個文件時搏嗡,就會檢查這個文件是否存在窿春。

Java訪問磁盤文件.png

當讀取文件時,會根據(jù)File來創(chuàng)建真正讀取文件的操作對象FileDescriptor彻况。通過這個對象可以直接控制這個磁盤文件谁尸。[如:getFD()來獲取真正操作的底層操作系統(tǒng)關聯(lián)的文件描述。且sync()方法將操作系統(tǒng)緩存中的數(shù)據(jù)強制刷新到物理磁盤中]

而由于需要讀取的事字符格式纽甘,所以需要StreamDecoder類將byte解碼為char格式良蛮。至于如何從磁盤驅(qū)動器上讀取一段數(shù)據(jù),操作系統(tǒng)會幫我們完成

2.6.2 磁盤IO優(yōu)化

2.6.2.1 性能檢測

磁盤I/O通常都很耗時悍赢,有一些參數(shù)指標可以判斷I/O是否是一個瓶頸

  1. 壓力測試應用程序查看系統(tǒng)的I/O wait指標是否正常决瞳,例如:測試有4個CPU,那么理解I/O wait參數(shù)不應該超過25%左权。若超過則可能成為瓶頸皮胡,Linux可以通過iostat命令查看
  2. IOPS:應用程序需要的最低IOPS,磁盤提供的IOPS赏迟。每個磁盤IOPS通常都在一個范圍內(nèi)屡贺,這和存儲在磁盤上的數(shù)據(jù)塊的大小和訪問方式也有關,但主要是由磁盤的轉(zhuǎn)速決定的锌杀,轉(zhuǎn)速越高甩栈,IOPS也越高

為了提高磁盤I/O性能,通常采用RAID技術糕再,就是將不同的磁盤組合起來提高I/O性能量没。目前有多種RAID技術,每種RAID技術對I/O性能的提高會有不同突想,可以用一個RAID因子來代表殴蹄,磁盤的讀寫吞吐量可以通過iostat命令來獲取,于是可以計算出一個理論的IOPS值猾担,公式如下:

IOPS = (磁盤數(shù) * 每塊磁盤的IOPS) / (磁盤讀的吞吐量 + RAID因子 * 磁盤寫的吞吐量)

2.6.2.2 提高I/O性能

主要方法:

  1. 增加緩存袭灯,減少磁盤訪問次數(shù)
  2. 優(yōu)化磁盤的管理系統(tǒng),設計最優(yōu)的磁盤方式策略绑嘹,以及磁盤的尋址策略妓蛮,這是底層操作系統(tǒng)層面考慮
  3. 設計合理的磁盤存儲數(shù)據(jù)塊,以及訪問這些數(shù)據(jù)塊的策略圾叼,這是應用層面考慮的蛤克。例如:給存放的數(shù)據(jù)設計索引,通過尋址索引來加快和減少磁盤的訪問量夷蚊,也可以采用異步和非阻塞方式加快磁盤的訪問速度
  4. 應用合理的RAID策略提升磁盤IO构挤,如下表
磁盤陣列 說明
RAID 0 數(shù)據(jù)被平均寫到多少個磁盤陣列中,寫數(shù)據(jù)和杜數(shù)據(jù)都是并行的惕鼓,所以磁盤的IOPS可以提高一倍
RAID 1 提高數(shù)據(jù)的安全性筋现,將一份數(shù)據(jù)分別復制到多個磁盤陣列中,并不能提升IOPS箱歧,但是相同的數(shù)據(jù)有多個備份矾飞。用于對數(shù)據(jù)安全性較高的場合中
RAID 5 這種方式,將數(shù)據(jù)平均寫到所有磁盤陣列總數(shù)減一的磁盤中呀邢,往另外一個磁盤中寫入這份數(shù)據(jù)的奇偶校驗信息洒沦。如果其中一個磁盤損壞,可以通過其他磁盤的數(shù)據(jù)和這個數(shù)據(jù)的奇偶校驗信息來恢復這份數(shù)據(jù)
RAID 0 + 1 根據(jù)數(shù)據(jù)備份情況進行分組价淌,一份數(shù)據(jù)同時寫到多個備份磁盤分組中申眼,同時多個分組也會并行讀寫

2.7 IO包中的其他類

2.7.1 RandomAccessFile(不是IO體系中的子類, 是Object的子類)

特點:

  1. 該對象既能讀, 又能寫
  2. 該對象內(nèi)部維護一個byte數(shù)組, 并通過指針可以操作數(shù)組中的元素
  3. 可以通過getFilePointer方法獲取指針的位置, 和通過seek方法設置指針的位置
  4. 其實該對象就是將字節(jié)輸入流和輸出流進行了封裝
  5. 該對象的源或匯只能是文件. 通過構造函數(shù)就可以看出
(basic.demo18.RandomAccessFileDemo)
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * RandomAccessFile(不是IO體系中的子類, 是Object的子類)
 * 特點: 
 *  1).該對象既能讀, 又能寫
 *  2).該對象內(nèi)部維護一個byte數(shù)組, 并通過指針可以操作數(shù)組中的元素
 *  3).可以通過getFilePointer方法獲取指針的位置, 和通過seek方法設置指針的位置
 *  4).其實該對象就是將字節(jié)輸入流和輸出流進行了封裝
 *  5).該對象的源或匯只能是文件. 通過構造函數(shù)就可以看出
 */
public class RandomAccessFileDemo {
    public static void main(String[] args) throws IOException {
//      writeFile();
//      readFile();
        randomWrite();
    }
    
    public static void randomWrite() throws IOException {
        RandomAccessFile raf = new RandomAccessFile("ranacc.txt", "rw");
        // 往指定位置寫入數(shù)據(jù)。
        raf.seek(3 * 8);
        raf.write("哈哈".getBytes());
        raf.writeInt(108);
        raf.close();
    }
    
    public static void readFile() throws IOException {
        RandomAccessFile raf = new RandomAccessFile("ranacc.txt", "r");
        // 通過seek設置指針的位置蝉衣。
        raf.seek(1 * 8);// 隨機的讀取括尸。只要指定指針的位置即可。
        byte[] buf = new byte[4];
        raf.read(buf);
        String name = new String(buf);
        int age = raf.readInt();
        System.out.println("name=" + name);
        System.out.println("age=" + age);
        System.out.println("pos:" + raf.getFilePointer());
        raf.close();
    }

    // 使用RandomAccessFile對象寫入一些人員信息病毡,比如姓名和年齡濒翻。
    public static void writeFile() throws IOException {
        // 如果文件不存在,則創(chuàng)建啦膜,如果文件存在有送,不創(chuàng)建
        RandomAccessFile raf = new RandomAccessFile("ranacc.txt", "rw");
        raf.write("張三".getBytes());
        raf.writeInt(97);
        raf.write("小強".getBytes());
        raf.writeInt(99);
        raf.close();
    }
}
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市功戚,隨后出現(xiàn)的幾起案子娶眷,更是在濱河造成了極大的恐慌,老刑警劉巖啸臀,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件届宠,死亡現(xiàn)場離奇詭異,居然都是意外死亡乘粒,警方通過查閱死者的電腦和手機豌注,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來灯萍,“玉大人轧铁,你說我怎么就攤上這事〉┟蓿” “怎么了齿风?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵药薯,是天一觀的道長。 經(jīng)常有香客問我救斑,道長童本,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任脸候,我火速辦了婚禮穷娱,結果婚禮上,老公的妹妹穿的比我還像新娘运沦。我一直安慰自己泵额,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布携添。 她就那樣靜靜地躺著嫁盲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪薪寓。 梳的紋絲不亂的頭發(fā)上亡资,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音向叉,去河邊找鬼锥腻。 笑死,一個胖子當著我的面吹牛母谎,可吹牛的內(nèi)容都是我干的瘦黑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼奇唤,長吁一口氣:“原來是場噩夢啊……” “哼幸斥!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎譬嚣,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體经窖,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年梭灿,在試婚紗的時候發(fā)現(xiàn)自己被綠了画侣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡堡妒,死狀恐怖配乱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤搬泥,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布桑寨,位于F島的核電站,受9級特大地震影響佑钾,放射性物質(zhì)發(fā)生泄漏西疤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一休溶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扰她,春花似錦兽掰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至忧勿,卻和暖如春杉女,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鸳吸。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工熏挎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晌砾。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓坎拐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親养匈。 傳聞我的和親對象是個殘疾皇子哼勇,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容