Java?NIO?Channel?使用詳情
Java NIO 中的 Channel 分類:
- FileChannel
- SocketChannel
- ServerSocketChannel
- DatagramChannel
FileChannel: 主要用于文件的讀寫,可以從磁盤上讀取文件,也可以向磁盤上寫入文件。
SocketChannel:用于 Socket 的 TCP 連接的數(shù)據(jù)讀寫,既可以從 Channel 讀數(shù)據(jù),也可以向 Channle 中寫入數(shù)據(jù)
ServerSocketChannel:通過 ServerSocketChannel 可以監(jiān)聽 TCP 連接,服務(wù)端監(jiān)聽到連接之后,會(huì)為每個(gè)請(qǐng)求創(chuàng)建一個(gè) SocketChannel
DatagramChannel:用于 UDP 協(xié)議的數(shù)據(jù)讀寫
接下來就分別介紹一下。
FileChannel
主要用于操作文件,廢話不多說,直接看例子。
準(zhǔn)備文件
test-file.txt
,內(nèi)容shDEQuanZhanBiJi
輸入 FileInputStream
用于從 FileChannel 中讀取數(shù)據(jù),例如將指定文件輸入到 FileChannel 中,我們就能獲取到文件的內(nèi)容,接下來編寫 FileChannel 的 輸入流 核心代碼:
public static void main(String[] args) throws IOException { // 創(chuàng)建一個(gè)輸入流 FileInputStream fileInputStream = new FileInputStream("test-file.txt"); // 通過輸入流獲取到 channel FileChannel fileChannel = fileInputStream.getChannel(); // 準(zhǔn)備好 ByteBuffer ByteBuffer buffer = ByteBuffer.allocate(16); // 將 輸入流 的 channel 的數(shù)據(jù)讀入 buffer 中 fileChannel.read(buffer); // 簡單打印 buffer 的內(nèi)容 printBuffer(buffer); // shDEQuanZhanBiJi }
這里面的 ByteBuffer 是 channel 進(jìn)行讀、寫數(shù)據(jù)的中間媒介。要從 channel 中讀取數(shù)據(jù)(也就是上面這個(gè)例子),需要先將數(shù)據(jù)讀到 ByteBuffer 中;同理,要想向 channel 中寫入數(shù)據(jù),也需要先將數(shù)據(jù)寫入 ByteBuffer(下面講輸出流的時(shí)候會(huì)講)。
對(duì) ByteBuffer 不熟悉的可以先看看我之前寫的 《玩轉(zhuǎn) ByteBuffer》,
printBuffer
的代碼里面也有
輸出 FileOutputStream
顧名思義,是 FileChannel 要向外輸出數(shù)據(jù),例如將數(shù)據(jù)寫入到磁盤文件上,接下來通過例子看看效果:
public static void main(String[] args) throws IOException { // 指定需要生成的文件名稱 String generateFileName = "generate-file.txt"; // 創(chuàng)建一個(gè)輸出流 FileOutputStream fileOutputStream = new FileOutputStream(generateFileName); // 通過輸出流獲取到 channel FileChannel fileChannel = fileOutputStream.getChannel(); // 準(zhǔn)備好 ByteBuffer, 并向里面寫入數(shù)據(jù) ByteBuffer buffer = ByteBuffer.allocate(16); buffer.put("shDEQuanZhanBiJi".getBytes(StandardCharsets.UTF_8)); // 將 輸入流 的 channel 的數(shù)據(jù)讀入 buffer 中 fileChannel.write(buffer); fileChannel.close(); }
相應(yīng)的注釋都已經(jīng)貼在對(duì)應(yīng)的代碼上了,細(xì)節(jié)在此不再贅述。唯一需要關(guān)注的是,調(diào)用 write
寫文件到磁盤上時(shí),也是先傳入的 ByteBuffer。
好了,當(dāng)你運(yùn)行完代碼你會(huì)發(fā)現(xiàn),雖然文件是生成的了,但是里面卻是空白的…這其實(shí)就涉及到對(duì) ByteBuffer 的熟悉程度了,算是埋的一個(gè)坑。
如果不知道為啥文件是空的,可以去看看上面講 ByteBuffer 的文章,接下來是解答。
這是因?yàn)槲覀儎?chuàng)建一個(gè) ByteBuffer 的時(shí)候默認(rèn)是處于寫模式的,此時(shí)如果去通過 position 和 limit 去讀取數(shù)據(jù)是讀不到的。所以在調(diào)用 write
之前,我們需要先將 ByteBuffer 切換到讀模式,
完整代碼如下:
public static void main(String[] args) throws IOException { // 指定需要生成的文件名稱 String generateFileName = "generate-file.txt"; // 創(chuàng)建一個(gè)輸出流 FileOutputStream fileOutputStream = new FileOutputStream(generateFileName); // 通過輸出流獲取到 channel FileChannel fileChannel = fileOutputStream.getChannel(); // 準(zhǔn)備好 ByteBuffer, 并向里面寫入數(shù)據(jù) ByteBuffer buffer = ByteBuffer.allocate(16); buffer.put("shDEQuanZhanBiJi".getBytes(StandardCharsets.UTF_8)); // 將 ByteBuffer 切換到讀模式 buffer.flip(); // 將 輸入流 的 channel 的數(shù)據(jù)讀入 buffer 中 fileChannel.write(buffer); fileChannel.close(); }
可以看到,文件生成了,內(nèi)容也有了:
但是呢,上面將的兩種要么只能寫,要么只能讀。例如 FileInputStream
如果你硬要往 channel 里懟數(shù)據(jù),程序最后會(huì)拋出 NonWritableChannelException
異常,告訴你這玩意兒寫不了。
那有沒有一個(gè)既能寫,又能讀還能唱跳的實(shí)現(xiàn)呢?當(dāng)然有,那就是 RandomAccessFile
。
這里提一嘴,調(diào)用完 write 并不是立即就寫入磁盤,也可以在操作系統(tǒng)的緩存里。如果需要立即刷盤,則調(diào)用
channel.force(true);
即可。
RandomAccessFile
怎么用的呢?其實(shí)跟之前兩個(gè)差不多:
public static void main(String[] args) throws IOException { // 指定需要生成的文件名稱 String targetFileName = "target-file.txt"; // 創(chuàng)建 RandomAccessFile, 賦予可讀(r)、可寫(w)的權(quán)限 RandomAccessFile accessFile = new RandomAccessFile(targetFileName, "rw"); FileChannel fileChannel = accessFile.getChannel(); // 創(chuàng)建 ByteBuffer 并寫入數(shù)據(jù) ByteBuffer buffer = ByteBuffer.allocate(16); buffer.put("shDEQuanZhanBiJi".getBytes(StandardCharsets.UTF_8)); // 切換到 buffer 的讀模式 buffer.flip(); // 調(diào)用 write 將 buffer 的數(shù)據(jù)寫入到 channel, channel 再寫數(shù)據(jù)到磁盤文件 fileChannel.write(buffer); // 相當(dāng)于清空 buffer buffer.clear(); // 將之前寫入到 channel 的數(shù)據(jù)再讀入到 buffer fileChannel.read(buffer); // 打印 buffer 中的內(nèi)容 printBuffer(buffer); fileChannel.close(); }
運(yùn)行之后的效果就是,會(huì)生成一個(gè)名為 target-file.txt
的文件,內(nèi)容就是 shDEQuanZhanBiJi
。并且控制臺(tái)會(huì)將之前寫入 channel 的 shDEQuanZhanBiJi
打印出來。
老規(guī)矩,細(xì)節(jié)都在注釋中。值得注意的是 new RandomAccessFile(targetFileName, "rw");
里的 rw
。注釋里也寫了,代表賦予可讀、可寫的權(quán)限。
再值得注意的是,你不能說把 rw
改成 w
。
不能這么玩,因?yàn)樗褪且粋€(gè)單純的字符串匹配,可供選擇的就這么些:
可以看到,r
必不可少…:
r
只能讀rw
既能讀,也能寫rws
和rwd
功能和rw
大致是相同的,可讀、可寫。唯一區(qū)別是他們會(huì)將每次改動(dòng)強(qiáng)制刷到磁盤,并且rws
會(huì)將操作系統(tǒng)對(duì)該文件的元數(shù)據(jù)也一起刷盤,體現(xiàn)就是文件的更新時(shí)間會(huì)更新,而rwd
不會(huì)將文件的元數(shù)據(jù)刷盤
兩個(gè) SocketChannel
由于這倆一個(gè)負(fù)責(zé)連接傳輸,另一個(gè)負(fù)責(zé)連接的監(jiān)聽,所以就放在一起來講了。這一小節(jié)我們大概要做這件事:
但是為了能讓大家直接運(yùn)行起來,客戶端這側(cè)就不從磁盤文件讀取了,直接用 ByteBuffer。大家可以運(yùn)行起來之后,自己嘗試從磁盤上去加載。還是先看代碼,首先是服務(wù)器的:
ServerSocketChannel
public static void main(String[] args) throws IOException { // 打開一個(gè) ServerSocketChannel ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 綁定 8080 端口 serverSocketChannel.bind(new InetSocketAddress(8080)); // 開始接受客戶端連接 SocketChannel socketChannel = serverSocketChannel.accept(); // 獲取連接成功 System.out.printf("socketChannel %s connected\n", socketChannel); // 準(zhǔn)備 ByteBuffer 以從 socketChannel 中讀取數(shù)據(jù) ByteBuffer buffer = ByteBuffer.allocate(16); // 開始讀取數(shù)據(jù) System.out.println("before read"); int read = socketChannel.read(buffer); System.out.printf("read complete, read bytes length: %s \n", read); printBuffer(buffer); }
這里我們使用的是 Java NIO 中默認(rèn)的阻塞模式,僅僅作為一個(gè)掩飾,如果想要 ServerSocketChannel 進(jìn)入非阻塞模式,可在 open
之后,調(diào)用:
serverSocketChannel.configureBlocking(false);
由于我們這里是阻塞模式,所以在代碼運(yùn)行到 serverSocketChannel.accept();
時(shí),會(huì)陷入阻塞狀態(tài),直到有客戶端過來建立連接。同理,read
方法也是阻塞的,如果客戶端一直沒有寫入數(shù)據(jù),那么服務(wù)器就會(huì)一直阻塞在 read
。
SocketChannel
直接先給代碼:
public static void main(String[] args) throws IOException { // 打開一個(gè) SocketChannel SocketChannel socketChannel = SocketChannel.open(); // 連接到 localhost 的 8080 端口 socketChannel.connect(new InetSocketAddress("localhost", 8080)); // 準(zhǔn)備 ByteBuffer ByteBuffer buffer = ByteBuffer.allocate(16); buffer.put(Charset.defaultCharset().encode("test")); // 將 buffer 切換成讀模式 & 向 channel 中寫入數(shù)據(jù) buffer.flip(); socketChannel.write(buffer); }
先啟動(dòng)服務(wù)器,再啟動(dòng)客戶端??梢钥吹椒?wù)器側(cè)的控制臺(tái)有如下的輸出:
socketChannel java.nio.channels.SocketChannel[connected local=/127.0.0.1:8080 remote=/127.0.0.1:64373] connected before read read complete, read bytes length: 4 BUFFER VALUE: test
Datagram
這個(gè)就比較簡單,首先是客戶端的代碼:
public static void main(String[] args) throws IOException { DatagramChannel datagramChannel = DatagramChannel.open(); // 構(gòu)建 buffer 數(shù)據(jù) ByteBuffer buffer = ByteBuffer.allocate(16); buffer.put(Charset.defaultCharset().encode("test")); // 切換到 buffer 的讀模式 buffer.flip(); datagramChannel.send(buffer, new InetSocketAddress("localhost", 8080)); }
然后是服務(wù)器:
public static void main(String[] args) throws IOException { DatagramChannel datagramChannel = DatagramChannel.open(); datagramChannel.bind(new InetSocketAddress(8080)); ByteBuffer buffer = ByteBuffer.allocate(16); datagramChannel.receive(buffer); printBuffer(buffer); }
到此這篇關(guān)于Java NIO Channel 使用詳情的文章就介紹到這了,更多相關(guān)Java NIO Channel內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Web Fragment在項(xiàng)目中使用方法詳解
這篇文章主要介紹了Web Fragment在項(xiàng)目中使用方法詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02深入學(xué)習(xí)java位運(yùn)算的基礎(chǔ)知識(shí)
位運(yùn)算是直接對(duì)整數(shù)在內(nèi)存中的二進(jìn)制位進(jìn)行操作嗎,位運(yùn)算即可以節(jié)約內(nèi)存,同時(shí)使程序速度更快效率更高。文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,下面我們來一起學(xué)習(xí)下吧2019-06-06springboot2啟動(dòng)時(shí)執(zhí)行,初始化(或定時(shí)任務(wù))servletContext問題
這篇文章主要介紹了springboot2啟動(dòng)時(shí)執(zhí)行,初始化(或定時(shí)任務(wù))servletContext問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01mybatis-plus 關(guān)于savebatch,saveorupdatebatch遇到的坑及解決辦法
本文主要介紹了mybatis-plus 關(guān)于savebatch,saveorupdatebatch遇到的坑及解決辦法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01SpringBoot結(jié)合JWT實(shí)現(xiàn)用戶登錄、注冊(cè)、鑒權(quán)
用戶登錄、注冊(cè)及鑒權(quán)是我們基本所有系統(tǒng)必備的,也是很核心重要的一塊,本文主要介紹了SpringBoot結(jié)合JWT實(shí)現(xiàn)用戶登錄、注冊(cè)、鑒權(quán),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2023-05-05