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

Netty實戰(二)

2023-05-25 07:51:50 後端開發

一、環境準備

Netty需要的運行環境很簡單,只有2個,

  • JDK 1.8+
  • Apache Maven 3.3.9+

二、Netty 客戶端/服務器概覽

在這里插入圖片描述
如圖,展示了一個我們將要撰寫的 Echo 客戶端和服務器應用程式,該圖展示是多個客戶端同時連接到一臺服務器,所能夠支持的客戶端數量,在理論上,僅受限于系統的可用資源(以及所使用的 JDK 版本可能會施加的限制),

Echo 客戶端和服務器之間的互動是非常簡單的;在客戶端建立一個連接之后,它會向服務器發送一個或多個訊息,反過來服務器又會將每個訊息回送給客戶端,雖然它本身看起來好像用處不大,但它充分地體現了客戶端/服務器系統中典型的請求-回應互動模式

三、撰寫 Echo 服務器

所有的 Netty 服務器都需要以下兩部分,

  • 至少一個 ChannelHandler—該組件實作了服務器對從客戶端接收的資料的處理,即它的業務邏輯,
  • 引導—這是配置服務器的啟動代碼,至少,它會將服務器系結到它要監聽連接請求的埠上,

3.1 ChannelHandler 和業務邏輯

上一篇博文我們介紹了 Future 和回呼,并且闡述了它們在事件驅動設計中的應用,我們還討論了 ChannelHandler,它是一個介面族的父介面,它的實作負責接收并回應事件通知,

在 Netty 應用程式中,所有的資料處理邏輯都包含在這些核心抽象的實作中,因為你的 Echo 服務器會回應傳入的訊息,所以它需要實作ChannelInboundHandler 介面,用來定義回應入站事件的方法,簡單的應用程式只需要用到少量的這些方法,所以繼承 ChannelInboundHandlerAdapter 類也就足夠了,它提供了ChannelInboundHandler 的默認實作,

我們將要用到的方法是:

  • channelRead() :對于每個傳入的訊息都要呼叫;
  • channelReadComplete() : 通知ChannelInboundHandler最后一次對channelRead()的呼叫是當前批量讀取中的最后一條訊息;
  • exceptionCaught() :在讀取操作期間,有例外拋出時會呼叫,

該 Echo 服務器的 ChannelHandler 實作是 EchoServerHandler,如代碼:

package com.example.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

/**
 * @author lhd
 * @date 2023/05/16 15:05
 * @notes Netty Echo服務端簡單邏輯
 */

//表示channel可以并多個實體共享,它是執行緒安全的
@ChannelHandler.Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf in = (ByteBuf) msg;
        //將訊息列印到控制臺
        System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8));
        //將收到的訊息寫給發送者,而不沖刷出站訊息
        ctx.write(in);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        //將未決訊息沖刷到遠程節點,并且關閉該 Channe
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
                .addListener(ChannelFutureListener.CLOSE);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        //列印例外堆疊跟蹤
        cause.printStackTrace();
        //關閉該channel
        ctx.close();
    }
}

ChannelInboundHandlerAdapter 有一個直觀的 API,并且它的每個方法都可以被重寫以掛鉤到事件生命周期的恰當點上,

因為需要處理所有接收到的資料,所以我們重寫了 channelRead() 方法,在這個服務器應用程式中,我們將資料簡單地回送給了遠程節點,

重寫 exceptionCaught() 方法允許我們對 Throwable 的任何子型別做出反應,在這里你記錄了例外并關閉了連接,

雖然一個更加完善的應用程式也許會嘗試從例外中恢復,但在這個場景下,只是通過簡單地關閉連接來通知遠程節點發生了錯誤,

ps:如果不捕獲例外,會發生什么呢?

