`
616050468
  • 浏览: 10136 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

lua源码之TString和Table数据结构分析

    博客分类:
  • lua
阅读更多

    最近游戏项目改用c++/lua开发,于是开始学习lua,lua是一种轻量小巧的脚本语言,据说lua是最快的脚本语言也不无道理。这篇文章从lua的数据结构入手,把lua的实现描述出来,加深自己的理解。(lua源码版本为5.2.3)

    所谓lua虚拟机其实就是一个c的struct结构体(lua_State),所有lua代码都通过解析器加载到lua_State结构中保存。lua中的基础数据类型分为8种:nil, boolean, lightuserdata, number, string, table, function(包括c函数cfunction和lua函数lfunction), userdata, thread, 其中最重要的就是table,因为所有的数据其实都是保存在table中的。程序就是数据结构和算法,那么在lua中是怎么表示这些数据类型呢。


                                                             图 1-1

如图1-1所示,lua中对基础数据类型使用统一的数据结构TValue表示,value_表示值,tt_表示数据类型。由此可知Value是一个union结构,结合源码(lobject.h 184行开始/* Macros to set values */)可知,对于nil,boolean,lightuserdata,number,cfunction这些数据类型的值都是直接存放在TValue中,其他类型的数据都用GCObject来表示,TValue中只是保存GCObject结构的指针。下面重点讲一下Table和TString两种类型,后面深入其他东西的时候会继续讲到。

    1.   TString

/*
** creates a new string object
*/
static TString *createstrobj (lua_State *L, const char *str, size_t l,
                              int tag, unsigned int h, GCObject **list) {
  TString *ts;
  size_t totalsize;  /* total size of TString object */
  totalsize = sizeof(TString) + ((l + 1) * sizeof(char));
  ts = &luaC_newobj(L, tag, totalsize, list, 0)->ts;
  ts->tsv.len = l;
  ts->tsv.hash = h;
  ts->tsv.extra = 0;
  memcpy(ts+1, str, l*sizeof(char));
  ((char *)(ts+1))[l] = '\0';  /* ending 0 */
  return ts;
}

                                      图1-2

   从代码可以看出,字符串在lua的内存分配结构,如图1-2所示。lua字符串都自动加上结束符。
 

 

/*
** new string (with explicit length)
*/
TString *luaS_newlstr (lua_State *L, const char *str, size_t l) {
  if (l <= LUAI_MAXSHORTLEN)  /* short string? */
    return internshrstr(L, str, l);
  else {
    if (l + 1 > (MAX_SIZET - sizeof(TString))/sizeof(char))
      luaM_toobig(L);
    return createstrobj(L, str, l, LUA_TLNGSTR, G(L)->seed, NULL);
  }
}
   
    在实际中对字符串的使用大部分都是很短的,所以lua保存字符串分为短字符串和长字符串,短字符串都保存在全局的字符串hash表中,长字符串则放在全局的可gc对象列表中。

 

 

/*
** checks whether short string exists and reuses it or creates a new one
*/
static TString *internshrstr (lua_State *L, const char *str, size_t l) {
  GCObject *o;
  global_State *g = G(L);
  unsigned int h = luaS_hash(str, l, g->seed);
  for (o = g->strt.hash[lmod(h, g->strt.size)];
       o != NULL;
       o = gch(o)->next) {
    TString *ts = rawgco2ts(o);
    if (h == ts->tsv.hash &&
        l == ts->tsv.len &&
        (memcmp(str, getstr(ts), l * sizeof(char)) == 0)) {
      if (isdead(G(L), o))  /* string is dead (but was not collected yet)? */
        changewhite(o);  /* resurrect it */
      return ts;
    }
  }
  return newshrstr(L, str, l, h);  /* not found; create a new string */
}
   
    短字符串的hash表采用开放寻址hash算法,在处理一个短字符串的时候对首先判断字符串在hash表中是否已存在,存在则直接返回其地址;不存在则创建该字符串,并求出其hash值。长字符串则都重新分配内存保存。因此在对比两个字符串是否相等时,短字符串只要比较地址是否相等就行了,而对于长字符串则需要对比所有字符。由此可见lua中对于短字符串的处理很高效,一般用于字符串的比较,或者用作table的key。

    2.   Table



                                               图1-3

     CommonHeader先忽略,lu_byte 是typedef unsigned char lu_byte,然后结合源码来讲解Table的实现(ltable.c, ltable.h),对lua中对table的操作函数接口都定义在ltable.h中。lua中的table是key-value的形式来存放数据的,table分为两部分:数组部分array和hash部分。array和sizearray为数组部分,node,lastfree,lsizenode为hash部分。

 

Table *luaH_new (lua_State *L) {
  Table *t = &luaC_newobj(L, LUA_TTABLE, sizeof(Table), NULL, 0)->h;
  t->metatable = NULL;
  t->flags = cast_byte(~0);
  t->array = NULL;
  t->sizearray = 0;
  setnodevector(L, t, 0);
  return t;
}

    创建一个空的table时,数组部分和hash部分都为0。

 

    返回表的一个key的值,以指针的形式返回:

 

/*
** main search function
*/
const TValue *luaH_get (Table *t, const TValue *key) {
  switch (ttype(key)) {
    case LUA_TSHRSTR: return luaH_getstr(t, rawtsvalue(key));
    case LUA_TNIL: return luaO_nilobject;
    case LUA_TNUMBER: {
      int k;
      lua_Number n = nvalue(key);
      lua_number2int(k, n);
      if (luai_numeq(cast_num(k), n)) /* index is int? */
        return luaH_getint(t, k);  /* use specialized version */
      /* else go through */
    }
    default: {
      Node *n = mainposition(t, key);
      do {  /* check whether `key' is somewhere in the chain */
        if (luaV_rawequalobj(gkey(n), key))
          return gval(n);  /* that's it */
        else n = gnext(n);
      } while (n);
      return luaO_nilobject;
    }
  }
}

 

 当key为LUA_TNUMBER时,调用luaH_getint

 

