主頁 > 後端開發 > 手寫一個類SpringBoot的HTTP框架:幾十行代碼基于Netty搭建一個 HTTP Server

手寫一個類SpringBoot的HTTP框架:幾十行代碼基于Netty搭建一個 HTTP Server

2020-10-08 20:16:09 後端開發

本文已經收錄進 : https://github.com/Snailclimb/netty-practical-tutorial (Netty 從入門到實戰:手寫 HTTP Server+RPC 框架),
相關專案:https://github.com/Snailclimb/jsoncat (仿 Spring Boot 但不同于 Spring Boot 的一個輕量級的 HTTP 框架)

目前正在寫的一個叫做 jsoncat 的輕量級 HTTP 框架內置的 HTTP 服務器是我自己基于 Netty 寫的,所有的核心代碼加起來不過就幾十行,這得益于 Netty 提供的各種開箱即用的組件,為我們節省了太多事情,

這篇文章我會手把手帶著小伙伴們實作一個簡易的 HTTP Server,

如果文章有任何需要改善和完善的地方,歡迎在評論區指出,共同進步!

開始之前為了避免有小伙伴不了解 Netty ,還是先來簡單介紹它!

什么是 Netty?

簡單用 3 點來概括一下 Netty 吧!

  1. Netty 是一個基于 NIO 的 client-server(客戶端服務器)框架,使用它可以快速簡單地開發網路應用程式,
  2. Netty 極大地簡化并優化了 TCP 和 UDP 套接字服務器等網路編程,并且性能以及安全性等很多方面都要更好,
  3. Netty 支持多種協議 如 FTP,SMTP,HTTP 以及各種二進制和基于文本的傳統協議,本文所要寫的 HTTP Server 就得益于 Netty 對 HTTP 協議(超文本傳輸協議)的支持,

Netty 應用場景有哪些?

憑借自己的了解,簡單說一下吧!理論上來說,NIO 可以做的事情 ,使用 Netty 都可以做并且更好,

不過,我們還是首先要明確的是 Netty 主要用來做網路通信

  1. 實作框架的網路通信模塊 : Netty 幾乎滿足任何場景的網路通信需求,因此,框架的網路通信模塊可以基于 Netty 來做,拿 RPC 框架來說! 我們在分布式系統中,不同服務節點之間經常需要相互呼叫,這個時候就需要 RPC 框架了,不同服務指點的通信是如何做的呢?那就可以使用 Netty 來做了!比如我呼叫另外一個節點的方法的話,至少是要讓對方知道我呼叫的是哪個類中的哪個方法以及相關引數吧!
  2. 實作一個自己的 HTTP 服務器 :通過 Netty ,我們可以很方便地使用少量代碼實作一個簡單的 HTTP 服務器,Netty 自帶了編解碼器和訊息聚合器,為我們開發節省了很多事!
  3. 實作一個即時通訊系統 : 使用 Netty 我們可以實作一個可以聊天類似微信的即時通訊系統,這方面的開源專案還蠻多的,可以自行去 Github 找一找,
  4. 實作訊息推送系統 :市面上有很多訊息推送系統都是基于 Netty 來做的,
  5. ......

那些開源專案用到了 Netty?

我們平常經常接觸的 Dubbo、RocketMQ、Elasticsearch、gRPC 、Spring Cloud Gateway 等等都用到了 Netty,

可以說大量的開源專案都用到了 Netty,所以掌握 Netty 有助于你更好的使用這些開源專案并且讓你有能力對其進行二次開發,

實際上還有很多很多優秀的專案用到了 Netty,Netty 官方也做了統計,統計結果在這里:https://netty.io/wiki/related-projects.html ,

實作 HTTP Server 必知的前置知識

既然,我們要實作 HTTP Server 那必然先要回顧一下 HTTP 協議相關的基礎知識,

HTTP 協議

超文本傳輸協議(HTTP,HyperText Transfer Protocol)主要是為 Web 瀏覽器與 Web 服務器之間的通信而設計的,

當我們使用瀏覽器瀏覽網頁的時候,我們網頁就是通過 HTTP 請求進行加載的,整個程序如下圖所示,

HTTP請求程序

https://www.seobility.net/en/wiki/HTTP_headers

HTTP 協議是基于 TCP 協議的,因此,發送 HTTP 請求之前首先要建立 TCP 連接也就是要經歷 3 次握手,目前使用的 HTTP 協議大部分都是 1.1,在 1.1 的協議里面,默認是開啟了 Keep-Alive 的,這樣的話建立的連接就可以在多次請求中被復用了,

