主頁 > 後端開發 > Netty實戰(四)

Netty實戰(四)

2023-05-26 15:50:56 後端開發

本節我們看看Netty的傳輸(全是干貨,自帶水杯

一、Java的NIO和OIO

流經網路的資料總是具有相同的型別:位元組,這些位元組是如何流動的主要取決于我們所說的網路傳輸,

1.1 OIO

我們先來看一段Java的阻塞應用程式程式:

package com.example.javademo;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;

/**
 * @author lhd
 * @date 2023/05/17 13:19
 * @notes java oio演示
 */
public class PlainOioServer {

    public void serve(int port) throws IOException {
        //系結服務器到指定埠
        final ServerSocket socket = new ServerSocket(port);
        try {
            for (;;) {
                //接受連接
                final Socket clientSocket = socket.accept();
                System.out.println(
                        "Accepted connection from " + clientSocket);
                //創建一個新的執行緒來處理該連接
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        OutputStream out;
                        try {
                            out = clientSocket.getOutputStream();
                            //將訊息寫給新連接的客戶端
                            out.write("Hi!\r\n".getBytes(Charset.forName("UTF-8")));
                            out.flush();
                            //關閉連接
                            clientSocket.close();
                        }
                        catch (IOException e) {
                            e.printStackTrace();
                        }
                        finally {
                            try {
                                clientSocket.close();
                            }
                            catch (IOException ex) {
                            // 關閉時忽略
                            }
                        }
                    }
                }).start(); //啟動執行緒
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}

這段代碼完全可以處理中等數量的并發客戶端,但是隨著應用程式變得流行起來,你會發現它并不能很好地伸縮到支撐成千上萬的并發連入連接,

1.2 NIO

同樣,來看一段Java的非阻塞應用程式代碼:

package com.example.javademo;

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;

/**
 * @author lhd
 * @date 2023/05/17 13:27
 * @notes java NIO演示
 */
public class PlainNioServer {

    public void serve(int port) throws IOException {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        ServerSocket ssocket = serverChannel.socket();
        //將服務系結到埠
        InetSocketAddress address = new InetSocketAddress(port);
        ssocket.bind(address);
        //打開選擇器來處理Channel
        Selector selector = Selector.open();
        //將serverChannel注冊到選擇器用來接收連接
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
        for (;;) {
            try {
                //等待需要處理的新事件;阻塞 將一直持續到下一個傳入事件
                selector.select();
            } catch (IOException ex) {
                ex.printStackTrace();
           // handle exception
                break;
            }
            //獲取所有接收事件的SelectionKey 實體
            Set<SelectionKey> readyKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = readyKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                try {
                    //檢查事件是否是一個新的已經就緒可以被接受的連接
                    if (key.isAcceptable()) {
                        ServerSocketChannel server = (ServerSocketChannel)key.channel();
                        SocketChannel client = server.accept();
                        client.configureBlocking(false);
                        //接受客戶端,并將它注冊到選擇器
                        client.register(selector, SelectionKey.OP_WRITE |
                                SelectionKey.OP_READ, msg.duplicate());
                        System.out.println("Accepted connection from " + client);
                    }
                    //檢查套接字是否已經準備好寫資料
                    if (key.isWritable()) {
                        SocketChannel client = (SocketChannel)key.channel();
                        ByteBuffer buffer = (ByteBuffer)key.attachment();
                        while (buffer.hasRemaining()) {
                            //將資料寫到已連接的客戶端
                            if (client.write(buffer) == 0) {
                                break;
                            }
                        }
                        //關閉連接
                        client.close();
                    }
                } catch (IOException ex) {
                    key.cancel();
                    try {
                        key.channel().close();
                    } catch (IOException cex) {
                    }
                }
            }
        }
    }
}

可以看出,兩段Java的OIO和NIO都做了相同的事:連接客戶端,并發送“Hi”,區別在于一個是阻塞的,另一個是非阻塞的,但兩段代碼卻完全不同,如果為了用于非阻塞 I/O 而重新實作這個簡單的應用程式,都需要一次完全的重寫的話,那么不難想象,移植真正復雜的應用程式需要付出什么樣的努力,

二、Netty的NIO和OIO

