Lua5.4源码阅读—字符串

Posted xhjh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lua5.4源码阅读—字符串相关的知识,希望对你有一定的参考价值。

在Lua虚拟机中存在一个全局的数据区,用来存放当前系统中的所有字符串,同一个字符串数据,只会有一份副本,一个字符串一旦创建,将不可改变,字符串变量存放的仅是字符串的引用,而不是实际的内容。根据字符串的长度分为两种类型,一种短字符串类型为正常的字符串类型,一种为长字符串类型,会将类型的第5位置为1,如下:

#define makevariant(t,v)	((t) | ((v) << 4))

#define LUA_VSHRSTR	makevariant(LUA_TSTRING, 0)  /* 短字符串类型 */
#define LUA_VLNGSTR	makevariant(LUA_TSTRING, 1)  /* 长字符串类型 */

字符串的实现

typedef struct TString 
  CommonHeader;
  lu_byte extra;  /* 短字符串标识该字符串是否为保留字符串存的是保留字的索引1-NUM_RESERVED+1,长字符串标识有没有进行散列值计算 */
  lu_byte shrlen;  /* 短字符串的长度 长度不会超过 LUAI_MAXSHORTLEN,因此一个字节可以表示长度 */
  unsigned int hash;	/* 该字符串的散列值 */
  union 
    size_t lnglen;  /* 为长字符串的长度 */
    struct TString *hnext;  /* 指向散列表中的下一个字符串 */
   u;
  char contents[1];	/* 字符串的内容 */
 TString;

所有短字符串存放在全局变量global_State的strt成员里,结构如下:

typedef struct stringtable 
  TString **hash;
  int nuse;  /* 散列表中元素的数量 */
  int size;	/* 散列表的大小,值为2的幂 */
 stringtable;

所有长字符串存放在全局变量global_State的allgc链表成员里,所有需要GC的对象都会添加到该链表中。
lua虚拟机启动时会调用f_luaopen函数,该函数中会调用luaS_init函数对字符串相关进行初始化,在该函数中会对短字符串散列表进行初始化,首次分配大小为MINSTRTABSIZE的散列表,并创建一个固定的内存错误信息字符串对象,设置其不会被GC,将其从GC对象链表中转移到global_State的fixedgc固定对象链表中,随后创建字符串缓冲区,该缓冲区的作用是每次用luaS_new创建字符串对象时会先从该缓冲区内查找最近缓冲的字符串中是否存在相同的,如果存在则复用,不存在则走luaS_newlstr创建:

/*
** 初始化短字符串表和字符串缓冲
*/
void luaS_init (lua_State *L) 
  global_State *g = G(L);
  int i, j;
  stringtable *tb = &G(L)->strt;
  tb->hash = luaM_newvector(L, MINSTRTABSIZE, TString*);     /* 分配一个128大小的短字符串散列表 */
  tablerehash(tb->hash, 0, MINSTRTABSIZE);  /* 初始化散列表 */
  tb->size = MINSTRTABSIZE;
  g->memerrmsg = luaS_newliteral(L, MEMERRMSG); /* 创建内存错误信息字符串 */
  luaC_fix(L, obj2gco(g->memerrmsg));  /* 设置为不会被GC对象 */
  for (i = 0; i < STRCACHE_N; i++)  /* 用上面创建的字符串填充字符串缓冲区内容 */
    for (j = 0; j < STRCACHE_M; j++)
      g->strcache[i][j] = g->memerrmsg;


/*
** 对字符串散列表重新散列,osize旧的散列表大小,nsize新的大小
*/
static void tablerehash (TString **vect, int osize, int nsize) 
  int i;
  for (i = osize; i < nsize; i++)  /* 清除掉新增的桶数据 */
    vect[i] = NULL;
  for (i = 0; i < osize; i++)   /* 重新散列旧的数据 */
    TString *p = vect[i];
    vect[i] = NULL;
    while (p)   /* 遍历每个一个桶链表中的字符串 */
      TString *hnext = p->u.hnext;  /* save next */
      unsigned int h = lmod(p->hash, nsize);  /* 更加新的散列表大小确定新的位置 */
      p->u.hnext = vect[h];  /* 插入新位置桶链表的开始位置 */
      vect[h] = p;
      p = hnext;
    
  


