日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java NIO

發(fā)布時(shí)間:2025/4/16 java 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java NIO 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、Java I/O系統(tǒng)

Java I/O系統(tǒng)從流結(jié)構(gòu)上可分為字節(jié)流(以字節(jié)為處理單位或稱(chēng)面向字節(jié))和字符流(以字符為處理單位或稱(chēng)面向字符)。

字節(jié)流的輸人流和輸出流基礎(chǔ)是InputStream和OutputStream這兩個(gè)抽象類(lèi),字節(jié)流的輸人輸出操作由這兩個(gè)類(lèi)的子類(lèi)實(shí)現(xiàn)。字符流是Java1.1版后新增加的以字符為單位進(jìn)行輸人輸出處理的流,字符流輸入輸出的基礎(chǔ)是抽象類(lèi)Reader和Writer。但是最底層都是字節(jié)流,字符流的出現(xiàn)有助于我們對(duì)文件的讀取,例如按行讀取之類(lèi)的。

流又分為節(jié)點(diǎn)流和過(guò)濾流:
節(jié)點(diǎn)流:從特定的地方讀寫(xiě)的流類(lèi),例如:磁盤(pán)或一塊內(nèi)存區(qū)域或者鍵盤(pán)的輸入。
過(guò)濾流:使用節(jié)點(diǎn)流作為輸人或輸出。過(guò)濾流是使用一個(gè)已經(jīng)存在的輸入流或輸出流(可以是節(jié)點(diǎn)流也可以是過(guò)濾流)連接創(chuàng)建的。
如下圖所示:


以數(shù)據(jù)寫(xiě)入讀取為例,我們可以以以下方式構(gòu)建流的鏈:

以下是字符流的輸入輸出的繼承關(guān)系:

以上就是傳統(tǒng)的JAVA I/O系統(tǒng)。

二、初步了解Java NIO

java io的核心概念是流,面向流的編程。
java nio中有3個(gè)核心概念:Selector、Channel、Buffer,面向塊編程。

他們的關(guān)系如下圖所示,每個(gè)Channel里又有Buffer。Selector可以選擇連接到哪個(gè)Channel,從而從中讀取寫(xiě)入數(shù)據(jù)。我們的數(shù)據(jù)交換通過(guò)Buffer來(lái)進(jìn)行,所以可以使用Buffer來(lái)讀也可以寫(xiě)。所有數(shù)據(jù)都是通過(guò)Buffer來(lái)進(jìn)行的。我們也可以單獨(dú)使用Channle和Buffer而不使用Selector,如下面的文件讀取示例,關(guān)于Selector和Channle還有Buffer的結(jié)合使用,在講Selector的時(shí)候會(huì)給出例子。流是單向的,而Channel是雙向的。此外,Java中的8中原生數(shù)據(jù)類(lèi)型除了boolean都有對(duì)應(yīng)的Buffer類(lèi)型,IntBuffer、LongBuffer等等。

下面是使用NIO的一個(gè)例子:

package com.chester.netty.nio;import java.io.FileInputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel;public class NioTest1 {public static void main(String[] args) throws Exception{FileInputStream fileInputStream = new FileInputStream("Hello.txt");FileChannel fileChannel = fileInputStream.getChannel();ByteBuffer byteBuffer = ByteBuffer.allocate(10);fileChannel.read(byteBuffer);byteBuffer.flip();while(byteBuffer.remaining() > 0){byte b = byteBuffer.get();System.out.println("Character: " + (char)b);}fileInputStream.close();} }

三、Java NIO中的Buffer詳解

Buffer有三個(gè)重要的狀態(tài)屬性:position、limit、capacity;

位置(position):當(dāng)前緩沖區(qū)(Buffer)的位置,將從該位置往后讀或?qū)憯?shù)據(jù)。
容量(capacity):緩沖區(qū)的總?cè)萘可舷?#xff08;不會(huì)改變)。
上限(limit):緩沖區(qū)的實(shí)際容量大小。

