Erlang学习之ETS表

1. ETS表

ETS表是一种用于存储和检索Erlang数据的内存数据库。采用键值(key-value)结构的存储方式。

ETS表有4种类型:

  • set : 默认的表类型。每个键(key)对应一个值(value)。键是唯一的。
  • ordered_set : 与 set 类似,但是按照 Erlang/Elixir 项式来排序。需要注意的是这里键的比较方式。键可以不同,只要「相等」即可,例如 1 和 1.0 就是「相等」的。
  • bag : 每个键可以包括多个对象,但一个对象在一个键中只能有一个实例。
  • duplicate_bag : 每个键可以包括多个对象,也允许对象重复.

基本操作

建表

ets:new(Name,Options) -> tid() | atom()
创建一个新的ETS表,并返回表的标识符。
Name是一个原子类型,如果想通过表明访问ETS表,则只需在Options添加named_table
例子:

> ets:new(t,[]).
40977
> ets:new(tab, [set, named_table]).
tab
> ets:new(tab2,[bag]).
16401

插入

ets:insert(Tab, ObjectOrObjects) -> true
向ETS表中插入一条记录
Tab是表的标识符即前面new/2的返回值,ObjectOrObjects是一个元组或者元组列表
例子:

> ets:insert(tab,{a,1}).
true
> ets:insert(tab,[{a,1,32}]).
true
> ets:lookup(tab,a).
[{a,1,32}]

ets:insert_new(Tab,ObjectOrObjects) -> boolean()
与ets:insert/2类似,不同之处在于不会进行数据覆盖,发现已存在key,则插入失败

查询

ets:lookup(Tab,Key)-> [tuple()]
根据键查找并返回匹配的记录
例子:

> ets:lookup(tab,a).
[{a,1,32}]

ets:match(Tab,Pattern) -> [term()]
根据模式匹配查找并返回符合条件的记录列表
例子:

> ets:match(tab,{‘_’,‘$3’,‘$2’}).
[[32,1]]

这里的{‘$1’,‘$3’,‘$2’}是匹配的语法
变量可以放在元组中进行模式匹配。常规变量的书写形式为’$0’、‘$1’、 ‘$2’等(数值编号不重要,只用来标识获取的结果的位置)。“可忽略”变量可被书写成’_'。即原本的1,32因为写的数值编号位置从而变为了32,1

ets:match_object(Tab,Pattern)->[tuple]
与ets:match/2类似,不过返回的是和模式匹配的完整记录
例子

> ets:match_object(tab,{‘_’,‘$3’,‘$2’}).
[{a,1,32}]

