字元串在各類語言里是怎麼處理的?

題主已知的:

1. C++的 std::string

通過 copy on write 技術, 減少拷貝次數. 字元串也是屬於最耗的(好像比O(n)好一些)

2. Lua

通過源碼了解過,40個字元是分界點,40個字元內是放全局哈希表,40個以上算長字元串,經過隨機處理的 hash 後存儲。lua 編碼中,字元串不存在拷貝,字元串比較是整數級性能。

現在想了解其他語言, 例如Python,PHP,Golang,Rust, Erlang, C#,Java,JavaScript,Swift 等語言的字元串拷貝和比較的性能到底是整數級別的還是類似C/C++那樣的原始字串比較?


相比之下Erlang的字元串處理就要簡單粗暴的多了。

不妨先直接無視各種不常用的情況。

iolist的類型定義可以寫成

-type iolist() :: binary() | [iolist()].

在拼接字元串的時候完全不Copy。直接構造一個新的list就完了。

IOList3 = [IOList1, IOList2].

反正 gen_tcp:send 什麼的都能接受,直接就當 iovec 發出去了嘛。

我就是要把兩個binary拼起來怎麼辦?比如

Bin3 = &<&&>

Erlang會機智的計算出Bin1, Bin2 長度之和,先佔好坑,再把字元串都複製進去。


std::string真心不是cow的,至少VC的不是

老實講,本人喜歡c++的原因之一就是c++沒有一些內部cow的玩意,cow很煩,超級煩。

補個Delphi的

首先,字元串是基本數據結構,由編譯器和運行時支持,不是對象。

AnsiString:

引用計數+copy on write

字元部分所佔用內存只能在堆上分配

當引用計數為0時銷毀

注意,當作為const參數傳入時,不修改引用計數

WideString:

之前的描述錯誤,此處已糾正

copy on assign ,沒有引用計數和非copy on write

與Windows OLE/COM BSTR兼容(或者說就是BSTR),底層調用系統API(如SysAllocString)來管理

ShortString:

本質上是自帶長度的限長字元數組

棧上堆上皆可

字元串和字元數組直接=或&<&>比較時,運行時內部為字元數組轉換為字元串,然後再二進位比較(這一塊rtl和fastcode都是彙編實現的)

如果比較雙方已經都是字元串了,則是直接二進位比較

因此一些性能關鍵的應用,如何避免臨時字元串的創建,也是注意事項之一


補充一下 Pascal 字元串的一些內容。

ANSIString 作為非常量時分配在堆上,棧或全局區上有一個指針指向首字元。作為常量時分配在全局區,引用數恆定為 -1 。

Free Pascal 3.0.0 開始的 ANSIString 和舊版本不一樣了。

新的 ANSIString 在字串本體前面是

-15..-14 代碼頁(Word) ——用於不同代碼頁的自動轉換

-11 字元位元組數(Byte) ——用於多位元組字元串的處理

-7..-4 引用數(LongInt) ——用於 copy on write 的處理

-3..0 實際位元組長度(LongInt)

1~實際長度是字元串本體,後面補一個 Char(0) 兼容 C 字元串。

舊版本的前置結構沒有代碼頁和字元位元組數。

舊版本程序的對新版本 ANSIString 進行只讀操作時不會出錯。但一旦改動的話,因為堆空間地址頭位置不同,發生重新分配或釋放內存時會出錯。

新版本的程序對舊版本 ANSIString 改動時也可能出錯。只讀操作時需要忽略代碼頁的特性才能盡量保證不出錯。

UnicodeString 則是舊版本 ANSIString 把字元換成 WideChar 的版本。

WideString 是和微軟的 BSTR 兼容的類型。和 UnicodeString 不同,沒有引用數的結構,也不使用 copy on write 。


C++11的std::string沒有使用copy-on-write技術,因為這麼做不滿足標準需求,參見N2668


  • Go

    一段只讀的內存,賦值是淺拷貝,取子串會 share 原字元串的空間。字元串相連是創建新字元串然後拷貝。沒有 builtin 的 interning。

  • Java

    一個 immutable 的 class,賦值是淺拷貝。取子串和字元串相連都需要創建新的 class 並做內存拷貝。有 interning 支持,但不自動做。


Rust 標準庫源碼很好懂 https://doc.rust-lang.org/src/collections/string.rs.html#36-38

pub struct String {
vec: Vec&,
}

是一個 UTF-8 字元組成的 Vector (和 C++ 的 Vector 一個東西)

實際上 Rust 和 C++ 這種你可以自己輕鬆地實現一個字元池(HashSet&),做到你說的那種效率。不默認提供是因為這是 Runtime 的事情,這種語言連 GC 都不要,怎麼可能搞這個……

一般 Lua Python PHP Ruby JS 這種語言都有類似設施。術語是 String interning。

Python 我記得和 Lua 的實現有點像,短字元串放在全局的池裡面,但是長的字元串我記得是一個獨立的內存空間(記錯的話指出)。C# 的 String.Intern Method (System),Java 也有。我不會 C# 和 Java,這個應該是手動的進行?


C#:字元串創造出來之後就不能改了,搞並發的時候效果拔群。


String interning is supported by some modern object-oriented programming languages, including Python, Lua, Ruby (with its symbols), Java and .NET languages. Lisp, Scheme, and Smalltalk are among the languages with a symbol type that are basically interned strings.

via http://en.wikipedia.org/wiki/String_interning

不過誰能告訴我「String interning」的中文翻譯應該是什麼?


最近由於項目需要,剛好在看Python裡面string intern相關的資料。

「Python會對短字元串做intern」這句話是很籠統並且也是不準確的。

具體可以看看這篇博客:The internals of Python string interning

如果不想看英文的,可以看我翻譯的中文版:Python string interning原理


pascal:首位存放字元串長度


推薦閱讀:

為什麼 Win7 CMD 下執行兩條 copy 命令,其中一條會完整匹配擴展名而另一條不會?
為什麼十年來新興的編程語言,大多是動態類型語言?
如何將主機上的CVS文件入庫到oracle數據中?
請問寫SQL腳本的算不算程序員?

TAG:編程 | 腳本語言 | C# | Go語言 | 字元串 |