日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
為什么一個(gè)還沒畢業(yè)的大學(xué)生能夠把 IO 講的這么好?

Java IO 是一個(gè)龐大的知識體系,很多人學(xué)著學(xué)著就會學(xué)懵了,包括我在內(nèi)也是如此,所以本文將會從 Java 的 BIO 開始,一步一步深入學(xué)習(xí),引出 JDK1.4 之后出現(xiàn)的 NIO 技術(shù),對比 NIO 與 BIO 的區(qū)別,然后對 NIO 中重要的三個(gè)組成部分進(jìn)行講解(緩沖區(qū)、通道、選擇器),最后實(shí)現(xiàn)一個(gè)簡易的客戶端與服務(wù)器通信功能。

從網(wǎng)站建設(shè)到定制行業(yè)解決方案,為提供成都網(wǎng)站制作、成都網(wǎng)站設(shè)計(jì)服務(wù)體系,各種行業(yè)企業(yè)客戶提供網(wǎng)站建設(shè)解決方案,助力業(yè)務(wù)快速發(fā)展。創(chuàng)新互聯(lián)公司將不斷加快創(chuàng)新步伐,提供優(yōu)質(zhì)的建站服務(wù)。

傳統(tǒng)的 BIO

Java IO流是一個(gè)龐大的生態(tài)環(huán)境,其內(nèi)部提供了很多不同的輸入流和輸出流,細(xì)分下去還有字節(jié)流和字符流,甚至還有緩沖流提高 IO 性能,轉(zhuǎn)換流將字節(jié)流轉(zhuǎn)換為字符流······看到這些就已經(jīng)對 IO 產(chǎn)生恐懼了,在日常開發(fā)中少不了對文件的 IO 操作,雖然 apache 已經(jīng)提供了 Commons IO 這種封裝好的組件,但面對特殊場景時(shí),我們?nèi)孕枰约喝シ庋b一個(gè)高性能的文件 IO 工具類,本文將會解析 Java IO 中涉及到的各個(gè)類,以及講解如何正確、高效地使用它們。

BIO NIO 和 AIO 的區(qū)別

我們會以一個(gè)經(jīng)典的燒開水的例子通俗地講解它們之間的區(qū)別

類型燒開水
BIO一直監(jiān)測著某個(gè)水壺,該水壺?zé)_水后再監(jiān)測下一個(gè)水壺
NIO每隔一段時(shí)間就看看所有水壺的狀態(tài),哪個(gè)水壺?zé)_水就去處理哪個(gè)水壺
AIO不用監(jiān)測水壺,每個(gè)水壺?zé)_水后都會主動通知線程說:“我的水燒開了,來處理我吧”

BIO (同步阻塞 I/O)

這里假設(shè)一個(gè)燒開水的場景,有一排水壺在燒開水,BIO的工作模式就是, 小菠蘿一直看著著這個(gè)水壺,直到這個(gè)水壺?zé)_,才去處理下一個(gè)水壺。線程在等待水壺?zé)_的時(shí)間段什么都沒有做。

NIO(同步非阻塞 I/O)

還拿燒開水來說,NIO的做法是小菠蘿一邊玩著手機(jī),每隔一段時(shí)間就看一看每個(gè)水壺的狀態(tài),看看是否有水壺的狀態(tài)發(fā)生了改變,如果某個(gè)水壺?zé)_了,可以先處理那個(gè)水壺,然后繼續(xù)玩手機(jī),繼續(xù)隔一段時(shí)間又看看每個(gè)水壺的狀態(tài)。

AIO (異步非阻塞 I/O)

小菠蘿覺得每隔一段時(shí)間就去看一看水壺太費(fèi)勁了,于是購買了一批燒開水時(shí)可以嗶嗶響的水壺,于是開始燒水后,小菠蘿就直接去客廳玩手機(jī)了,水燒開時(shí),就發(fā)出“嗶嗶”的響聲,通知小菠蘿來關(guān)掉水壺。

什么是流

知識科普:我們知道任何一個(gè)文件都是以二進(jìn)制形式存在于設(shè)備中,計(jì)算機(jī)就只有 0 和1,你能看見的東西全部都是由這兩個(gè)數(shù)字組成,你看這篇文章時(shí),這篇文章也是由01組成,只不過這些二進(jìn)制串經(jīng)過各種轉(zhuǎn)換演變成一個(gè)個(gè)文字、一張張圖片躍然屏幕上。

而流就是將這些二進(jìn)制串在各種設(shè)備之間進(jìn)行傳輸,如果你覺得有些抽象,我舉個(gè)例子就會好理解一些:

“下圖是一張圖片,它由01串組成,我們可以通過程序把一張圖片拷貝到一個(gè)文件夾中,

把圖片轉(zhuǎn)化成二進(jìn)制數(shù)據(jù)集,把數(shù)據(jù)一點(diǎn)一點(diǎn)地傳遞到文件夾中 , 類似于水的流動 , 這樣整體的數(shù)據(jù)就是一個(gè)數(shù)據(jù)流”

