Lua字符串机制详解
3.1 核心特性
Lua语言采用不可变字符串模型,当创建新字符串时会生成独立副本,原有数据保持不变。
3.2 内部实现原理
// luaconf.h:595
// 实现8字节对齐机制
typedef union { double u; void *s; long l; } LUAI_USER_ALIGNMENT_T;
// limits.h:47
typedef LUAI_USER_ALIGNMENT_T L_Umaxalign;
// lobject.h:199
typedef union TString {
L_Umaxalign dummy; // 确保最大对齐方式
struct {
CommonHeader;
unsigned char isReserved; // 标识保留字标记
unsigned int hashValue;
size_t length;
} tsv;
} TString;
// lstate.h:68
typedef struct global_State {
stringtable stringTable; /* 字符串哈希表 */
// 其他字段...
} global_State;
// lstate.h:38
typedef struct stringtable {
GCObject **hashTable; // 哈希桶数组,采用链式存储结构
lu_int32 elementCount; // 元素数量
int capacity;
} stringtable;
// 动态调整哈希表容量:
void resizeStringTable(lua_State *L, int newCapacity) {
GCObject **newTable;
stringtable *table = &G(L)->stringTable;
// 避免GC遍历期间调整
if (G(L)->gcState == GCSsweepstring) return;
newTable = luaM_newvector(L, newCapacity, GCObject *);
memset(newTable, 0, newCapacity * sizeof(GCObject*));
// 重哈希操作
for (int i=0; i<table->capacity; i++) {
GCObject *current = table->hashTable[i];
while (current) {
GCObject *next = current->gch.next;
unsigned int hash = gco2ts(current)->hashValue;
int newIndex = lmod(hash, newCapacity);
current->gch.next = newTable[newIndex];
newTable[newIndex] = current;
current = next;
}
}
// 释放旧表资源
luaM_freearray(L, table->hashTable, table->capacity, TString *);
table->capacity = newCapacity;
table->hashTable = newTable;
}
系统中存在两个关键调用场景:
// lgc.c:431
void checkTableSizes(lua_State *L) {
global_State *g = G(L);
// 检查字符串哈希表容量
if (g->stringTable.elementCount < (g->stringTable.capacity / 4) &&
g->stringTable.capacity > MINSTRTABSIZE * 2)
resizeStringTable(L, g->stringTable.capacity / 2);
// 检查缓冲区容量
if (luaZ_sizebuffer(&g->buff) > LUA_MINBUFFER * 2) {
size_t newSize = luaZ_sizebuffer(&g->buff) / 2;
luaZ_resizebuffer(L, &g->buff, newSize);
}
}
// lstring.c:75
TString *createString(lua_State *L, const char *input, size_t length) {
GCObject *node;
unsigned int hashSeed = (unsigned int)length;
size_t stepSize = (length >> 5) + 1;
// 计算哈希值
for (size_t i=length; i>=stepSize; i-=stepSize)
hashSeed ^= ((hashSeed << 5) + (hashSeed >> 2) + (unsigned char)input[i-1]);
// 查找已存在字符串
for (node = G(L)->stringTable.hashTable[lmod(hashSeed, G(L)->stringTable.capacity)];
node != NULL;
node = node->gch.next) {
TString *existing = rawgco2ts(node);
if (existing->tsv.length == length && memcmp(input, getstr(existing), length) == 0) {
if (isdead(G(L), node)) changewhite(node);
return existing;
}
}
// 创建新字符串
return allocateString(L, input, length, hashSeed);
}
// lstring.c:50
static TString *allocateString(lua_State *L, const char *input, size_t length, unsigned int hash) {
TString *newString;
stringtable *table = &G(L)->stringTable;
// 检查内存限制
if (length + 1 > (MAX_SIZET - sizeof(TString))/sizeof(char))
luaL_error(L, "memory allocation failed");
// 分配内存
newString = cast(TString *, luaM_malloc(L, (length+1)*sizeof(char)+sizeof(TString)));
newString->tsv.length = length;
newString->tsv.hashValue = hash;
newString->tsv.marked = luaC_white(G(L));
newString->tsv.tt = LUA_TSTRING;
newString->tsv.isReserved = 0;
// 复制字符串内容
memcpy(newString+1, input, length);
((char *)(newString+1))[length] = '\0';
// 插入哈希表
hash = lmod(hash, table->capacity);
newString->tsv.next = table->hashTable[hash];
table->hashTable[hash] = obj2gco(newString);
table->elementCount++;
// 触发扩容
if (table->elementCount > (lu_int32)table->capacity && table->capacity <= MAX_INT/2)
resizeStringTable(L, table->capacity * 2);
return newString;
}
保留字标记字段说明:
// llex.c:37
const char *const reservedKeywords[] = {
"and", "break", "do", "else", "elseif",
"end", "false", "for", "function", "if",
"in", "local", "nil", "not", "or", "repeat",
"return", "then", "true", "until", "while",
"..", "...", "==", ">=", "<=", "~=",
"<number>", "<name>", "<string>", "<eof>",
NULL
};
// llex.h:24
enum ReservedTokens {
TK_AND = FIRST_RESERVED, TK_BREAK,
TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION,
TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT,
TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE,
TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, TK_NUMBER,
TK_NAME, TK_STRING, TK_EOS
};
// lstring.h:20
#define createString(L, s) createStringImpl(L, s, strlen(s))
// lgc.h:60
#define FIXED_BIT 5
// lstring.h:24
#define markAsReserved(s) l_setbit((s)->tsv.marked, FIXED_BIT)
// llex.h:36
#define RESERVED_COUNT (cast(int, TK_WHILE-FIRST_RESERVED+1))
// llex.c:64
void initializeKeywords(lua_State *L) {
int index;
for (index=0; index<RESERVED_COUNT; index++) {
TString *keyword = createString(L, reservedKeywords[index]);
markAsReserved(keyword);
lua_assert(strlen(reservedKeywords[index])+1 <= TOKEN_LEN);
keyword->tsv.isReserved = cast_byte(index+1);
}
}