/*
** search function for integers
*/
const TValue *luaH_getint (Table *t, int key) {
  /* (1 <= key && key <= t->sizearray) */
  if (cast(unsigned int, key-1) < cast(unsigned int, t->sizearray))
    return &t->array[key-1];
  else {
    lua_Number nk = cast_num(key);
    Node *n = hashnum(t, nk);
    do {  /* check whether `key' is somewhere in the chain */
      if (ttisnumber(gkey(n)) && luai_numeq(nvalue(gkey(n)), nk))
        return gval(n);  /* that's it */
      else n = gnext(n);
    } while (n);
    return luaO_nilobject;
  }
}
    当key小于数组长度时,则直接返回数组中的值,否则计算key的hash值,从表的hash部分查找key的值。

 

 

当key为LUA_TSHRSTR时,调用luaH_getstr:

 

/*
** search function for short strings
*/
const TValue *luaH_getstr (Table *t, TString *key) {
  Node *n = hashstr(t, key);
  lua_assert(key->tsv.tt == LUA_TSHRSTR);
  do {  /* check whether `key' is somewhere in the chain */
    if (ttisshrstring(gkey(n)) && eqshrstr(rawtsvalue(gkey(n)), key))
      return gval(n);  /* that's it */
    else n = gnext(n);
  } while (n);
  return luaO_nilobject;
}
#define hashpow2(t,n)		(gnode(t, lmod((n), sizenode(t))))
#define hashstr(t,str)		hashpow2(t, (str)->tsv.hash)

 

当key为其他类型时,则统一调用mainposition获取其hash值对应的散列地址。我们来看看mainposition支持哪些类型。

 

static Node *mainposition (const Table *t, const TValue *key) {
  switch (ttype(key)) {
    case LUA_TNUMBER:
      return hashnum(t, nvalue(key));
    case LUA_TLNGSTR: {
      TString *s = rawtsvalue(key);
      if (s->tsv.extra == 0) {  /* no hash? */
        s->tsv.hash = luaS_hash(getstr(s), s->tsv.len, s->tsv.hash);
        s->tsv.extra = 1;  /* now it has its hash */
      }
      return hashstr(t, rawtsvalue(key));
    }
    case LUA_TSHRSTR:
      return hashstr(t, rawtsvalue(key));
    case LUA_TBOOLEAN:
      return hashboolean(t, bvalue(key));
    case LUA_TLIGHTUSERDATA:
      return hashpointer(t, pvalue(key));
    case LUA_TLCF:
      return hashpointer(t, fvalue(key));
    default:
      return hashpointer(t, gcvalue(key));
  }
}
  表的key可以是lua中能表示的任意类型。

 

 

当给table的key赋值的时候,会先查找key是否存在,如果存在则对value重新赋值,如果不存在则表示key也不存在,会调用luaH_newkey创建key,然后再对value赋值。在创建key的时候如果table的大小不够会触发rehash对表进行扩大。

 