IO 流讀寫數(shù)據(jù)的特點(diǎn):

  • 順序讀寫。讀寫數(shù)據(jù)時(shí),大部分情況下都是按照順序讀寫,讀取時(shí)從文件開頭的第一個(gè)字節(jié)到最后一個(gè)字節(jié),寫出時(shí)也是也如此(RandomAccessFile 可以實(shí)現(xiàn)隨機(jī)讀寫)
  • 字節(jié)數(shù)組。讀寫數(shù)據(jù)時(shí)本質(zhì)上都是對字節(jié)數(shù)組做讀取和寫出操作,即使是字符流,也是在字節(jié)流基礎(chǔ)上轉(zhuǎn)化為一個(gè)個(gè)字符,所以字節(jié)數(shù)組是 IO 流讀寫數(shù)據(jù)的本質(zhì)。

流的分類

根據(jù)數(shù)據(jù)流向不同分類:輸入流 和 輸出流

  • 輸入流:從磁盤或者其它設(shè)備中將數(shù)據(jù)輸入到進(jìn)程中
  • 輸出流:將進(jìn)程中的數(shù)據(jù)輸出到磁盤或其它設(shè)備上保存

圖示中的硬盤只是其中一種設(shè)備,還有非常多的設(shè)備都可以應(yīng)用在IO流中,例如:打印機(jī)、硬盤、顯示器、手機(jī)······

根據(jù)處理數(shù)據(jù)的基本單位不同分類:字節(jié)流 和 字符流

  • 字節(jié)流:以字節(jié)(8 bit)為單位做數(shù)據(jù)的傳輸
  • 字符流:以字符為單位(1字符 = 2字節(jié))做數(shù)據(jù)的傳輸

“字符流的本質(zhì)也是通過字節(jié)流讀取,Java 中的字符采用 Unicode 標(biāo)準(zhǔn),在讀取和輸出的過程中,通過以字符為單位,查找對應(yīng)的碼表將字節(jié)轉(zhuǎn)換為對應(yīng)的字符。”

面對字節(jié)流和字符流,很多讀者都有疑惑:什么時(shí)候需要用字節(jié)流,什么時(shí)候又要用字符流?

我這里做一個(gè)簡單的概括,你可以按照這個(gè)標(biāo)準(zhǔn)去使用:

字符流只針對字符數(shù)據(jù)進(jìn)行傳輸,所以如果是文本數(shù)據(jù),優(yōu)先采用字符流傳輸;除此之外,其它類型的數(shù)據(jù)(圖片、音頻等),最好還是以字節(jié)流傳輸。

根據(jù)這兩種不同的分類,我們就可以做出下面這個(gè)表格,里面包含了 IO 中最核心的 4 個(gè)頂層抽象類:

數(shù)據(jù)流向 / 數(shù)據(jù)類型字節(jié)流字符流
輸入流InputStreamReader
輸出流OutputStreamWriter

現(xiàn)在看 IO 是不是有一些思路了,不會覺得很混亂了,我們來看這四個(gè)類下的所有成員。

[來自于 cxuan 的 《Java基礎(chǔ)核心總結(jié)》]

看到這么多的類是不是又開始覺得混亂了,不要慌,字節(jié)流和字符流下的輸入流和輸出流大部分都是一一對應(yīng)的,有了上面的表格支撐,我們不需要再擔(dān)心看見某個(gè)類會懵逼的情況了。

看到 Stream 就知道是字節(jié)流,看到 Reader / Writer 就知道是字符流。

這里還要額外補(bǔ)充一點(diǎn):Java IO 提供了字節(jié)流轉(zhuǎn)換為字符流的轉(zhuǎn)換類,稱為轉(zhuǎn)換流。

轉(zhuǎn)換流 / 數(shù)據(jù)類型字節(jié)流與字符流之間的轉(zhuǎn)換
(輸入)字節(jié)流 => 字符流InputStreamReader
(輸出)字符流 => 字節(jié)流OutputStreamWriter

注意字節(jié)流與字符流之間的轉(zhuǎn)換是有嚴(yán)格定義的:

  • 輸入流:可以將字節(jié)流 => 字符流
  • 輸出流:可以將字符流 => 字節(jié)流

為什么在輸入流不能字符流 => 字節(jié)流,輸出流不能字節(jié)流 => 字符流?

“在存儲設(shè)備上,所有數(shù)據(jù)都是以字節(jié)為單位存儲的,所以輸入到內(nèi)存時(shí)必定是以字節(jié)為單位輸入,輸出到存儲設(shè)備時(shí)必須是以字節(jié)為單位輸出,字節(jié)流才是計(jì)算機(jī)最根本的存儲方式,而字符流是在字節(jié)流的基礎(chǔ)上對數(shù)據(jù)進(jìn)行轉(zhuǎn)換,輸出字符,但每個(gè)字符依舊是以字節(jié)為單位存儲的。”

節(jié)點(diǎn)流和處理流

