欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java經(jīng)典面試題之NIO多路復(fù)用

 更新時(shí)間:2023年06月01日 10:47:44   作者:肖恩Sean  
JAVA?NIO?的多路復(fù)用是面試中經(jīng)常被問的問題,今天我們徹底搞明白究竟是怎么回事,文中的示例代碼講解詳細(xì),希望對大家學(xué)習(xí)Java有所幫助

JAVA NIO 的多路復(fù)用是面試中經(jīng)常被問的問題,今天我們徹底搞明白究竟是怎么回事,首先我們先通過代碼來實(shí)現(xiàn) JAVA NIO 多路復(fù)用,然后我們會深入多路復(fù)用原理,讓大家面對這類面試能夠回答的很自如。

Java NIO 代碼

我們還是通過一個(gè)有 Server 端和 Client 端的網(wǎng)絡(luò)通信的例子來說明問題,不過這次是用 Java NIO 來實(shí)現(xiàn)。

Server

首先,上 Server 端代碼:

public class NioServer {
    private static ByteBuffer readBuffer;
    private static Selector selector;
    public static void main(String[] args)  throws Exception{
          // 服務(wù)端的初始化
          init();
          listen();
    }
    private static void init(){
        // 讀取請求數(shù)據(jù)的 Buffer
        readBuffer = ByteBuffer.allocate(128);
        ServerSocketChannel serverSocketChannel;
        try{
            // 1.打開服務(wù)端
            serverSocketChannel = ServerSocketChannel.open();
            // 配置為非阻塞IO
            serverSocketChannel.configureBlocking(false);
            // 設(shè)置端口,
            serverSocketChannel.socket().bind(new InetSocketAddress(9000),100);
            // 打開 多路復(fù)用選擇器
            selector = Selector.open();
            // 2.把 serverSocketChannel 注冊到 selector 上。 監(jiān)聽 serverSocketChannel 的連接請求事件,與各個(gè)客戶端建立連接請求
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        }catch (IOException e){
            e.printStackTrace();
        }
    }
    private static void listen(){
        while (true){
            try{
                // 3. selector查看是否有注冊在 selector 上的 Channel 有網(wǎng)絡(luò)事件發(fā)生。這個(gè)方法是阻塞的。
                selector.select();
                Iterator<SelectionKey> keysIterator = selector.selectedKeys().iterator();
                // 4. 輪詢 SelectionKey 集合里的 SelectionKey,一個(gè) SelectionKey 代表一個(gè)網(wǎng)絡(luò)事件。
                while (keysIterator.hasNext()){
                    SelectionKey key = (SelectionKey) keysIterator.next();
                    keysIterator.remove();
                    handleKey(key);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    private static void handleKey(SelectionKey key){
        SocketChannel channel = null;
        // 5. 判斷網(wǎng)絡(luò)事件的類型并做出相應(yīng)的處理
        try {
            // 如果是客戶端要求連接的請求
            if(key.isAcceptable()){
                ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                // 通過 TCP 三次握手,建立和獲取獲取客戶端和服務(wù)器的連接SocketChannel
                channel = serverSocketChannel.accept();
                channel.configureBlocking(false);
                // 注冊網(wǎng)絡(luò)讀事件
                channel.register(selector,SelectionKey.OP_READ);
                //如果是可以網(wǎng)絡(luò)讀的事件
            }else if(key.isReadable()){
                channel = (SocketChannel) key.channel();
                readBuffer.clear();// postion 變?yōu)?,limit = capacity,也就是復(fù)位操作,準(zhǔn)備把數(shù)據(jù)寫入Buffer了
                int count = channel.read(readBuffer); // 通過 Socket 來讀取數(shù)據(jù)并把數(shù)據(jù)寫入 Buffer 中。
                if(count > 0){
                    readBuffer.flip();// 開始從Buffer讀數(shù)據(jù)。
                    String request = StandardCharsets.UTF_8.decode(readBuffer).toString();
                    System.out.println("Server receive the request: "+request);
                    String response = "Server accept request";
                    channel.write(ByteBuffer.wrap(response.getBytes()));
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

給大家講解一下代碼步驟。

1.初始化。首先初始化一個(gè) ServerSocketChannel 對象,然后調(diào)用它的 open() 方法,設(shè)置服務(wù)端服務(wù)的 TCP 端口號為 9000,代表服務(wù)端可以接收外部客戶端的請求了。

2.創(chuàng)建 selector,并把 serverSocketChannel 注冊在 selector 內(nèi),并讓 selector 監(jiān)聽serverSocketChannel 的網(wǎng)絡(luò)連接事件。ServerSocketChannel 只有服務(wù)端會使用,是服務(wù)端與客戶端建立連接用的,我再給大家畫張圖來講解。

這里說明一下 ServerSocketChannel 的工作流程。

1.首先,創(chuàng)建完一個(gè) serverSocketChannel 對象后,我們要把 serverSocketChannel 注冊到 selector上,并對這個(gè) serverSocketChannel 的 OP_ACCEPT 事件進(jìn)行監(jiān)聽,OP_ACCEPT 其實(shí)就是外部的客戶端要連接服務(wù)端的請求連接的事件。

2.當(dāng)客戶端發(fā)起請求后,Seletor 會監(jiān)聽到 OP_ACCEPT 事件,隨后通過三次握手建立連接,這樣客戶端和服務(wù)端之間的連接就建立好了。 很明顯,ServerSocketChannel 是為連接服務(wù)的,所有客戶端要想與服務(wù)端建立連接都要通過 ServerSocketChannel 來建立連接。

3.開始輪詢 selector 上注冊的事件。調(diào)用 selector.select() 方法來查看是否有網(wǎng)絡(luò)事件,這個(gè)方法是阻塞方法,有網(wǎng)絡(luò)事件時(shí)會返回事件的數(shù)量,代碼在一個(gè) while(true) 循環(huán)中,會不斷地輪詢執(zhí)行 selector.select() 方法,所以只要有網(wǎng)絡(luò)事件,事件就會得到處理。

4.輪詢 SelectionKey 集合里的 SelectionKey,一個(gè) SelectionKey 代表一個(gè)網(wǎng)絡(luò)事件。

5.這里的網(wǎng)絡(luò)事件主要有可連接事件和可讀取事件:

  • 如果是 key.isAcceptable() == true,意味著可以連接了,那么我們首先調(diào)用 serverSocketChannel.accept() 通過三次握手來實(shí)現(xiàn) TCP 連接。
  • 如果是 key.isReadable() == true,意味著可以讀取了,我們通過創(chuàng)建一個(gè) ByteBuffer 類的對象把數(shù)據(jù)寫到 readBuffer,然后再從 readBuffer 來讀取數(shù)據(jù)。讀取客戶端數(shù)據(jù)結(jié)束后,服務(wù)端會給客戶端發(fā)送響應(yīng),調(diào)用 channel.write()來實(shí)現(xiàn),當(dāng)然寫數(shù)據(jù)也是通過與 ByteBuffer 配合來實(shí)現(xiàn)的。

好,我們用一副圖來更好地展示服務(wù)端的代碼流程:

Client

然后,我們再看看 Client 端代碼:

public class NioClient {
    public static void main(String[] args) throws Exception{
        // 啟動十個(gè)線程,模擬十個(gè)客戶端。
        for(int i=0;i<10;i++){
            new Worker().start();
        }
    }
    static class Worker extends Thread{
        @Override
        public void run() {
            SocketChannel channel = null;
            Selector selector = null;
            try{
                // 1.創(chuàng)建一個(gè) SocketChannel,用來與服務(wù)端連接,并實(shí)現(xiàn)網(wǎng)絡(luò)讀寫操作。
                channel = SocketChannel.open();
                channel.configureBlocking(false);
                // 指定網(wǎng)絡(luò)地址,通過三次握手實(shí)現(xiàn) TCP 連接
                channel.connect(new InetSocketAddress("localhost",9000));
                // 創(chuàng)建一個(gè) selector 對象, 并把 SocketChannel 注冊到 selector 上,并監(jiān)聽 請求連接事件 OP_CONNECT
                selector = Selector.open();
                channel.register(selector, SelectionKey.OP_CONNECT);
                // 2. 遍歷網(wǎng)絡(luò)事件
                while (true){
                    // 查找收到的網(wǎng)絡(luò)事件
                    selector.select();
                    Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
                    while (keyIterator.hasNext()){
                        SelectionKey key = (SelectionKey) keyIterator.next();
                        keyIterator.remove();
                        // 3. 判斷是否可以連接
                        if(key.isConnectable()){
                            // 如果連接成功了
                            if(channel.finishConnect()){
                                // 監(jiān)聽網(wǎng)絡(luò)讀事件
                                key.interestOps(SelectionKey.OP_READ);
                                // 向服務(wù)端發(fā)送數(shù)據(jù)
                                channel.write(ByteBuffer.wrap("hello,I'm client".getBytes()));
                            }else {
                                key.cancel();
                            }
                            // 4. 如果有數(shù)據(jù)需要讀取
                        }else if(key.isReadable()){
                            ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                            // 把數(shù)據(jù)寫入 buffer
                            channel.read(byteBuffer);
                            byteBuffer.flip();
                            // 把數(shù)據(jù)讀出來。
                            String response = StandardCharsets.UTF_8.decode(byteBuffer).toString();
                            System.out.println("["+Thread.currentThread().getName()+"] receive response:"+response);
                            Thread.sleep(5000);
                            // 向服務(wù)端發(fā)送數(shù)據(jù)
                            channel.write(ByteBuffer.wrap("hello".getBytes()));
                        }
                    }
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                if(channel != null){
                    try{
                        channel.close();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

客戶端代碼通過多線程模擬了十個(gè)客戶端同時(shí)向服務(wù)端發(fā)送請求,每一個(gè)線程的執(zhí)行過程我給大家講解一下。

1.創(chuàng)建一個(gè) SocketChannel 用來與服務(wù)端連接,這個(gè) SocketChannel 是用來保持與客戶端連接的 Channel,用來實(shí)現(xiàn)網(wǎng)絡(luò)讀寫操作。其實(shí),本質(zhì)上還是通過三次握手來實(shí)現(xiàn) TCP 連接。然后,把 OP_CONNECT 事件注冊在新創(chuàng)建的 selector 上,作用跟服務(wù)端的 selector 是一樣的,這里就不再解釋了。

2.無限循環(huán)輪詢 SelectionKey 集合里的 SelectionKey。服務(wù)端輪詢基本一致,這里也會有兩個(gè)事件需要處理。

  • 判斷是否可以連接。如果 key.isConnectable() == true,那么就認(rèn)為是可以連接的,但是可以連接并不意味著已經(jīng)連接上了。如果 channel.finishConnect()==true,我們才認(rèn)為連接成功建立了,這時(shí)就要把 OP_READ 事件也就是讀事件也注冊到 selector 上,這樣我們就可以接收到服務(wù)端的數(shù)據(jù)了。然后,通過與 ByteBuffer 配合向服務(wù)端發(fā)送數(shù)據(jù)。
  • 判斷是否是可讀。如果 key.isReadable() == true,那么就認(rèn)為網(wǎng)絡(luò)讀事件來了,我們需要讀取服務(wù)端給我們發(fā)送的數(shù)據(jù),讀取后再次向服務(wù)端發(fā)送數(shù)據(jù)。

服務(wù)端和客戶端的代碼運(yùn)行起來后,會不斷相互收發(fā)數(shù)據(jù)。我建議大家可以在 IDE 上運(yùn)行一下服務(wù)端和客戶端的代碼,這樣能夠更好地理解 Java NIO 的機(jī)制。

NIO 核心原理

上面給大家講解了一個(gè) NIO 的例子,并把相關(guān)代碼給大家解釋清楚了,下面我們再來看看 Java NIO 的核心原理是什么。

其實(shí) Java NIO 的多路復(fù)用機(jī)制并不是 Java NIO 本身來實(shí)現(xiàn)的,而是通過操作系統(tǒng)實(shí)現(xiàn)的。Java NIO 在調(diào)用操作系統(tǒng)的 API 來實(shí)現(xiàn)多路復(fù)用。

服務(wù)端初始化過程

我們先看下服務(wù)端的初始化過程:

// 1.打開服務(wù)端
serverSocketChannel = ServerSocketChannel.open();
// 配置為非阻塞IO
serverSocketChannel.configureBlocking(false);
// 設(shè)置端口,
serverSocketChannel.socket().bind(new InetSocketAddress(9000),100);
// 打開 多路復(fù)用選擇器
selector = Selector.open();
// 2.把 serverSocketChannel 注冊到 selector 上。 監(jiān)聽 serverSocketChannel 的連接請求事件,與各個(gè)客戶端建立連接請求
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

首先,第一步會創(chuàng)建一個(gè) ServerSocketChannel 的實(shí)例,然后配置為非阻塞。下一步,調(diào)用 serverSocketChannel.socket(),大家可以看到這個(gè)調(diào)用的返回是與 ServerSocket,其實(shí)本質(zhì)上就是與 ServerSocketChannel 相關(guān)聯(lián)的 TCP 的 Socket,然后調(diào)用 bind() 方法,設(shè)置 Socket 的端口,這樣就有了 Socket 的端口。

所以說, ServerSocketChannel 的作用是 TCP 協(xié)議下用來監(jiān)聽對 TCP 某個(gè)端口的連接請求。

好,服務(wù)端初始化過程基本給大家講解完了,下面給大家講解另一個(gè)重要的內(nèi)容:Selector 是如何注冊 Channel的。

Selector 工作原理

Selector 也是基于底層操作系統(tǒng)來實(shí)現(xiàn)的,我們看一下 Selector.open() 這個(gè)方法就知道了。

public static Selector open() throws IOException {
    return SelectorProvider.provider().openSelector();
}

上面的代碼就是打開一個(gè) Selector,其實(shí)底層也是基于操作系統(tǒng)來實(shí)現(xiàn)的。操作系統(tǒng)實(shí)現(xiàn)了 select 機(jī)制,用 select 機(jī)制來監(jiān)聽注冊到自己上面的 channel 有沒有網(wǎng)絡(luò)事件。當(dāng)每次注冊某個(gè) Channel 的某個(gè)網(wǎng)絡(luò)事件到 Selector 上的時(shí)候,都會為網(wǎng)絡(luò)事件指定一個(gè) SelectionKey。

SelectionKey 是一個(gè)枚舉類型,有下面幾種類型。

  • OP_CONNECT:客戶端的請求連接的事件??蛻舳说?SocketChannel 會向 Selector 注冊這個(gè)事件,需要 Selector 監(jiān)聽服務(wù)端是否連接準(zhǔn)備好了。
  • OP_ACCEPT:服務(wù)端的接收連接的事件。服務(wù)端的 ServerSocketChannel 會向 Selector 注冊這個(gè)事件,需要 Selector 監(jiān)聽接收到客戶端的請求連接。
  • OP_READ:服務(wù)端和客戶端的網(wǎng)絡(luò)讀事件。服務(wù)端和客戶端的 SocketChannel 都會向 Selector 注冊這個(gè)事件。需要 Selector 監(jiān)聽是否接收到對方發(fā)送的數(shù)據(jù)了。
  • OP_WRITE:服務(wù)端和客戶端的網(wǎng)絡(luò)寫事件。服務(wù)端和客戶端的 SocketChannel 都會向 Selector 注冊這個(gè)事件。需要 Selector 監(jiān)聽是否可以向?qū)Ψ桨l(fā)送數(shù)據(jù)了。

也就是說,我們可以根據(jù)需要指定需要監(jiān)控的網(wǎng)絡(luò)事件。隨著注冊在 Selector 上的 Channel 越來越多,隨之而來的注冊在 Selector 上的網(wǎng)絡(luò)事件越來越多,那么,Selector 就能夠監(jiān)聽很多請求連接及讀寫的網(wǎng)絡(luò)事件,這樣我們通過多路復(fù)用來響應(yīng)海量客戶端,從而實(shí)現(xiàn)了高性能網(wǎng)絡(luò)服務(wù)端的目的。

當(dāng)然,當(dāng)我們對某個(gè)網(wǎng)絡(luò)事件不感興趣了,我們也可以取消對網(wǎng)絡(luò)事件的監(jiān)聽,比如當(dāng)客戶端已經(jīng)連接到了服務(wù)端,就可以取消對 OP_CONNECT 事件的關(guān)注,好處是能夠通過減少關(guān)注的事件數(shù)量減少 Selector 的負(fù)載,從而提升多路復(fù)用的效率。

多路復(fù)用的原理(建立連接過程)

我們應(yīng)該都知道 Java NIO 采用了多路復(fù)用的思想。那么,多路復(fù)用的原理是什么呢?

我先給大家解釋一下多路復(fù)用的原理,這里還是用一張圖來引出多路復(fù)用的原理

根據(jù)這幅圖,我們只體現(xiàn)了網(wǎng)絡(luò)連接事件(OP_ACCEPT 和 OP_CONNECT),讀寫事件忽略,這樣能讓大家看的更簡潔一些。

大家可以看到:客戶端向服務(wù)器連接前,服務(wù)端會把接收連接的事件(OP_ACCEPT)注冊到服務(wù)端的 NIO Selector,然后 NIO Selector 再把事件注冊到操作系統(tǒng)上,也就是說操作系統(tǒng)才是能真正實(shí)現(xiàn)多路復(fù)用。然后,服務(wù)端通過不斷循環(huán)來監(jiān)聽是否有客戶端發(fā)送連接的請求。如果有,操作系統(tǒng)會通過修改某些 JAVA 的屬性來異步通知給 JAVA 程序。 如果 NIO Selector 輪詢到某些屬性變化了,比如 key.isAcceptable() == true,那么就可以執(zhí)行相應(yīng)的業(yè)務(wù)邏輯。

程序會使用組件 Selector 來創(chuàng)建和管理多個(gè)連接的網(wǎng)絡(luò)事件,最重要的是 Selector 是在一個(gè)線程里工作,這樣,服務(wù)端用一個(gè)線程就能管理 N 個(gè)客戶端的連接。也就是說,Selecot 組件是 JAVA NIO 多路復(fù)用的靈魂。

Selector 的 select()方法

如果說 Selector 是 JAVA NIO 多路復(fù)用的靈魂,那么 Selector 的 select 方法就是核心,這個(gè)方法負(fù)責(zé)收集監(jiān)聽到的網(wǎng)絡(luò)事件,我們先看看這個(gè)方法有幾種重載方式:

  • select(): 阻塞方法,返回值int。只要監(jiān)聽到有注冊在 Selector 上的事件準(zhǔn)備好了,就會返回監(jiān)聽到的事件的數(shù)量,否則一直阻塞。 這個(gè)方法的優(yōu)點(diǎn)是,線程不會空轉(zhuǎn),只有監(jiān)聽到了事件才會執(zhí)行。缺點(diǎn)是線程阻塞,這個(gè)線程內(nèi)的其它功能不能及時(shí)執(zhí)行。
  • select(long timeout):限時(shí)阻塞方法。在一個(gè)時(shí)間段內(nèi)阻塞,如果在這個(gè)時(shí)間段內(nèi)監(jiān)聽到準(zhǔn)備好的網(wǎng)絡(luò)事件就返回網(wǎng)絡(luò)事件的數(shù)量,否則一直等到這個(gè)時(shí)間結(jié)束在返回。 這個(gè)方法的優(yōu)點(diǎn)是,線程在一個(gè)時(shí)間段內(nèi)不會空轉(zhuǎn),只有監(jiān)聽到了事件才會執(zhí)行。缺點(diǎn)是還是會有線程阻塞,這個(gè)線程內(nèi)的其它功能不能及時(shí)執(zhí)行。
  • selectNow():不會阻塞的方法,只要執(zhí)行到這里就返回,如果沒有監(jiān)聽到事件就返回0。 這個(gè)方法的優(yōu)點(diǎn)是:由于沒有阻塞,線程內(nèi)的邏輯都會及時(shí)執(zhí)行,但是如果業(yè)務(wù)邏輯比較簡單,而且網(wǎng)絡(luò)事件出現(xiàn)的頻率不高這個(gè)線程就會一直空轉(zhuǎn),浪費(fèi)了 CPU 的資源。

現(xiàn)在大家看起來可能有些復(fù)雜,建議大家先把 demo 代碼搞明白,然后再對照上圖,相信大家會理解的更透徹。

到此這篇關(guān)于Java經(jīng)典面試題之NIO多路復(fù)用的文章就介紹到這了,更多相關(guān)Java NIO多路復(fù)用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 如何搭建一個(gè)完整的Java開發(fā)環(huán)境

    如何搭建一個(gè)完整的Java開發(fā)環(huán)境

    這篇文章主要教大家如何搭建一個(gè)完整的Java開發(fā)環(huán)境,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • Java調(diào)用WebService服務(wù)的三種方式總結(jié)

    Java調(diào)用WebService服務(wù)的三種方式總結(jié)

    雖然WebService這個(gè)框架已經(jīng)過時(shí),但是有些公司還在使用,在調(diào)用他們的服務(wù)的時(shí)候就不得不面對各種問題,本篇文章總結(jié)了最近我調(diào)用?WebService的心路歷程,3種方式可以分別嘗試,需要的朋友可以參考下
    2023-08-08
  • Java多線程繼承Thread類詳解

    Java多線程繼承Thread類詳解

    Java多線程的兩種實(shí)現(xiàn)方式:繼承Thread類 & 實(shí)現(xiàn)Runable接口,今天我們來學(xué)習(xí)下繼承Thread類,希望大家能夠喜歡
    2016-06-06
  • Spring責(zé)任鏈模式使用實(shí)例講解

    Spring責(zé)任鏈模式使用實(shí)例講解

    責(zé)任鏈?zhǔn)切袨樾驮O(shè)計(jì)模式的一種,通過前一個(gè)處理者記錄下一個(gè)處理者的方式形成一條處理鏈??蛻舳嗽谡{(diào)用時(shí)只需要將請求傳遞到責(zé)任上即可,無需關(guān)注鏈路中的具體的傳遞過程。而鏈路中內(nèi)部的處理,是按照前一個(gè)處理者記錄的下一個(gè)處理者依次執(zhí)行
    2023-01-01
  • 基于Integer值判斷是否相等的問題

    基于Integer值判斷是否相等的問題

    這篇文章主要介紹了基于Integer值判斷是否相等的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-01-01
  • @DS注解的使用,動態(tài)數(shù)據(jù)源,事務(wù)詳解

    @DS注解的使用,動態(tài)數(shù)據(jù)源,事務(wù)詳解

    在項(xiàng)目中使用多數(shù)據(jù)源時(shí),可以借助苞米豆的dynamic-datasource-spring-boot-starter進(jìn)行配置,首先需引入相應(yīng)的jar包,并在application.yml中設(shè)置主從數(shù)據(jù)源,其中一般選擇master作為默認(rèn)數(shù)據(jù)源,在實(shí)現(xiàn)類中通過@DS注解指定數(shù)據(jù)源
    2024-09-09
  • SpringBoot接收參數(shù)使用的注解實(shí)例講解

    SpringBoot接收參數(shù)使用的注解實(shí)例講解

    這篇文章主要介紹了詳解SpringBoot接收參數(shù)使用的幾種常用注解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • Eclipse?Jetty?server漏洞解決辦法

    Eclipse?Jetty?server漏洞解決辦法

    最近給?個(gè)客戶部署項(xiàng)?,但是客戶的安全稽核有點(diǎn)變態(tài),居然說 Eclipse Jetty Server?危漏洞,這篇文章主要給大家介紹了關(guān)于Eclipse?Jetty?server漏洞解決的相關(guān)資料,需要的朋友可以參考下
    2023-11-11
  • 詳解java 拼音首字母搜索內(nèi)容功能的示例

    詳解java 拼音首字母搜索內(nèi)容功能的示例

    這篇文章主要介紹了詳解java 拼音首字母搜索內(nèi)容功能的示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • Java/Android引用類型及其使用全面分析

    Java/Android引用類型及其使用全面分析

    下面小編就為大家?guī)硪黄狫ava/Android引用類型及其使用全面分析。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-09-09

最新評論