【Linux網絡#18】:深入理解select多路轉接:傳統I/O復用的基石

在這里插入圖片描述

📃個人主頁:island1314

🔥個人專欄:Linux—登神長階


目錄

    • 一、前言:🔥 I/O 多路轉接
      • 為什么需要I/O多路轉接?
    • 二、I/O 多路轉接之 select
      • 1. 初識 select
      • 2. select 函數原型
        • 2.1 關于 fd_set 結構
        • 2.2 函數返回值
      • 3. 理解 select 執行過程
        • 3.1 socket 就緒條件
          • 讀就緒
          • 寫就緒
          • 異常就緒(選學)
        • 3.2 select 的特點
        • 3.3 select 優缺點
        • 3.4 注意事項
      • 4. 代碼示例
      • 5. 使用場景
    • 三、后言


一、前言:🔥 I/O 多路轉接

💻 多路I/O轉接服務器? \colorbox{cyan}{ 多路I/O轉接服務器 } ?多路I/O轉接服務器??(或稱為多任務I/O服務器)是一種高效管理多個I/O操作的技術,允許單線程或單進程同時監控和處理多個I/O事件(如網絡套接字、文件描述符等)

  • 核心思想:利用操作系統提供的多路I/O轉接機制(如 selectpollepoll 等),由內核幫助應用程序高效地監視多個文件描述符(包括網絡連接、管道、文件等)的狀態變化,而不是讓應用程序自己輪詢每個連接的狀態
  • 核心目標:用最小資源開銷實現高并發I/O處理,尤其適用于需要同時處理大量連接的場景(如Web服務器、實時通信系統等)
  • 這種方式能夠顯著提高服務器的性能和可擴展性,尤其是在處理大量并發連接時

為什么需要I/O多路轉接?

傳統阻塞I/O模型中,每個I/O操作會阻塞線程直至完成。若需處理多個連接,通常需為每個連接分配獨立線程/進程,導致資源消耗大、上下文切換頻繁。
而I/O多路轉接通過單線程監控多個I/O流,僅在I/O就緒時觸發操作,避免了阻塞和資源浪費。


二、I/O 多路轉接之 select

1. 初識 select

💻 系統提供 select? \colorbox{pink}{ select } ?select?? 函數來實現多路復用 輸入 / 輸出 模型.

  • select 系統調用是用來讓我們的程序監視多個文件描述符的狀態變化的;
  • 程序會停在 select 這里等待, 直到被監視的文件描述符有一個或多個發生了狀態改變;

核心原理

  • select 是一種 同步I/O多路復用 機制,允許程序在一個線程中監聽多個文件描述符(如套接字、文件等)的可讀、可寫或異常事件
  • 其核心是通過 **輪詢(polling)**檢查文件描述符狀態,并阻塞等待直到至少一個描述符就緒或超時。

2. select 函數原型

💤 select 的函數原型如下:

#include <sys/select.h>int select(int nfds,               // 監控的最大文件描述符值 +1fd_set *readfds,         // 監聽可讀事件的描述符集合fd_set *writefds,        // 監聽可寫事件的描述符集合fd_set *exceptfds,       // 監聽異常事件的描述符集合struct timeval *timeout  // 超時時間(NULL為無限等待)
);// 操作fd_set的宏:
FD_ZERO(fd_set *set);        // 清空集合
FD_SET(int fd, fd_set *set); // 添加描述符到集合
FD_ISSET(int fd, fd_set *set); // 檢查描述符是否在集合中
FD_CLR(int fd, fd_set *set); // 從集合移除描述符

📚 參數解釋:

  • nfds 是需要監視的最大的文件描述符值 +1
  • rdset, wrset, exset 分別對應于需要檢測的可讀文件描述符的集合 , 可寫文件描述符的集合 及 異常文件描述符的集合
  • timeout 為 結構體 timeval, 用來設置 select() 的等待時間
/* A time value that is accurate to the nearestmicrosecond but also has a range of years.  */
struct timeval
{__time_t tv_sec;        /* Seconds.  */__suseconds_t tv_usec;    /* Microseconds.  */
};