在這里需要額外插入一個(gè)小節(jié)講解節(jié)點(diǎn)流和處理流。

  • 節(jié)點(diǎn)流:節(jié)點(diǎn)流是真正傳輸數(shù)據(jù)的流對象,用于向特定的一個(gè)地方(節(jié)點(diǎn))讀寫數(shù)據(jù),稱為節(jié)點(diǎn)流。例如 FileInputStream
  • 處理流:處理流是對節(jié)點(diǎn)流的封裝,使用外層的處理流讀寫數(shù)據(jù),本質(zhì)上是利用節(jié)點(diǎn)流的功能,外層的處理流可以提供額外的功能。處理流的基類都是以 Filter 開頭。

上圖將 ByteArrayInputStream封裝成 DataInputStream,可以將輸入的字節(jié)數(shù)組轉(zhuǎn)換為對應(yīng)數(shù)據(jù)類型的數(shù)據(jù)。例如希望讀入int類型數(shù)據(jù),就會以2個(gè)字節(jié)為單位轉(zhuǎn)換為一個(gè)數(shù)字。

Java IO 的核心類 File

Java 提供了 File類,它指向計(jì)算機(jī)操作系統(tǒng)中的文件和目錄,通過該類只能訪問文件和目錄,無法訪問內(nèi)容。它內(nèi)部主要提供了 3 種操作:

  • 訪問文件的屬性:絕對路徑、相對路徑、文件名······
  • 文件檢測:是否文件、是否目錄、文件是否存在、文件的讀/寫/執(zhí)行權(quán)限······
  • 操作文件:創(chuàng)建目錄、創(chuàng)建文件、刪除文件······

上面舉例的操作都是在開發(fā)中非常常用的,F(xiàn)ile 類遠(yuǎn)不止這些操作,更多的操作可以直接去 API 文檔中根據(jù)需求查找。

訪問文件的屬性:

API功能
String getAbsolutePath()返回該文件處于系統(tǒng)中的絕對路徑名
String getPath()返回該文件的相對路徑,通常與 new File() 傳入的路徑相同
String getName()返回該文件的文件名

文件檢測:

API功能
boolean isFIle()校驗(yàn)該路徑指向是否一個(gè)文件
boolean isDirectory()校驗(yàn)該路徑指向是否一個(gè)目錄
boolean isExist()校驗(yàn)該路徑指向的文件/目錄是否存在
boolean canWrite()校驗(yàn)該文件是否可寫
boolean canRead()校驗(yàn)該文件是否可讀
boolean canExecute()校驗(yàn)該文件/目錄是否可以被執(zhí)行

操作文件:

API功能
mkdirs()遞歸創(chuàng)建多個(gè)文件夾,路徑中間有可能某些文件夾不存在
createNewFile()創(chuàng)建新文件,它是一個(gè)原子操作,有兩步:檢查文件是否存在、創(chuàng)建新文件
delete()刪除文件或目錄,刪除目錄時(shí)必須保證該目錄為空

多了解一些

文件的讀/寫/執(zhí)行權(quán)限,在 Windows 中通常表現(xiàn)不出來,而在 Linux 中可以很好地體現(xiàn)這一點(diǎn),原因是 Linux 有嚴(yán)格的用戶權(quán)限分組,不同分組下的用戶對文件有不同的操作權(quán)限,所以這些方法在 Linux 下會比在 Windows 下更好理解。下圖是 redis 文件夾中的一些文件的詳細(xì)信息,被紅框標(biāo)注的是不同用戶的執(zhí)行權(quán)限:

  • r(Read):代表該文件可以被當(dāng)前用戶讀,操作權(quán)限的序號是 4
  • w(Write):代表該文件可以被當(dāng)前用戶寫,操作權(quán)限的序號是 2
  • x(Execute):該文件可以被當(dāng)前用戶執(zhí)行,操作權(quán)限的序號是 1

root root 分別代表:當(dāng)前文件的所有者,當(dāng)前文件所屬的用戶分組。Linux 下文件的操作權(quán)限分為三種用戶:

  • 文件所有者:擁有的權(quán)限是紅框中的前三個(gè)字母,-代表沒有某個(gè)權(quán)限
  • 文件所在組的所有用戶:擁有的權(quán)限是紅框中的中間三個(gè)字母
  • 其它組的所有用戶:擁有的權(quán)限是紅框中的最后三個(gè)字母

Java IO 流對象

回顧流的分類有2種:

  • 根據(jù)數(shù)據(jù)流向分為輸入流和輸出流
  • 根據(jù)數(shù)據(jù)類型分為字節(jié)流和字符流

所以,本小節(jié)將以字節(jié)流和字符流作為主要分割點(diǎn),在其內(nèi)部再細(xì)分為輸入流和輸出流進(jìn)行講解。

字節(jié)流對象

字節(jié)流對象大部分輸入流和輸出流都是成雙成對地出現(xiàn),所以學(xué)習(xí)的時(shí)候可以將輸入流和輸出流一一對應(yīng)的流對象關(guān)聯(lián)起來,輸入流和輸出流只是數(shù)據(jù)流向不同,而處理數(shù)據(jù)的方式可以是相同的。