每個 Channel 都擁有一個與之相關聯的 ChannelPipeline,其持有一個 ChannelHandler 的實體鏈,在默認的情況下,ChannelHandler 會把對它的方法的呼叫轉發給鏈中的下一個 ChannelHandler,因此,如果 exceptionCaught()方法沒有被該鏈中的某處實作,那么所接收的例外將會被傳遞到 ChannelPipeline 的尾端并被記錄,為此,你的應用程式應該提供至少有一個實作exceptionCaught()方法的 ChannelHandler,

除了 ChannelInboundHandlerAdapter 之外,還有很多需要學習ChannelHandler的子型別和實作,這些之后會一一說明,目前,我們只關注:

  • 針對不同型別的事件來呼叫 ChannelHandler;
  • 應用程式通過實作或者擴展 ChannelHandler 來掛鉤到事件的生命周期,并且提供自定義的應用程式邏輯;
  • 在架構上,ChannelHandler 有助于保持業務邏輯與網路處理代碼的分離,這簡化了開發程序,因為代碼必須不斷地演化以回應不斷變化的需求,

3.2 引導服務器

下面我們準備開始構建服務器,構建服務器涉及到兩個內容:

  • 系結到服務器將在其上監聽并接受傳入連接請求的埠;
  • 配置 Channel,以將有關的入站訊息通知給 EchoServerHandler 實體,
package com.example.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.net.InetSocketAddress;

/**
 * @author lhd
 * @date 2023/05/16 15:21
 * @notes Netty引導服務器
 */
public class EchoServer {

    public static void main(String[] args) throws Exception {
        //呼叫服務器的 start()方法
        new EchoServer().start();
    }

    public void start() throws Exception {
        final EchoServerHandler serverHandler = new EchoServerHandler();
        //創建EventLoopGroup
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //創建ServerBootstra
            ServerBootstrap b = new ServerBootstrap();
            //指定服務器監視埠
             int port = 8080;
            b.group(group)
                    //指定所使用的 NIO 傳輸 Channel
                    //因為我們正在使用的是 NIO 傳輸,所以你指定了 NioEventLoopGroup 來接受和處理新的連接,
                    // 并且將 Channel 的型別指定為 NioServerSocketChannel ,
                    .channel(NioServerSocketChannel.class)
                    //使用指定的埠設定套接字地址
                    //將本地地址設定為一個具有選定埠的 InetSocketAddress ,服務器將系結到這個地址以監聽新的連接請求
                    .localAddress(new InetSocketAddress(port))
                    //添加一個EchoServerHandler 到子Channel的 ChannelPipeline
                    //這里使用了一個特殊的類——ChannelInitializer,這是關鍵,
                    // 當一個新的連接被接受時,一個新的子 Channel 將會被創建,而 ChannelInitializer 將會把一個你的
                    //EchoServerHandler 的實體添加到該 Channel 的 ChannelPipeline 中,正如我們之前所解釋的,
                    // 這個 ChannelHandler 將會收到有關入站訊息的通知,
                    .childHandler(new ChannelInitializer<SocketChannel>(){
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            //EchoServerHandler 被標注為 @Shareable,所以我們可以總是使用同樣的實體
                            //實際上所有客戶端都是使用的同一個EchoServerHandler
                            ch.pipeline().addLast(serverHandler);
                        }
                    });
            //異步地系結服務器,呼叫 sync()方法阻塞等待直到系結完成
            //sync()方法的呼叫將導致當前 Thread阻塞,一直到系結操作完成為止
            ChannelFuture f = b.bind().sync();
            //獲取 Channel 的CloseFuture,并且阻塞當前線
            //該應用程式將會阻塞等待直到服務器的 Channel關閉(因為你在 Channel 的 CloseFuture 上呼叫了 sync()方法)
            f.channel().closeFuture().sync();
        } finally {
            //關閉 EventLoopGroup,釋放所有的資源,包括所有被創建的執行緒
            group.shutdownGracefully().sync();
        }
    }
}

