主頁 > 後端開發 > Java NIO原理 (Selector、Channel、Buffer、零拷貝、IO多路復用)

Java NIO原理 (Selector、Channel、Buffer、零拷貝、IO多路復用)

2023-06-12 07:41:52 後端開發

系列文章目錄和關于我

零丶背景

最近有很多想學的,像netty的使用、原理原始碼,但是苦于自己對于作業系統和nio了解不多,有點無從下手,遂學習之,

一丶網路io的程序

image-20230610201828763

上圖粗略描述了網路io的程序,了解其中的拷貝程序有利于我們理解非阻塞io,以及IO多路復用的必要性,

  1. 資料從網卡到內核緩沖區
    網卡通過DMA的方式將網路幀copy到內核空間

    并不是拷貝到內核空間就完事了,因為還需要根據協議對資料進行處理,

    所以網卡使用硬中斷通知cpu,cpu回應后會使用網卡注冊函式進行收包,然后協議層處理網路幀,

  2. 資料從內核緩沖區到用戶空間

    根據協議處理好的資料,還需要拷貝到用戶空間才能被運行在內核態的應用程式使用==>cpu進行資料拷貝,隨后內核喚醒用戶行程,相當于我們的java程式從阻塞io中被喚醒,繼續執行下一行代碼的執行,

二丶Socket通信程序與其中的阻塞點

img

這其中有幾個阻塞的程序

  • accept 系統呼叫:等待客戶端建立tcp連接

    這個問題不大,沒有連接那么阻塞服務端執行緒,可以節約cpu資源,

  • read系統呼叫:等待請求資料來到用戶空間

    資料從網卡到用戶空間的程序,執行緒時阻塞的

  • Servlet#service 處理請求是一個同步程序

    tomcat根據http協議構造request,并和response作為引數,找到對應Servlet呼叫service方法,Servlet#service方法執行結束,回傳內容才能通過write系統呼叫回應資料,

    這導致在業務處理上需要使用執行緒池來讓服務端可以處理多個并發請求,

  • write系統呼叫:回應資料寫回

    write系統呼叫將servlet處理后的回應資料,寫回到檔案描述符中,

三丶NIO解決了什么問題

1.單執行緒監測若干個檔案描述符是否可以執行IO操作

這就是常說的IO多路復用,那為什么需要IO多路復用?

盡量使用較少的系統資源處理更多的連接,如果當前單臺服務器接收了1w個請求,服務端當如何處理?

1.1 傳統BIO模型

image-20230611122304883

上面是一段java BIO模型并發處理多請求的實體代碼,它有以下不足

  • 大量的執行緒占用很大的記憶體空間
  • 執行緒切換會帶來很大的開銷
  • process方法中需要需要呼叫read系統呼叫,阻塞直到可讀,并沒有真正進行讀寫操作,

1.2. 非阻塞IO

image-20230611124511674

上面是非阻塞IO的一個實體

socketChannel.configureBlocking(false)可以讓后續的read在通道資料沒有就緒的時候直接回傳-1,而不是讓執行緒阻塞,這個特性讓調度執行緒池中的執行緒減少了阻塞,從而節省了執行緒資源,

image-20230405164149306

但是這種方式也不是沒有任何缺點,多次系統意味著多次系統呼叫,每次系統呼叫都需要,用戶態<=>內核態的來回切換,需要cpu保存行程的背景關系,呼叫結束還需要恢復行程的背景關系,

image-20230611133144345

1.3 IO多路復用

image-20230611135115721

如上是Java IO多路復用的簡陋例子,作業系統提供了多路復用的機制,將連接上來的客戶端都進行注冊,然后不斷回圈掃描各個客戶端連接,監聽客戶端的請求,但是,多路復用輪詢掃描各個客戶端連接的程序在作業系統內核中進行極大的加快了多路復用的效率,減少了用戶態和內核態的切換

2.減少堆內記憶體<=>堆外記憶體的拷貝開銷

使用NIO Channel讀寫時需要需要先讀到堆外記憶體,然后拷貝到堆內記憶體,如果直接使用堆外記憶體則可以減少堆外到堆內的拷貝程序,

下圖是將Channel資料讀取到Buffer,呼叫IOUtil#read的原始碼