了解了 HTTP 協議之后,我們再來看一下 HTTP 報文的內容,這部分內容很重要!(參考圖片來自:https://iamgopikrishna.wordpress.com/2014/06/13/4/)

HTTP 請求報文:

HTTP 請求報文

HTTP 回應報文:

HTTP 回應報文

我們的 HTTP 服務器會在后臺決議 HTTP 請求報文內容,然后根據報文內容進行處理之后回傳 HTTP 回應報文給客戶端,

Netty 編解碼器

如果我們要通過 Netty 處理 HTTP 請求,需要先進行編解碼,所謂編解碼說白了就是在 Netty 傳輸資料所用的 ByteBuf 和 Netty 中針對 HTTP 請求和回應所提供的物件比如 HttpRequestHttpContent之間互相轉換,

Netty 自帶了 4 個常用的編解碼器:

  1. HttpRequestEncoder (HTTP 請求編碼器):將 HttpRequestHttpContent 編碼為 ByteBuf
  2. HttpRequestDecoder (HTTP 請求解碼器):將 ByteBuf 解碼為 HttpRequestHttpContent
  3. HttpResponsetEncoder (HTTP 回應編碼器):將 HttpResponseHttpContent 編碼為 ByteBuf
  4. HttpResponseDecoder(HTTP 回應解碼器):將 ByteBuf 解碼為 HttpResponstHttpContent

網路通信最終都是通過位元組流進行傳輸的, ByteBuf 是 Netty 提供的一個位元組容器,其內部是一個位元組陣列, 當我們通過 Netty 傳輸資料的時候,就是通過 ByteBuf 進行的,

HTTP Server 端用于接收 HTTP Request,然后發送 HTTP Response,因此我們只需要 HttpRequestDecoderHttpResponseEncoder 即可,

我手繪了一張圖,這樣看著應該更容易理解了,

Netty 對 HTTP 訊息的抽象

為了能夠表示 HTTP 中的各種訊息,Netty 設計了抽象了一套完整的 HTTP 訊息結構圖,核心繼承關系如下圖所示,

  1. HttpObject : 整個 HTTP 訊息體系結構的最上層介面,HttpObject 介面下又有 HttpMessageHttpContent兩大核心介面,
  2. HttpMessage: 定義 HTTP 訊息,為HttpRequestHttpResponse提供通用屬性
  3. HttpRequest : HttpRequest對應 HTTP request,通過 HttpRequest 我們可以訪問查詢引數(Query Parameters)和 Cookie,和 Servlet API 不同的是,查詢引數是通過QueryStringEncoderQueryStringDecoder來構造和決議查詢查詢引數,
  4. HttpResponseHttpResponse 對應 HTTP response,和HttpMessage相比,HttpResponse 增加了 status(相應狀態碼) 屬性及其對應的方法,
  5. HttpContent : 分塊傳輸編碼Chunked transfer encoding)是超文本傳輸協議(HTTP)中的一種資料傳輸機制(HTTP/1.1 才有),允許 HTTP 由應用服務器發送給客戶端應用( 通常是網頁瀏覽器)的資料可以分成多“塊”(資料量比較大的情況),我們可以把 HttpContent 看作是這一塊一塊的資料,
  6. LastHttpContent : 標識 HTTP 請求結束,同時包含 HttpHeaders 物件,
  7. FullHttpRequestFullHttpResponseHttpMessageHttpContent 聚合后得到的物件,

HTTP 訊息聚合器

HttpObjectAggregator 是 Netty 提供的 HTTP 訊息聚合器,通過它可以把 HttpMessageHttpContent 聚合成一個 FullHttpRequest 或者 FullHttpResponse(取決于是處理請求還是回應),方便我們使用,

另外,訊息體比較大的話,可能還會分成好幾個訊息體來處理,HttpObjectAggregator 可以將這些訊息聚合成一個完整的,方便我們處理,

使用方法:將 HttpObjectAggregator 添加到 ChannelPipeline 中,如果是用于處理 HTTP Request 就將其放在 HttpResponseEncoder 之后,反之,如果用于處理 HTTP Response 就將其放在 HttpResponseDecoder 之后,

因為,HTTP Server 端用于接收 HTTP Request,對應的使用方式如下,

ChannelPipeline p = ...;
 p.addLast("decoder", new HttpRequestDecoder())
  .addLast("encoder", new HttpResponseEncoder())
  .addLast("aggregator", new HttpObjectAggregator(512 * 1024))
  .addLast("handler", new HttpServerHandler());

基于 Netty 實作一個 HTTP Server

通過 Netty,我們可以很方便地使用少量代碼構建一個可以正確處理 GET 請求和 POST 請求的輕量級 HTTP Server,

