Linux下的I/O復用與epoll詳解

http://www.cnblogs.com/lojunren/p/3856290.html

前言

? ? ? I/O多路復用有很多種實現。在linux上,2.4內核前主要是select和poll,自Linux 2.6內核正式引入epoll以來,epoll已經成為了目前實現高性能網絡服務器的必備技術。盡管他們的使用方法不盡相同,但是本質上卻沒有什么區別。本文將重點探討將放在EPOLL的實現與使用詳解。

為什么會是EPOLL

select的缺陷

高并發的核心解決方案是1個線程處理所有連接的“等待消息準備好”,這一點上epoll和select是無爭議的。但select預估錯誤了一件事,當數十萬并發連接存在時,可能每一毫秒只有數百個活躍的連接,同時其余數十萬連接在這一毫秒是非活躍的。select的使用方法是這樣的:
返回的活躍連接 ==select(全部待監控的連接)。
什么時候會調用select方法呢?在你認為需要找出有報文到達的活躍連接時,就應該調用。所以,調用select在高并發時是會被頻繁調用的。這樣,這個頻繁調用的方法就很有必要看看它是否有效率,因為,它的輕微效率損失都會被“頻繁”二字所放大。它有效率損失嗎?顯而易見,全部待監控連接是數以十萬計的,返回的只是數百個活躍連接,這本身就是無效率的表現。被放大后就會發現,處理并發上萬個連接時,select就完全力不從心了。
此外,在Linux內核中,select所用到的FD_SET是有限的,即內核中有個參數__FD_SETSIZE定義了每個FD_SET的句柄個數。
? ? ? ?
1 /linux/posix_types.h:
2 
3 #define __FD_SETSIZE         1024
? ? ? 其次,內核中實現?select是用輪詢方法,即每次檢測都會遍歷所有FD_SET中的句柄,顯然,select函數執行時間與FD_SET中的句柄個數有一個比例關系,即?select要檢測的句柄數越多就會越費時。看到這里,您可能要要問了,你為什么不提poll?筆者認為select與poll在內部機制方面并沒有太大的差異。相比于select機制,poll只是取消了最大監控文件描述符數限制,并沒有從根本上解決select存在的問題。
接下來我們看張圖,當并發連接為較小時,select與epoll似乎并無多少差距。可是當并發連接上來以后,select就顯得力不從心了。
? ? ? ? 圖 1.主流I/O復用機制的benchmark

?epoll高效的奧秘

? ? ? epoll精巧的使用了3個方法來實現select方法要做的事:

  1. 新建epoll描述符==epoll_create()
  2. epoll_ctrl(epoll描述符,添加或者刪除所有待監控的連接)
  3. 返回的活躍連接 ==epoll_wait( epoll描述符?)
與select相比,epoll分清了頻繁調用和不頻繁調用的操作。例如,epoll_ctrl是不太頻繁調用的,而epoll_wait是非常頻繁調用的。這時,epoll_wait卻幾乎沒有入參,這比select的效率高出一大截,而且,它也不會隨著并發連接的增加使得入參越發多起來,導致內核執行效率下降。
?
? ? ? 筆者在這里不想過多貼出epoll的代碼片段。如果大家有興趣,可以參考文末貼出的博文鏈接和Linux相關源碼。

? ? ??要深刻理解epoll,首先得了解epoll的三大關鍵要素:mmap、紅黑樹、鏈表

? ? ??epoll是通過內核與用戶空間mmap同一塊內存實現的。mmap將用戶空間的一塊地址和內核空間的一塊地址同時映射到相同的一塊物理內存地址(不管是用戶空間還是內核空間都是虛擬地址,最終要通過地址映射映射到物理地址),使得這塊物理內存對內核和對用戶均可見,減少用戶態和內核態之間的數據交換。內核可以直接看到epoll監聽的句柄,效率高。

? ? ? 紅黑樹將存儲epoll所監聽的套接字。上面mmap出來的內存如何保存epoll所監聽的套接字,必然也得有一套數據結構,epoll在實現上采用紅黑樹去存儲所有套接字,當添加或者刪除一個套接字時(epoll_ctl),都在紅黑樹上去處理,紅黑樹本身插入和刪除性能比較好,時間復雜度O(logN)。