我們總結一下服務器實作中的重要步驟,下面這些是服務器的主要代碼組件:

  • EchoServerHandler 實作了業務邏輯;
  • main()方法引導了服務器;
    引導程序中所需要的步驟如下:
    • 創建一個 ServerBootstrap 的實體以引導和系結服務器;
    • 創建并分配一個 NioEventLoopGroup 實體以進行事件的處理,如接受新連接以及讀/寫資料;
    • 指定服務器系結的本地的 InetSocketAddress;
    • 使用一個 EchoServerHandler 的實體初始化每一個新的 Channel;
    • 呼叫 ServerBootstrap.bind()方法以系結服務器,

到此我們的引導服務器已經完成,

四、撰寫 Echo 客戶端

Echo 客戶端將會:
(1)連接到服務器;
(2)發送一個或者多個訊息;
(3)對于每個訊息,等待并接收從服務器發回的相同的訊息;
(4)關閉連接,
撰寫客戶端所涉及的兩個主要代碼部分也是業務邏輯和引導,和你在服務器中看到的一樣,

4.1 通過 ChannelHandler 實作客戶端邏輯

如同服務器,客戶端將擁有一個用來處理資料的 ChannelInboundHandler,在這個場景下,我們將擴展 SimpleChannelInboundHandler 類以處理所有必須的任務,這要求重寫下面的方法:

  • channelActive() : 在到服務器的連接已經建立之后將被呼叫;
  • channelRead0() : 當從服務器接收到一條訊息時被呼叫;
  • exceptionCaught() :在處理程序中引發例外時被呼叫,

具體代碼可以參考如下:

package com.example.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

/**
 * @author lhd
 * @date 2023/05/16 15:45
 * @notes Netty 簡單的客戶端邏輯
 */

//標記該類的實體可以被多個 Channel 共享
@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    //當被通知 Channel是活躍的時候,發送一條訊息
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
    }

    //記錄已接收訊息的轉儲
    @Override
    public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
        System.out.println("Client received: " + in.toString(CharsetUtil.UTF_8));
    }

    //在發生例外時,記錄錯誤并關閉Channel
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

首先,我們重寫了 channelActive() 方法,其將在一個連接建立時被呼叫,這確保了資料將會被盡可能快地寫入服務器,其在這個場景下是一個編碼了字串"Netty rocks!"的位元組緩沖區,

接下來,我們重寫了 channelRead0() 方法,每當接收資料時,都會呼叫這個方法,由服務器發送的訊息可能會被分塊接收,也就是說,如果服務器發送了 5 位元組,那么不能保證這 5 位元組會被一次性接收,即使是對于這么少量的資料,channelRead0()方法也可能會被呼叫兩次,第一次使用一個持有 3 位元組的 ByteBuf(Netty 的位元組容器),第二次使用一個持有 2 位元組的 ByteBuf,作為一個面向流的協議,TCP 保證了位元組陣列將會按照服務器發送它們的順序被接收,

ps:所以channelRead0()的呼叫次數不一定等于服務器發布訊息的次數

重寫的第三個方法是 exceptionCaught(),如同在 EchoServerHandler(3.1中的代碼示例)中所示,記錄 Throwable,關閉 Channel,在這個場景下,終止到服務器的連接,

ps:為什么客戶端繼承SimpleChannelInboundHandler 而不是ChannelInboundHandler?

在客戶端,當 channelRead0()方法完成時,我們已經有了傳入訊息,并且已經處理完它了,當該方法回傳時,SimpleChannelInboundHandler 負責釋放指向保存該訊息的 ByteBuf 的記憶體參考,

在 EchoServerHandler 中,我們仍然需要將傳入訊息回送給發送者,而 write()操作是異步的,直到 channelRead()方法回傳后可能仍然沒有完成,為此,EchoServerHandler擴展了 ChannelInboundHandlerAdapter,其在這個時間點上不會釋放訊息,訊息在 EchoServerHandler 的 channelReadComplete()方法中,當 writeAndFlush()方法被呼叫時被釋放,

