ets跟Dets
需要实际的系统都需要在有限的时间存储和检索大量的数据。
在程序中主要使用的是一个数据组合类型是一些项的聚合。Erlang的列表提供了实现聚合的一种方法,但是如果列表中的项超过一定数量,存取元素过程就会变慢。平均来说,我们需要校验聚合中50%的元素来确定一个给定元素的存在,而需要遍历所有的元素来确定一个给定值还存在。
为处理快速检索,Erlang使用两种机制,一个是Erlang项元存储(Erlang Term Storage,ETS)机制和Erlang磁盘项元存储(Erlang Disk Term Storage,Dets)机制,分别提供内存高效和磁盘高效的大量数据聚合的存储和检索。Erlang同时也提供了一个完整的数据库应用程序Mnesia。
ETS表
1.ETS表存储元组,通过元组中的关键字字段存储元素,这些表是通过散列表和二叉树实现的,不同的表现形式提供不同类型的聚合。
2.集合和袋是两种不同的集合,它们都包含元素,但不同点是集合每个元素只包含一次,袋可以包含某个元素多次。
3.Erlang中有四种不同的ETS表:
集合(Set):在集合中每个关键字只出现一次,所以使用这种表作为索引的例子,意味着每个词只对应表中的一个元素。
有序集合(Ordered Set):一个有序集合与集合具有同样的属性,但其元素按关键字的字典顺序存储,比如说,记录"refactoring"会位于记录"reply"之前的位置。
袋(Bag):袋允许不同的记录对应同一个关键字,就像{"refactoring",4}和{"refactoring",34}是允许的,元素必须间隔开,在索引的例子中,这意味着只有一个项元对应于特定行上的一个特定单词。
复袋(Duplicate Bag):一个复袋允许包含重复的元素,也可以有重复的关键字,所以,表中可能包含项元{"refactoring",4}两次或多次。
4.在Erlang语言中可以表达为对关键字的处理:集合和有序集合只包含每个关键字一次,而袋和复袋对于关键字的重复给以两种不同的变化。
实现和平衡
1.对于这些集合的实现,除了对有序集合中的元素访问时间相对于聚合大小有对数级别变化外,其他的检索时间都是恒定的。两种情况的表现都要远好于基于列表形式的线性访问时间。
2.集合、袋和复袋都被存储为散列表形式,其中存储元组的位置是通过函数(hash function,散列函数)把元组的关键字映射到存储这个值的内存地址来确定。假设我们的散列表对10个项元分配了空间,那么散列函数会返回10个独一无二的内存位置,每个项元一个。如果需要插入一个附加项元,此表会重新计算散列值(重新组织),为更多的项元创建空间并且返回更多互不相同的内存空间作为关键字的散列结果。这给我们一个从关键字到对应元组、常量的存取时间,但当我们需要写入一个项元和重新计算散列表时,这个时间将会变化。
3.一个有序集合存储在一个AVL平衡二叉树中,其顺序由关键字字段内在的顺序确定。这意味着分支的长度决定存取的时间复杂度,它是存储在二叉树中聚合大小的对数。有序集允许集合按照键的顺序来遍历,然而其它表现形式只是简单按照存储顺序来遍历。在集合中,顺序依赖于散列函数的实现细节,同时依赖于它们在内存中的存储顺序。
4.选择使用哪种形式的表取决于特定的应用。如果要写出一个复杂的索引,需要按字母(关键字)顺序遍历索引,但是要从在线索引中查找单个词汇,只需要存取某个特定的元组,这样一个无序的集合就足够了。
5.Erlang发行版中ETS表的实现非常灵活,允许关键字字段是任何类型,包括复杂数据结构。
创建表
1.ets模块包含了一系列创建、操作和删除ETS表的函数。通过调用ets:new/2创建一个表,第一个参数是表的名字,第二个参数由可选项的列表组成。函数调用ets:new(myTable,opts)返回表的标识符来用作表的引用。
2.在默认设置中,如果传入一个空选项表给ets:new/2函数,则创建一个集合,其关键字在第一个位置,并提供表中值的受保护访问允许所有进程读取表,但只有表的所有者可以写入。其它选项包括:
set,ordered_set,bag,duplicate_bag:如果选项表中包括这些中的任意一个,则创建响应类别的ETS表。
{keypos,Pos}:创建一个表,其关键字在位置Pos上。
public,protected,private:所有进程可读写一个公共表,仅表的所有者可读写一个私有表。
named_table:静态注册表的名字,并且可以将其在ETS操作中用作表的引用。
3.通过传入表的标识符使用ets:info/1函数获取表的信息。
4.如果表是通过named_table创建的,那么既可以通过名字又可以通过标识符对表进行存取。
5.警告:即使每个表都是通过ets:new/2函数创建的,且表的名字通过表的信息可以查到,也不可以将表的名字用于表的存取,除非把named_table选项设置为enabed。如果选项没有enabled,试图通过这种方式存取表将导致坏参数运行错误信息。
6.当不再有程序引用表的时候,该表使用的存储空间是不会被自动回收的(也就是说,它们无法进行"垃圾回收")。因此,需要通过调用ets:deleted(TabId)来手动删除这个表。然而,表与创建它的进程相连,如果该进程终止了,那么该表会被自动删除。
7.警告:因为ETS表和创建它的表相连,所以在终端测试时需要特别小心,如果引发了运行时错误,那么终端进程将崩溃并重启,结果是将会失去所有的ETS表和与它们相关的数据。如果这种情况发生了,那么可以通过终端命令f()清除所有与表引用有关的变量并重新启动。
处理表元素
1.可以使用ets:insert/2函数插入元素到表格,并且通过ets:lookup/2函数根据关键字来存取它们:
ets:insert(TabId,{alison,sweden}).
ets:lookup(TabId,alison).
在这个例子中,TabId是一个集合,插入以alison为关键字的第二个元素会覆盖第一个元素。当处理集合时一个通常的错误做法是,在插入一个更新前先删除一个元素。这个删除操作是多余的,因为插入会自动覆盖旧的项元。
ets:insert(TabId,{alison,italy}).
ets:lookup(TabId,alison).
如果删除了现有表,并重建这个表为袋,将会看到一些不同的变化:
ets:delete(TabId).
TabId2=ets:new(myTable,[bag]).
ets:insert(TabId2,{alison,sweden}).
ets:insert(TabId2,{alison,italy}).
ets:lookup(TabId2,alison)结果为[{alison,sweden},{alison,italy}]
保留袋中元素的插入顺序,在结果中元素的顺序即时它们加入袋中的顺序,最先进入的最先出来。
因为这个ETS表与复袋相比更像是一个袋,所以第二次插入同一个元组没有任何效果。
ets:insert(TabId2,{alison,italy}).
ets:lookup(TabId2,alison)结果为[{alison,sweden},{alison,italy}]