源代碼地址:https://github.com/Snailclimb/netty-practical-tutorial/tree/master/example/http-server ,

添加所需依賴到 pom.xml

第一步,我們需要將實作 HTTP Server 所必需的第三方依賴的坐標添加到 pom.xml中,

<!--netty-->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.42.Final</version>
</dependency>
<!-- log -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.25</version>
</dependency>
<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.8</version>
    <scope>provided</scope>
</dependency>
<!--commons-codec-->
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.14</version>
</dependency>

創建服務端

@Slf4j
public class HttpServer {

    private static final int PORT = 8080;

    public void start() {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    // TCP默認開啟了 Nagle 演算法,該演算法的作用是盡可能的發送大資料快,減少網路傳輸,TCP_NODELAY 引數的作用就是控制是否啟用 Nagle 演算法,
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    // 是否開啟 TCP 底層心跳機制
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    //表示系統用于臨時存放已完成三次握手的請求的佇列的最大長度,如果連接建立頻繁,服務器處理創建新連接較慢,可以適當調大這個引數
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast("decoder", new HttpRequestDecoder())
                                    .addLast("encoder", new HttpResponseEncoder())
                                    .addLast("aggregator", new HttpObjectAggregator(512 * 1024))
                                    .addLast("handler", new HttpServerHandler());
                        }
                    });
            Channel ch = b.bind(PORT).sync().channel();
            log.info("Netty Http Server started on port {}.", PORT);
            ch.closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("occur exception when start server:", e);
        } finally {
            log.error("shutdown bossGroup and workerGroup");
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

簡單決議一下服務端的創建程序具體是怎樣的!

1.創建了兩個 NioEventLoopGroup 物件實體:bossGroupworkerGroup

  • bossGroup : 用于處理客戶端的 TCP 連接請求,
  • workerGroup : 負責每一條連接的具體讀寫資料的處理邏輯,真正負責 I/O 讀寫操作,交由對應的 Handler 處理,

舉個例子:我們把公司的老板當做 bossGroup,員工當做 workerGroup,bossGroup 在外面接完活之后,扔給 workerGroup 去處理,一般情況下我們會指定 bossGroup 的 執行緒數為 1(并發連接量不大的時候) ,workGroup 的執行緒數量為 CPU 核心數 *2 ,另外,根據原始碼來看,使用 NioEventLoopGroup 類的無參建構式設定執行緒數量的默認值就是 CPU 核心數 *2

2.創建一個服務端啟動引導/輔助類: ServerBootstrap,這個類將引導我們進行服務端的啟動作業,

3.通過 .group() 方法給引導類 ServerBootstrap 配置兩大執行緒組,確定了執行緒模型,

4.通過channel()方法給引導類 ServerBootstrap指定了 IO 模型為NIO

  • NioServerSocketChannel :指定服務端的 IO 模型為 NIO,與 BIO 編程模型中的ServerSocket對應
  • NioSocketChannel : 指定客戶端的 IO 模型為 NIO, 與 BIO 編程模型中的Socket對應

5.通過 .childHandler()給引導類創建一個ChannelInitializer ,然后指定了服務端訊息的業務處理邏輯也就是自定義的ChannelHandler 物件

6.呼叫 ServerBootstrap 類的 bind()方法系結埠

//bind()是異步的,但是,你可以通過 sync()方法將其變為同步,
ChannelFuture f = b.bind(port).sync();

自定義服務端 ChannelHandler 處理 HTTP 請求

我們繼承SimpleChannelInboundHandler ,并重寫下面 3 個方法:

  1. channelRead() :服務端接收并處理客戶端發送的 HTTP 請求呼叫的方法,
  2. exceptionCaught() :處理客戶端發送的 HTTP 請求發生例外的時候被呼叫,
  3. channelReadComplete() : 服務端消費完客戶端發送的 HTTP 請求之后呼叫的方法,

另外,客戶端 HTTP 請求引數型別為 FullHttpRequest,我們可以把 FullHttpRequest物件看作是 HTTP 請求報文的 Java 物件的表現形式,

@Slf4j
public class HttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    private static final String FAVICON_ICO = "/favicon.ico";
    private static final AsciiString CONNECTION = AsciiString.cached("Connection");
    private static final AsciiString KEEP_ALIVE = AsciiString.cached("keep-alive");
    private static final AsciiString CONTENT_TYPE = AsciiString.cached("Content-Type");
    private static final AsciiString CONTENT_LENGTH = AsciiString.cached("Content-Length");

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) {
        log.info("Handle http request:{}", fullHttpRequest);
        String uri = fullHttpRequest.uri();
        if (uri.equals(FAVICON_ICO)) {
            return;
        }
        RequestHandler requestHandler = RequestHandlerFactory.create(fullHttpRequest.method());
        Object result;
        FullHttpResponse response;
        try {
            result = requestHandler.handle(fullHttpRequest);
            String responseHtml = "<html><body>" + result + "</body></html>";
            byte[] responseBytes = responseHtml.getBytes(StandardCharsets.UTF_8);
            response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(responseBytes));
            response.headers().set(CONTENT_TYPE, "text/html; charset=utf-8");
            response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            String responseHtml = "<html><body>" + e.toString() + "</body></html>";
            byte[] responseBytes = responseHtml.getBytes(StandardCharsets.UTF_8);
            response = new DefaultFullHttpResponse(HTTP_1_1, INTERNAL_SERVER_ERROR, Unpooled.wrappedBuffer(responseBytes));
            response.headers().set(CONTENT_TYPE, "text/html; charset=utf-8");
        }
        boolean keepAlive = HttpUtil.isKeepAlive(fullHttpRequest);
        if (!keepAlive) {
            ctx.write(response).addListener(ChannelFutureListener.CLOSE);
        } else {
            response.headers().set(CONNECTION, KEEP_ALIVE);
            ctx.write(response);
        }
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

}