image-20230611154939538

下圖是將Buffer資料寫入到Channel,呼叫IOUtil#write的原始碼

image-20230611155307043

2.1 為什么需要再堆外記憶體和堆內記憶體來回捯飭?

寫入Buffer資料到檔案描述符,or讀取檔案描述符資料到Buffer都是需要進行系統呼叫的,執行系統呼叫依賴于執行native方法,而執行native方法的執行緒被認為是處于SafePoint,處于SafePoint就有可能發生 GC 重排列物件記憶體的情況,

并且這個寫入和讀取是針對地址的(如下圖,最終的native呼叫需要傳入地址)如果寫入或者讀取buffer由于gc移動,那么地址會改變,但是native方法呼叫可不管這個,就導致讀寫出現錯誤,因此需要依賴于堆外記憶體,

image-20230611155717440

2.2 為什么Socket基于Inpustream,OutputStream沒有這個問題

image-20230611160657076

以SokcetInputStream的讀為例,讀最終呼叫socktRead0這個native方法,入參fd是當前Socket對應的檔案描述符,byte陣列就是資料最終讀入的目的地,

下圖是native 方法socketRead0的實作

image-20230611161135896

可以看到,其實是先將socket fd內容讀取到c語言宣告的陣列,然后拷貝到Java byte[],這個c語言宣告的陣列其實作用類似于直接記憶體!

3.減少內核空間和用戶空間的拷貝開銷

上面說了直接記憶體的作用:減少堆外堆內的拷貝開銷,無論堆外堆內,都是用戶空間的拷貝,

3.1 DMA控制器替CPU打工

image-20230611162822090

上圖是讀取磁盤檔案的時序圖,可以看到如果沒有DMA技術,藍色部分需要CPU來完成,將浪費寶貴的資源,

再DMA讀取到足夠資料后,會發送中斷信號給CPU,讓CPU將內核緩沖區資料,拷貝到用戶緩沖區,隨后CPU再來調度Java程式,Java程式才能操作到用戶緩沖區的資料,

3.2 零拷貝

3.2.1 傳統檔案傳輸

如下圖是我們使用IO流,讀取磁盤檔案,通過Socket API 發送的流程,其中需要read,和 write 系統呼叫,每次系統呼叫都意味著用戶態與內核態的背景關系切換

并且還有四次資料拷貝,其中兩次由DMA負責打工,兩次由CPU負責拷貝,

image-20230611163605920

如何優化:

  • 如果Java程式不需要對磁盤資料內容進行再加工(業務操作)那么不需要拷貝到用戶空間,從而減少拷貝次數
  • 由于用戶空間沒有操作網卡和磁盤的權限,操作這些設備需要由作業系統內核完成,那么如果作業系統提供新的系統呼叫函式,豈不是就可以減少用戶態與內核態的背景關系切換
3.2.2 mmap + write

image-20230611164712477

  • 應用行程呼叫了 mmap() 后,DMA 會把磁盤的資料拷貝到內核的緩沖區里,接著,應用行程跟作業系統內核共享這個緩沖區;
  • 應用行程再呼叫 write(),作業系統直接將內核緩沖區的資料拷貝到 socket 緩沖區中,這一切都發生在內核態,由 CPU 來搬運資料;
  • 最后,把內核的 socket 緩沖區里的資料,拷貝到網卡的緩沖區里,這個程序是由 DMA 搬運的

所以mmap優化了什么?

mmap并沒有減少系統呼叫帶來的內核態用戶態切換開銷,只是應用程式和內核共享緩沖區,從而讓cpu可以直接將內核緩沖區的資料,拷貝到socket緩沖區,不需要拷貝到用戶緩沖區,再從用戶緩沖區拷貝到socket緩沖區,

3.2.3 sendfile

linux 提供sendfile系統呼叫,只需這一個系統呼叫就可以從一個檔案描述符拷貝資料到另外一個檔案描述符

image-20230611165644768

image-20230611165437243

sendfile可以減少write,read導致的系統呼叫,從而優化效率,

如果網卡支持 SG-DMA(The Scatter-Gather Direct Memory Access)技術,那么還可以進一步優化,