4.2 引導客戶端

引導客戶端類似于引導服務器,不同的是,客戶端是使用主機和埠引數來連接遠程地址,也就是這里的 Echo 服務器的地址,而不是系結到一個一直被監聽的埠,

package com.example.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.net.InetSocketAddress;

/**
 * @author lhd
 * @date 2023/05/16 15:59
 * @notes 引導客戶端
 */
public class EchoClient {
  
    public void start() throws Exception {
        //指定 EventLoopGroup 以處理客戶端事件;需要適用于 NIO 的實作
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //創建 Bootstrap
            Bootstrap b = new Bootstrap();
            b.group(group)
                    //適用于 NIO 傳輸的 Channel 型別
                    .channel(NioSocketChannel.class)
                    //設定服務器的InetSocketAddress
                    .remoteAddress(new InetSocketAddress("127.0.0.1", 8080))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            //在創建Channel時,向 ChannelPipeline中添加一個 EchoClientHandler 實體
                            ch.pipeline().addLast(new EchoClientHandler());}
                    });
            //連接到遠程節點,阻塞等待直到連接完成
            ChannelFuture f = b.connect().sync();
            //阻塞,直到Channel 關閉
            f.channel().closeFuture().sync();
        } finally {
            //關閉執行緒池并且釋放所有的資源
            group.shutdownGracefully().sync();
        }
    }
 public static void main(String[] args) throws Exception {
        new EchoClient().start();
    }
}

總結一下要點:

  • 為初始化客戶端,創建了一個 Bootstrap 實體;
  • 為進行事件處理分配了一個 NioEventLoopGroup 實體,其中事件處理包括創建新的連接以及處理入站和出站資料;
  • 為服務器連接創建了一個 InetSocketAddress 實體;
  • 當連接被建立時,一個 EchoClientHandler 實體會被安裝到(該 Channel 的)
    ChannelPipeline 中;
  • 在一切都設定完成后,呼叫 Bootstrap.connect()方法連接到遠程節點;

綜上客戶端的構建已經完成,

五、構建和運行 Echo 服務器和客戶端

將我們上面的代碼復制到IDEA中運行,先啟動服務端在啟動客戶端會得到以下預期效果:

服務端控制臺列印:
在這里插入圖片描述
客戶端控制臺列印:
在這里插入圖片描述
我們關閉服務端后,客戶端控制臺列印:
在這里插入圖片描述
因為服務端關閉,觸發了客戶端 EchoClientHandler 中的exceptionCaught()方法,列印出了例外堆疊并關閉了連接,

這只是一個簡單的應用程式,但是它可以伸縮到支持數千個并發連接——每秒可以比普通的基于套接字的 Java 應用程式處理多得多的訊息,

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

標籤:其他

上一篇:springboot~sharding-jdbc實作分庫分表

下一篇:返回列表