我們回傳給客戶端的訊息體是 FullHttpResponse 物件,通過 FullHttpResponse 物件,我們可以設定 HTTP 回應報文的 HTTP 協議版本、回應的具體內容 等內容,

我們可以把 FullHttpResponse 物件看作是 HTTP 回應報文的 Java 物件的表現形式,

FullHttpResponse response;

String responseHtml = "<html><body>" + result + "</body></html>";
byte[] responseBytes = responseHtml.getBytes(StandardCharsets.UTF_8);
// 初始化 FullHttpResponse ,并設定 HTTP 協議 、回應狀態碼、回應的具體內容
response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(responseBytes));

我們通過 FullHttpResponseheaders()方法獲取到 HttpHeaders,這里的 HttpHeaders 對應于 HTTP 回應報文的頭部,通過 HttpHeaders物件,我們就可以對 HTTP 回應報文的頭部的內容比如 Content-Typ 進行設定,

response.headers().set(CONTENT_TYPE, "text/html; charset=utf-8");
response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());

本案例中,為了掩飾我們設定的 Content-Type 為 text/html ,也就是回傳 html 格式的資料給客戶端,

常見的 Content-Type

Content-Type 解釋
text/html html 格式
text/plain 純文本格式
text/css css 格式
text/javascript js 格式
application/json json 格式(前后端分離專案常用)
image/gif gif 圖片格式
image/jpeg jpg 圖片格式
image/png png 圖片格式

請求的具體處理邏輯實作

因為有這里有 POST 請求和 GET 請求,因此我們需要首先定義一個處理 HTTP Request 的介面,

public interface RequestHandler {
    Object handle(FullHttpRequest fullHttpRequest);
}

HTTP Method 不只是有 GET 和 POST,其他常見的還有 PUT、DELETE、PATCH,只是本案例中實作的 HTTP Server 只考慮了 GET 和 POST,

  • GET :請求從服務器獲取特定資源,舉個例子:GET /classes(獲取所有班級)
  • POST :在服務器上創建一個新的資源,舉個例子:POST /classes(創建班級)
  • PUT :更新服務器上的資源(客戶端提供更新后的整個資源),舉個例子:PUT /classes/12(更新編號為 12 的班級)
  • DELETE :從服務器洗掉特定的資源,舉個例子:DELETE /classes/12(洗掉編號為 12 的班級)
  • PATCH :更新服務器上的資源(客戶端提供更改的屬性,可以看做作是部分更新),使用的比較少,這里就不舉例子了,

GET 請求的處理

@Slf4j
public class GetRequestHandler implements RequestHandler {
    @Override
    public Object handle(FullHttpRequest fullHttpRequest) {
        String requestUri = fullHttpRequest.uri();
        Map<String, String> queryParameterMappings = this.getQueryParams(requestUri);
        return queryParameterMappings.toString();
    }

    private Map<String, String> getQueryParams(String uri) {
        QueryStringDecoder queryDecoder = new QueryStringDecoder(uri, Charsets.toCharset(CharEncoding.UTF_8));
        Map<String, List<String>> parameters = queryDecoder.parameters();
        Map<String, String> queryParams = new HashMap<>();
        for (Map.Entry<String, List<String>> attr : parameters.entrySet()) {
            for (String attrVal : attr.getValue()) {
                queryParams.put(attr.getKey(), attrVal);
            }
        }
        return queryParams;
    }

}