注意不要認(rèn)為用什么流讀入數(shù)據(jù),就需要用對應(yīng)的流寫出數(shù)據(jù),在 Java 中沒有這么規(guī)定,下圖只是各個(gè)對象之間的一個(gè)對應(yīng)關(guān)系,不是兩個(gè)類使用時(shí)必須強(qiáng)制關(guān)聯(lián)使用。

“下面有非常多的類,我會介紹基類的方法,了解這些方法是非常有必要的,子類的功能基于父類去擴(kuò)展,只有真正了解父類在做什么,學(xué)習(xí)子類的成本就會下降?!?/p>

InputStream

InputStream 是字節(jié)輸入流的抽象基類,提供了通用的讀方法,讓子類使用或重寫它們。下面是 InputStream 常用的重要的方法。

重要方法功能
public abstract int read()從輸入流中讀取下一個(gè)字節(jié),讀到尾部時(shí)返回 -1
public int read(byte b[])從輸入流中讀取長度為 b.length 個(gè)字節(jié)放入字節(jié)數(shù)組 b 中
public int read(byte b[], int off, int len)從輸入流中讀取指定范圍的字節(jié)數(shù)據(jù)放入字節(jié)數(shù)組 b 中
public void close()關(guān)閉此輸入流并釋放與該輸入流相關(guān)的所有資源

還有其它一些不太常用的方法,我也列出來了。

其它方法功能
public long skip(long n)跳過接下來的 n 個(gè)字節(jié),返回實(shí)際上跳過的字節(jié)數(shù)
public long available()返回下一次可讀?。ㄌ^)且不會被方法阻塞的字節(jié)數(shù)的估計(jì)值
public synchronized void mark(int readlimit)標(biāo)記此輸入流的當(dāng)前位置,對 reset() 方法的后續(xù)調(diào)用將會重新定位在 mark() 標(biāo)記的位置,可以重新讀取相同的字節(jié)
public boolean markSupported()判斷該輸入流是否支持 mark() 和 reset() 方法,即能否重復(fù)讀取字節(jié)
public synchronized void reset()將流的位置重新定位在最后一次調(diào)用 mark() 方法時(shí)的位置

(1)ByteArrayInputStream

ByteArrayInputStream 內(nèi)部包含一個(gè) buf 字節(jié)數(shù)組緩沖區(qū),該緩沖區(qū)可以從流中讀取的字節(jié)數(shù),使用 pos 指針指向讀取下一個(gè)字節(jié)的下標(biāo)位置,內(nèi)部還維護(hù)了一個(gè)count 屬性,代表能夠讀取 count 個(gè)字節(jié)。

bytearrayinputstream

“必須保證 pos 嚴(yán)格小于 count,而 count 嚴(yán)格小于 buf.length 時(shí),才能夠從緩沖區(qū)中讀取數(shù)據(jù)”

(2)FileInputStream

文件輸入流,從文件中讀入字節(jié),通常對文件的拷貝、移動等操作,可以使用該輸入流把文件的字節(jié)讀入內(nèi)存中,然后再利用輸出流輸出到指定的位置上。

(3)PipedInputStream

管道輸入流,它與 PipedOutputStream 成對出現(xiàn),可以實(shí)現(xiàn)多線程中的管道通信。PipedOutputStream 中指定與特定的 PipedInputStream 連接,PipedInputStream 也需要指定特定的 PipedOutputStream 連接,之后輸出流不斷地往輸入流的 buffer 緩沖區(qū)寫數(shù)據(jù),而輸入流可以從緩沖區(qū)中讀取數(shù)據(jù)。

(4)ObjectInputStream

對象輸入流,用于對象的反序列化,將讀入的字節(jié)數(shù)據(jù)反序列化為一個(gè)對象,實(shí)現(xiàn)對象的持久化存儲。

(5)PushBackInputStream

它是 FilterInputStream 的子類,是一個(gè)處理流,它內(nèi)部維護(hù)了一個(gè)緩沖數(shù)組buf。

  • 在讀入字節(jié)的過程中可以將讀取到的字節(jié)數(shù)據(jù)回退給緩沖區(qū)中保存,下次可以再次從緩沖區(qū)中讀出該字節(jié)數(shù)據(jù)。所以PushBackInputStream 允許多次讀取輸入流的字節(jié)數(shù)據(jù),只要將讀到的字節(jié)放回緩沖區(qū)即可。

需要注意的是如果回推字節(jié)時(shí),如果緩沖區(qū)已滿,會拋出 IOException異常。

它的應(yīng)用場景:對數(shù)據(jù)進(jìn)行分類規(guī)整。

假如一個(gè)文件中存儲了數(shù)字和字母兩種類型的數(shù)據(jù),我們需要將它們交給兩種線程各自去收集自己負(fù)責(zé)的數(shù)據(jù),如果采用傳統(tǒng)的做法,把所有的數(shù)據(jù)全部讀入內(nèi)存中,再將數(shù)據(jù)進(jìn)行分離,面對大文件的情況下,例如1G、2G,傳統(tǒng)的輸入流在讀入數(shù)組后,由于沒有緩沖區(qū),只能對數(shù)據(jù)進(jìn)行拋棄,這樣每個(gè)線程都要讀一遍文件。

