Lua 5.3 源碼分析(五)字符串 TString

# 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 */
}
發佈了198 篇原創文章 · 獲贊 36 · 訪問量 27萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章