我這里只是簡單得把 URI 的查詢引數的對應關系直接回傳給客戶端了,

實際上,獲得了 URI 的查詢引數的對應關系,再結合反射和注解相關的知識,我們很容易實作類似于 Spring Boot 的 @RequestParam 注解了,

建議想要學習的小伙伴,可以自己獨立實作一下,不知道如何實作的話,你可以參考我開源的輕量級 HTTP 框架jsoncat (仿 Spring Boot 但不同于 Spring Boot 的一個輕量級的 HTTP 框架),

POST 請求的處理

@Slf4j
public class PostRequestHandler implements RequestHandler {

    @Override
    public Object handle(FullHttpRequest fullHttpRequest) {
        String requestUri = fullHttpRequest.uri();
        log.info("request uri :[{}]", requestUri);
        String contentType = this.getContentType(fullHttpRequest.headers());
        if (contentType.equals("application/json")) {
            return fullHttpRequest.content().toString(Charsets.toCharset(CharEncoding.UTF_8));
        } else {
            throw new IllegalArgumentException("only receive application/json type data");
        }

    }

    private String getContentType(HttpHeaders headers) {
        String typeStr = headers.get("Content-Type");
        String[] list = typeStr.split(";");
        return list[0];
    }
}

對于 POST 請求的處理,我們這里只接受處理 Content-Type 為 application/json 的資料,如果 POST 請求傳過來的不是 application/json 型別的資料,我們就直接拋出例外,

實際上,我們獲得了客戶端傳來的 json 格式的資料之后,再結合反射和注解相關的知識,我們很容易實作類似于 Spring Boot 的 @RequestBody 注解了,

建議想要學習的小伙伴,可以自己獨立實作一下,不知道如何實作的話,你可以參考我開源的輕量級 HTTP 框架jsoncat (仿 Spring Boot 但不同于 Spring Boot 的一個輕量級的 HTTP 框架),

請求處理工廠類

public class RequestHandlerFactory {
    public static final Map<HttpMethod, RequestHandler> REQUEST_HANDLERS = new HashMap<>();

    static {
        REQUEST_HANDLERS.put(HttpMethod.GET, new GetRequestHandler());
        REQUEST_HANDLERS.put(HttpMethod.POST, new PostRequestHandler());
    }

    public static RequestHandler create(HttpMethod httpMethod) {
        return REQUEST_HANDLERS.get(httpMethod);
    }
}

我這里用到了工廠模式,當我們額外處理新的 HTTP Method 方法的時候,直接實作 RequestHandler 介面,然后將實作類添加到 RequestHandlerFactory 即可,

啟動類

public class HttpServerApplication {
    public static void main(String[] args) {
        HttpServer httpServer = new HttpServer();
        httpServer.start();
    }
}

效果

運行 HttpServerApplicationmain()方法,控制臺列印出:

[nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0x9bb1012a] REGISTERED
[nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0x9bb1012a] BIND: 0.0.0.0/0.0.0.0:8080
[nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0x9bb1012a, L:/0:0:0:0:0:0:0:0:8080] ACTIVE
[main] INFO server.HttpServer - Netty Http Server started on port 8080.

GET 請求

POST 請求

參考

  1. Netty 學習筆記-http objects

我的開源專案推薦

  1. JavaGuide :「Java學習+面試指南」一份涵蓋大部分Java程式員所需要掌握的核心知識,準備 Java 面試,首選 JavaGuide!
  2. guide-rpc-framework :A custom RPC framework implemented by Netty+Kyro+Zookeeper.(一款基于 Netty+Kyro+Zookeeper 實作的自定義 RPC 框架-附詳細實作程序和相關教程)
  3. jsoncat :仿 Spring Boot 但不同于 Spring Boot 的一個輕量級的 HTTP 框架
  4. programmer-advancement :程式員應該有的一些好習慣+面試必知事項!
  5. springboot-guide :Not only Spring Boot but also important knowledge of Spring(不只是SpringBoot還有Spring重要知識點)
  6. awesome-java :Collection of awesome Java project on Github(Github 上非常棒的 Java 開源專案集合).
    我是 Guide 哥,一 Java 后端開發,會一點前端,自由的少年,我們下期再見!微信搜“JavaGuide”回復“面試突擊”領取我整理的 4 本原創PDF

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

標籤:Java

上一篇:程式員翻車時的 30 種常見反應!

下一篇:編程體系結構(06):Java面向物件

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(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
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more