📚 參數 timeout 取值:

  • NULL: 則表示 select() 沒有 timeoutselect 將一直被阻塞, 直到某個文件描述符上發生了事件

  • 0: 僅檢測描述符集合的狀態, 然后立即返回, 并不等待外部事件的發生(非阻塞

  • 特定的時間值struct timeval timeout = {10, 0} : 如果在指定的時間段里沒有事件發生,select 將超時返回

2.1 關于 fd_set 結構
typedef long int __fd_mask;/* It's easier to assume 8-bit bytes than to get CHAR_BIT. */
#define __NFDBITS (8 * (int) sizeof (__fd_mask))
#define __FDELT(d) ((d) / __NFDBITS)
#define __FDMASK(d) ((__fd_mask) 1 << ((d) % __NFDBITS))/* fd_set for select and pselect. */
typedef struct{/* XPG4.2 requires this member name. Otherwise avoid the namefrom the global namespace. */
#ifdef __USE_XOPEN__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif} fd_set;/* Maximum number of file descriptors in `fd_set'. */
#define FD_SETSIZE __FD_SETSIZE   //__FD_SETSIZE等于1024/* Access macros for `fd_set'.  */
#define FD_SET(fd, fdsetp)      __FD_SET (fd, fdsetp)
#define FD_CLR(fd, fdsetp)      __FD_CLR (fd, fdsetp)
#define FD_ISSET(fd, fdsetp)    __FD_ISSET (fd, fdsetp)
#define FD_ZERO(fdsetp)         __FD_ZERO (fdsetp)
  • 其實這個結構就是一個 整數數組,更嚴格的說, 是一個 “位圖” . 使用位圖中對應的位來表示要監視的文件描述符.
    • 一個long int類型的數組。因為每一位可以代表一個文件描述符。所以fd_set最多表示1024個文件描述符!
  • 提供了一組操作 fd_set 的接口, 來比較方便的操作位圖
void FD_CLR(int fd, fd_set *set);     // 用來清除描述詞組 set 中相關 fd 的位
int  FD_ISSET(int fd, fd_set *set); // 用來測試描述詞組 set 中相關 fd 的位是否為真
void FD_SET(int fd, fd_set *set);     // 用來設置描述詞組 set 中相關 fd 的位
void FD_ZERO(fd_set *set);             // 用來清除描述詞組 set 的全部位
2.2 函數返回值
  • 執行成功則返回 文件描述符狀態已改變的個數
  • 如果返回 0 代表在描述符狀態改變前已超過 timeout 時間
  • 當有錯誤發生時則返回-1, 錯誤原因存于 errno, 此時參數 readfds, writefds, exceptfds 和 timeout 的值變成不可預測

🙅 錯誤值可能為:

  • EBADF文件描述詞為無效的或該文件已關閉
  • EINTR此調用被信號所中斷
  • EINVAL參數 n 為負值
  • ENOMEM核心內存不足

3. 理解 select 執行過程

🦈 理解 select 模型的關鍵在于理解 fd_set, 為說明方便, 取 fd_set 長度為 1 字節, fd_set 中的每一 bit 可以對應一個文件描述符 fd_set。 則 1 字節長的 fd_set 最大可以對應 8 個 fd.

  • 執行 fd_set ; FD_ZERO(&set);set 用位表示是 0000,0000
  • 若 fd= 5,執行 FD_SET(fd,&set); 后 set 變為 0001,0000(第 5 位置為 1)
  • 若再加入 fd= 2, fd=1,則 set 變為 0001,0011
  • 執行 select(6,&set,0,0,0) 阻塞等待
  • select 返回, 此時 set 變為 0000,0011。 注意: 沒有事件發生的 fd=5 被清空
3.1 socket 就緒條件
讀就緒
  • socket 內核中, 接收緩沖區中的字節數, 大于等于低水位標記 SO_RCVLOWAT. 此時可以無阻塞的讀該文件描述符, 并且返回值大于 0;
  • socket TCP 通信中, 對端關閉連接, 此時對該 socket 讀, 則返回 0;
  • 監聽的 socket 上有新的連接請求;
  • socket 上有未處理的錯誤;
寫就緒
  • socket 內核中, 發送緩沖區中的可用字節數(發送緩沖區的空閑位置大小), 大于等于低水位標記 SO_SNDLOWAT, 此時可以無阻塞的寫, 并且返回值大于 0;
  • socket 的寫操作被關閉(close 或者 shutdown). 對一個寫操作被關閉的 socket 進行寫操作, 會觸發 SIGPIPE 信號;
  • socket 使用非阻塞 connect 連接成功或失敗之后;
  • socket 上有未讀取的錯誤;
異常就緒(選學)
  • socket 上收到帶外數據. 關于帶外數據, 和 TCP 緊急模式相關(回憶 TCP 協議頭中, 有一個緊急指針的字段), 自己收集相關資料
3.2 select 的特點
  • 可監控的文件描述符個數取決于 sizeof(fd_set) 的值. 我這邊服務器上 sizeof(fd_set)= 512, 每 bit 表示一個文件描述符, 則我服務器上支持的最大文件描述符是 512*8=4096.
  • I將 fd 加入 select 監控集的同時, 還要再使用一個數據結構 array 保存放到 select 監控集中的 fd
    1. 用于再 select 返回后, array 作為源數據和 fd_set 進行 FD_ISSET 判斷**
    2. select 返回后會把以前加入的但并無事件發生的 fd 清空, 則每次開始 select 前都要重新從 array 取得 fd 逐一加入(FD_ZERO 最先), 掃描 array 的同時 取得 fd 最大值 maxfd, 用于 select 的第一個參數

備注: fd_set 的大小可以調整, 可能涉及到重新編譯內核.

3.3 select 優缺點
優點缺點
跨平臺支持(所有UNIX/Linux系統)文件描述符數量受限(默認1024,由FD_SETSIZE定義)
簡單易用,適合少量并發場景線性掃描,時間復雜度O(n)(效率隨描述符數量下降)
超時機制靈活每次調用需重置fd_set(額外內存拷貝開銷)
  • 每次調用 select:都需要手動設置 fd 集合(從用戶態拷貝到內核態), 從接口使用角度來說也非常不便,而且 這個開銷在 fd 很多時會很大
  • 同時每次調用 select 都需要在內核遍歷傳遞進來的所有 fd, 這個開銷在 fd 很多時也很大
3.4 注意事項
  1. 描述符上限:通過 FD_SETSIZE 宏定義(通常1024),需重新編譯內核修改。
  2. 性能問題:當監控數千描述符時,select 的輪詢效率遠低于 epollkqueue
  3. 水平觸發select 是水平觸發模式,若未處理就緒事件,會持續通知。
  4. 非阻塞I/O:結合非阻塞socket可避免單次read/write阻塞整個程序。

4. 代碼示例

示例一:檢測標準輸入輸出

#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>int main()
{fd_set read_fds;FD_ZERO(&read_fds); // 清空FD_SET(0, &read_fds);while(true){printf("> ");fflush(stdout);int ret = select(1, &read_fds, NULL, NULL, NULL);if(ret < 0){perror("Select");continue;}if(FD_ISSET(0, &read_fds)){char buf[1024] = {0};read(0, buf, sizeof(buf) - 1);printf("Input: %s", buf);}else{printf("Error! Invalid fd\n");continue;}FD_ZERO(&read_fds);FD_SET(0, &read_fds);}return 0;
}
  • 當只檢測文件描述符 0(標準輸入)時,因為輸入條件只有在你有輸入信息的時候才成立,所以如果一直不輸入,就會產生超時信息

示例二:TCP 服務器使用 select 處理多客戶端

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/select.h>
#include <cstring>#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);char buffer[BUFFER_SIZE] = {0};// 創建TCP socketif ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 設置socket選項(允許地址重用)if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {perror("setsockopt");exit(EXIT_FAILURE);}address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(8080);// 綁定socket到端口if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");exit(EXIT_FAILURE);}// 開始監聽if (listen(server_fd, 3) < 0) {perror("listen");exit(EXIT_FAILURE);}fd_set readfds;  // 描述符集合int client_sockets[MAX_CLIENTS] = {0}; // 客戶端socket數組int max_sd;while (true) {FD_ZERO(&readfds);           // 清空集合FD_SET(server_fd, &readfds); // 添加服務器socket到監聽集合max_sd = server_fd;// 添加所有客戶端socket到集合for (int i = 0; i < MAX_CLIENTS; i++) {int sd = client_sockets[i];if (sd > 0) {FD_SET(sd, &readfds);if (sd > max_sd) max_sd = sd;}}// 調用select,阻塞等待事件int activity = select(max_sd + 1, &readfds, NULL, NULL, NULL);if ((activity < 0) && (errno != EINTR)) {perror("select error");}// 檢查服務器socket是否有新連接if (FD_ISSET(server_fd, &readfds)) {if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}// 將新客戶端socket加入數組for (int i = 0; i < MAX_CLIENTS; i++) {if (client_sockets[i] == 0) {client_sockets[i] = new_socket;std::cout << "New client connected, socket fd: " << new_socket << std::endl;break;}}}// 處理客戶端數據for (int i = 0; i < MAX_CLIENTS; i++) {int sd = client_sockets[i];if (FD_ISSET(sd, &readfds)) {int valread = read(sd, buffer, BUFFER_SIZE);if (valread == 0) {  // 客戶端斷開連接getpeername(sd, (struct sockaddr*)&address, (socklen_t*)&addrlen);std::cout << "Client disconnected" << std::endl;close(sd);client_sockets[i] = 0;  // 清除socket} else {  // 處理數據buffer[valread] = '\0';std::cout << "Received: " << buffer << std::endl;send(sd, buffer, strlen(buffer), 0); // 回顯數據}}}}return 0;
}
  1. 初始化服務器
    • 創建TCP socket,綁定端口并開始監聽。
    • 設置 SO_REUSEADDR 允許地址重用(避免端口占用)。
  2. select 監聽流程
    • 使用 fd_set 管理需要監聽的描述符集合。
    • 每次循環重新初始化集合,添加服務器socket和所有客戶端socket。
    • 調用 select 阻塞等待事件,返回就緒的描述符數量。
  3. 處理新連接
    • 當服務器socket就緒(FD_ISSET),調用 accept 接受新連接。
    • 將新客戶端socket存入數組。
  4. 處理客戶端數據
    • 遍歷所有客戶端socket,檢查是否有數據可讀。
    • read 返回0,表示客戶端斷開連接,關閉socket并清理數組。
    • 否則回顯接收到的數據。

5. 使用場景

  • 需要兼容多平臺的輕量級應用。
  • 并發連接數較少(如<1000)。
  • 超時機制需要精細控制的場景(如同時等待I/O和定時任務)

三、后言

★,°:.☆( ̄▽ ̄)/$:.°★ 】那么本篇到此就結束啦,如果有不懂 和 發現問題的小伙伴可以在評論區說出來哦,同時我還會繼續更新關于【Linux】的內容,比如:多路轉接之 epollpoll 模型,請持續關注我 !!

在這里插入圖片描述

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

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

相關文章

高級:微服務架構面試題全攻略

一、引言 在現代軟件開發中&#xff0c;微服務架構被廣泛應用于構建復雜、可擴展的應用程序。面試官通過相關問題&#xff0c;考察候選人對微服務架構的理解、拆分原則的掌握、服務治理的能力以及API網關的運用等。本文將深入剖析微服務架構相關的面試題&#xff0c;結合實際開…

使用MQTTX軟件連接阿里云

使用MQTTX軟件連接阿里云 MQTTX軟件阿里云配置MQTTX軟件設置 MQTTX軟件 阿里云配置 ESP8266連接阿里云這篇文章里有詳細的創建過程&#xff0c;這里就不再重復了&#xff0c;需要的可以點擊了解一下。 MQTTX軟件設置 打開軟件之后&#xff0c;首先點擊添加進行創建。 在阿…

【HFP】藍牙Hands-Free Profile(HFP)核心技術解析

藍牙 Hands-Free Profile&#xff08;HFP&#xff09;作為車載通信和藍牙耳機的核心協議&#xff0c;定義了設備間語音交互的標準化流程&#xff0c;并持續推動著無線語音交互體驗的革新。自2002年首次納入藍牙核心規范以來&#xff0c;HFP歷經多次版本迭代&#xff08;最新為v…

輕量化大模型微調工具XTuner指令微調實戰(下篇)

接著上篇文章《輕量化大模型微調工具XTuner指令微調實戰&#xff08;上篇&#xff09;》來接著寫教程。 一、模型轉換 模型訓練后會自動保存成 PTH 模型&#xff08;例如 iter_500.pth&#xff09;&#xff0c;我們需要利用 xtuner convert pth_to_hf 將其轉換為 HuggingFace…

pyTorch框架使用CNN進行手寫數字識別

目錄 1.導包 2.torchvision數據處理的方法 3.下載加載手寫數字的訓練數據集 4.下載加載手寫數字的測試數據集 5. 將訓練數據與測試數據 轉換成dataloader 6.轉成迭代器取數據 7.創建模型 8. 把model拷到GPU上面去 9. 定義損失函數 10. 定義優化器 11. 定義訓練…

強化學習課程:stanford_cs234 學習筆記(3)introduction to RL

文章目錄 前言7 markov 實踐7.1 markov 過程再敘7.2 markov 獎勵過程 MRP&#xff08;markov reward process&#xff09;7.3 markov 價值函數與貝爾曼方程7.4 markov 決策過程MDP&#xff08;markov decision process&#xff09;的 狀態價值函數7.4.1 狀態價值函數7.4.2 狀態…

操作系統 4.5-文件使用磁盤的實現

通過文件進行磁盤操作入口 // 在fs/read_write.c中 int sys_write(int fd, const char* buf, int count) {struct file *file current->filp[fd];struct m_inode *inode file->inode;if (S_ISREG(inode->i_mode))return file_write(inode, file, buf, count); } 進程…

libreoffice-help-common` 的版本(`24.8.5`)與官方源要求的版本(`24.2.7`)不一致

出現此錯誤的原因主要是軟件包依賴沖突&#xff0c;具體分析如下&#xff1a; ### 主要原因 1. **軟件源版本不匹配&#xff08;國內和官方服務器版本有差距&#xff09; 系統中可能啟用了第三方軟件源&#xff08;如 PPA 或 backports 源&#xff09;&#xff0c;導致 lib…

使用Geotools中的原始方法來操作PostGIS空間數據庫

目錄 前言 一、原生PostGIS連接介紹 1、連接參數說明 2、創建DataStore 二、工程實戰 1、Maven Pom.xml定義 2、空間數據庫表 3、讀取空間表的數據 三、總結 前言 在當今數字化與信息化飛速發展的時代&#xff0c;空間數據的處理與分析已成為眾多領域不可或缺的一環。從…

訊飛語音合成(流式版)語音專業版高質量的分析

一、引言 在現代的 Web 應用開發中&#xff0c;語音合成技術為用戶提供了更加便捷和人性化的交互體驗。訊飛語音合成&#xff08;流式版&#xff09;以其高效、穩定的性能&#xff0c;成為了眾多開發者的首選。本文將詳細介紹在 Home.vue 文件中實現訊飛語音合成&#xff08;流…

走進未來的交互世界:下一代HMI設計趨勢解析

在科技日新月異的今天&#xff0c;人機交互界面&#xff08;HMI&#xff09;設計正以前所未有的速度發展&#xff0c;不斷引領著未來的交互世界。從簡單的按鈕和圖標&#xff0c;到如今的智能助手和虛擬現實&#xff0c;HMI設計不僅改變了我們的生活方式&#xff0c;還深刻影響…

洛谷題單3-P1217 [USACO1.5] 回文質數 Prime Palindromes-python-流程圖重構

題目描述 因為 151 151 151 既是一個質數又是一個回文數&#xff08;從左到右和從右到左是看一樣的&#xff09;&#xff0c;所以 151 151 151 是回文質數。 寫一個程序來找出范圍 [ a , b ] ( 5 ≤ a < b ≤ 100 , 000 , 000 ) [a,b] (5 \le a < b \le 100,000,000…

學習筆記,DbContext context 對象是保存了所有用戶對象嗎

DbContext 并不會將所有用戶對象保存在內存中&#xff1a; DbContext 是 Entity Framework Core (EF Core) 的數據庫上下文&#xff0c;它是一個數據庫訪問的抽象層它實際上是與數據庫的一個連接會話&#xff0c;而不是數據的內存緩存當您通過 _context.Users 查詢數據時&…

本地命令行啟動服務并連接MySQL8

啟動服務命令 net start mysql8 關閉服務命令 net stop mysql8 本地連接MySQL數據庫mysql -u [用戶名] -p[密碼] 這里&#xff0c;我遇到了個問題 —— 啟動、關閉服務時&#xff0c;顯示 “發生系統錯誤 5。拒絕訪問。 ” 解法1&#xff1a;在 Windows 上以管理員身份打開…

數據蒸餾:Dataset Distillation by Matching Training Trajectories 論文翻譯和理解

一、TL&#xff1b;DR 數據集蒸餾的任務是合成一個較小的數據集&#xff0c;使得在該合成數據集上訓練的模型能夠達到在完整數據集上訓練的模型相同的測試準確率&#xff0c;號稱優于coreset的選擇方法本文中&#xff0c;對于給定的網絡&#xff0c;我們在蒸餾數據上對其進行幾…

【spring cloud Netflix】Ribbon組件

1.基本概念 SpringCloud Ribbon是基于Netflix Ribbon 實現的一套客戶端負載均衡的工具。簡單的說&#xff0c;Ribbon 是 Netflix 發布的開源項目&#xff0c;主要功能是提供客戶端的軟件負載均衡算法&#xff0c;將 Netflix 的中間層服務連接在一 起。Ribbon 的客戶端組件提供…

P1036 [NOIP 2002 普及組] 選數(DFS)

題目描述 已知 n 個整數 x1?,x2?,?,xn?&#xff0c;以及 1 個整數 k&#xff08;k<n&#xff09;。從 n 個整數中任選 k 個整數相加&#xff0c;可分別得到一系列的和。例如當 n4&#xff0c;k3&#xff0c;4 個整數分別為 3,7,12,19 時&#xff0c;可得全部的組合與它…

在響應式網頁的開發中使用固定布局、流式布局、彈性布局哪種更好

一、首先看下固定布局與流體布局的區別 &#xff08;一&#xff09;固定布局 固定布局的網頁有一個固定寬度的容器&#xff0c;內部組件寬度可以是固定像素值或百分比。其容器元素不會移動&#xff0c;無論訪客屏幕分辨率如何&#xff0c;看到的網頁寬度都相同。現代網頁設計…

二分查找與二叉樹中序遍歷——面試算法

目錄 二分查找與分治 循環方式 遞歸方式 元素中有重復的二分查找 基于二分查找的拓展問題 山脈數組的頂峰索引——局部有序 旋轉數字中的最小數字 找缺失數字 優化平方根 中序與搜索樹 二叉搜索樹中搜索特定值 驗證二叉搜索樹 有序數組轉化為二叉搜索樹 尋找兩個…

字符串——面試考察高頻算法題

目錄 轉換成小寫字母 字符串轉化為整數 反轉相關的問題 反轉字符串 k個一組反轉 僅僅反轉字母 反轉字符串里的單詞 驗證回文串 判斷是否互為字符重排 最長公共前綴 字符串壓縮問題 轉換成小寫字母 給你一個字符串 s &#xff0c;將該字符串中的大寫字母轉換成相同的…