# Lua 5.3 源碼分析 (五) 字符串 TString
typedef struct TString {
CommonHeader;
lu_byte extra; /* reserved words for short strings; "has hash" for longs */
unsigned int hash;
size_t len; /* number of characters in string */
struct TString *hnext; /* linked list for hash table */
} TString;
typedef union UTString {
L_Umaxalign dummy; /* ensures maximum alignment for strings */
TString tsv;
} UTS
TSTRING 類型有 短字符串與長字符串之分。LUA-TSHRSTR(短字符串 ( 0x100 | 0x0 = 0x4 = 4)) ;LUA-TLNGSTR(長字符串0x100 | 0x10000 = 0x10100 = 20); 根據字符串的長度(luaconf.h中的LUAI-MAXSHORTLEN,默認爲40)的不同來區別。
1. 其中 extra 字段 在短字符串中 如果 extra >0,則表示這是一個系統保留的關鍵字,extra的值直接對應着詞法分析時的一個token值,這樣可以加速詞法分析的速度,同時也保證不被GC 回收; 對於長字符串,一般很少做索引或者比較,所以長字符串直接鏈接到allgc 鏈表上 做GC 對象來處理。Lua不會對新創建的長字符串對象計算哈希值,也不保證長字符串對象的唯一性。當長字符串需要被用來當作索引時,會爲其計算一次哈希值,並使用extra來記錄是否已經爲其計算了哈希值。
2. hash字段則是用存儲在全局字符串池裏的哈希值;
3. len表示長度,lua的字符串 不同於 C 字符串 (並不以0結尾),所以需要記錄長度信息;
4. hnext是用來把全局TString串起來,整個鏈表就是字符串池;
對於短字符串,在實際使用中一般用來作爲索引或者需要進行字符串比較。不同於其他的對象,Lua並不是將其連接到全局的allgc對象鏈表上,而是將其放到全局狀態global_State中的字符串表中進行管理。
typedef struct stringtable {
TString **hash;
int nuse; /* number of elements */
int size;
} stringtable;
這個字符串表是一個stringtable類型的全局唯一的哈希表。當需要創建一個短字符串對象時,會首先在這個表中查找已有對象。所有的短字符串都是全局唯一的,不會存在兩個相同的短字符串對象。如果需要比較兩個短字符串是否相等,只需要看他們指向的是否是同一個TString對象就可以了,速度非常快。
當一個字符串被放入到字符串表 stringtable 的時候,需要先檢查一下表中是否已經存在相同的字符。如果沒有則創建一個新的。
這個哈希表是開散列實現(又稱爲鏈地址法)。當碰到哈希值相同的字符串,只需要鏈到同一個哈希位的鏈表上。
Lua 的GC 是分步完成的,這裏需要檢查 stringtable 表中的字符串是否爲 死掉的字符串。 向 stringtable 中添加新字符串在任何步驟之間都可能發生。 有可能在標記完字符串後發現有些字符串沒有任何引用,但在下一個步驟中又產生了相同的字符串導致這個字符串激活。
static TString *internshrstr (lua_State *L, const char *str, size_t l) {
TString *ts;
global_State *g = G(L);
unsigned int h = luaS_hash(str, l, g->seed);
TString **list = &g->strt.hash[lmod(h, g->strt.size)];
for (ts = *list; ts != NULL; ts = ts->hnext) {
if (l == ts->len &&
(memcmp(str, getstr(ts), l * sizeof(char)) == 0)) {
/* found! */
if (isdead(g, ts)) /* dead (but not collected yet)? */
changewhite(ts); /* resurrect it */
return ts;
}
}
if (g->strt.nuse >= g->strt.size && g->strt.size <= MAX_INT/2) {
luaS_resize(L, g->strt.size * 2);
list = &g->strt.hash[lmod(h, g->strt.size)]; /* recompute with new size */
}
ts = createstrobj(L, str, l, LUA_TSHRSTR, h);
ts->hnext = *list;
*list = ts;
g->strt.nuse++;
return ts;
}
當stringtable->nuse 域 超過了預定容量(stringtable->size 域)時,說明hash 衝突必然發生。 需要調用 luaS_resize 函數將 stringtable 的哈希鏈表數組擴大,重新排列所有字符串的位置。
void luaS_resize (lua_State *L, int newsize) {
int i;
stringtable *tb = &G(L)->strt;
if (newsize > tb->size) { /* grow table if needed */
luaM_reallocvector(L, tb->hash, tb->size, newsize, TString *);
for (i = tb->size; i < newsize; i++)
tb->hash[i] = NULL;
}
for (i = 0; i < tb->size; i++) { /* rehash */
TString *p = tb->hash[i];
tb->hash[i] = NULL;
while (p) { /* for each node in the list */
TString *hnext = p->hnext; /* save next */
unsigned int h = lmod(p->hash, newsize); /* new position */
p->hnext = tb->hash[h]; /* chain it */
tb->hash[h] = p;
p = hnext;
}
}
if (newsize < tb->size) { /* shrink table if needed */
/* vanishing slice should be empty */
lua_assert(tb->hash[newsize] == NULL && tb->hash[tb->size - 1] == NULL);
luaM_reallocvector(L, tb->hash, tb->size, newsize, TString *);
}
tb->size = newsize;
}
字符串比較操作
對於 LUA_TSHRSTR
define eqshrstr(a,b) check_exp((a)->tt == LUA_TSHRSTR, (a) == (b))
對於長字符串比較,先比較字符串長度。當長度相同的時候,需要逐個字節比較。
int luaS_eqlngstr (TString *a, TString *b) {
size_t len = a->len;
lua_assert(a->tt == LUA_TLNGSTR && b->tt == LUA_TLNGSTR);
return (a == b) || /* same instance or... */
((len == b->len) && /* equal length and ... */
(memcmp(getstr(a), getstr(b), len) == 0)); /* equal contents */
}