下來我們來看以下Netty 實作該應用程式將會是什么樣子,

2.1 OIO

來看一段Netty的阻塞代碼

package com.example.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.oio.OioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.oio.OioServerSocketChannel;

import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;

/**
 * @author lhd
 * @date 2023/05/17 13:47
 * @notes Netty 阻塞程式演示
 * ps:Netty已啟用OIO傳輸,使用 NIO / EPOLL / KQUEUE 傳輸,
 */
public class NettyOioServer {
    public void server(int port) throws InterruptedException {
        final ByteBuf buf = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Hi!\r\n", StandardCharsets.UTF_8));
        EventLoopGroup group = new OioEventLoopGroup();
        try {
            //創建ServerBootstrap
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)
                    //使用 OioEventLoopGroup以允許阻塞模式
                    .channel(OioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(port))
                    //指定 ChannelInitializer,對于每個已接受的連接都呼叫它
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch){
                            //添加一個 ChannelInboundHandlerAdapter 以攔截和處理事件
                            ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) {
                                    //將訊息寫到客戶端,并添加 ChannelFutureListener,以便訊息一被寫完就關閉連接
                                    ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE);
                                }
                            });
                        }
                    });
            //系結服務器以接受連接
            ChannelFuture f = b.bind().sync();
            //釋放所有的資源
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();
        }
    }
}

ps:這段代碼和我們上一篇文章中的有所區別,不要搞混淆了

2.2 NIO

我們再來看看Netty的非阻塞代碼:

package com.example.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;

/**
 * @author lhd
 * @date 2023/05/17 14:05
 * @notes Netty 非阻塞代碼演示
 */
public class NettyNioServer {

    public void server(int port) throws InterruptedException {
        final ByteBuf buf = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Hi!\r\n", StandardCharsets.UTF_8));
       EventLoopGroup group = new NioEventLoopGroup(); // 1
        try {
            ServerBootstrap b = new ServerBootstrap();
            //為非阻塞模式使用NioEventLoopGroup
            b.group(group).channel(NioServerSocketChannel.class) // 2
                    .localAddress(new InetSocketAddress(port))
                    //指定 ChannelInitializer,對于每個已接受的連接都呼叫它
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            //添加 ChannelInboundHandlerAdapter 以接收和處理事件
                            ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) {
                                    //將訊息寫到客戶端,并添加ChannelFutureListener,以便訊息一被寫完就關閉連接
                                    ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE);
                                }
                            });
                        }
                    });
            //系結服務器以接受連接
            ChannelFuture f = b.bind().sync();
            f.channel().closeFuture().sync();
        } finally {
            //釋放所有的資源
            group.shutdownGracefully().sync();
        }
    }
}

可以看到,Netty的阻塞代碼和非阻塞代碼只有兩處不同(代碼中1 2 處),這使得Netty的阻塞和非阻塞切換非常容易,Netty 為每種傳輸的實作都暴露了相同的 API,所以無論選用哪一種傳輸的實作,你的代碼都仍然幾乎不受影響,在所有的情況下,傳輸的實作都依賴于 interface Channel、ChannelPipeline 和 ChannelHandler,

三、傳輸API

傳輸 API 的核心是 interface Channel,它被用于所有的 I/O 操作,下圖是它的層次結構:
在這里插入圖片描述
如圖所示,每個 Channel 都將會被分配一個 ChannelPipeline和ChannelConfig,ChannelConfig 包含了該 Channel 的所有配置設定,并且支持熱更新,由于特定的傳輸可能具有獨特的設定,所以它可能會實作一個 ChannelConfig 的子型別,

由于 Channel 是獨一無二的,所以為了保證順序將 Channel 宣告為 java.lang.Comparable 的一個子介面,因此,如果兩個不同的 Channel 實體都回傳了相同的散列碼,那么 AbstractChannel 中的 compareTo()方法的實作將會拋出一個 Error,

ChannelPipeline 持有所有將應用于入站和出站資料以及事件的 ChannelHandler 實體,這些 ChannelHandler 實作了應用程式用于處理狀態變化以及資料處理的邏輯,

