一些不同字符串的總結

這篇主要寫關于字符串的使用,包括STL中的string,C風格的字符串,還包括Redis's sds,和Facebook的開源的folly中的String,從代碼結構上,性能上,平時使用時遇到的坑,和結合具體使用場景(業務)去選擇;后三者都可以從Github上下到源碼分析。

使用純C語言編碼時,使用到字符串時,沒得選擇,如char szName[iCount]或char * pName = (char *)malloc(iCount),那么在使用一些C庫函數時,一方面會引起性能問題:如線性時間strlen函數;增加一個字符時,可能引起存儲空間的重新分配,字符的移動,原空間的釋放[malloc的分配原理挺復雜的];另一方面是安全問題:如二進制安全,串中不能包括空白結尾符;緩沖區溢出而導致的漏洞等等;

正因為考慮到C字符串的一些問題,使用C編寫的Redis自定義了sds數據類型,解決了以上C字符串的一些問題。通過分析sds源碼發現,它包含了free,len,buf成員,分別表示buf未使用的字節個數,buf已使用的字節個數,存儲數據的buf空間,那么常數時間可以獲得字符的個數而不是strlen,通過在buf中存儲'\0'空白符是安全的,因為可以通過len決定有沒有到串的結尾,通過free視情況重分配去避免緩沖區溢出的問題;每次重分配時可能多分配一些空間,如STL中的string分配問題;刪除空間時并不是真正的重新分配更小的內存,而是延緩,保留原內存不變,只更新其他兩個成員,防止后來又要更加內存,這樣可以減少頻繁的malloc,free調用。

而STL中的string,則在某種程度上高效許多,找到頭文件中有如下語句:

typedef basic_string<char>? string,有個_M_dataplus,里面有個_CharT* _M_p; // The actual data,所以當出現這樣的語句時:string str和string str1("helloworld"),他們的sizeof大小都是一樣的,在棧上分配大小相同的字節,然后_M_p指向堆內存,分配的時候會多分配sizeof(_Rep)個的字節空間用于存儲計數,容量,大小信息;然而實際占用的空間要多一些。

當插入時,內存不夠了會自動分配,不用去擔心使用C風格字符串的一些問題,但還是無法避免內存的申請與釋放,內容的拷貝等等,但是與stl中的vector一樣,會去減少內存的分配,在vector的實現中有如下語句:const size_type __len = size() + std::max(size(), __n);即每次擴容時為當前容量的兩倍;而在string的實現中有:__capacity = 2 * __old_capacity,也是如此。然而都沒有辦法自動縮小不用的空間,這里有個辦法就是與臨時對象交換,像這樣:vector<type> vec; vector<type>(vec).swap(vec),創建一個臨時對象,然后以vec的元素個數初始化,而不容量,這樣再交換后,出了生命周期的范圍后,臨時對象析構,vec的大小只包含元素個數的空間了,多余的也就釋放了,string也是一樣的做法,內部實現差不多類似:std::swap(this->_M_impl._M_start, __x._M_impl._M_start);僅交換指針而已。

string的一些重載接口使用會有一定的性能影響,比如構造函數:

string( );

string(const string & str );

string(const string & str, size_t pos, size_t n = npos );

string(const char * s, size_t n );

string(const char * s );

string( size_t n, char c );

其中string(const string & str );效率最快的,使用了cow(copy-on-write),先共享,如果需要更新了再拷貝,也算是種延緩分配資源的做法吧,但在string中使用cow會引起其他問題,如源碼中的atomic_add_dispatch實現計數加一,使用原子操作保證操作引用計數的安全,但stl's string本身不是線程安全的,“那需要鎖定包含目標地址的一片內存區域,防止其他CPU在此期間的并發訪問,從而序列化對同一地址的訪問;系統通常會lock住比目標地址更大的一片區域,影響邏輯上不相關的地址訪問;lock指令具有”同步“語義,會阻止CPU本身的亂序執行優化”

網上有關于stl的其他實現,如eager copy,short string optimization[陳碩的那本Linux多線程服務端編程有相關的講解(muduo :))]。

Folly中的string則使用了三層的存儲策略(three-tiered storage strategy),根據長度將fbstring分為三類:small/medium/large,分別采取不同的優化措施,以達到最佳性能[沒使用過,看源碼有點復雜]。

一些引用:

深入剖析linux GCC 4.4的STL string

std::string的Copy-on-Write:不如想象中美好

漫步Facebook開源C++庫folly(1):string類的設計

Is std::string thead-safe with gcc 4.3?

C++ 工程實踐(10):再探std::string

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容