#define luaS_newliteral(L, s)	(luaS_newlstr(L, "" s, \\
                                 (sizeof(s)/sizeof(char))-1))
/*
** 添加字符串
*/
TString *luaS_newlstr (lua_State *L, const char *str, size_t l) 
  if (l <= LUAI_MAXSHORTLEN)  /* 短字符串 */
    return internshrstr(L, str, l);
  else 
    TString *ts;
    if (l_unlikely(l >= (MAX_SIZE - sizeof(TString))/sizeof(char)))
      luaM_toobig(L);
    ts = luaS_createlngstrobj(L, l);    /* 创建长字符串对象 */
    memcpy(getstr(ts), str, l * sizeof(char));
    return ts;
  


/*
** 检查短字符串是否存在,存在则复用,否则创建一个新的
*/
static TString *internshrstr (lua_State *L, const char *str, size_t l) 
  TString *ts;
  global_State *g = G(L);
  stringtable *tb = &g->strt;
  unsigned int h = luaS_hash(str, l, g->seed);   /* 计算字符串的散列值 */
  TString **list = &tb->hash[lmod(h, tb->size)];    /* 根据散列值确定在散列表中的位置 */
  lua_assert(str != NULL);  /* otherwise 'memcmp'/'memcpy' are undefined */
  for (ts = *list; ts != NULL; ts = ts->u.hnext)   /* 检查散列值对应位置的桶链表中的每一个字符串是否和当前字符串相同 */
    if (l == ts->shrlen && (memcmp(str, getstr(ts), l * sizeof(char)) == 0)) 
      /* 找到相同的 */
      if (isdead(g, ts))  /* 如果已经处于将被回收状态 */
        changewhite(ts);  /* 恢复正常状态*/
      return ts;
    
  
  /* 未找到对应的字符串 */
  if (tb->nuse >= tb->size)   /* 如果散列表中的字符串数量大于等于散列表大小,则需要扩展散列表 */
    growstrtab(L, tb);
    list = &tb->hash[lmod(h, tb->size)];  /* 根据散列值重新确定在散列表中的位置 */
  
  ts = createstrobj(L, l, LUA_VSHRSTR, h);  /* 创建短字符串类型的字符串 */
  memcpy(getstr(ts), str, l * sizeof(char));    /* 将字符串内容copy到创建的字符串 */
  ts->shrlen = cast_byte(l);
  ts->u.hnext = *list;
  *list = ts;
  tb->nuse++;
  return ts;


/*
** 获取字符串的散列值
*/
unsigned int luaS_hash (const char *str, size_t l, unsigned int seed) 
  unsigned int h = seed ^ cast_uint(l);
  for (; l > 0; l--)
    h ^= ((h<<5) + (h>>2) + cast_byte(str[l - 1]));
  return h;


/*
** 扩展字符串散列表
*/
static void growstrtab (lua_State *L, stringtable *tb) 
  if (l_unlikely(tb->nuse == MAX_INT))   /* 字符串数量过大 */
    luaC_fullgc(L, 1);  /* 尝试一次GC,释放一些字符串 */
    if (tb->nuse == MAX_INT)  /* still too many? */
      luaM_error(L);  /* cannot even create a message... */
  
  if (tb->size <= MAXSTRTB / 2)  /* 检查散列表的大小是否还可以扩展 */
    luaS_resize(L, tb->size * 2);


/*
** 调整字符串散列表,如果分配失败则保持当前大小
*/
void luaS_resize (lua_State *L, int nsize) 
  stringtable *tb = &G(L)->strt;
  int osize = tb->size;
  TString **newvect;
  if (nsize < osize)  /* 缩小当前表 */
    tablerehash(tb->hash, osize, nsize);  /* depopulate shrinking part */
  newvect = luaM_reallocvector(L, tb->hash, osize, nsize, TString*);//如果osize>=nsize缩小原来的内存块,如果nsize>osize重新分配一块新的内存并将原来的内存块内容copy到新的中
  if (l_unlikely(newvect == NULL))   /* 分配内存失败 */
    if (nsize < osize)  /* 如果是缩小表 */
      tablerehash(tb->hash, nsize, osize);  /* 回退 */
    /* leave table as it was */
  
  else   /* 分配成功 */
    tb->hash = newvect;
    tb->size = nsize;
    if (nsize > osize)
      tablerehash(newvect, osize, nsize);  /* 如果是扩展内存,则重新散列之前的字符串 */
  