ChannelHandler 的典型用途包括:

  • 將資料從一種格式轉換為另一種格式;
  • 提供例外的通知;
  • 提供 Channel 變為活動的或者非活動的通知;
  • 提供當 Channel 注冊到 EventLoop 或者從 EventLoop 注銷時的通知;
  • 提供有關用戶自定義事件的通知,

ps:ChannelPipeline 實作了一種常見的設計模式—攔截過濾器(InterceptingFilter),UNIX 管道是另外一個熟悉的例子:多個命令被鏈接在一起,其中一個命令的輸出端將連接到命令列中下一個命令的輸入端,

也可以根據需要通過添加或者移除ChannelHandler實體來修改ChannelPipeline,
通過利用Netty的這項能力可以構建出高度靈活的應用程式,例如,STARTTLS方法名協議被請求時,你可以簡單地通過 向 ChannelPipeline 添 加 一個適當的 ChannelHandler(SslHandler)來按需地支持STARTTLS協議,除了訪問所分配的 ChannelPipeline 和 ChannelConfig 之外,也可以利用 Channel的其他方法,

比較重要的Channel方法如下:

方法名 描述
eventLoop 回傳分配給 Channel 的 EventLoop
pipeline 回傳分配給 Channel 的 ChannelPipeline
isActive 如果 Channel 是活動的,則回傳 true,活動的意義可能依賴于底層的傳輸,例如,一個 Socket 傳輸一旦連接到了遠程節點便是活動的,而一個 Datagram 傳輸一旦被打開便是活動的
localAddress 回傳本地的 SokcetAddress
remoteAddress 回傳遠程的 SocketAddress
write 將資料寫到遠程節點,這個資料將被傳遞給 ChannelPipeline,并且排隊直到它被沖刷
flush 將之前已寫的資料沖刷到底層傳輸,如一個 SocketwriteAndFlush 一個簡便的方法,等同于呼叫 write()并接著呼叫 flush()

Netty 所提供的廣泛功能只依賴于少量的介面,所以我們可以對我們的應用程式邏輯進行重大的修改,而又無需大規模地重構你的代碼庫,

下面是一個寫資料并將其沖刷到遠程節點的例子,

Channel channel = ...
//創建要發送的資料
ByteBuf buf = Unpooled.copiedBuffer("your data", CharsetUtil.UTF_8);
//寫資料并沖刷它
ChannelFuture cf = channel.writeAndFlush(buf);
//添加監聽,以便完后接收通知
cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) {
//如果成功,在控制臺列印成功
if (future.isSuccess()) {
System.out.println("Write successful");
} else {
//錯誤則列印錯誤和堆疊跟蹤
System.err.println("Write error");
future.cause().printStackTrace();
}
}
});

ps:Netty 的 Channel 實作是執行緒安全的,因此你可以存盤一個到 Channel 的參考,并且每當你需要向遠程節點寫資料時,都可以使用它,即使當時許多執行緒都在使用它,

上一篇博文說過,一個Channel對應一個eventLoop,一個eventLoop持有一個執行緒卻不一定只持有一個Channel,下面展示多個執行緒使用同一個Channel,

final Channel channel = ...
//創建資料
final ByteBuf buf = Unpooled.copiedBuffer("your data",
CharsetUtil.UTF_8).retain();
//創建寫資料的Runable
Runnable writer = new Runnable() {
@Override
public void run() {
channel.writeAndFlush(buf.duplicate());
}
};
//獲取執行緒池的參考
Executor executor = Executors.newCachedThreadPool();
//遞交任務給執行緒池,以便某個執行緒呼叫
executor.execute(writer);
//遞交另一個寫任務以便在另一個執行緒中執行
executor.execute(writer);
...

這些訊息將會保證按照順序發送~

四、內置的傳輸

Netty 內置了一些可開箱即用的傳輸,因為并不是它們所有的傳輸都支持每一種協議,所以你必須選擇一個和你的應用程式所使用的協議相容的傳輸,

下面是一些Netty提供的傳輸,

