// lobject.h
/*
** String headers for string table
*/
typedef union TString {
L_Umaxalign dummy; /* ensures maximum alignment for strings */
struct {
CommonHeader;
lu_byte reserved;
unsigned int hash;
size_t len;
} tsv;
} TString;
上面是lua中字符串的数据结构。可以看到,TString
中并没有出现显式的char*
变量,而是存储了哈希值和长度。
所以,这里面没有存储字符吗?我们来看一下TString
的创建函数。
先贴一下对应的源码:
// lstring.c
TString *luaS_newlstr (lua_State *L, const char *str, size_t l) {
GCObject *o;
unsigned int h = cast(unsigned int, l); /* seed */
size_t step = (l>>5)+1; /* if string is too long, don't hash all its chars */
size_t l1;
for (l1=l; l1>=step; l1-=step) /* compute hash */
h = h ^ ((h<<5)+(h>>2)+cast(unsigned char, str[l1-1]));
for (o = G(L)->strt.hash[lmod(h, G(L)->strt.size)];
o != NULL;
o = o->gch.next) {
TString *ts = rawgco2ts(o);
if (ts->tsv.len == l && (memcmp(str, getstr(ts), l) == 0)) {
/* string may be dead */
if (isdead(G(L), o)) changewhite(o);
return ts;
}
}
return newlstr(L, str, l, h); /* not found */
}
函数传入字符串str
和长度l
,首先计算字符串的哈希值h
,然后在全局的字符串表中查找,比较字符串长度和内存,如果找到则直接返回已经创建好的字符串,否则新建。
看一下全局字符串表strt
的定义:
// lstate.h
/*
** `global state', shared by all threads of this state
*/
typedef struct global_State {
stringtable strt; /* hash table for strings */
...
typedef struct stringtable {
GCObject **hash;
lu_int32 nuse; /* number of elements */
int size;
} stringtable;
strt
定义在全局状态中,hash
是一个保存了所有TString
的哈希表。遍历这个表可以找到lua中所有的字符串。
在hash
的查找过程中有一个条件:if (ts->tsv.len == l && (memcmp(str, getstr(ts), l) == 0))
,这里先比较了长度,然后又比较了字符串内存,来看一下getstr
:
// lobject.h
#define getstr(ts) cast(const char *, (ts) + 1)
这里把str
与已创建字符串ts
后面的内存进行比较,所以我们可以猜测,TString
中也保存了字符串,就在紧邻的内存区域中。
进一步来看一下新建字符串的函数TString *newlstr (lua_State *L, const char *str, size_t l, unsigned int h)
:
// lstring.c
static TString *newlstr (lua_State *L, const char *str, size_t l,
unsigned int h) {
TString *ts;
stringtable *tb;
if (l+1 > (MAX_SIZET - sizeof(TString))/sizeof(char))
luaM_toobig(L);
ts = cast(TString *, luaM_malloc(L, (l+1)*sizeof(char)+sizeof(TString)));
ts->tsv.len = l;
ts->tsv.hash = h;
ts->tsv.marked = luaC_white(G(L));
ts->tsv.tt = LUA_TSTRING;
ts->tsv.reserved = 0;
memcpy(ts+1, str, l*sizeof(char));
((char *)(ts+1))[l] = '\0'; /* ending 0 */
tb = &G(L)->strt;
h = lmod(h, tb->size);
ts->tsv.next = tb->hash[h]; /* chain new entry */
tb->hash[h] = obj2gco(ts);
tb->nuse++;
if (tb->nuse > cast(lu_int32, tb->size) && tb->size <= MAX_INT/2)
luaS_resize(L, tb->size*2); /* too crowded */
return ts;
}
字符串表中没有找到时,就会进入这个函数新建字符串。
分配内存时,除了TString
的空间,还多分配了l+1
字节,用来存储传入的字符串,这里也验证了之前的猜想。TString
新建之后,根据字符串哈希值再次计算新的哈希值h
,然后插入全局字符串表strt
中。可以看到,strt
的哈希表与table
的哈希表不同:table
的哈希表采用的是开放寻址法,而strt
的哈希表则用的链地址法。
如果插入后发现全局字符串表太大,就会调用void luaS_resize (lua_State *L, int newsize)
重新给哈希表分配内存,空间为之前的两倍,并且把旧哈希表中的数据插入到新哈希表中,然后释放掉之前的内存。
https://blog.csdn.net/fujia_jzyl/article/details/88394343
原创不易,转载请注明出处,谢谢!