void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) {
  int loop;
  for (loop = 0; loop < MAXTAGLOOP; loop++) {
    const TValue *tm;
    if (ttistable(t)) {  /* `t' is a table? */
      Table *h = hvalue(t);
      // 先判断key值是否存在
      TValue *oldval = cast(TValue *, luaH_get(h, key));
      /* if previous value is not nil, there must be a previous entry
         in the table; moreover, a metamethod has no relevance */
      if (!ttisnil(oldval) ||
         /* previous value is nil; must check the metamethod */
         ((tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL &&
         /* no metamethod; is there a previous entry in the table? */
         (oldval != luaO_nilobject ||
         /* no previous entry; must create one. (The next test is
            always true; we only need the assignment.) */
         // key值不存在则创建key,如果table不够大了,就会扩大table,扩大table的时候需要对所有的key,value键对重新挪动位置
         (oldval = luaH_newkey(L, h, key), 1)))) {
........
    看下luaH_get调用的重新分配table大小的函数rehash
static void rehash (lua_State *L, Table *t, const TValue *ek) {
  int nasize, na;
  int nums[MAXBITS+1];  /* nums[i] = number of keys with 2^(i-1) < k <= 2^i */
  int i;
  int totaluse;
  for (i=0; i<=MAXBITS; i++) nums[i] = 0;  /* reset counts */
  //计算数组部分的大小
  nasize = numusearray(t, nums);  /* count keys in array part */
  totaluse = nasize;  /* all those keys are integer keys */
  //对hash部分进行遍历,计算hash部分中key为number的大小和不是number的大小
  totaluse += numusehash(t, nums, &nasize);  /* count keys in hash part */
  /* count extra key */
  //加上要创建的key,如果key为number,则nasize加1
  nasize += countint(ek, nums);
  totaluse++;
  /* compute new size for array part */
  //根据当前key的统计,重新计算数组部分的大小,结果保存到na中
  na = computesizes(nums, &nasize);
  /* resize the table to new computed sizes */
  luaH_resize(L, t, nasize, totaluse - na);
}
  来看看怎样重新计算数组部分大小的:
static int computesizes (int nums[], int *narray) {
  int i;
  int twotoi;  /* 2^i */
  int a = 0;  /* number of elements smaller than 2^i */
  int na = 0;  /* number of elements to go to array part */
  int n = 0;  /* optimal size for array part */
  for (i = 0, twotoi = 1; twotoi/2 < *narray; i++, twotoi *= 2) {
    if (nums[i] > 0) {
      a += nums[i];
      if (a > twotoi/2) {  /* more than half elements present? */
        n = twotoi;  /* optimal size (till now) */
        na = a;  /* all elements smaller than n will go to array part */
      }
    }
    if (a == *narray) break;  /* all elements already counted */
  }
  *narray = n;
  lua_assert(*narray/2 <= na && na <= *narray);
  return na;
}
    数组nums是按块保存个数的,2^(i-1) < k <=  2^i (i=1,2,3,..., 31)的属于同一块,比如key=1,则key放在nums[0]的块,key=2则放在nums[1]的块,key=3,4则存放在nums[2]的块,k=5, 8存放在nums[3]的块,如此类推。分配数组大小的规则是:遍历nums的所有块,所有块的已被使用的key的总和(对应a)大于当前块所能存放的key的上限的一半(对应twotoi/2),则数组大小取当前key的上限(对应twotoi)。可以认为a是key的密集程度,如果所有key都使用了,则密集程度为1,如果没有key被使用,则密集程度为0,computessizes就是求出key的密集程度大于0.5小于1的情况。
    以上就是对lua中TString和Table数据结构的分析。记录下来供自己查看。其他的结构以后继续分析
  • 大小: 31.1 KB
  • 大小: 28.6 KB
  • 大小: 2.2 KB
分享到:
评论

相关推荐

    云风-lua源码欣赏-lua-5.21

    首先,第一章概览中,作者深入到源代码层面,分析了Lua的源文件结构,这包括了源文件的组织方式以及代码风格。通过了解源文件划分,读者可以更好地理解Lua的模块化设计,而代码风格部分则有助于我们了解Lua编程的...

    json转lua-table工具

    JSON(JavaScript Object Notation)和Lua Table 是两种广泛使用的数据序列化格式,分别在Web开发和游戏编程领域中占据重要地位。JSON因其简洁明了的结构而被广泛用于数据交换,而Lua Table则是Lua编程语言中的核心...

    Lua源码分析

    对于理解整个Lua的源码结构,选择合适的阅读顺序至关重要。官方文档中建议的阅读顺序可以帮助我们更好地梳理整个Lua实现的脉络。 8. 全局状态机及内存管理: Lua的全局状态机负责协调Lua虚拟机的全局状态,而内存...

    lua源码分析

    由于Lua的源码相对较为简洁,且注重性能,因此对于想要深入学习C语言、数据结构、算法设计,或者是希望了解语言设计和实现的程序员而言,Lua源码分析可以成为一个非常有价值的学习资源。通过研究Lua的源码,开发者...

    lua源码鉴赏云风

    2. **语法解析**:Lua的词法分析和语法分析是通过Lemon解析器完成的,这是一个小型但功能强大的解析工具。云风会解释如何从源代码字符串解析出抽象语法树(AST),进而转换成虚拟机指令。 3. **垃圾回收**:Lua使用...

    lua源码下载 Lua-5.3.4 源码 最新 截止2017-3-7

    Lua的源码结构清晰,易于阅读。主要包含以下几个关键部分: 1. **lprefix.h**: 这个头文件包含了所有Lua源文件通用的预处理定义,如版本信息、编译选项等。 2. **lua.h**: 定义了Lua API,包括所有对外暴露的函数...

    Lua跟C之间交互Table

    详细描述Lua和C之间相互传递Table类型数据 /* ====================================================== */ // 遍历Lua传入的Table类型参数, 获取它的Key/Value, 其关键操作是 lua_next() // lua_next() 返回1表示...

    lua 源码剖析

    源码中包含了lua虚拟机的实现、语法解析器、垃圾回收机制、表和字符串的数据结构、以及各种内置函数和库的源代码。 在阅读《lua 源码剖析》时,你可以学习到以下关键知识点: 1. **lua虚拟机(VM)**:lua的虚拟机...

    云风《Lua源码欣赏》1积分

    第七章深入分析了Lua的虚拟机架构,涵盖了指令结构、字节码的执行细节、寄存器赋值、表处理、表达式运算、分支和跳转、函数调用、不定长参数、生成闭包以及For循环等虚拟机内部的工作原理。 最后,第八章探讨了内置...

    Lua源码欣赏2012年11月-云风.pdf

    文档主要围绕着Lua语言的核心源码进行深入的分析和解读,涵盖了源代码文件划分、代码风格、核心实现、内置库实现、内存管理以及协程和函数执行等多个方面。 文档的标题中出现了“Lua源码欣赏”,表明作者云风意在与...

    lua源码导读---云风

    - **数据结构**:Lua 使用两种不同的数据结构来表示字符串:短字符串和长字符串。 - **字符串比较**:Lua 通过比较字符串的地址或内容来确定两个字符串是否相等。 - **短字符串的内部化**:为了节省内存,Lua 对长度...

    打地鼠lua源码

    3. **数据结构与对象**:lua中的table可以作为基本的数据结构,用于表示地鼠的状态(是否出现、位置、分数等),以及游戏的全局状态(如总分数、剩余时间等)。 4. **条件判断与循环**:lua的if...then...else...和...

    所有版本LUA源码

    所有版本LUA源码 lua-5.3.5 lua-5.3.4 lua-5.3.3 lua-5.3.2 lua-5.3.1 lua-5.3.0 lua-5.2.4 lua-5.2.3 lua-5.2.2 lua-5.2.1 lua-5.2.0 lua-5.1.5 lua-5.1.4 lua-5.1.3 lua-5.1.2 lua-5.1.1 lua-5.1 lua-5.0.3 lua-...

    lua 源码,包含工程文件

    2. **表(Table)数据结构**: Lua的表是其核心数据结构,类似于其他语言中的关联数组或哈希表,可以用来表示数组、集合、对象等多种数据类型。表可以作为键值对存储,甚至可以用其他表作为键,实现灵活的数据组织...

    Lua 源码赏析.pdf

    Lua 源码赏析.pdf, 其中有着非常棒的lua源代码资源可供学习。下载文件为该书的百度云连接地址。

    Lua源码剖析11

    Load 机制包括两个阶段:词法分析和语法分析。词法分析阶段负责将 Lua 代码分割成 Token,语法分析阶段负责将 Token 构建成语法树。语法树是 Lua 代码的抽象语法树,负责描述 Lua 代码的结构。 在 Load 机制中,...

    lua 源码分析

    三、Lua源码结构概览 Lua的源码组织结构清晰,主要包含以下几个部分: 1. **lapi.h**:这是Lua API的头文件,定义了Lua虚拟机与外部C函数交互的接口。 2. **lobject.h**:包含了Lua对象(如表、字符串、用户数据等)...

    lua-5.3.4源码

    1. 表(Table):Lua的主要数据结构,可以作为数组、哈希表或类来使用。表的实现基于开放寻址的哈希表,具备动态调整大小的能力。 2. 常量池:存放字符串、数值等常量,减少内存分配。 五、垃圾回收机制 Lua 5.3.4...

Global site tag (gtag.js) - Google Analytics