? ? ??

? ? ? 下面幾個關鍵數據結構的定義? ?

?View Code

? ? ??

?View Code

? ? ??添加以及返回事件

? ? ??通過epoll_ctl函數添加進來的事件都會被放在紅黑樹的某個節點內,所以,重復添加是沒有用的。當把事件添加進來的時候時候會完成關鍵的一步,那就是該事件都會與相應的設備(網卡)驅動程序建立回調關系,當相應的事件發生后,就會調用這個回調函數,該回調函數在內核中被稱為:ep_poll_callback,這個回調函數其實就所把這個事件添加到rdllist這個雙向鏈表中。一旦有事件發生,epoll就會將該事件添加到雙向鏈表中。那么當我們調用epoll_wait時,epoll_wait只需要檢查rdlist雙向鏈表中是否有存在注冊的事件,效率非常可觀。這里也需要將發生了的事件復制到用戶態內存中即可。

? ? ?

? ? ? epoll_wait的工作流程:

  1. epoll_wait調用ep_poll,當rdlist為空(無就緒fd)時掛起當前進程,直到rdlist不空時進程才被喚醒。
  2. 文件fd狀態改變(buffer由不可讀變為可讀或由不可寫變為可寫),導致相應fd上的回調函數ep_poll_callback()被調用。
  3. ep_poll_callback將相應fd對應epitem加入rdlist,導致rdlist不空,進程被喚醒,epoll_wait得以繼續執行。
  4. ep_events_transfer函數將rdlist中的epitem拷貝到txlist中,并將rdlist清空。
  5. ep_send_events函數(很關鍵),它掃描txlist中的每個epitem,調用其關聯fd對用的poll方法。此時對poll的調用僅僅是取得fd上較新的events(防止之前events被更新),之后將取得的events和相應的fd發送到用戶空間(封裝在struct?epoll_event,從epoll_wait返回)。? ???

小結

? ? ? ?表 1. select、poll和epoll三種I/O復用模式的比較( 摘錄自《linux高性能服務器編程》)

系統調用

select

poll

epoll

?

事件集合

用哦過戶通過3個參數分別傳入感興趣的可讀,可寫及異常等事件

內核通過對這些參數的在線修改來反饋其中的就緒事件

這使得用戶每次調用select都要重置這3個參數

統一處理所有事件類型,因此只需要一個事件集參數。

用戶通過pollfd.events傳入感興趣的事件,內核通過

修改pollfd.revents反饋其中就緒的事件

內核通過一個事件表直接管理用戶感興趣的所有事件。

因此每次調用epoll_wait時,無需反復傳入用戶感興趣

的事件。epoll_wait系統調用的參數events僅用來反饋就緒的事件

應用程序索引就緒文件

描述符的時間復雜度

O(n)

O(n)

O(1)

最大支持文件描述符數

一般有最大值限制

65535

65535

工作模式

LT

LT

支持ET高效模式

內核實現和工作效率 采用輪詢方式檢測就緒事件,時間復雜度:O(n)

采用輪詢方式檢測就緒事件,時間復雜度:O(n)

采用回調方式檢測就緒事件,時間復雜度:O(1)

? ? ? 行文至此,想必各位都應該已經明了為什么epoll會成為Linux平臺下實現高性能網絡服務器的首選I/O復用調用。

? ? ??需要注意的是:epoll并不是在所有的應用場景都會比select和poll高很多。尤其是當活動連接比較多的時候,回調函數被觸發得過于頻繁的時候,epoll的效率也會受到顯著影響!所以,epoll特別適用于連接數量多,但活動連接較少的情況。

? ? ? 接下來,筆者將介紹一下epoll使用方式的注意點。

?EPOLL的使用?

?文件描述符的創建?

?View Code

? ? ? 在epoll早期的實現中,對于監控文件描述符的組織并不是使用紅黑樹,而是hash表。這里的size實際上已經沒有意義。