使用 PushBackInputStream 可以讓一個(gè)專門的線程讀取文件,喚醒不同的線程讀取字符:

  • 第一次讀取緩沖區(qū)的數(shù)據(jù),判斷該數(shù)據(jù)由哪些線程讀取
  • 回退數(shù)據(jù),喚醒對應(yīng)的線程讀取數(shù)據(jù)
  • 重復(fù)前兩步
  • 關(guān)閉輸入流

到這里,你是否會想到 AQS 的 Condition 等待隊(duì)列,多個(gè)線程可以在不同的條件上等待被喚醒。

(6)BufferedInputStream

緩沖流,它是一種處理流,對節(jié)點(diǎn)流進(jìn)行封裝并增強(qiáng),其內(nèi)部擁有一個(gè) buffer 緩沖區(qū),用于緩存所有讀入的字節(jié),當(dāng)緩沖區(qū)滿時(shí),才會將所有字節(jié)發(fā)送給客戶端讀取,而不是每次都只發(fā)送一部分?jǐn)?shù)據(jù),提高了效率。

(7)DataInputStream

數(shù)據(jù)輸入流,它同樣是一種處理流,對節(jié)點(diǎn)流進(jìn)行封裝后,能夠在內(nèi)部對讀入的字節(jié)轉(zhuǎn)換為對應(yīng)的 Java 基本數(shù)據(jù)類型。

(8)SequenceInputStream

將兩個(gè)或多個(gè)輸入流看作是一個(gè)輸入流依次讀取,該類的存在與否并不影響整個(gè) IO 生態(tài),在程序中也能夠做到這種效果

(9)StringBufferInputStream

將字符串中每個(gè)字符的低 8 位轉(zhuǎn)換為字節(jié)讀入到字節(jié)數(shù)組中,目前已過期

InputStream 總結(jié):

  • InputStream 是所有輸入字節(jié)流的抽象基類
  • ByteArrayInputStream 和 FileInputStream 是兩種基本的節(jié)點(diǎn)流,他們分別從字節(jié)數(shù)組 和 本地文件中讀取數(shù)據(jù)
  • DataInputStream、BufferedInputStream 和 PushBackInputStream 都是處理流,對基本的節(jié)點(diǎn)流進(jìn)行封裝并增強(qiáng)
  • PipiedInputStream 用于多線程通信,可以與其它線程公用一個(gè)管道,讀取管道中的數(shù)據(jù)。
  • ObjectInputStream 用于對象的反序列化,將對象的字節(jié)數(shù)據(jù)讀入內(nèi)存中,通過該流對象可以將字節(jié)數(shù)據(jù)轉(zhuǎn)換成對應(yīng)的對象

OutputStream

OutputStream 是字節(jié)輸出流的抽象基類,提供了通用的寫方法,讓繼承的子類重寫和復(fù)用。

方法功能
public abstract void write(int b)將指定的字節(jié)寫出到輸出流,寫入的字節(jié)是參數(shù) b 的低 8 位
public void write(byte b[])將指定字節(jié)數(shù)組中的所有字節(jié)寫入到輸出流當(dāng)中
public void write(byte b[], int off, int len)指定寫入的起始位置 offer,字節(jié)數(shù)為 len 的字節(jié)數(shù)組寫入到輸出流當(dāng)中
public void flush()刷新此輸出流,并強(qiáng)制寫出所有緩沖的輸出字節(jié)到指定位置,每次寫完都要調(diào)用
public void close()關(guān)閉此輸出流并釋放與此流關(guān)聯(lián)的所有系統(tǒng)資源

OutputStream 中大多數(shù)的類和 InputStream 是對應(yīng)的,只不過數(shù)據(jù)的流向不同而已。從上面的圖可以看出:

  • OutputStream 是所有輸出字節(jié)流的抽象基類
  • ByteArrayOutputStream 和 FileOutputStream 是兩種基本的節(jié)點(diǎn)流,它們分別向字節(jié)數(shù)組和本地文件寫出數(shù)據(jù)
  • DataOutputStream、BufferedOutputStream 是處理流,前者可以將字節(jié)數(shù)據(jù)轉(zhuǎn)換成基本數(shù)據(jù)類型寫出到文件中;后者是緩沖字節(jié)數(shù)組,只有在緩沖區(qū)滿時(shí),才會將所有的字節(jié)寫出到目的地,減少了 IO 次數(shù)。
  • PipedOutputStream 用于多線程通信,可以和其它線程共用一個(gè)管道,向管道中寫入數(shù)據(jù)
  • ObjectOutputStream 用于對象的序列化,將對象轉(zhuǎn)換成字節(jié)數(shù)組后,將所有的字節(jié)都寫入到指定位置中
  • PrintStream 在 OutputStream 基礎(chǔ)之上提供了增強(qiáng)的功能,即可以方便地輸出各種類型的數(shù)據(jù)(而不僅限于byte型)的格式化表示形式,且 PrintStream 的方法從不拋出 IOEception,其原理是寫出時(shí)將各個(gè)數(shù)據(jù)類型的數(shù)據(jù)統(tǒng)一轉(zhuǎn)換為 String 類型,我會在講解完