名 稱 描 述
NIO io.netty.channel.socket.nio 使用 java.nio.channels 包作為基礎——基于選擇器的方式
Epoll io.netty.channel.epoll 由 JNI 驅動的 epoll()和非阻塞 IO,這個傳輸支持只有在Linux上可用的多種特性,如SO_REUSEPORT,比 NIO 傳輸更快,而且是完全非阻塞的
OIO io.netty.channel.socket.oio 使用 java.net 包作為基礎使用阻塞流
Local io.netty.channel.local 可以在 VM 內部通過管道進行通信的本地傳輸
Embedded io.netty.channel.embedded Embedded 傳輸,允許使用 ChannelHandler 而又不需要一個真正的基于網路的傳輸,這在測驗你的ChannelHandler 實作時非常有用

之后我們回詳細介紹這些傳輸,

ps:Epoll這個是 Netty 特有的實作,更加適配 Netty 現有的執行緒模型,具有更高的性能以及更低的垃圾回收壓力

4.1 NIO

NIO 提供了一個所有 I/O 操作的全異步的實作,它利用了自 NIO 子系統被引入JDK 1.4 時便可用的基于選擇器的 API,

選擇器背后的基本概念是充當一個注冊表,在那里你將可以請求在 Channel 的狀態發生變化時得到通知,可能的狀態變化有:

  • 新的 Channel 已被接受并且就緒;
  • Channel 連接已經完成;
  • Channel 有已經就緒的可供讀取的資料;
  • Channel 可用于寫資料,

選擇器運行在一個檢查狀態變化并對其做出相應回應的執行緒上,在應用程式對狀態的改變做出回應之后,選擇器將會被重置,并將重復這個程序,

下表中的常量值代表了由class java.nio.channels.SelectionKey定義的位模式,
這些位模式可以組合起來定義一組應用程式正在請求通知的狀態變化集,

名稱 描述
OP_ACCEPT 請求在接受新連接并創建 Channel 時獲得通知
OP_CONNECT 請求在建立一個連接時獲得通知
OP_READ 請求當資料已經就緒,可以從 Channel 中讀取時獲得通知
OP_WRITE 請求當可以向 Channel 中寫更多的資料時獲得通知,這處理了套接字緩沖區被完全填滿時的情況,這種情況通常發生在資料的發送速度比遠程節點可處理的速度更快的時候

內置傳輸的處理流程圖:
在這里插入圖片描述

ps:零拷貝(zero-copy)是一種目前只有在使用 NIO 和 Epoll 傳輸時才可使用的特性,它使你可以快速高效地將資料從檔案系統移動到網路介面,而不需要將其從內核空間復制到用戶空間,其在像 FTP 或者HTTP 這樣的協議中可以顯著地提升性能,但是,并不是所有的作業系統都支持這一特性,特別地,它對于實作了資料加密或者壓縮的檔案系統是不可用的——只能傳輸檔案的原始內容,反過來說,傳輸已被加密的檔案則不是問題

4.2 Epoll—用于 Linux 的本地非阻塞傳輸

Netty 的 NIO 傳輸基于 Java 提供的異步/非阻塞網路編程的通用抽象,雖然這保證了 Netty 的非阻塞 API 可以在任何平臺上使用,但它也包含了相應的限制,因為 JDK為了在所有系統上提供相同的功能,必須做出妥協,

Netty為Linux提供了一組NIO API,其以一種和它本身的設計更加一致的方式使用epoll,并且以一種更加輕量的方式使用中斷,如果你的應用程式旨在運行于Linux系統,那么請考慮利用這個版本的傳輸;你將發現在高負載下它的性能要優于JDK的NIO實作,

這個傳輸的語意和上一節內置傳輸的處理流程圖 所示的完全相同,而且它的用法也是簡單直接的,相關示例參照上面的Netty NIO代碼,如果要在那個代碼中使用 epoll 替代 NIO,只需要將 NioEventLoopGroup替換為EpollEventLoopGroup ,并且將 NioServerSocketChannel.class 替換為EpollServerSocketChannel.class 即可,

4.3 OIO

這個可以參照Netty的OIO代碼即可,最新的Netty中拋棄了OIO的傳輸方式~

下圖是OIO的傳輸處理邏輯:
在這里插入圖片描述

4.4 用于 JVM 內部通信的 Local 傳輸

Netty 提供了一個 Local 傳輸,用于在同一個 JVM 中運行的客戶端和服務器程式之間的異步通信,同樣,這個傳輸也支持對于所有 Netty 傳輸實作都共同的 API,