? 注冊監控事件

?View Code
函數說明:
fd:要操作的文件描述符
op:指定操作類型
操作類型:
EPOLL_CTL_ADD:往事件表中注冊fd上的事件
EPOLL_CTL_MOD:修改fd上的注冊事件
EPOLL_CTL_DEL:刪除fd上的注冊事件
event:指定事件,它是epoll_event結構指針類型
epoll_event定義:
?View Code
結構體說明:
events:描述事件類型,和poll支持的事件類型基本相同(兩個額外的事件:EPOLLET和EPOLLONESHOT,高效運作的關鍵)
data成員:存儲用戶數據
?View Code

? epoll_wait函數

?View Code
函數說明:
返回:成功時返回就緒的文件描述符的個數,失敗時返回-1并設置errno
timeout:指定epoll的超時時間,單位是毫秒。當timeout為-1是,epoll_wait調用將永遠阻塞,直到某個時間發生。當timeout為0時,epoll_wait調用將立即返回。
maxevents:指定最多監聽多少個事件
events:檢測到事件,將所有就緒的事件從內核事件表中復制到它的第二個參數events指向的數組中。

?EPOLLONESHOT事件

使用場合:
一個線程在讀取完某個socket上的數據后開始處理這些數據,而數據的處理過程中該socket又有新數據可讀,此時另外一個線程被喚醒來讀取這些新的數據。
于是,就出現了兩個線程同時操作一個socket的局面。可以使用epoll的EPOLLONESHOT事件實現一個socket連接在任一時刻都被一個線程處理。
作用:
對于注冊了EPOLLONESHOT事件的文件描述符,操作系統最多出發其上注冊的一個可讀,可寫或異常事件,且只能觸發一次。
使用:
注冊了EPOLLONESHOT事件的socket一旦被某個線程處理完畢,該線程就應該立即重置這個socket上的EPOLLONESHOT事件,以確保這個socket下一次可讀時,其EPOLLIN事件能被觸發,進而讓其他工作線程有機會繼續處理這個sockt。
效果:
盡管一個socket在不同事件可能被不同的線程處理,但同一時刻肯定只有一個線程在為它服務,這就保證了連接的完整性,從而避免了很多可能的競態條件。

?LT與ET模式

? ? ? 在這里,筆者強烈推薦《徹底學會使用epoll》系列博文,這是筆者看過的,對epoll的ET和LT模式講解最為詳盡和易懂的博文。下面的實例均來自該系列博文。限于篇幅原因,很多關鍵的細節,不能完全摘錄。

? ? ? 話不多說,直接上代碼。

程序一:

?View Code

編譯并運行,結果如下:

?

  1. 當用戶輸入一組字符,這組字符被送入buffer,字符停留在buffer中,又因為buffer由空變為不空,所以ET返回讀就緒,輸出”welcome to epoll's world!”。
  2. 之后程序再次執行epoll_wait,此時雖然buffer中有內容可讀,但是根據我們上節的分析,ET并不返回就緒,導致epoll_wait阻塞。(底層原因是ET下就緒fd的epitem只被放入rdlist一次)。
  3. 用戶再次輸入一組字符,導致buffer中的內容增多,根據我們上節的分析這將導致fd狀態的改變,是對應的epitem再次加入rdlist,從而使epoll_wait返回讀就緒,再次輸出“Welcome to epoll's world!”。

接下來我們將上面程序的第11行做如下修改:

?View Code

編譯并運行,結果如下:

?

? ? ? 程序陷入死循環,因為用戶輸入任意數據后,數據被送入buffer且沒有被讀出,所以LT模式下每次epoll_wait都認為buffer可讀返回讀就緒。導致每次都會輸出”welcome to epoll's world!”。

程序二:

復制代碼
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <sys/epoll.h>
 4 
 5 int main(void)
 6 {
 7     int epfd,nfds;
 8     struct epoll_event ev,events[5];                    //ev用于注冊事件,數組用于返回要處理的事件
 9     epfd = epoll_create(1);                                //只需要監聽一個描述符——標準輸入
10     ev.data.fd = STDIN_FILENO;
11     ev.events = EPOLLIN;                                //監聽讀狀態同時設置LT模式
12     epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev);    //注冊epoll事件
13     for(;;)
14     {
15         nfds = epoll_wait(epfd, events, 5, -1);
16         for(int i = 0; i < nfds; i++)
17         {
18             if(events[i].data.fd==STDIN_FILENO)
19             {
20                 char buf[1024] = {0};
21                 read(STDIN_FILENO, buf, sizeof(buf));
22                 printf("welcome to epoll's word!\n");
23             }            
24         }
25     }
26 }
復制代碼

編譯并運行,結果如下:

?

? ? ? 本程序依然使用LT模式,但是每次epoll_wait返回讀就緒的時候我們都將buffer(緩沖)中的內容read出來,所以導致buffer再次清空,下次調用epoll_wait就會阻塞。所以能夠實現我們所想要的功能——當用戶從控制臺有任何輸入操作時,輸出”welcome to epoll's world!”

程序三:

復制代碼
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <sys/epoll.h>
 4 
 5 int main(void)
 6 {
 7     int epfd,nfds;
 8     struct epoll_event ev,events[5];                    //ev用于注冊事件,數組用于返回要處理的事件
 9     epfd = epoll_create(1);                                //只需要監聽一個描述符——標準輸入
10     ev.data.fd = STDIN_FILENO;
11     ev.events = EPOLLIN|EPOLLET;                        //監聽讀狀態同時設置ET模式
12     epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev);    //注冊epoll事件
13     for(;;)
14     {
15         nfds = epoll_wait(epfd, events, 5, -1);
16         for(int i = 0; i < nfds; i++)
17         {
18             if(events[i].data.fd==STDIN_FILENO)
19             {
20                 printf("welcome to epoll's word!\n");
21                 ev.data.fd = STDIN_FILENO;
22                 ev.events = EPOLLIN|EPOLLET;                        //設置ET模式
23                 epoll_ctl(epfd, EPOLL_CTL_MOD, STDIN_FILENO, &ev);    //重置epoll事件(ADD無效)
24             }            
25         }
26     }
27 }
復制代碼

編譯并運行,結果如下:

?

?

? ? ?程序依然使用ET,但是每次讀就緒后都主動的再次MOD?IN事件,我們發現程序再次出現死循環,也就是每次返回讀就緒。但是注意,如果我們將MOD改為ADD,將不會產生任何影響。別忘了每次ADD一個描述符都會在epitem組成的紅黑樹中添加一個項,我們之前已經ADD過一次,再次ADD將阻止添加,所以在次調用ADD?IN事件不會有任何影響。

程序四:

復制代碼
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <sys/epoll.h>
 4 
 5 int main(void)
 6 {
 7     int epfd,nfds;
 8     struct epoll_event ev,events[5];                    //ev用于注冊事件,數組用于返回要處理的事件
 9     epfd = epoll_create(1);                                //只需要監聽一個描述符——標準輸入
10     ev.data.fd = STDOUT_FILENO;
11     ev.events = EPOLLOUT|EPOLLET;                        //監聽讀狀態同時設置ET模式
12     epoll_ctl(epfd, EPOLL_CTL_ADD, STDOUT_FILENO, &ev);    //注冊epoll事件
13     for(;;)
14     {
15         nfds = epoll_wait(epfd, events, 5, -1);
16         for(int i = 0; i < nfds; i++)
17         {
18             if(events[i].data.fd==STDOUT_FILENO)
19             {
20                 printf("welcome to epoll's word!\n");
21             }            
22         }
23     }
24 }
復制代碼

編譯并運行,結果如下:

?

? ? ? 這個程序的功能是只要標準輸出寫就緒,就輸出“welcome to epoll's world”。我們發現這將是一個死循環。下面具體分析一下這個程序的執行過程:

  1. 首先初始buffer為空,buffer中有空間可寫,這時無論是ET還是LT都會將對應的epitem加入rdlist,導致epoll_wait就返回寫就緒。
  2. 程序想標準輸出輸出”welcome to epoll's world”和換行符,因為標準輸出為控制臺的時候緩沖是“行緩沖”,所以換行符導致buffer中的內容清空,這就對應第二節中ET模式下寫就緒的第二種情況——當有舊數據被發送走時,即buffer中待寫的內容變少得時候會觸發fd狀態的改變。所以下次epoll_wait會返回寫就緒。如此循環往復。