我們結(jié)合下圖來(lái)理解:

首先,在寫(xiě)模式中。
position最開(kāi)始指向0的位置,limit和capacity都指向最后一個(gè)位置,然后每寫(xiě)一個(gè)position就向后移動(dòng)一個(gè)。limit就像字面意思一樣,最多寫(xiě)到limit指向的位置。

我們調(diào)用 byteBuffer.flip();轉(zhuǎn)換到讀模式(也就是 limit = position; position = 0;)

寫(xiě)模式中各個(gè)位置如上圖所示,position指向0的位置,limit指向之前寫(xiě)到的最大的位置,capacity不變。每讀一個(gè)position就向后移動(dòng)一個(gè),limit是最多能讀到的位置。

如果查看NIO的Buffer API文檔,你會(huì)發(fā)現(xiàn)Buffer的很多操作都是圍繞這三個(gè)值來(lái)進(jìn)行的。比如上面的NIO例子代碼中有:byteBuffer.remaining(),它就是返回當(dāng)前位置和限制之間的元素?cái)?shù), 即limit減position的值;byteBuffer.get()會(huì)增加一個(gè)position的值。

更多內(nèi)容可以查看JAVA API文檔。查看Buffer的源代碼也是一個(gè)不錯(cuò)的選擇,例如你可以查看到在Buffer中的flip方法:

public final Buffer flip() {limit = position;position = 0;mark = -1;return this;}

代碼中很直白的告訴里你將會(huì)對(duì)上面所說(shuō)的值做哪些操作。

現(xiàn)在我們關(guān)注一下Buffer申請(qǐng)內(nèi)存的代碼。即:ByteBuffer.allocate(10)。實(shí)際上分析它還是比較容易的,我們直接在IDEA中按住Ctrl點(diǎn)擊這方法來(lái)到實(shí)現(xiàn):

public static ByteBuffer allocate(int capacity) {if (capacity < 0)throw new IllegalArgumentException();return new HeapByteBuffer(capacity, capacity); }

再點(diǎn)擊HeapByteBuffer