在這個傳輸中,和服務器 Channel 相關聯的 SocketAddress 并沒有系結物理網路地址;相反,只要服務器還在運行,它就會被存盤在注冊表里,并在 Channel 關閉時注銷,因為這個傳輸并不接受真正的網路流量,所以它并不能夠和其他傳輸實作進行互操作,因此,客戶端希望連接到(在同一個 JVM 中)使用了這個傳輸的服務器端時也必須使用它,除了這個限制,它的使用方式和其他的傳輸一模一樣,

4.5 Embedded 傳輸

Netty 提供了一種額外的傳輸,使得你可以將一組 ChannelHandler 作為幫助器類嵌入到其他的 ChannelHandler 內部,通過這種方式,你將可以擴展一個ChannelHandler 的功能,而又不需要修改其內部代碼,

Embedded 傳輸的關鍵是一個被稱為 EmbeddedChannel 的具體的 Channel
實作,我將在之后的文章中為大家演示這種方式,

五、傳輸的用例

Netty支持的傳輸和網路協議:

傳 輸 TCP UDP SCTP UDT
NIO × × × ×
Epoll(僅 Linux) × × - -
OIO × × × ×

PS:UDT 協議實作了基于 UDP 協議的可靠傳輸;

在 Linux 上啟用 SCTP
SCTP 需要內核的支持,并且需要安裝用戶庫,
例如,對于 Ubuntu,可以使用下面的命令:# sudo apt-get install libsctp1
對于 Fedora,可以使用 yum:#sudo yum install kernel-modules-extra.x86_64 lksctp-tools.x86_64

雖然只有SCTP傳輸有這些特殊要求,但是其他傳輸可能也有它們自己的配置選項需要考慮,此外,如果只是為了支持更高的并發連接數,服務器平臺可能需要配置得和客戶端不一樣,

這里是一些很可能會遇到的用例

  • 非阻塞代碼庫——如果你的代碼庫中沒有阻塞呼叫(或者你能夠限制它們的范圍),那么在 Linux 上使用 NIO 或者 epoll 始終是個好主意,雖然 NIO/epoll 旨在處理大量的并發連接,但是在處理較小數目的并發連接時,它也能很好地作業,尤其是考慮到它在連接之間共享執行緒的方式,
  • 阻塞代碼庫——正如我們已經指出的,如果你的代碼庫嚴重地依賴于阻塞 I/O,而且你的應用程式也有一個相應的設計,那么在你嘗試將其直接轉換為 Netty 的 NIO 傳輸時,你將可能會遇到和阻塞操作相關的問題,不要為此而重寫你的代碼,可以考慮分階段遷移:先從OIO 開始,等你的代碼修改好之后,再遷移到 NIO(或者使用 epoll,如果你在使用 Linux),
  • 在同一個 JVM 內部的通信——在同一個 JVM 內部的通信,不需要通過網路暴露服務,是Local 傳輸的完美用例,這將消除所有真實網路操作的開銷,同時仍然使用你的 Netty 代碼庫,如果隨后需要通過網路暴露服務,那么你將只需要把傳輸改為 NIO 或者 OIO 即可,
  • 測驗你的 ChannelHandler 實作——如果你想要為自己的 ChannelHandler 實作撰寫單元測驗,那么請考慮使用 Embedded 傳輸,這既便于測驗你的代碼,而又不需要創建大量的模擬(mock)物件,你的類將仍然符合常規的 API 事件流,保證該 ChannelHandler在和真實的傳輸一起使用時能夠正確地作業,

應用程式的最佳傳輸建議:

應用程式的需求 推薦的傳輸
非阻塞代碼庫或者一個常規的起點 NIO(或者在 Linux 上使用 epoll)
阻塞代碼庫 OIO
在同一個 JVM 內部的通信 Local
測驗 ChannelHandler 的實作 Embedded

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553497.html

標籤:Java

上一篇:Java設計模式-策略模式

下一篇:返回列表