/*
** 创建一个字符串对象
*/
static TString *createstrobj (lua_State *L, size_t l, int tag, unsigned int h) 
  TString *ts;
  GCObject *o;
  size_t totalsize;  /* total size of TString object */
  totalsize = sizelstring(l);
  o = luaC_newobj(L, tag, totalsize);   /* 创建字符串类型的GC对象 创建的GC对象会添加到g->allgc链表中*/
  ts = gco2ts(o);
  ts->hash = h;
  ts->extra = 0;
  getstr(ts)[l] = '\\0';  /* ending 0 */
  return ts;


/*
** 创建一个长字符串对象
*/
TString *luaS_createlngstrobj (lua_State *L, size_t l) 
  TString *ts = createstrobj(L, l, LUA_VLNGSTR, G(L)->seed); /* 创建长字符串类型的字符串对象 */
  ts->u.lnglen = l;
  return ts;


/*
** 将指定的短字符串从字符串表里移除
*/
void luaS_remove (lua_State *L, TString *ts) 
  stringtable *tb = &G(L)->strt;
  TString **p = &tb->hash[lmod(ts->hash, tb->size)];
  while (*p != ts)  /* 从桶链表中查找指定的字符串 */
    p = &(*p)->u.hnext;
  *p = (*p)->u.hnext;  /* 从桶链表中移除,该处如果指定的字符串在散列表中不存在,是否会宕,调用该函数的地方移除的字符串必现存在散列表中*/
  tb->nuse--;


/*
** 获取长字符串的散列值,如果没有分配散列值,则分配
*/
unsigned int luaS_hashlongstr (TString *ts) 
  lua_assert(ts->tt == LUA_VLNGSTR);
  if (ts->extra == 0)   /* no hash? */
    size_t len = ts->u.lnglen;
    ts->hash = luaS_hash(getstr(ts), len, ts->hash);
    ts->extra = 1;  /* now it has its hash */
  
  return ts->hash;


/*
** 清除字符串缓冲区中将被GC的字符串
*/
void luaS_clearcache (global_State *g) 
  int i, j;
  for (i = 0; i < STRCACHE_N; i++)
    for (j = 0; j < STRCACHE_M; j++) 
      if (iswhite(g->strcache[i][j]))  /* will entry be collected? */
        g->strcache[i][j] = g->memerrmsg;  /* replace it with something fixed */
    


/*
** 创建一个字符串对象,首先从字符串缓冲里面通过字符串的地址值查找,找到相同的则复用,
** 如果未找到,则调用luaS_newlstr创建字符串对象,并将创建的字符串对象记录到缓冲区内
*/
TString *luaS_new (lua_State *L, const char *str) 
  unsigned int i = point2uint(str) % STRCACHE_N;  /* 用字符串的地址值取余作为索引 */
  int j;
  TString **p = G(L)->strcache[i];
  for (j = 0; j < STRCACHE_M; j++) 
    if (strcmp(str, getstr(p[j])) == 0)  /* 编译对应索引的字符串,是否存在相同字符串 */
      return p[j];  /* that is it */
  
  /* normal route */
  for (j = STRCACHE_M - 1; j > 0; j--)
    p[j] = p[j - 1];  /* 不存在相同的,则对应索引的元素往后移一个 */
  /* 创建一个字符串对象,并将其记录在对应索引的第一个位置 */
  p[0] = luaS_newlstr(L, str, strlen(str));
  return p[0];

以上是关于Lua5.4源码阅读—字符串的主要内容,如果未能解决你的问题,请参考以下文章

Lua5.4源码阅读—表

JDK源码阅读-StringStringBuilderStringBuffer

lua5.4 beta中的to-be-closed变量的用法

Lua 5.4.0/LuaRocks 安装

xmake v2.6.1 发布,使用 Lua5.4 运行时,Rust 和 C++ 混合编译支持

xmake v2.6.1 发布,使用 Lua5.4 运行时,Rust 和 C++ 混合编译支持