HeapByteBuffer(int cap, int lim) { // package-privatesuper(-1, 0, lim, cap, new byte[cap], 0);/*hb = new byte[cap];offset = 0;*/ }

點(diǎn)擊super,注意這里的new byte[cap]參數(shù)。

ByteBuffer(int mark, int pos, int lim, int cap, // package-privatebyte[] hb, int offset) {super(mark, pos, lim, cap);this.hb = hb;this.offset = offset; }

最后可以看到,this.hb = hb,結(jié)合new byte[cap]參數(shù),也就是直接在堆上申請(qǐng)一塊內(nèi)存,作為Buffer對(duì)象具體存放數(shù)據(jù)的空間。實(shí)際上也比較符合HeapByteBuffer這個(gè)名字。在ByteBuffer中還有一個(gè)申請(qǐng)內(nèi)存來(lái)存放數(shù)據(jù)的方法:allocateDirect;要理解這個(gè)方法,就必須對(duì)JVM的內(nèi)存模型有一些了解,我們知道JVM中有一個(gè)堆外內(nèi)存的概念。ByteBuffer中使用allocateDirect申請(qǐng)內(nèi)存就是在堆外內(nèi)存中申請(qǐng)的。在使用allocateDirect申請(qǐng)的Buffer對(duì)象中有一個(gè)address變量指向這個(gè)堆外內(nèi)存的地址。

為什么不直接使用堆上的內(nèi)存呢?實(shí)際上是出于對(duì)效率的考慮,在Java堆上的內(nèi)存,操作系統(tǒng)不會(huì)直接使用(注意是不會(huì)而不是不能),而是在操作系統(tǒng)內(nèi)存中又申請(qǐng)一塊空間。JAVA堆上的數(shù)據(jù)還要拷貝到操作系統(tǒng)申請(qǐng)的空間上,操作系統(tǒng)再和IO設(shè)備進(jìn)行交互,而使用allocateDirect,將省略這樣一次拷貝,也就是0拷貝的概念。

這里做一個(gè)擴(kuò)展,為什么操作系統(tǒng)不直接使用JAVA堆上的數(shù)據(jù)來(lái)和IO設(shè)備交互呢?這里就需要對(duì)GC有一個(gè)理解了,GC的過(guò)程中會(huì)改變對(duì)象的內(nèi)存位置。而在和IO設(shè)備交互的時(shí)候顯然這個(gè)地址不能改變,JVM就必須保證不會(huì)進(jìn)行GC,和IO設(shè)備交互是比較耗費(fèi)時(shí)間的,而長(zhǎng)時(shí)間不進(jìn)行GC的話必然導(dǎo)致堆的溢出,所以我們可以先拷貝到操作系統(tǒng)的內(nèi)存中,而后這塊內(nèi)存再和IO設(shè)備交互。因?yàn)榭截惐戎苯雍虸O設(shè)備交互要快很多,而短時(shí)間的停止GC是可以接受的。

下面說(shuō)說(shuō)一個(gè)常用的概念。

  • 內(nèi)存映射文件

內(nèi)存映射文件能讓你創(chuàng)建和修改那些因?yàn)樘蠖鵁o(wú)法放入內(nèi)存的文件。有了內(nèi)存映射文件,你就可以認(rèn)為文件已經(jīng)全部讀進(jìn)了內(nèi)存,然后把它當(dāng)成一個(gè)非常大的數(shù)組來(lái)訪問(wèn)。這種解決辦法能大大簡(jiǎn)化修改文件的代碼。直接修改內(nèi)存數(shù)據(jù),就會(huì)修改文件數(shù)據(jù),而不用像通常一樣需要文件讀寫(xiě),加快了文件訪問(wèn)的速度。內(nèi)存映射文件的實(shí)現(xiàn)是操作系統(tǒng)來(lái)維護(hù)的。在Nio中可以使用ByteBuffer的子類(lèi)MappedByteBuffer來(lái)進(jìn)行相應(yīng)的操作,具體做法可以參考:https://blog.csdn.net/akon_vm/article/details/7429245

四、Java NIO中的Scattering 和 Gathering (散開(kāi)和聚合)

之前我們的Channel只有一個(gè)Buffer,但是實(shí)際上我們可以將一個(gè)Channel中的數(shù)據(jù)寫(xiě)到多個(gè)Buffer中(Scattering散開(kāi)),或者將多個(gè)Buffer中的數(shù)據(jù)寫(xiě)到一個(gè)Channel中(Gathering聚合)。

在從Channel中讀數(shù)據(jù)寫(xiě)到多個(gè)Buffer中的時(shí)候,先寫(xiě)滿第一個(gè)Buffer。多個(gè)Buffer中的數(shù)據(jù)寫(xiě)到Channel中的時(shí)候,也是先寫(xiě)完第一個(gè)Buffer。具體的操作可以查看下面的實(shí)例代碼。

