什么是ETS?
说到ETS那不得不提到DETS,ets和dets是两个系统模块,可以用来高效存储海量的Erlang数据。ETS是Erlang Term Storage(Erlang数据存储)的缩写,DETS则是Disk ETS(磁盘ETS)的缩写。
- 两者的主要区别如下:
ETS常驻内存,可以用它存储海量的数据(只要有足够的内存),执行查找的时间也是恒定的(在某些情况下是对数时间),但数据是容易丢失的;DETS则常驻磁盘,因此DETS远远慢与ETS,但数据是不容易丢失的,且DETS运行时占用的内存会小很多 - 共同点如下:
DETS提供了几乎和ETS一样的接口,但它会把表保
存在磁盘上,另外,ETS和DETS表可以被多个进程共享,这就让跨进程的公共数据访问变得非常高效。
ETS是怎么读写数据的呢?
ETS和DETS表保存的是元组。元组里的某一个元素(默认是第一个)被称为该表的键。通过键来向表里插入和提取元组。当我们向表里插入一个元组时会发生什么,取决于表的类型和键的值。
start() ->
lists:foreach(fun test_ets/1, [set, ordered_set, bag, duplicate_bag]).
test_ets(Mod) ->
TableId = ets:new(test, [Mod]),
ets:insert(TableId, {a, 1}),
ets:insert(TableId, {b, 2}),
ets:insert(TableId, {a, 1}),
ets:insert(TableId, {a, 3}),
ets:insert(TableId, {b, 1}),
List = ets:tab2list(TableId),
io:format("~-13w -> ~p~n", [Mod, List]),
ets:delete(TableId).
运行结果为:
1> c(ets_test).
{ok,ets_test}
2> ets_test:start().
set -> [{b,1},{a,3}]
ordered_set -> [{a,3},{b,1}]
bag -> [{b,2},{b,1},{a,1},{a,3}]
duplicate_bag -> [{b,2},{b,1},{a,1},{a,1},{a,3}]
ok
3>
这里卫门展示了如何插入和将表数据转化成list输出到控制台,但是不清楚这四个[set, ordered_set, bag, duplicate_bag]是什么用来干嘛的?
ETS的表类型有哪些?
一些表被称为异键表(set),它们要求表里所有的键都是唯一的。另一些被称为同键表(bag),它们允许多个元素拥有相同的键。选择正确类型的表对应用程序的性能意义重大。基本的表类型(异键表和同键表)各有两个变种,它们共同构成四种表类型:异键、有序异键(ordered set)、同键和副本同键(duplicate bag)。在异键表里,各个元组里的键都必须是独一无二的。如上代码所示。
影响 ETS 表效率的因素
ETS表在内部是用散列表表示的(除了有序异键表,它是用平衡二叉树表示的)。这就意味着使用异键表会带来少许空间开销,而使用有序异键表会带来时间开销。插入异键表所需的时间是恒定的,而插入有序异键表所需的时间与表内条目数量的对数成比例。
当你在异键表和有序异键表之间进行选择时,应该考虑构建表之后会拿它做什么。如果想要一个排过序的表,就应该使用有序异键表。使用同键表的代价比使用副本同键表更高,因为每次插入时都需要与所有拥有相同键的元素比较是否相等。如果有大量元组都拥有同一个键,这么做就会很低效。
ETS表保存在一个单独的存储区域里,与正常的进程内存无关。可以认为ETS表归创建它的进程所有,当这个进程挂了或者调用ets:delete时,表就会被删除。ETS表不会进行垃圾收集,这就意味着即使表里存储了海量的数据也不会产生垃圾收集的开销。
当一个元组被插入ETS表时,所有代表这个元组的数据结构都会从进程的栈复制到ETS表里。当你对表执行查询操作时,找到的元组会从ETS表复制到进程的栈上。
所有的数据结构都是如此,除了大型的二进制数据。这些二进制型会存储在它们自己的堆外存储区域里。这个区域可以被多个进程和ETS表共享,各个二进制型则由一个引用计数垃圾收集器管理,它会记录有多少个不同的进程和ETS表在使用这个二进制型。如果某个特定二进制型的
进程和表使用计数降至零,那么这个二进制型的存储区域就可以被回收。
所有这些听上去可能比较复杂,但最终的结论是:在进程间传递包含大型二进制数据的消息是非常高效的,向ETS表插入包含二进制型的元组也非常高效。一个不错的经验法则是尽可能多用二进制型来表示字符串和大块的无类型内存。