image-20230611165914874

  1. 通過 DMA 將磁盤上的資料拷貝到內核緩沖區里;
  2. 緩沖區描述符和資料長度傳到 socket 緩沖區,這樣網卡的 SG-DMA 控制器就可以直接將內核快取中的資料拷貝到網卡的緩沖區里,此程序不需要將資料從作業系統內核緩沖區拷貝到 socket 緩沖區中,這樣就減少了一次資料拷貝,

這便是所謂的零拷貝,減少記憶體層面拷貝資料的次數,以及系統呼叫內核態用戶態的切換,從而優化性能,

3.3 NIO中的零拷貝

3.3.1 FileChannel#map

NIO中的FileChannel.map()方法使用了mmap系統呼叫實作記憶體映射方式

將內核緩沖區的記憶體和用戶緩沖區的記憶體做了一個地址映射,這種方式適合讀取大檔案,同時也能對檔案內容進行更改,但是如果其后要通過SocketChannel發送,還是需要CPU進行資料的拷貝,

image-20230611172227076

如上是MappedByteBuffer的獲取方式,其實底層是通過反射呼叫DirectByteBuffer的構造方法實作的,其中的cleaner是直接記憶體的回收器,傳入的unmapper會被回呼,從而呼叫native方法實作資源釋放,

image-20230611172125469

這種方式適合讀取大檔案,同時也能對檔案內容進行更改,

3.3.2 FileChannel#transferTo,transerFrom

image-20230611173326560

在作業系統層面是呼叫的一個sendFile系統呼叫,通過這個系統呼叫,可以在內核層直接完成檔案內容的拷貝,

4.FileChannel#force強制刷盤

由于CPU的運行速度非常快,所以CPU在執行指令時,通常只能與快取進行互動,而不適合直接操作像磁盤、網卡這樣的硬體,也因此,在進行檔案寫入時,作業系統也是先寫入到page cache中,快取起來,然后再往硬體寫入,

快取有利也有弊,使用page cache頁快取,應用程式將資料都寫入到了page cache中,但是卻沒有真正寫入磁盤,如果這個時候出現斷電,那么將出現快取資料丟失,

FileChannel#force會進行fsync系統呼叫

image-20230611174940240

fsync可以實作將page cache快取內容進行落盤,從而保證不丟失(redis aof可以設定持久化機制,通常設定每秒落盤一次,這里落盤也是fsync系統呼叫),為了性能考慮,應用程式不可能每寫入一點資料就呼叫fsync,fsync也是有性能損耗的,

四丶IO多路復用 select/poll/epoll

上面我們聊到了IO多路復用解決了什么問題,以及NIO Selector的基本使用,但是沒有探究在作業系統層面是如何實作的,下面來學習一下,

1.select系統呼叫

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout)
  • nfds: 最大的檔案描述符+1,代表監聽這一組描述符(為什么要+1?因為除了當前最大描述符之外,還有可能有新的fd連接上來)
  • fd_set: 是一個位圖集合, 對于同一個檔案描述符,可以監聽不同的事件
  • readfds:檔案描述符“可讀”事件
  • writefds:檔案描述符“可寫”事件
  • exceptfds:檔案描述符“例外”事件,一般內核用的,實際編程很少使用
  • timeout:超時時間:0是立即回傳,-1是一直阻塞,如果大于0,則達到設定值的微秒數即回傳
  • 回傳值: 所監聽的所有監聽集合中滿足條件的總數(滿足條件的讀、寫、例外事件的總數),出錯時回傳-1,并設定errno,如果超時時間觸發,則回傳0

select 其實就是把NIO中用戶態要遍歷的fd陣列拷貝到了內核態,讓內核態來遍歷,因為用戶態判斷socket是否有資料依舊需要通過系統呼叫,切換到內核態進行,

可以看到select依賴了很多位圖引數,系統呼叫完后需要用戶程式遍歷一次位圖才能直到哪一個fd具備了io事件,并且這個位圖大小最大為1024,導致select用起來需要很多位操作并且最多只能支持1024路IO,

2.poll系統呼叫

int poll(struct pollfd *fds, nfds_t nfds/*最大監聽的檔案描述符個數*/, int timeout/*最大監聽的檔案描述符個數*/);

其中pollfd為:

struct pollfd {
      int   fd;         /* file descriptor */
      short events;     /* requested events */
      short revents;    /* returned events */
};

poll可以看作升級版select,它突破了1024個檔案描述符的限制,并且poll函式的監聽和回傳是分開的,簡化了代碼實作,

雖然poll不需要遍歷所有的檔案描述符了,只需要遍歷加入陣列中的描述符,范圍縮小了很多,但缺點仍然是需要遍歷,當加入陣列描述符很多,但是存在事件的fd很少,這個遍歷操作還是有點不劃算的,

3.epoll系統呼叫

在linux環境下,java nio中的selector就是基于epoll實作的,

3.1 epoll_create

int epoll_create(int size)
    //回傳一個fd
    //傳入大小作為參考值

epoll_create回傳一個特殊的檔案描述符,它代表紅黑樹的根節點,size則是樹的大小,它代表你將監聽多少個檔案描述符,epoll_create將按照傳入的大小,構造出一棵大小為size的紅黑樹,

3.2 epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// epfd 是epoll_create的回傳值,也就說紅黑樹的根節點
// op 表示操作,比如增加,修改,洗掉
//fd 是需要增加,修改,洗掉的檔案描述符
// struct epoll_event *event 是一個結構體,如下
struct epoll_event {
               uint32_t     events;      /* Epoll events 讀事件or寫事件,or 例外事件*/
               epoll_data_t data;        /* User data variable */
           };
 typedef union epoll_data {
               void        *ptr;
               int          fd;//代表一個檔案描述符,初始化的時候傳入需要監聽的檔案描述符,當監聽回傳時,此處會傳出一個有事件發生的檔案描述符,因此,無需我們遍歷得到結果了
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;

用來操作epoll句柄,可以使用該函式往紅黑樹里增加檔案描述符,修改檔案描述符,和洗掉檔案描述符,

3.3 epoll_wait

int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);
//epfd 是epoll_create的回傳值,也就說紅黑樹的根節點
// struct epoll_event *events 是一個陣列,回傳的所有觸發了事件的檔案描述符集合
//maxevents代表這個陣列的大小
//timeout 0代表立即回傳,-1代表永久阻塞,如果大于0,則代表超時等待毫秒數

3.4 水平觸發,邊緣觸發

epoll有兩種觸發方式,分別為水平觸發邊沿觸發

  • 水平觸發

    只要有資料處于就緒狀態,那么可讀事件就會一直觸發,

    舉個例子,假設客戶端一次性發來了4K資料 ,但是服務器recv函式定義的buffer大小僅為1024位元組,那么一次肯定是不能將所有資料都讀取完的,這時候就會繼續觸發可讀事件,直到所有資料都處理完成,

    epoll默認的觸發方式就是水平觸發,

  • 邊緣觸發

    只有資料發送過來的時候會觸發一次,即使資料沒有讀取完,也不會繼續觸發,

  • 觸發方式的設定:

    水平觸發和邊沿觸發在內核里 使用兩個bit mask區分,分別為:

    • EPOLLLT 水平 觸發
    • EPOLLET 邊沿觸發

    需要在注冊事件的時候將其與需要注冊的事件做一個位或運算即可:

    ev.events = EPOLLIN;    //LT
    ev.events = EPOLLIN | EPOLLET;   //ET
    

4.總結

select函式需要一次性傳入所有需要監控的連接(在內核中是FD),并在內核中對這些FD進行持續的掃描,當發現其中有FD不老實時,就會通知應用程式有客戶端事件發生了, 上層應用接到通知后,就只能自己再去遍歷所有的FD,尋找有事件發生的連接,然后進行業務處理,
但是select受限于作業系統,掃描的FD個數是受限的,

于是出現了Poll函式,解決了slelect檔案描述符受限的問題,但是,上層應用程式依然要自己去遍歷所有客戶端,尋找哪個客戶端上有事件發 生,高并發場景下,性能依然嚴重受限,
于是又出現了epoll機制,

epoll機制會直接回傳有事件發生的FD,這樣就省掉了上層應用頻繁掃描所有客戶端的消耗,進一步解決多路復用的高并發問題,

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

標籤:其他

上一篇:【VS Code 與 Qt6】運用事件過濾器批量操作子級組件