標籤雲
其他(159759) Python(38169) JavaScript(25456) Java(18135) C(15231) 區塊鏈(8268) C#(7972) AI(7469) 爪哇(7425) MySQL(7213) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5343) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4576) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2434) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1977) 功能(1967) Web開發(1951) HtmlCss(1948) C++(1922) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1878) .NETCore(1862) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Netty實戰(四)

    本節我們看看Netty的傳輸(全是干貨,自帶水杯 # 一、Java的NIO和OIO 流經網路的資料總是具有相同的型別:位元組。這些位元組是如何流動的主要取決于我們所說的網路傳輸。 ## 1.1 OIO 我們先來看一段Java的阻塞應用程式程式: ```java package com.example.j ......

    uj5u.com 2023-05-26 15:50:56 more
  • Java設計模式-策略模式

    # 簡介 在軟體開發中,設計模式是為了解決常見問題而提供的一套可重用的解決方案。策略模式(Strategy Pattern)是其中一種常見的設計模式,它屬于行為型模式。該模式的核心思想是將不同的演算法封裝成獨立的策略類,使得它們可以相互替換,而不影響客戶端的使用。 策略模式與其他設計模式有一些明顯的區 ......

    uj5u.com 2023-05-26 15:37:54 more
  • Fastjson 很快,但不適合我....

    作者:nyingping\ 來源:juejin.cn/post/7215886869199863869 > 記者:大爺您有什么特長呀? > > FastJson:我很快。 > > 記者:23423 乘以 4534 等于多少? > > FastJson:等于 2343. > > 記者:?? > > F ......

    uj5u.com 2023-05-26 15:32:26 more
  • JavaWeb編程面試題——Spring Web MVC

    面試題==知識點,這里所記錄的面試題并不針對于面試者,而是將這些面試題作為技能知識點來看待。不以刷題進大廠為目的,而是以學習為目的。這里的知識點會持續更新,目錄也會隨時進行調整。 ......

    uj5u.com 2023-05-26 15:32:13 more
  • 聊聊「短信」渠道的設計與實作

    短信在實作的邏輯上,也遵循訊息中心的基礎設計,即訊息生產之后,通過訊息中心進行投遞和消費,屬于典型的生產消費模型; ......

    uj5u.com 2023-05-26 15:31:57 more
  • javaer你還在手寫分表分庫?來看看這個框架怎么做的 干貨滿滿

    # java orm框架easy-query分庫分表之分表 高并發三駕馬車:分庫分表、MQ、快取。今天給大家帶來的就是分庫分表的干貨解決方案,哪怕你不用我的框架也可以從中聽到不一樣的結局方案和實作。 一款支持自動分表分庫的orm框架[`easy-query`](https://github.com/ ......

    uj5u.com 2023-05-26 15:31:40 more
  • 【RocketMQ】NameServer總結

    NameServer是一個注冊中心,提供服務注冊和服務發現的功能。NameServer可以集群部署,集群中每個節點都是對等的關系(沒有像ZooKeeper那樣在集群中選舉出一個Master節點),節點之間互不通信。 **服務注冊** Broker啟動的時候會向所有的NameServer節點進行注冊, ......

    uj5u.com 2023-05-26 08:23:14 more
  • JVM系統引數

    JVM(Java虛擬機)是Java程式的運行環境,它可以通過一些系統引數進行配置和優化。以下是一些常用的JVM系統引數: 1. -Xmx: 用于設定JVM堆的最大記憶體大小。例如,-Xmx1g表示將堆的最大大小設定為1GB。 2. -Xms: 用于設定JVM堆的初始記憶體大小。例如,-Xms512m表示 ......

    uj5u.com 2023-05-26 08:23:06 more
  • 跟著hsp學springmvc--數字格式化介紹

    基本資料型別和字串型別的自動轉換<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ page contentType="text/html;charset=UTF-8" language="j ......

    uj5u.com 2023-05-26 08:22:54 more
  • Python豎版大屏2 | 用pyecharts開發可視化的奇妙探索!

    [toc] 你好!我是[@馬哥python說](https://www.zhihu.com/people/13273183132),一名10年程式猿,正在試錯用pyecharts開發可視化大屏的非常規排版。 以下,我用8種ThemeType展示的同一個可視化資料大屏,可視化主題是分析**“淄博燒烤” ......

    uj5u.com 2023-05-26 08:22:28 more