字符流對象

字符流對象也會有對應(yīng)關(guān)系,大多數(shù)的類可以認(rèn)為是操作的數(shù)據(jù)從字節(jié)數(shù)組變?yōu)樽址?,類的功能和字?jié)流對象是相似的。

“字符輸入流和字節(jié)輸入流的組成非常相似,字符輸入流是對字節(jié)輸入流的一層轉(zhuǎn)換,所有文件的存儲都是字節(jié)的存儲,在磁盤上保留的不是文件的字符,而是先把字符編碼成字節(jié),再保存到文件中。在讀取文件時(shí),讀入的也是一個(gè)一個(gè)字節(jié)組成的字節(jié)序列,而 Java 虛擬機(jī)通過將字節(jié)序列,按照2個(gè)字節(jié)為單位轉(zhuǎn)換為 Unicode 字符,實(shí)現(xiàn)字節(jié)到字符的映射。”

Reader

Reader 是字符輸入流的抽象基類,它內(nèi)部的重要方法如下所示。

重要方法方法功能
public int read(java.nio.CharBuffer target)將讀入的字符存入指定的字符緩沖區(qū)中
public int read()讀取一個(gè)字符
public int read(char cbuf[])讀入字符放入整個(gè)字符數(shù)組中
abstract public int read(char cbuf[], int off, int len)將字符讀入字符數(shù)組中的指定范圍中

還有其它一些額外的方法,與字節(jié)輸入流基類提供的方法是相同的,只是作用的對象不再是字節(jié),而是字符。

  • Reader 是所有字符輸入流的抽象基類
  • CharArrayReader 和 StringReader 是兩種基本的節(jié)點(diǎn)流,它們分別從讀取 字符數(shù)組和 字符串 數(shù)據(jù),StringReader 內(nèi)部是一個(gè) String 變量值,通過遍歷該變量的字符,實(shí)現(xiàn)讀取字符串,本質(zhì)上也是在讀取字符數(shù)組
  • PipedReader 用于多線程中的通信,從共用地管道中讀取字符數(shù)據(jù)
  • BufferedReader 是字符輸入緩沖流,將讀入的數(shù)據(jù)放入字符緩沖區(qū)中,實(shí)現(xiàn)高效地讀取字符
  • InputStreamReader 是一種轉(zhuǎn)換流,可以實(shí)現(xiàn)從字節(jié)流轉(zhuǎn)換為字符流,將字節(jié)數(shù)據(jù)轉(zhuǎn)換為字符

Writer

Reader 是字符輸出流的抽象基類,它內(nèi)部的重要方法如下所示。

重要方法方法功能
public void write(char cbuf[])將 cbuf 字符數(shù)組寫出到輸出流
abstract public void write(char cbuf[], int off, int len)將指定范圍的 cbuf 字符數(shù)組寫出到輸出流
public void write(String str)將字符串 str 寫出到輸出流,str 內(nèi)部也是字符數(shù)組
public void write(String str, int off, int len)將字符串 str 的某一部分寫出到輸出流
abstract public void flush()刷新,如果數(shù)據(jù)保存在緩沖區(qū),調(diào)用該方法才會真正寫出到指定位置
abstract public void close()關(guān)閉流對象,每次 IO 執(zhí)行完畢后都需要關(guān)閉流對象,釋放系統(tǒng)資源

  • Writer 是所有的輸出字符流的抽象基類
  • CharArrayWriter、StringWriter 是兩種基本的節(jié)點(diǎn)流,它們分別向Char 數(shù)組、字符串中寫入數(shù)據(jù)。StringWriter 內(nèi)部保存了 StringBuffer 對象,可以實(shí)現(xiàn)字符串的動態(tài)增長
  • PipedWriter 可以向共用的管道中寫入字符數(shù)據(jù),給其它線程讀取。
  • BufferedWriter 是緩沖輸出流,可以將寫出的數(shù)據(jù)緩存起來,緩沖區(qū)滿時(shí)再調(diào)用 flush() 寫出數(shù)據(jù),減少 IO 次數(shù)。
  • PrintWriter 和 PrintStream 類似,功能和使用也非常相似,只是寫出的數(shù)據(jù)是字符而不是字節(jié)。
  • OutputStreamWriter 將字符流轉(zhuǎn)換為字節(jié)流,將字符寫出到指定位置

字節(jié)流與字符流的轉(zhuǎn)換

從任何地方把數(shù)據(jù)讀入到內(nèi)存都是先以字節(jié)流形式讀取,即使是使用字符流去讀取數(shù)據(jù),依然成立,因?yàn)閿?shù)據(jù)永遠(yuǎn)是以字節(jié)的形式存在于互聯(lián)網(wǎng)和硬件設(shè)備中,字符流是通過字符集的映射,才能夠?qū)⒆止?jié)轉(zhuǎn)換為字符。