import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Arrays; /*** Scattering:在將數(shù)據(jù)寫(xiě)入到buffer中時(shí),可以采用buffer數(shù)組,依次寫(xiě)入,一個(gè)buffer滿了就寫(xiě)下一個(gè)。* Gatering:在將數(shù)據(jù)讀出到buffer中時(shí),可以采用buffer數(shù)組,依次讀入,一個(gè)buffer滿了就讀下一個(gè)。*//*** 使用方式:打開(kāi)cmd telnet locakhost 8899* 連接后輸入字符串,在控制臺(tái)會(huì)輸出每個(gè)Buffer信息。*/ public class NioTestServer {public static void main(String[] args) throws IOException {ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();InetSocketAddress address=new InetSocketAddress(8899);serverSocketChannel.socket().bind(address);int messageLength=2+3+5;ByteBuffer[] byteBuffers=new ByteBuffer[3];byteBuffers[0]=ByteBuffer.allocate(2);byteBuffers[1]=ByteBuffer.allocate(3);byteBuffers[2]=ByteBuffer.allocate(5);SocketChannel socketChannel=serverSocketChannel.accept();while (true){int byteRead=0;//接受客戶(hù)端寫(xiě)入的的字符串while(byteRead<messageLength){long r=socketChannel.read(byteBuffers);byteRead+=r;System.out.println("byteRead:"+byteRead);//通過(guò)流打印每個(gè)Buffer的position和limit信息Arrays.asList(byteBuffers).stream().map(buffer -> "position:"+ buffer.position() +",limit:"+buffer.limit()).forEach(System.out::println);}//將所有buffer都flip。Arrays.asList(byteBuffers).forEach(buffer -> {buffer.flip();});//將數(shù)據(jù)讀出回顯到客戶(hù)端long byteWrite=0;while (byteWrite < messageLength) {long r=socketChannel.write(byteBuffers);byteWrite+=r;}//將所有buffer都clearArrays.asList(byteBuffers).forEach(buffer -> {buffer.clear();});System.out.println("byteRead:"+byteRead+",byteWrite:"+byteWrite+",messageLength:"+messageLength);}} }

五、Java NIO中的Selector

在普通的server操作中:

..... while(ture){Socket socket = serverSocket.accept();//阻塞直到有客戶(hù)端連接過(guò)來(lái)socket.getInputSteam();............... } ..... ..... .....

如果有多個(gè)客戶(hù)端,這樣顯然是不合適的,因?yàn)闊o(wú)法即時(shí)響應(yīng)客戶(hù)端。改進(jìn)如下:

..... while(true){Socket socket = serverSocket.accept();//阻塞直到有客戶(hù)端連接過(guò)來(lái)new Thread(socket); } ..... ..... .....

但是對(duì)于多個(gè)客戶(hù)端連接的話,服務(wù)器肯定會(huì)起多個(gè)線程,這對(duì)服務(wù)器來(lái)說(shuō)壓力肯定是非常大的,而且頻繁的線程間切換也會(huì)損耗性能,NIO的出現(xiàn)解決了這個(gè)問(wèn)題,一個(gè)線程可以處理多個(gè)客戶(hù)端事件,具體的工作我們可以再交給具體的任務(wù)線程。Selector的具體操作可以查看這里;下面是一個(gè)例子:

import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set;public class NioTest4Server {public static void main(String[] args) throws IOException {int [] ports=new int[5];ports[0]=5000;ports[1]=5001;ports[2]=5002;ports[3]=5003;ports[4]=5004;//一般創(chuàng)建selector的方法Selector selector=Selector.open();//for循環(huán)用來(lái)將多個(gè)端口地址和通道綁定for (int i=0;i<5;i++){//打開(kāi)ServerSocketChannel通道ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();//通道必須配置成非阻塞狀態(tài),FileChannel是無(wú)法配置成非阻塞狀態(tài)的,所以它不能使用下面的訪問(wèn)方式。serverSocketChannel.configureBlocking(false);//通過(guò)ServerSocketChannel的socket()方法獲得serverSocket對(duì)象。ServerSocket serverSocket=serverSocketChannel.socket();//將每一個(gè)serverSocket和端口號(hào)綁定InetSocketAddress address=new InetSocketAddress(ports[i]);serverSocket.bind(address);// 將channel注冊(cè)到selector上,只對(duì)感興趣的事件監(jiān)聽(tīng)serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);/*** 通道觸發(fā)了一個(gè)事件意思是該事件已經(jīng)就緒。四種事件,用四種key表示。* SelectionKey.OP_CONNECT:某個(gè)channel成功連接到另一個(gè)服務(wù)器稱(chēng)為“連接就緒”。* SelectionKey.OP_ACCEPT:一個(gè)server socket channel準(zhǔn)備好接收新進(jìn)入的連接稱(chēng)為“接收就緒”。* SelectionKey.OP_READ:一個(gè)有數(shù)據(jù)可讀的通道可以說(shuō)是“讀就緒”。* SelectionKey.OP_WRITE:等待寫(xiě)數(shù)據(jù)的通道可以說(shuō)是“寫(xiě)就緒”。*/System.out.println("監(jiān)聽(tīng)端口為:"+ports[i]);}/*** 一個(gè)Selector對(duì)象會(huì)包含3種類(lèi)型的SelectionKey集合:** all-keys:當(dāng)前所有向Selector注冊(cè)的SelectionKey的集合,Selector的keys()方法返回該集合** 當(dāng)register()方法執(zhí)行時(shí),新建一個(gè)SelectioKey,并把它加入Selector的all-keys集合中。** selected-keys:相關(guān)事件已經(jīng)被Selector捕獲的SelectionKey的集合,Selector的selectedKeys()方法返回該集合** 在執(zhí)行Selector的select()方法時(shí),如果與SelectionKey相關(guān)的事件發(fā)生了,* 這個(gè)SelectionKey就被加入到selected-keys集合中,程序直接調(diào)用selected-keys集合的remove()方法,* 或者調(diào)用它的iterator的remove()方法,都可以從selected-keys集合中刪除一個(gè)SelectionKey對(duì)象。** cancelled-keys:已經(jīng)被取消的SelectionKey的集合,Selector沒(méi)有提供訪問(wèn)這種集合的方法** 如果關(guān)閉了與SelectionKey對(duì)象關(guān)聯(lián)的Channel對(duì)象,或者調(diào)用了SelectionKey對(duì)象的cancel方法,* 這個(gè)SelectionKey對(duì)象就會(huì)被加入到cancelled-keys集合中,表示這個(gè)SelectionKey對(duì)象已經(jīng)被取消。*/while(true){//阻塞,直到有事件發(fā)送int keyNumbers=selector.select();System.out.println("返回key的數(shù)量:"+ keyNumbers);//獲得所有SelectionKey,因?yàn)橥粫r(shí)間可能連接多個(gè)channel,從而產(chǎn)生多個(gè)SelectionKeySet<SelectionKey> selectionKeys=selector.selectedKeys();//迭代所有已經(jīng)獲得的SelectedkeyIterator<SelectionKey> iterator= selectionKeys.iterator();//迭代selectionKeyswhile (iterator.hasNext()){SelectionKey selectionKey=iterator.next();if (selectionKey.isAcceptable()){//通過(guò)key來(lái)獲得發(fā)送事件的通道ServerSocketChannel serverSocketChannel= (ServerSocketChannel) selectionKey.channel();//如果客戶(hù)端連接,獲得客戶(hù)端channelSocketChannel socketChannel=serverSocketChannel.accept();socketChannel.configureBlocking(false);//通過(guò)selector來(lái)監(jiān)聽(tīng)讀事件socketChannel.register(selector, SelectionKey.OP_READ);//如果不移除這個(gè)key,他還存在在Selectedkey集合中,那么下次迭代他還存在iterator.remove();System.out.println("獲取客戶(hù)端連接:"+socketChannel);} else if (selectionKey.isReadable()){SocketChannel socketChannel= (SocketChannel) selectionKey.channel();ByteBuffer byteBuffer=ByteBuffer.allocate(512);while(true){byteBuffer.clear();int read= socketChannel.read(byteBuffer);if (read<=0){break;}byteBuffer.flip();socketChannel.write(byteBuffer);}Charset charset = Charset.forName("utf-8");String massage = String.valueOf(charset.decode(byteBuffer).array());System.out.println("讀取:"+massage+",來(lái)自于"+socketChannel);iterator.remove();}}}} }

以上就是JAVA NIO系統(tǒng)主要內(nèi)容的講解了,Selector是NIO中最重要的內(nèi)容,一定要好好理解。

總結(jié)

以上是生活随笔為你收集整理的Java NIO的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。