程序五:

復制代碼
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <sys/epoll.h>
 4 
 5 int main(void)
 6 {
 7     int epfd,nfds;
 8     struct epoll_event ev,events[5];                    //ev用于注冊事件,數組用于返回要處理的事件
 9     epfd = epoll_create(1);                                //只需要監聽一個描述符——標準輸入
10     ev.data.fd = STDOUT_FILENO;
11     ev.events = EPOLLOUT|EPOLLET;                        //監聽讀狀態同時設置ET模式
12     epoll_ctl(epfd, EPOLL_CTL_ADD, STDOUT_FILENO, &ev);    //注冊epoll事件
13     for(;;)
14     {
15         nfds = epoll_wait(epfd, events, 5, -1);
16         for(int i = 0; i < nfds; i++)
17         {
18             if(events[i].data.fd==STDOUT_FILENO)
19             {
20                 printf("welcome to epoll's word!");
21             }            
22         }
23     }
24 }
復制代碼

編譯并運行,結果如下:

?

? ? ? 與程序四相比,程序五只是將輸出語句的printf的換行符移除。我們看到程序成掛起狀態。因為第一次epoll_wait返回寫就緒后,程序向標準輸出的buffer中寫入“welcome to epoll's world!”,但是因為沒有輸出換行,所以buffer中的內容一直存在,下次epoll_wait的時候,雖然有寫空間但是ET模式下不再返回寫就緒。回憶第一節關于ET的實現,這種情況原因就是第一次buffer為空,導致epitem加入rdlist,返回一次就緒后移除此epitem,之后雖然buffer仍然可寫,但是由于對應epitem已經不再rdlist中,就不會對其就緒fd的events的在檢測了。

程序六:

復制代碼
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <sys/epoll.h>
 4 
 5 int main(void)
 6 {
 7     int epfd,nfds;
 8     struct epoll_event ev,events[5];                    //ev用于注冊事件,數組用于返回要處理的事件
 9     epfd = epoll_create(1);                                //只需要監聽一個描述符——標準輸入
10     ev.data.fd = STDOUT_FILENO;
11     ev.events = EPOLLOUT;                                //監聽讀狀態同時設置LT模式
12     epoll_ctl(epfd, EPOLL_CTL_ADD, STDOUT_FILENO, &ev);    //注冊epoll事件
13     for(;;)
14     {
15         nfds = epoll_wait(epfd, events, 5, -1);
16         for(int i = 0; i < nfds; i++)
17         {
18             if(events[i].data.fd==STDOUT_FILENO)
19             {
20                 printf("welcome to epoll's word!");
21             }            
22         }
23     }
24 }
復制代碼

編譯并運行,結果如下:

?

? ? ? ?程序六相對程序五僅僅是修改ET模式為默認的LT模式,我們發現程序再次死循環。這時候原因已經很清楚了,因為當向buffer寫入”welcome to epoll's world!”后,雖然buffer沒有輸出清空,但是LT模式下只有buffer有寫空間就返回寫就緒,所以會一直輸出”welcome to epoll's world!”,當buffer滿的時候,buffer會自動刷清輸出,同樣會造成epoll_wait返回寫就緒。

程序七:

復制代碼
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <sys/epoll.h>
 4 
 5 int main(void)
 6 {
 7     int epfd,nfds;
 8     struct epoll_event ev,events[5];                    //ev用于注冊事件,數組用于返回要處理的事件
 9     epfd = epoll_create(1);                                //只需要監聽一個描述符——標準輸入
10     ev.data.fd = STDOUT_FILENO;
11     ev.events = EPOLLOUT|EPOLLET;                                //監聽讀狀態同時設置LT模式
12     epoll_ctl(epfd, EPOLL_CTL_ADD, STDOUT_FILENO, &ev);    //注冊epoll事件
13     for(;;)
14     {
15         nfds = epoll_wait(epfd, events, 5, -1);
16         for(int i = 0; i < nfds; i++)
17         {
18             if(events[i].data.fd==STDOUT_FILENO)
19             {
20                 printf("welcome to epoll's word!");
21                 ev.data.fd = STDOUT_FILENO;
22                 ev.events = EPOLLOUT|EPOLLET;                        //設置ET模式
23                 epoll_ctl(epfd, EPOLL_CTL_MOD, STDOUT_FILENO, &ev);    //重置epoll事件(ADD無效)
24             }            
25         }
26     }
27 }
復制代碼

編譯并運行,結果如下:

?

? ? ? 程序七相對于程序五在每次向標準輸出的buffer輸出”welcome to epoll's world!”后,重新MOD?OUT事件。所以相當于每次都會返回就緒,導致程序循環輸出。

? ? ? 經過前面的案例分析,我們已經了解到,當epoll工作在ET模式下時,對于讀操作,如果read一次沒有讀盡buffer中的數據,那么下次將得不到讀就緒的通知,造成buffer中已有的數據無機會讀出,除非有新的數據再次到達。對于寫操作,主要是因為ET模式下fd通常為非阻塞造成的一個問題——如何保證將用戶要求寫的數據寫完。

? ? ? 要解決上述兩個ET模式下的讀寫問題,我們必須實現:

  1. 對于讀,只要buffer中還有數據就一直讀;
  2. 對于寫,只要buffer還有空間且用戶請求寫的數據還未寫完,就一直寫。

?ET模式下的accept問題

? ? ? 請思考以下一種場景:在某一時刻,有多個連接同時到達,服務器的?TCP?就緒隊列瞬間積累多個就緒連接,由于是邊緣觸發模式,epoll?只會通知一次,accept?只處理一個連接,導致?TCP?就緒隊列中剩下的連接都得不到處理。在這種情形下,我們應該如何有效的處理呢?

? ? ? 解決的方法是:解決辦法是用?while?循環抱住?accept?調用,處理完?TCP?就緒隊列中的所有連接后再退出循環。如何知道是否處理完就緒隊列中的所有連接呢??accept??返回?-1?并且?errno?設置為?EAGAIN?就表示所有連接都處理完。?

? ? ? 關于ET的accept問題,這篇博文的參考價值很高,如果有興趣,可以鏈接過去圍觀一下。

ET模式為什么要設置在非阻塞模式下工作

? ? ? 因為ET模式下的讀寫需要一直讀或寫直到出錯(對于讀,當讀到的實際字節數小于請求字節數時就可以停止),而如果你的文件描述符如果不是非阻塞的,那這個一直讀或一直寫勢必會在最后一次阻塞。這樣就不能在阻塞在epoll_wait上了,造成其他文件描述符的任務饑餓。

epoll的使用實例

? ? ? 這樣的實例,網上已經有很多了(包括參考鏈接),筆者這里就略過了。

小結

? ? ? ?LT:水平觸發,效率會低于ET觸發,尤其在大并發,大流量的情況下。但是LT對代碼編寫要求比較低,不容易出現問題。LT模式服務編寫上的表現是:只要有數據沒有被獲取,內核就不斷通知你,因此不用擔心事件丟失的情況。

? ? ? ?ET:邊緣觸發,效率非常高,在并發,大流量的情況下,會比LT少很多epoll的系統調用,因此效率高。但是對編程要求高,需要細致的處理每個請求,否則容易發生丟失事件的情況。

? ? ??從本質上講:與LT相比,ET模型是通過減少系統調用來達到提高并行效率的。

總結

? ? ? epoll使用的梳理與總結到這里就告一段落了。限于篇幅原因,很多細節都被略過了。后面參考給出的鏈接,強烈推薦閱讀。疏謬之處,萬望斧正!? ?

備注