所以 Java 提供了兩種轉(zhuǎn)換流:

  • InputStreamReader:從字節(jié)流轉(zhuǎn)換為字符流,將字節(jié)數(shù)據(jù)轉(zhuǎn)換為字符數(shù)據(jù)讀入到內(nèi)存
  • OutputStreamWriter:從字符流轉(zhuǎn)換為字節(jié)流,將字符數(shù)據(jù)轉(zhuǎn)換為字節(jié)數(shù)據(jù)寫出到指定位置

了解了 Java 傳統(tǒng)的 BIO 中字符流和字節(jié)流的主要成員之后,至少要掌握以下兩個(gè)關(guān)鍵點(diǎn):

(1)傳統(tǒng)的 BIO 是以流為基本單位處理數(shù)據(jù)的,想象成水流,一點(diǎn)點(diǎn)地傳輸字節(jié)數(shù)據(jù),IO 流傳輸?shù)倪^程永遠(yuǎn)是以字節(jié)形式傳輸。

(2)字節(jié)流和字符流的區(qū)別在于操作的數(shù)據(jù)單位不相同,字符流是通過將字節(jié)數(shù)據(jù)通過字符集映射成對應(yīng)的字符,字符流本質(zhì)上也是字節(jié)流。

接下來我們再繼續(xù)學(xué)習(xí) NIO 知識,NIO 是當(dāng)下非?;馃岬囊环N IO 工作方式,它能夠解決傳統(tǒng) BIO 的痛點(diǎn):阻塞。

  • BIO 如果遇到 IO 阻塞時(shí),線程將會被掛起,直到 IO 完成后才喚醒線程,線程切換帶來了額外的開銷。
  • BIO 中每個(gè) IO 都需要有對應(yīng)的一個(gè)線程去專門處理該次 IO 請求,會讓服務(wù)器的壓力迅速提高。

我們希望做到的是當(dāng)線程等待 IO 完成時(shí)能夠去完成其它事情,當(dāng) IO 完成時(shí)線程可以回來繼續(xù)處理 IO 相關(guān)操作,不必干干的坐等 IO 完成。在 IO 處理的過程中,能夠有一個(gè)專門的線程負(fù)責(zé)監(jiān)聽這些 IO 操作,通知服務(wù)器該如何操作。所以,我們聊到 IO,不得不去接觸 NIO 這一塊硬骨頭。

新潮的 NIO

我們來看看 BIO 和 NIO 的區(qū)別,BIO 是面向流的 IO,它建立的通道都是單向的,所以輸入和輸出流的通道不相同,必須建立2個(gè)通道,通道內(nèi)的都是傳輸==0101001···==的字節(jié)數(shù)據(jù)。

而在 NIO 中,不再是面向流的 IO 了,而是面向緩沖區(qū),它會建立一個(gè)通道(Channel),該通道我們可以理解為鐵路,該鐵路上可以運(yùn)輸各種貨物,而通道上會有一個(gè)緩沖區(qū)(Buffer)用于存儲真正的數(shù)據(jù),緩沖區(qū)我們可以理解為一輛火車。

通道(鐵路)只是作為運(yùn)輸數(shù)據(jù)的一個(gè)連接資源,而真正存儲數(shù)據(jù)的是緩沖區(qū)(火車)。即通道負(fù)責(zé)傳輸,緩沖區(qū)負(fù)責(zé)存儲。

理解了上面的圖之后,BIO 和 NIO 的主要區(qū)別就可以用下面這個(gè)表格簡單概括。

BIONIO
面向流(Stream)面向緩沖區(qū)(Buffer)
單向通道雙向通道
阻塞 IO非阻塞 IO
 選擇器(Selectors)

緩沖區(qū)(Buffer)

緩沖區(qū)是存儲數(shù)據(jù)的區(qū)域,在 Java 中,緩沖區(qū)就是數(shù)組,為了可以操作不同數(shù)據(jù)類型的數(shù)據(jù),Java 提供了許多不同類型的緩沖區(qū),除了布爾類型以外,其它基本數(shù)據(jù)類型都有對應(yīng)的緩沖區(qū)數(shù)組對象。

“為什么沒有布爾類型的緩沖區(qū)呢?

在 Java 中,boolean 類型數(shù)據(jù)只占用 1 bit,而在 IO 傳輸過程中,都是以字節(jié)為單位進(jìn)行傳輸?shù)模?boolean 的 1 bit 完全可以使用 byte 類型的某一位,或者 int 類型的某一位來表示,沒有必要為了這 1 bit 而專門提供多一個(gè)緩沖區(qū)?!?/p>

緩沖區(qū)解釋
ByteBuffer存儲字節(jié)數(shù)據(jù)的緩沖區(qū)
CharBuffer存儲字符數(shù)據(jù)的緩沖區(qū)
ShortBuffer存儲短整型數(shù)據(jù)的緩沖區(qū)
IntBuffer存儲整型數(shù)據(jù)的緩沖區(qū)
LongBuffer存儲長整型數(shù)據(jù)的緩沖區(qū)
FloatBuffer存儲單精度浮點(diǎn)型數(shù)據(jù)的緩沖區(qū)
DoubleBuffer存儲雙精度浮點(diǎn)型數(shù)據(jù)的緩沖區(qū)

