Lua 源码分析之String

因为每一版本的源码可能会有差别,现在基于lua 5.2.1来分析,保持一致性。

从虚拟机的大体来看,字符串通过一个结构体存放在global_State里,这个结构stringtable(lstate.h)是:

GCObject(lstate.h)的结构是:

stringtable结构体的字段含义是:

GCObject **hash: GCObject指针的指针,通过Hash值可以指向Hash值存放的GCObject,其中实际引用的是TString.

lu_int32 nuse: 已经创建的TString个数

int size:TString的总个数(初始值为32)

在global_State中,会有一个stringtable,通过它来访问虚拟机中的所有字符串(包括长短字符串),现在5.2.1的版本中,长短字符串的存放有点略不同,后面会细说。

扫描二维码关注公众号,回复: 8793008 查看本文章

具体看看字符串的实现,主要数据结构体是TString(lobject.h)

TString关联的宏定义, L_Umaxalign(llimits.h)

TString关联的宏定义, CommonHeader(lobject.h),这一个定义很多地方会使用到

这是TString相关的结构体。

下面来是分析如何创建一个字符串,实现的文件是在lstring.c。

在lua里面,创建一个字符串,会根据字符串的长短来区分到底创建长还是短的字符串。

如果长度小于LUAI_MAXSHORTLEN(值为40),它就会创建一个短字符串;

否则的话就会创建长字符串。

短字符串的区别是:如果在创建的过程,发现hash值、长度和内容都一样的短字符串,就会复用它,不再去进行分配。

但是长的字符串就会不管三七二一都会创建一个新的TString.

在创建字符串之前,stringtable的长度会动态扩展,当字符串table分配的size都被用完了,并且当size不大于最大数量的一半时,即需要调用resize进行扩展:

if (tb->nuse >= cast(lu_int32, tb->size) && tb->size <= MAX_INT/2)
    luaS_resize(L, tb->size*2);  /* too crowded */

既然有动态扩展,也有动态缩小,在GC的时候,stringtable也会检查是否需要回收释放空间,当字符串table使用率小于50%的时候,会进行resize。

if (g->gckind != KGC_EMERGENCY) {  /* do not change sizes in emergency */
    int hs = g->strt.size / 2;  /* half the size of the string table */
    if (g->strt.nuse < cast(lu_int32, hs))  /* using less than that half? */
      luaS_resize(L, hs);  /* halve its size */
    luaZ_freebuffer(L, &g->buff);  /* free concatenation buffer */
  }

具体创建逻辑

1. 创建字符串对象:

字符串的内存结构是:TString+字符串内容+'\0','\0'是结尾标识,这一个结构的设计实在太棒棒了,直接省掉一个指针指向具体字符串内容,读取的时候只需要读一块连续内存。

2.最最关键的地方来了,既然都创建了,那怎么存到stringtable里呢?怎么构造?好,前面的介绍其实并不是没用的,那是概览。stringtable里有一个GCObject** hash,其实就是需要通过它来索引到所有的字符串内容。

hash指向hash数组,数组里每一个单元存放的是该hash值下面所有TString链表的队尾元素的地址。(hash值的含义实质为字符串长度,只是通过hash计算和lmod取模)大概示意图如下。

构造链表的函数是luaC_newobj (lgc.c):

最后一个,前面提了很多hash关键字,在lua虚拟机里,hash算法采用的是JSHash,虚拟机启动的时候会生成一个随机种子,这个随机种子会在创建hash的时候一并带进去,以防被猜到hash值。

lstring的设计实在很棒棒,很多巧妙的设计,lua的源码小而精真不是盖的。

发布了70 篇原创文章 · 获赞 48 · 访问量 19万+

猜你喜欢

转载自blog.csdn.net/fwb330198372/article/details/80701906