? ? ?本文有相當份量的內容參考借鑒了網絡上各位網友的熱心分享,特別是一些帶有完全參考的文章,其后附帶的鏈接內容更直接、更豐富,筆者只是做了一下歸納&轉述,在此一并表示感謝。

參考

? ? ? 《Linux高性能服務器編程》

? ? ??《徹底學會使用epoll》(系列博文)

? ? ? 《epoll源碼分析(全)?》

? ? ??《linux kernel中epoll的設計和實現》

? ? ? 《poll&&epoll實現分析(二)——epoll實現》


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/384183.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/384183.shtml
英文地址,請注明出處:http://en.pswp.cn/news/384183.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

校門外的樹——樹狀數組+區間修改

校門外的樹 【題目分析】題目描述的是一種區間修改&#xff0c;看起來好像要用線段樹。但是對于這種區間內部沒有差別并且查詢的是區間內的類別的問題&#xff0c;是可以轉化為樹狀數組進行的。畢竟樹狀數組更加簡單。 我們的關注點應該放在區間的端點處&#xff0c;然后通過統…

數據結構--順序棧和鏈式棧

http://www.cnblogs.com/jingliming/p/4602458.html 棧是一種限定只在表尾進行插入或刪除操作,棧也是線性表表頭稱為棧的底部,表尾稱為棧的頂部,表為空稱為空棧&#xff0c;棧又稱為后進先出的線性表,棧也有兩種表示:順序棧與鏈式棧順序棧是利用一組地址連續的存儲單元&#xf…

CodeForces - 1144F搜索+簡單圖論

【題目鏈接】Graph Without Long Directed Paths 【題目分析】題目想要講一個無向圖變成一個最長路徑不超過1的有向圖。假如某個邊是從u到v的&#xff0c;那么所有和v相連的都必須是指向v的&#xff0c;所有和u相連的都必須是從u開始的。相當于涂色&#xff0c;相連的節點應該涂…

數據結構--雙鏈表的創建和操作

http://www.cnblogs.com/jingliming/p/4602144.html#0-tsina-1-42616-397232819ff9a47a7b7e80a40613cfe1 一、雙向鏈表的定義 雙向鏈表也叫雙鏈表&#xff0c;是鏈表的一種&#xff0c;它的每個數據結點中都有兩個指針&#xff0c;分別指向直接后繼和直接前驅。所以&#xff0c…

CodeForces - 1152B二進制+思維

【題目鏈接】Neko Performs Cat Furrier Transform 【題目分析】要求將一個數字變成2n-1,通過嘗試我們發現如果將最低位的全零位和對應的全一數字&#xff08;例如11000對應的就是111&#xff09;異或那么數字就會變成想要的結果&#xff08;11111&#xff09; 但是如果前面還有…

C語言文件操作之fgets()

http://blog.csdn.net/daiyutage/article/details/8540932 來說一說fgets(..)函數。 原型 char * fgets(char * s, int n,FILE *stream); 參數&#xff1a; s: 字符型指針&#xff0c;指向存儲讀入數據的緩沖區的地址。 n: 從流中讀入n-1個字符 stream &#xff1a; 指向讀取…

指針與零的比較以及浮點型與零的比較

指針和零的比較 int *p null;if(p ! null){p 20; } 整形和零的比較 int i 0; if(0i) {... } 浮點型和零的比較 判斷一個浮點數是不是零 #define EXP 0.0000000000001 float f 0.00001; if((f > -EXP)&&(f < EXP)) {... } 擴展后 判斷一個浮點數是不…

CodeForces 1138B暴力+剪枝

【題目鏈接】Circus 【題目分析】理解題意以后發現并沒有什么思路&#xff0c;沒有什么算法能用&#xff0c;這個時候就應該想到計算機解題的本質——暴力求解。相應的就要想到剪枝的條件&#xff0c;肯定不能盲目的暴力求解。 總共有四種人&#xff1a;00,01,10,11&#xff0c…

MYSQL錯誤代碼#1045 Access denied for user 'root'@'localhost'