分配一個(gè)緩沖區(qū)的方式都高度一致:使用allocate(int capacity)方法。

例如需要分配一個(gè) 1024 大小的字節(jié)數(shù)組,代碼就是下面這樣子。

 
 
 
 
  1. ByteBuffer byteBuffer = ByteBuffer.allocate(1024); 

緩沖區(qū)讀寫數(shù)據(jù)的兩個(gè)核心方法:

  • put():將數(shù)據(jù)寫入到緩沖區(qū)中
  • get():從緩沖區(qū)中讀取數(shù)據(jù)

緩沖區(qū)的重要屬性:

  • capacity:緩沖區(qū)中最大存儲數(shù)據(jù)的容量,一旦聲明則無法改變
  • limit:表示緩沖區(qū)中可以操作數(shù)據(jù)的大小,limit 之后的數(shù)據(jù)無法進(jìn)行讀寫。必須滿足 limit <= capacity
  • position:當(dāng)前緩沖區(qū)中正在操作數(shù)據(jù)的下標(biāo)位置,必須滿足 position <= limit
  • mark:標(biāo)記位置,調(diào)用 reset() 將 position 位置調(diào)整到 mark 屬性指向的下標(biāo)位置,實(shí)現(xiàn)多次讀取數(shù)據(jù)

緩沖區(qū)為高效讀寫數(shù)據(jù)而提供的其它輔助方法:

  • flip():可以實(shí)現(xiàn)讀寫模式的切換,我們可以看看里面的源碼

 
 
 
 
  1. public final Buffer flip() { 
  2.     limit = position; 
  3.     position = 0; 
  4.     mark = -1; 
  5.     return this; 

調(diào)用 flip() 會將可操作的大小 limit 設(shè)置為當(dāng)前寫的位置,操作數(shù)據(jù)的起始位置 position 設(shè)置為 0,即從頭開始讀取數(shù)據(jù)。

  • rewind():可以將 position 位置設(shè)置為 0,再次讀取緩沖區(qū)中的數(shù)據(jù)
  • clear():清空整個(gè)緩沖區(qū),它會將 position 設(shè)置為 0,limit 設(shè)置為 capacity,可以寫整個(gè)緩沖區(qū)

“更多的方法可以去查閱 API 文檔,本文礙于篇幅原因就不貼出其它方法了,主要是要理解緩沖區(qū)的作用”

我們來看一個(gè)簡單的例子

 
 
 
 
  1. public Class Main { 
  2.     public static void main(String[] args) { 
  3.          // 分配內(nèi)存大小為11的整型緩存區(qū) 
  4.         IntBuffer buffer = IntBuffer.allocate(11); 
  5.         // 往buffer里寫入2個(gè)整型數(shù)據(jù) 
  6.         for (int i = 0; i < 2; ++i) { 
  7.             int randomNum = new SecureRandom().nextInt(); 
  8.             buffer.put(randomNum); 
  9.         } 
  10.         // 將Buffer從寫模式切換到讀模式 
  11.         buffer.flip(); 
  12.         System.out.println("position >> " + buffer.position() 
  13.                            + "limit >> " + buffer.limit()  
  14.                            + "capacity >> " + buffer.capacity()); 
  15.         // 讀取buffer里的數(shù)據(jù) 
  16.         while (buffer.hasRemaining()) { 
  17.             System.out.println(buffer.get()); 
  18.         } 
  19.         System.out.println("position >> " + buffer.position() 
  20.                            + "limit >> " + buffer.limit()  
  21.                            + "capacity >> " + buffer.capacity()); 
  22.     } 

執(zhí)行結(jié)果如下圖所示,首先我們往緩沖區(qū)中寫入 2 個(gè)數(shù)據(jù),position 在寫模式下指向下標(biāo) 2,然后調(diào)用 flip() 方法切換為讀模式,limit 指向下標(biāo) 2,position 從 0 開始讀數(shù)據(jù),讀到下標(biāo)為 2 時(shí)發(fā)現(xiàn)到達(dá) limit 位置,不可繼續(xù)讀。

整個(gè)過程可以用下圖來理解,調(diào)用 flip() 方法以后,讀出數(shù)據(jù)的同時(shí) position 指針不斷往后挪動,到達(dá) limit 指針的位置時(shí),該次讀取操作結(jié)束。

“介紹完緩沖區(qū)后,我們知道它是存儲數(shù)據(jù)的空間,進(jìn)程可以將緩沖區(qū)中的數(shù)據(jù)讀取出來,也可以寫入新的數(shù)據(jù)到緩沖區(qū),那緩沖
本文名稱:為什么一個(gè)還沒畢業(yè)的大學(xué)生能夠把 IO 講的這么好?
網(wǎng)站鏈接:http://www.dlmjj.cn/article/djeophg.html