Erlang Thursday - ETS介绍第三篇:ETS表类型

今天的Erlang Thursday继续介绍ETS并且研究一下ETS支持的不同存储策略类型。

ETS支持的不同存储类型是:set,ordered_set,bag,和duplicate bag。

每种类型都可以在创建一个新ETS表的时候传给创建函数,不过我们来看看创建ETS表的时候不指定任何类型的话ETS表是什么类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ETS_Empty = ets:new(ets_empty, []).
% 36886
ets:info(ETS_Empty).
% [{read_concurrency,false},
% {write_concurrency,false},
% {compressed,false},
% {memory,305},
% {owner,<0.50.0>},
% {heir,none},
% {name,ets_empty},
% {size,0},
% {node,nonode@nohost},
% {named_table,false},
% {type,set},
% {keypos,1},
% {protection,protected}]

上面的输出中,type 标签元组显示类型是 set。

为了研究不同类型的ETS表如何工作的,我们将创建三个元组加入不同ETS表里来看看它们是如何存储的。

1
2
3
4
5
6
Item1 = {1, a}.
% {1,a}
Item2 = {1.0, "a"}.
% {1.0,"a"}
Item3 = {1, "one"}.
% {1,"one"}

我们有两个第一元素都是1的元组,还有一个第一元素是1.0的元组,这是为了看看在相同的键情况下不同类型ETS表如何处理。

为什么有1和1.0两个键?因为根据使用的比较操作符的不同,它们可以被看作相同的也可以被看作不相同的,所以把它们当作一种相同键的情况。

1
2
3
4
1 == 1.0.
% true
1 =:= 1.0.
% false

首先我们看看一个set类型的ETS表。

1
2
ETS_Set = ets:new(ets_set, [set]).
40978

我们插入Item1再插入Item2,然后用 ets:tab2list/1 来输出这个ETS表里存储了什么。

1
2
3
4
5
6
ets:insert(ETS_Set, Item1).
% true
ets:insert(ETS_Set, Item2).
% true
ets:tab2list(ETS_Set).
% [{1,a},{1.0,"a"}]

set类型的ETS表把1和1.0当作不同的键。那么我们插入Item3也就是插入一个已经存在的键会发生什么。

1
2
3
4
ets:insert(ETS_Set, Item3).
% true
ets:tab2list(ETS_Set).
% [{1,"one"},{1.0,"a"}]

原先1为键的元组已经被我们刚刚插入的Item3元组替换了。

我们再来看看ordered_set类型的ETS表。

1
2
ETS_OrdSet = ets:new(ets_ordset, [ordered_set]).
% 45075

我们还是插入Item1再插入Item2,然后用 ets:tab2list/1 来输出这个ETS表里存储了什么。

1
2
3
4
5
6
ets:insert(ETS_OrdSet, Item1).
% true
ets:insert(ETS_OrdSet, Item2).
% true
ets:tab2list(ETS_OrdSet).
% [{1.0,"a"}]

这个例子里,1.0被认为和先插入的1是相等的,所以它覆盖了第一个插入的元素。

我们再插入Iterm3,结果是它也覆盖了1.0那个元素。

1
2
3
4
ets:insert(ETS_OrdSet, Item3).
% true
ets:tab2list(ETS_OrdSet).
% [{1,"one"}]

现在我们来看看bag类型的ETS表。

1
2
ETS_Bag = ets:new(ets_bag, [bag]).
% 49172

我们还是给表里加入Item1和Item2。

1
2
3
4
5
6
ets:insert(ETS_Bag, Item1).
% true
ets:insert(ETS_Bag, Item2).
% true
ets:tab2list(ETS_Bag).
% [{1,a},{1.0,"a"}]

从 ets:tab2list/1 的输出我们看到bag类型的ETS表把Item1和Item2当作两个不同的元素。

再将Item3加入表里看看会有什么结果。

1
2
3
4
ets:insert(ETS_Bag, Item3).
% true
ets:tab2list(ETS_Bag).
% [{1,a},{1,"one"},{1.0,"a"}]

在这个bag类型的ETS表中,我们有了Item2以及Item1和Item3三个元素,甚至Item1和Item3有相同当键。

最后我们来看看duplicate_bag类型的ETS表。

1
2
ETS_DupBag = ets:new(ets_dupbag, [duplicate_bag]).
% 53269

像前面几种类型的ETS表一样,我们插入Item1再插入Item2。

1
2
3
4
5
6
ets:insert(ETS_DupBag, Item1).
% true
ets:insert(ETS_DupBag, Item2).
% true
ets:tab2list(ETS_DupBag).
% [{1,a},{1.0,"a"}]

也和前面的例子一样,我们接着插入Item3。

1
2
3
4
ets:insert(ETS_DupBag, Item3).
% true
ets:tab2list(ETS_DupBag).
% [{1,a},{1,"one"},{1.0,"a"}]

结果是我们在这个duplicate_bag表中有所有三个元素。

如果我们比较bag和duplicate_bag两种表,我们发现它们似乎有一样的行为。

那么它们两者之间有什么不同呢?

如果你深入挖掘官方文档,仔细阅读 ets:new/2 函数中关于表类型的描述,它说明一个bag表允许有重复键,但是只不允许两个一样的一个元素存在,而duplicate_bag允许相同元素存在即时它们的键值都相同。

为验证这个结论,我们往bag表和duplicate_bag表都加入Item1,看看结果是什么。

首先是bag表。

1
2
3
4
ets:insert(ETS_Bag, Item1).
% true
ets:tab2list(ETS_Bag).
% [{1,a},{1,"one"},{1.0,"a"}]

结果和原来没有不同,所以往bag里添加已经存在的元素不会改变表的内容。

那么duplicate_bag表呢?

1
2
3
4
ets:insert(ETS_DupBag, Item1).
% true
ets:tab2list(ETS_DupBag).
% [{1,a},{1,"one"},{1,a},{1.0,"a"}]

结果是元组{1, a}有两份,因为我们调用 ets:insert/2 将这个元素插入了两次。

原文链接: https://www.proctor-it.com/erlang-thursday-ets-introduction-part-3-ets-table-types/