下一篇:返回列表

標籤雲
其他(160788) Python(38219) JavaScript(25492) Java(18216) C(15237) 區塊鏈(8270) C#(7972) AI(7469) 爪哇(7425) MySQL(7246) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4589) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2435) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1984) 功能(1967) HtmlCss(1961) Web開發(1951) C++(1933) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1881) .NETCore(1863) 谷歌表格(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
最新发布
  • Java NIO原理 (Selector、Channel、Buffer、零拷貝、IO多路復用)

    [系列文章目錄和關于我](https://www.cnblogs.com/cuzzz/p/16609728.html) ## 零丶背景 最近有很多想學的,像netty的使用、原理原始碼,但是苦于自己對于作業系統和nio了解不多,有點無從下手,遂學習之。 ## 一丶網路io的程序 ![image-202 ......

    uj5u.com 2023-06-12 07:41:52 more
  • 【VS Code 與 Qt6】運用事件過濾器批量操作子級組件

    如果某個派生自 QObject 的類重寫 eventFilter 方法,那它就成了事件過濾器(Event Filter)。該方法的宣告如下: virtual bool eventFilter(QObject *watched, QEvent *event); watched 引數是監聽事件的物件,即 ......

    uj5u.com 2023-06-12 07:41:25 more
  • Go 連接 MySQL之 MySQL 預處理

    # Go 連接 MySQL之 MySQL 預處理 ## 一、ChatGPT 關于 MySQL 預處理 的回答 ### 問:什么是MySQL 的預處理 具體執行程序時什么 #### ChatGPT 答: MySQL的預處理是一種在執行SQL陳述句之前,先進行編譯和優化的機制。它將SQL陳述句分成兩個階段: ......

    uj5u.com 2023-06-12 07:40:49 more
  • Java 網路編程 —— 基于 UDP 的資料報和套接字

    ## UDP 簡介 UDP(User Datagram Protocol,用戶資料報協議)是傳輸層的另一種協議,比 TCP 具有更快的傳輸速度,但是不可靠。UDP 發送的資料單元被稱為 UDP 資料報,當網路傳輸 UDP 資料報時,無法保證資料報一定到達目的地,也無法保證各個資料報按發送的順序到達目 ......

    uj5u.com 2023-06-12 07:40:45 more
  • Go語言中的init函式: 特點、用途和注意事項

    # 1. 引言 在Go語言中,`init()`函式是一種特殊的函式,用于在程式啟動時自動執行一次。它的存在為我們提供了一種機制,可以在程式啟動時進行一些必要的初始化操作,為程式的正常運行做好準備。 在這篇文章中,我們將詳細探討`init()`函式的特點、用途和注意事項,希望能幫助你更好地理解和使用這 ......

    uj5u.com 2023-06-12 07:40:41 more
  • 【技識訓累】SpringBoot中的簡介與配置【一】

    博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ......

    uj5u.com 2023-06-12 07:40:36 more
  • Go 語言連接資料庫實作增刪改查

    title: "Go 語言連接資料庫實作增刪改查" date: 2023-06-10T18:55:16+08:00 draft: true tags: ["Go"] categories: ["Go"] # Go 連接 MySQL實作增刪改查 ## 一、初始化連接 ### 創建專案 ![](http ......

    uj5u.com 2023-06-12 07:35:18 more
  • Maven常用命令及其作用

    一、Maven常用命令及其作用 Maven的生命周期包括:clean、validate、compile、test、package、verify、install、site、deploy,其中需要注意的是:執行后面的命令時,前面的命令自動得到執行,(其中,也可以跳過其中的步驟,如:test,在mvn i ......

    uj5u.com 2023-06-12 07:35:10 more
  • 【技識訓累】Python中的Pandas庫【三】

    博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ......

    uj5u.com 2023-06-12 07:34:50 more
  • C++ 指標

    # 一、C++ 指標的算術運算 ## 遞增一個指標 我們喜歡在程式中使用指標代替陣列,因為變數指標可以遞增,而陣列不能遞增,因為陣列是一個常量指標。下面的程式遞增變數指標,以便順序訪問陣列中的每一個元素: ``` 實體 #include using namespace std; const int ......

    uj5u.com 2023-06-11 07:17:06 more