ets:select(Tab,MatchSpec) -> [term()]
除了可用与ets:match类似可用使用模式匹配的语法
[{{'$1', '_', '$3'}, [], ['$_']}
以上格式使用select/2 来做更高级的查询。分别表示匹配模式,条件语句,以及返回结果的格式。

可用更简单的使用
也可采用fun2ms/1进行匹配查询
例子:

> Fun = ets:fun2ms(fun ({,,V}) when V > 1 -> V end).
[{{‘‘,’’,‘$1’},[{‘>’,‘$1’,1}],[‘$1’]}]
*>*ets:select(tab,Fun).
" " ---------------//其实是[32]

删除

ets:delete(Tab) -> true
删除ETS表

ets:delete(Tab,Key) -> true
删除表中以Key为键的记录
与ets:lookup/2类似
例子:

> ets:delete(tab,a).
true
> ets:lookup(tab,a).
[]

2. 基于磁盘的 ETS (DETS)

DETS是ETS的磁盘实现版本,它们之间的主要不同在于:

  • DETS中没有ordered_set类型的表;
  • DETS磁盘文件的大小不能超过2 GB;
  • 表的创建和关闭有些变化。创建新的数据库表要调用dets:open_file/2,关闭这个表可以调用dets:close/1。此后,可以调用dets:open_file/1重新打开该表
  • ETS表格内部的操作是原子的,可以保证数据的一致性。对于ETS表格的操作,要么全部成功执行并提交,要么在失败时回滚。DETS表格没有提供像ETS那样的原子性操作。

基本操作

与ETS表操作类似,这里就只做简单演示

打开或创建表

dets:open_file(Name, Args) -> {'ok', Reference} | {'error', Reason}
在这里插入图片描述
第一个参数是表名,第二个Args是一个可选项

  • {type, type()}:指定表的类型,与ets表的类型一样,ordered_set类型除外
  • {repair, boolean()}:指定在打开表格时是否自动进行修复操作。如果设置为 true,则 DETS 表格在打开时会进行修复,以解决可能出现的错误。默认值为 false
  • {keypos, keypos()}:指定用于查找记录的键的位置。keypos() 是一个整数,表示记录元组中键的位置,从 1 开始计数。默认值为 1
  • {'access',access()}: 指定访问权限,read | read_write, 即只有读写两个值

例子:

> dets:open_file(tab,[{type,set}]).
{ok,tab}

可用看见在目录下多了一个文件
在这里插入图片描述
也可指定目录生成

> dets:open_file(“F:/tab”,[]).
{ok,“F:/tab”}

在这里插入图片描述

插入数据

dets:insert(Name, Objects) -> 'ok' | {'error', Reason}
与ets:insert/2类似,
Name 就是表名,Objects是元组或元组列表
例子:

> dets:insert(tab,{a,1}).
ok
> dets:lookup(tab,a).
[{a,1}]

磁盘中有了数据
在这里插入图片描述
此外还有dets:insert_new/2函数,与ets:insert_new/2类似。

查询数据

dets:lookup(Name,Key) ->Objects | {'error', Reason}
与ets:lookup/2函数类似
例子:

> dets:lookup(tab,a).
[{a,1}]

此外还有dets:match,dets:select,也是与ets中的函数对应

删除数据

delete(Name, Key) -> 'ok' | {'error', Reason}
与ets:delete/2函数类似
例子:

> dets:delete(tab,a).
ok
> dets:lookup(tab,a).
[]

遍历

有时候我们需要对表中数据进行遍历操作,例如查询表中所有数据并返回。
这时候我们可用使用dets:first(Name)和dets:next(Name, Key),
例子

-module(test).
-author("01").
%%%=======================EXPORT=======================
-export([execute_transaction/4, select_all/1]).
%%%=======================INCLUDE======================
%%%=======================RECORD=======================
%%%=======================DEFINE=======================
%%%=======================TYPE=========================
%%%=================EXPORTED FUNCTIONS=================
%% ----------------------------------------------------
%% Description: 查询表所有数据
%% ----------------------------------------------------
select_all(Tab) ->
  F = dets:first(Tab),   %%获取表中第一个key
  select_all(Tab, F, []).
select_all(_Tab, '$end_of_table', Acc) -> %% '$end_of_table' 是结束符
  Acc;
select_all(Tab, Key, Acc) ->
  NewAcc = lists:append(Acc, dets:lookup(Tab, Key)), %%获取下一个key
  NextKey = dets:next(Tab, Key),
  select_all(Tab, NextKey, NewAcc).

测试
在测试之前插入了多条数据与编译,这里不做演示

> {_,Tab} = dets:open_file(tab).
{ok,#Ref<0.0.10.188>}
> test:select_all(Tab).
[{b,21},{qw,{aeqw,wqeq}},{ws,ewq}]

当然,ets中也可以如此操作。

关闭表

dets:close(Name)
关闭表
例子:

> dets:close(tab).
ok
> dets:lookup(tab,a).
** exception error: bad argument
in function dets:lookup/2
called as dets:lookup(tab,a)

3. Mnesia

Mnesia是位于ETS和DETS之上的一个软件层,为这两个数据存储系统增加了许多新功能。它包含的大多数特性都是需要密集使用ETS和DETS软件系统所必需的。Mnesia可以自动管理ETS和DETS的写入,它既具有DETS的持久化能力,又具有ETS的性能,并且还能够自动地在多个Erlang节点间进行数据库的复制。

Mnesia的核心思想是:用记录来定义表的结构。每个表可以存储一批相似的记录,任何能够放到记录中的东西都可以存储到Mnesia表中,包括原子、进程标识、引用等。

Mnesia提供了对分区(sharding)、事务和分布式的支持。

启动 Mnesia

首先我定义了一个记录并把改记录使用rr(test)到erlang shell中
在这里插入图片描述

> mnesia:create_schema([node()]).
ok
> mnesia:start().
ok
> mnesia:create_table(person, [{attributes, record_info(fields, person)}]).
{atomic,ok}

mnesia:create_schema([node()])在本地节点上初始化一个空的 schema.
mnesia:start()启动mnesia.
mnesia:create_table(person, [{attributes, record_info(fields, person)}]). 来创建表 person.

如果想指定存储数据的目录可用在启动Erlang是使用参数-mnesia dir "" 指定了 Mnesia 存储数据的目录

操作

写mnesia:dirty_write(Val).

> mnesia:dirty_write({person, 1, “Seymour Skinner”, “Principal”}).
ok
> mnesia:dirty_write({person, 2, “Homer Simpson”, “Safety Inspector”}).
ok

读mnesia:dirty_read({Tab,Key}).

> mnesia:dirty_read({person,1}).
[#person{id = 1,name = “Seymour Skinner”,job = “Principal”}]
> mnesia:dirty_read({person,2}).
[#person{id = 2,name = “Homer Simpson”, job = “Safety Inspector”}]
> mnesia:dirty_read({person,3}).
[]

以上读写的方式是脏操作方式。一般情况下,我们都不会使用脏操作,因为脏操作并不一定保证成功。
此外还有mnesia:dirty_delete,mnesia:dirty_select等脏操作

事务

事务是一种数据库操作的执行单元,它将一组数据库操作作为一个不可分割的逻辑单元来执行。事务具有原子性,一致性,隔离性,持久性。

Mnesia 的事务是通过对数据库的多个操作包含到一个函数体中来实现

mnesia:transaction/1 函数接受一个函数作为参数,该函数表示要在事务中执行的操作。如果该函数成功完成(不抛出异常),则事务被提交。如果函数抛出异常或返回 {aborted, Reason},则事务被回滚。

测试回滚

%% ----------------------------------------------------
%% Description: 测试事务
%% ----------------------------------------------------
write() ->
  fun() ->
    mnesia:write({person, 4, "Marge 1 Simpson", "home maker"}),
    mnesia:write({person, 5, "Marge 2 Simpson", "home maker"}),
    mnesia:write({person, 6, "Marge 3 Simpson", "home maker"}),
    throw({error, "Some error"}),	%%抛出异常
    ok
  end.

测试

> mnesia:transaction(test:write()).
{aborted,{throw,{error,“Some error”}}}
> mnesia:dirty_read({person,4}).
[]

测试不回滚

%% ----------------------------------------------------
%% Description: 测试事务
%% ----------------------------------------------------
write() ->
  fun() ->
    mnesia:write({person, 4, "Marge 1 Simpson", "home maker"}),
    mnesia:write({person, 5, "Marge 2 Simpson", "home maker"}),
    mnesia:write({person, 6, "Marge 3 Simpson", "home maker"}),
    ok
  end.

> mnesia:transaction(test:write()).
{atomic,ok}
> mnesia:dirty_read({person,4}).
[#person{id = 4,name = “Marge 1 Simpson”,job = “home maker”}]
> mnesia:dirty_read({person,5}).
[#person{id = 5,name = “Marge 2 Simpson”,job = “home maker”}]
> mnesia:dirty_read({person,6}).
[#person{id = 6,name = “Marge 3 Simpson”,job = “home maker”}]

索引

Mnesia 也支持在非主键字段上添加索引,然后通过这个索引来查询数据
mnesia:add_table_index(Tab, Ix)
例子:

> mnesia:add_table_index(person,job).
{atomic,ok}

结果跟 mnesia:create_table/2 返回的相同:

{atomic, ok} 表示执行成功
{aborted, Reason} 表示执行失败

通过索引获取数据

*>*mnesia:transaction(fun()->mnesia:index_read(person,“Principal”,job) end).
{atomic,[{person,1,“Seymour Skinner”,“Principal”}]}

查询

mnesia:match_object/1 与ets:match函数类似
例子

> mnesia:transaction(fun()-> mnesia:match_object({person,‘‘,“Seymour Skinner”,’’}) end).
{atomic,[{person,1,“Seymour Skinner”,“Principal”}]}

mnesia:select/2 与ets:select函数类似
例子

查询id小于3的数据的名字

*>*mnesia:transaction(fun () -> mnesia:select(person, [{{person, ‘$1’, ‘$2’, ‘$3’}, [{‘<’, ‘$1’, 3}], [‘$2’]}])end).
{atomic,[“Seymour Skinner”,“Homer Simpson”]}

当然也可用所有fun2ms函数进行查询,与ets中类似

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值