標籤雲
其他(159590) Python(38169) JavaScript(25446) Java(18123) C(15231) 區塊鏈(8268) C#(7972) AI(7469) 爪哇(7425) MySQL(7208) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5340) 反应(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(2433) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1976) 功能(1967) Web開發(1951) HtmlCss(1942) C++(1922) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1878) .NETCore(1861) 谷歌表格(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需要的運行環境很簡單,只有2個。 - JDK 1.8+ - Apache Maven 3.3.9+ # 二、Netty 客戶端/服務器概覽 ![在這里插入圖片描述](https://img-blog.csdnimg.cn/c49191e6ee6e448f8c525b450 ......

    uj5u.com 2023-05-25 07:51:50 more
  • springboot~sharding-jdbc實作分庫分表

    # 原因 當mysql資料庫單表大于1千萬以后,查詢的性能就不能保證了,我們必須考慮分庫,分表的方案了,還好,sharding-jdbc可以很優雅的與springboot對接,完成對mysql的分庫和分表。 # 依賴整理 > 為了不影響其它小容量的表,所有添加了動態資料源,只對需要分庫分表的進行配置 ......

    uj5u.com 2023-05-25 07:51:43 more
  • Maven的概述

    # Maven的概述 @[toc] Java 專案開發程序中,構建指的是使用『原材料生產產品』的程序。 - 原材料 - Java 源代碼 - 基于 HTML 的 Thymeleaf 檔案 - 圖片 - 組態檔 - …… - 產品 - 一個可以在服務器上運行的專案 構建程序包含的主要的環節: - 清 ......

    uj5u.com 2023-05-25 07:50:11 more
  • 一步步完整搭建一個圖紙管理系統(Django+Vue3)

    # 圖紙管理系統 ## 一、初步構建后端專案 ### 1、打開已經創建好的虛擬環境:激活activate(推薦使用虛擬環境)并創建專案 ![](https://img2023.cnblogs.com/blog/2240937/202305/2240937-20230524164953360-1300 ......

    uj5u.com 2023-05-25 07:48:54 more
  • Python檔案讀寫、StringIO和BytesIO

    ### StringIO和BytesIO 很多時候,資料讀寫不一定是檔案,也可以在記憶體中讀寫。StringIO就是在記憶體中讀寫str。 要把str寫入StringIO,我們需要先創建一個StringIO,然后,像檔案一樣寫入即可: ```python >>> from io import Strin ......

    uj5u.com 2023-05-25 07:48:34 more
  • Python從0到1丨了解影像形態學運算中腐蝕和膨脹

    摘要:這篇文章將詳細講解影像形態學知識,主要介紹影像腐蝕處理和膨脹處理。 本文分享自華為云社區《[Python從零到壹] 四十七.影像增強及運算篇之腐蝕和膨脹詳解》,作者: eastmount 。 一.形態學理論知識 數學形態學的應用可以簡化影像資料,保持它們基本的形狀特征,并出去不相干的結構。數學 ......

    uj5u.com 2023-05-25 07:48:19 more
  • < Python全景系列-6 > 掌握Python面向物件編程的關鍵:深度探索類

    Python全景系列的第六篇,本文將深入探討Python語言中的核心概念:類(Class)和物件(Object)。我們將介紹這些基本概念,然后通過示例代碼詳細展示Python中的類和物件如何作業,包括定義、實體化和修改等操作。本文將幫助您更深入地理解Python中的面向物件編程(OOP),并從中提出... ......

    uj5u.com 2023-05-25 07:47:53 more
  • Java設計模式-享元模式

    # 簡介 在Java領域的軟體開發中,設計模式是提高代碼可維護性和可擴展性的重要工具。其中,享元模式是一種被廣泛使用的設計模式,它通過優化物件的重用來提升系統性能。 享元模式是一種結構型設計模式,旨在通過共享物件來減少系統中的物件數量,從而提升性能和減少記憶體消耗。在享元模式中,物件分為兩類:內部狀態 ......

    uj5u.com 2023-05-25 07:47:28 more
  • Java語法基礎

    # Java語法基礎 ## 注釋 注釋是對代碼的解釋和說明文字,可以提高程式的可讀性,因此在程式中添加必要的注釋文字十分重要。Java中的注釋分為三種: 單行注釋。單行注釋的格式是使用//,從//開始至本行結尾的文字將作為注釋文字。 ~~~java // 這是單行注釋文字 ~~~ 多行注釋。多行注釋 ......

    uj5u.com 2023-05-25 07:47:24 more
  • 【MyBatis】saveBatch 性能調優

    最近在壓測一批介面,發現介面處理速度慢的有點超出預期,感覺很奇怪,后面定位發現是資料庫批量保存這塊很慢。 這個專案用的是 mybatis-plus,批量保存直接用的是 mybatis-plus 提供的 saveBatch。 我點進去看了下原始碼,感覺有點不太對勁: 繼續追蹤了下,從這個代碼來看,確實是 ......

    uj5u.com 2023-05-25 07:41:50 more