`
616050468
  • 浏览: 9851 次
  • 性别: 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源码 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源码分析

    Lua源码分析Lua源码分析Lua源码分析Lua源码分析Lua源码分析Lua源码分析Lua源码分析Lua源码分析

    lua源码分析

    这个是lua的源码分析,lua的源码写的非常好,整个设计的结构非常巧妙,不管是对于学习lua本身还是c语言的数据结构都非常有帮助。

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

    第一章概览由甮由源文件划分 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮 甮

    Lua 源码赏析.pdf

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

    《Lua 源码欣赏》

    当然 Lua 5.0 我也读过,4.0 和 3.2 则读的不多。 最近有一点空闲,想续写我那本 Lua 源码欣赏。按我心里的计划,还有大约 6 章。虚拟机、字节码持久化、C API 、解释器、GC、库函数。 新添了一章关于虚拟机的,...

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

    云风写的Lua源码欣赏,基于lua5.2版本源代码解析,对于初初学习lua语言的有很不错的帮助

    lua源码导读---云风

    云风新作-----lua源码导读。目前网上最好的lua源代码阅读教程。帮助你快速理解lua实现原理。

    Lua跟C之间交互Table

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

    lua 源码剖析

    里面有风云 大神写的对lua 的源码剖析。还有lua5.2源码。

    lua源码欣赏

    lua源码欣赏 lua源代码分析,来之云风!

    Lua 5.3.0源码——包含VS2012项目文件

    Lua 5.3.0源码,其中带VS2012的解决方案和项目文件。...但是,Lua5.3.0的编译器luac.c中调用了非Lua Dll导出的函数和数据结构,所以Lua编译器只能编译成链接静态库。Lua解释器则没有这个问题,可以链接静态和动态库。

    Lua 5.2.3源码——包含VS2012项目文件

    Lua 5.2.3源码,其中带VS2012的解决方案和项目文件。...但是,Lua5.2.3的编译器luac.c中调用了非Lua Dll导出的函数和数据结构,所以Lua编译器只能编译成链接静态库。Lua解释器则没有这个问题,可以链接静态和动态库。

    微信62数据源码 LUA

    lua微信62数据源码,可以读取写入62数据,用62数据登录。

    Lua源码剖析,Lua虚拟机的机制分析,硕士论文

    Lua源码剖析,Lua虚拟机的机制分析,硕士论文

    lua源码及中文文档

    Lua被设计成很容易和传统的C/C++整合的语言。这种语言的二元性带来了极大的好处。Lua是一个小巧而简单的语言,因为Lua不致力于做C语言已经...Lua所提供的机制是C不善于的:高级语言、动态结构、简洁、易于测试和调试等

    cjson.lua源码

    用sublime调试lua,lua中需要 require 'cjson',使用cjson.dll报错,可以将该cjson.lua直接放入同一目录下。

    redis-lua 源码

    redis-lua 是 Redis 的 Lua 语言的客户端开发包。 示例代码: require 'redis' local redis = Redis.connect('127.0.0.1', 6379) local response = redis:ping() -- true redis:set('usr:nrk', 10) redis:set('usr...

    Lua源码欣赏中文版

    讲解lua语言的实现源代码,由于lua是基于C语言实现,所以需要有一定C语言基础。 请配合《the implementation of Lua5.0中文版》一起阅读。 http://download.csdn.net/detail/havesnag/5071833

    lua-5.3.4源码

    lua5.3.4源码,导入VS2015可以直接编译,想学习分析Lua源码的可以下载,研究。

Global site tag (gtag.js) - Google Analytics