数据结构

下面的结构体是lua中用于表示一个table,主要关注里面的arraynode

typedef struct Table {
CommonHeader;
lu_byte flags; /* 1<<p means tagmethod(p) is not present */ lu_byte lsizenode; /* log2 of size of 'node' array */
unsigned int alimit; /* "limit" of 'array' array */
TValue *array; //数组 Node *node; // 哈希表
Node *lastfree; // 辅助寻找冲突节点的指针 struct Table *metatable;
GCObject *gclist;
} Table;

设计原理

table可以当数组也可以当哈希表,这得益于其Table结构的设计与实现。array作数组,node作哈希表。数组部分只能存放value,所以key必须是整数,其他的都放在哈希表部分。仅当哈希表没空间的时候才会触发resize,这个时候会重新调整arraynode大小,数组满了并不会触发resize

具体操作

  • 查找

主要关注整型是怎么查到的。

const TValue *luaH_get (Table *t, const TValue *key) {
switch (ttype(key)) {
case LUA_TNIL:
return luaO_nilobject;
case LUA_TSTRING:
return luaH_getstr(t, rawtsvalue(key));
case LUA_TNUMBER: {
int k;
lua_Number n = nvalue(key);
lua_number2int(k, n);
// 如果double转int再转double还能相等,则认为是整数
if (luai_numeq(cast_num(k), nvalue(key))) /* index is int? */
return luaH_getnum(t, k); // 指向value的指针
/* else go through */
}
default: {
Node *n = mainposition(t, key);
do { /* check whether `key' is somewhere in the chain */
if (luaO_rawequalObj(key2tval(n), key))
return gval(n); /* that's it */
else n = gnext(n);
} while (n);
return luaO_nilobject;
}
}
}
const TValue *luaH_getnum (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); // 哈希部分
else n = gnext(n);
} while (n);
return luaO_nilobject;
}
}
  • 插入

同样是关注整数。

TValue *luaH_set (lua_State *L, Table *t, const TValue *key) {
const TValue *p = luaH_get(t, key); // 这里get到的是指向value的指针,有可能是数组部分
t->flags = 0;
if (p != luaO_nilobject)
return cast(TValue *, p);
else {
if (ttisnil(key)) luaG_runerror(L, "table index is nil");
else if (ttisnumber(key) && luai_numisnan(nvalue(key)))
luaG_runerror(L, "table index is NaN");
return newkey(L, t, key);
}
}
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);
TValue *oldval = luaH_set(L, h, key); // 这里取到value
if (!ttisnil(oldval) || /* result is no nil? */
(tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) { /* or no TM? */
setobj2t(L, oldval, val); // 将值设进去
luaC_barriert(L, h, val);
return;
}
/* else will try the tag method */
}
else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX)))
luaG_typeerror(L, t, "index");
if (ttisfunction(tm)) {
callTM(L, tm, t, key, val);
return;
}
t = tm; /* else repeat with `tm' */
}
luaG_runerror(L, "loop in settable");
}

rehash:

0. t->array数组,t->node是哈希表的桶。

  1. 只有正整数才允许放到array中,其他key必然都放到哈希表。
  2. 新t->array使用率必须超过50%总table元素个数,能放多少就放多少。
  3. 新t->node使用率必须超过50%,装下非array的部分。

解读:

  • 如果旧table当做数组来用,那么rehash之后所有元素都放在新t->array部分,新t->node指向dummynode。
  • 如果旧table的key都是正整数,但是比较零散,有可能新t->array极小。
  • 如果旧table的key都是正整数,但是删除了部分key,有可能新t->array比原来还小。
  • 如果旧table的key大部分都是非正整数,有可能所有key都被存哈希表了,t->array为NULL。
  • 如果旧table的key都是非正整数,则t->array必然是NULL。
  • t->array和t->node的长度都只可能是2的幂次。

哈希表部分解决冲突:

1. 通过将key1哈希到t->node上的某个位置,称为main position(简称mp)。

2. 如果mp1空闲,则直接存在mp1。若已被key2占用,计算key2的为mp2。

3. 若mp2不等于mp1,则将key1放在mp1位置上,再为key2找个其他空闲位置。

4. 若mp2等于mp1,则再为key1重新找个位置。

方案:桶+挂链。

大致的思想:为了节省内存,链表上的节点也是桶节点。即冲突的时候,在桶里面随便找一个空节点存放,再链接起来即可。

插入:如果通过哈希算出的桶节点空闲,直接使用。

若不空闲,分两种情况:

1)这个位置上的节点和新节点具有同样的哈希值。那这个节点肯定是桶头,随便找个空节点存放新节点,挂到桶头的后面。

2)这个位置上的节点和新节点具有不同的哈希值。那这个节点肯定是属于其他链表的,帮它随便找个位置放。新节点就是这个位置的桶头。

找空闲位置:有个空闲指针,从后往前移动,空节点就是可用的。若找不到空节点,持续到t->node头,就会触发rehash。

最新文章

  1. php构造函数和析构函数
  2. 查询mysql数据库中所有用户及用户权限
  3. Java远程方法调用(RMI)
  4. jQuery 源码分析 8: 回头看jQuery的构造器(jQuery.fn,jQury.prototype,jQuery.fn.init.prototype的分析)
  5. UML类图几种关系总结
  6. 根据dba_hist_osstat统计CPU占用情况
  7. 内存管理单元--MMU
  8. windows下编译Grafana前端
  9. logback 按时间和大小生成日志不生效的问题
  10. fROM PPV report
  11. Linux 远程工具Screen 的应用
  12. Zabbix Agent 源码编译安装
  13. PAT A1033 To Fill or Not to Fill (25 分)——贪心
  14. Flv的结构分析
  15. python摸爬滚打之day09----初识函数
  16. [博客迁移]探索Windows Azure 监控和自动伸缩系列1 - 连接中国区Azure
  17. 随手练——DFS小练
  18. eclipse中添加aptana插件(html.css.js自动提示)
  19. 强大的jQuery网格插件 ParamQuery
  20. Linux终端提示符PS1设置(颜色)

热门文章

  1. Domain Socket本地进程间通信
  2. Editor
  3. Redis字符串(String)
  4. java课后实验性问题2
  5. UML期末复习题——2.3:UML State Diagram
  6. [转][C#]基础连接已经关闭 未能为 SSL/TLS 安全通道建立信任关系
  7. &lt;JavaScript&gt; 稳妥构造函数模式与工厂模式的区别
  8. Flask之加载静态资源
  9. windows7安装docker异常:looks like something went wrong in step ‘looking for vboxmanage.exe’
  10. javascript两个数组内容合并