http://blog.csdn.net/lykezhan/article/details/70880845 遇到MYSQL“錯誤代碼#1045 Access denied for user rootlocalhost (using password:YES)” 需要重置root賬號權限密碼&#xff0c;這個一般還真不好解決。 不過&#xff0c;這幾天調試的時候真的遇到了這種問題&#x…

C語言操作符

移位表達式 左移操作符<< 左邊拋棄,右邊補零 右移操作符>> 1.邏輯右移 左邊補零,右邊丟棄 2.算數右移 左邊補符號位,右邊丟棄 注意: 1.左移一位相當于乘2,右移一位相當于除2,并且在內存中存放的是二進制的補碼,且移位操作符只對int型數操作 2.移位操作符不要移動…

棋盤問題——DFS

【題目描述】 在一個給定形狀的棋盤&#xff08;形狀可能是不規則的&#xff09;上面擺放棋子&#xff0c;棋子沒有區別。要求擺放時任意的兩個棋子不能放在棋盤中的同一行或者同一列&#xff0c;請編程求解對于給定形狀和大小的棋盤&#xff0c;擺放k個棋子的所有可行的擺放方…

linux安裝mysql和使用c語言操作數據庫的方法 c語言連接mysql

http://www.jb51.net/article/46139.htm 1. MySQL的安裝與配置&#xff1a; 在Ubuntu下安裝MySQL方法很簡單&#xff0c;使用如下命令&#xff1a; 復制代碼 代碼如下:sudo apt-get install mysql-server安裝的過程中系統會提示設置root密碼&#xff0c;此過程可以跳過&#…

常量變量以及循環

常量 1.三目運算詞 三字母詞表達字符???([??)]??<{??>} 2.循環 1).數組元素以及變量在內存中的分配順序 2)goto語句應用 //電腦關機程序 #include<stdio.h> #include <stdlib.h> #include <string.h> #include <windows.h> int ma…

Dungeon Master——BFS

【題目描述】 You are trapped in a 3D dungeon and need to find the quickest way out! The dungeon is composed of unit cubes which may or may not be filled with rock. It takes one minute to move one unit north, south, east, west, up or down. You cannot move …

Linux 環境 C語言 操作MySql 的接口范例

http://www.cnblogs.com/wunaozai/p/3876134.html 接上一小節&#xff0c;本來是計劃這一節用來講數據庫的增刪改查&#xff0c;但是在實現的過程中&#xff0c;出現了一點小問題&#xff0c;也不是技術的問題&#xff0c;就是在字符界面上比較不好操作。比如要注冊一個帳號&a…

二進制邏輯運算符有關練習題

//1.寫一個函數返回參數二進制中 1 的個數 #include<stdio.h> int div 0; //除數 int rem 0; //余數 int count 0; //計1 int count_one_bits(unsigned int div) {int con 0; //商while (div > 1){con div / 2;rem div % 2;div con;if (1 rem){count;}}…

Catch That Cow——BFS

【題目描述】 Farmer John has been informed of the location of a fugitive cow and wants to catch her immediately. He starts at a point N (0 ≤ N ≤ 100,000) on a number line and the cow is at a point K (0 ≤ K ≤ 100,000) on the same number line. Farmer Jo…

利用mysql提供的c語言接口操作數據庫

http://blog.csdn.net/bladeandmaster88/article/details/52980872 //1.工程要在c/c->常規->附加包含目錄添加mysql.h的路徑D:\mysql5.5\include //2.工程要在鏈接器->常規->附加庫目錄添加libmysql.lib的路徑D:\mysql5.5\lib #include <WinSock2.h>/…

數組相關運算

數組的初始化 數組及指針在內存中的存儲 一維數組在內存中的存儲 有關數組的運算 //一維數組 int a[] {1,2,3,4}; printf("%d\n",sizeof(a));//16這里的a表示的是整個數組,計算出的是整個數組的大小,單位為byte printf("%d\n",sizeof(a 0));/*a沒有單獨…

Find The Multiple——簡單搜索+大膽嘗試

【題目描述】 Given a positive integer n, write a program to find out a nonzero multiple m of n whose decimal representation contains only the digits 0 and 1. You may assume that n is not greater than 200 and there is a corresponding m containing no more t…