Lua 源碼分析之String

因爲每一個源碼可能會有差別,現在基於lua 5.2.1來分析,保持一致性。

從虛擬機的大體來看,字符串通過一個結構體存放在global_State裏,這個結構stringtable(lstate.h)是:


GCObject(lstate.h)的結構是:


stringtable結構體的字段含義是:

GCObject **hash: GCObject指針的指針,通過Hash值可以指向Hash值存放的GCObject,其中實際引用的是TString.

lu_int32 nuse: 已經創建的TString個數

int size:TString的總個數(初始值爲32)


在global_State中,會有一個stringtable,通過它來訪問虛擬機中的所有字符串(包括長短字符串),現在5.2.1的版本中,長短字符串的存放有點略不同,後面會細說。


具體看看字符串的實現,主要數據結構體是TString(lobject.h)


TString關聯的宏定義, L_Umaxalign(llimits.h)


TString關聯的宏定義, CommonHeader(lobject.h),這一個定義很多地方會使用到


這是TString相關的結構體。


下面來是分析如何創建一個字符串,實現的文件是在lstring.c。

在lua裏面,創建一個字符串,會根據字符串的長短來區分到底創建長還是短的字符串。

如果長度小於LUAI_MAXSHORTLEN(值爲40),它就會創建一個短字符串;

否則的話就會創建長字符串。


短字符串的區別是:如果在創建的過程,發現hash值、長度和內容都一樣的短字符串,就會複用它,不再去進行分配。

但是長的字符串就會不管三七二一都會創建一個新的TString.

在創建字符串之前,stringtable的長度會動態擴展,具體邏輯如下:

if (tb->nuse >= cast(lu_int32, tb->size) && tb->size <= MAX_INT/2)
    luaS_resize(L, tb->size*2);  /* too crowded */

既然有動態擴展,也有動態縮小,在GC的時候,stringtable也會檢查是否需要回收釋放空間

if (g->gckind != KGC_EMERGENCY) {  /* do not change sizes in emergency */
    int hs = g->strt.size / 2;  /* half the size of the string table */
    if (g->strt.nuse < cast(lu_int32, hs))  /* using less than that half? */
      luaS_resize(L, hs);  /* halve its size */
    luaZ_freebuffer(L, &g->buff);  /* free concatenation buffer */
  }

具體創建邏輯1,創建字符串對象:

字符串的內存結構是:TString+字符串內容+'\0','\0'是結尾標識,這一個結構的設計實在太棒棒了,直接省掉一個指針指向具體字符串內容,讀取的時候只需要讀一塊連續內存。


具體創建邏輯2,最最關鍵的地方來了,既然都創建了,那怎麼存到stringtable裏呢?怎麼構造?好,前面的介紹其實並不是沒用的,那是概覽。stringtable裏有一個GCObject** hash,其實就是需要通過它來索引到所有的字符串內容。

hash指向hash數組,數組裏每一個單元存放的是該hash值下面所有TString鏈表的隊尾元素的地址。(hash值的含義實質爲字符串長度,只是通過hash計算和lmod取模)大概示意圖如下。


構造鏈表的函數是luaC_newobj (lgc.c):



最後一個,前面提了很多hash關鍵字,在lua虛擬機裏,hash算法採用的是JSHash,虛擬機啓動的時候會生成一個隨機種子,這個隨機種子會在創建hash的時候一併帶進去,以防被猜到hash值。



lstring的設計實在很棒棒,很多巧妙的設計,lua的源碼小而精真不是蓋的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章