erlang初涉及7_ETS/DETS用法
ets、dets是两个系统模块,在erlang中可以高效存储海量数据。ETS的缩写是Erlang Term Storage(Erlang数据存储)的缩写,DETS则是Disk ETS(磁盘ETS)的缩写。今天我们来了解ETS和DETS的用法。
表的四种类型
保存的是元组,元组里的某一个元素(默认是第一个)被称为该表的键.通过键可以向表里插入和提取元组.分为四个基本的表类型
- 异键(set):即每个元组的键值不能相同
- 同键(bag):多个元组可以有相同的键值,但不能有两个完全相同的元组
- 有序异键(ordered set):排好序的set表
- 副本同键(duplicate bag):多个元组可以有相同的键值,且同一个元组可以在表中出现多次
对表的几个基本操作
- 创建一个新表或打开现有的表: 用ets:new或dets:open_file实现
- 向表里插入一个或多个元组: insert(TableId,X),其中X是一个元组或元组列表
- 在表里查找某个元组: lookup(TableId,Key) ,得到的结果是一个匹配key的元组列表
- 丢弃某个表: dets:close(TableId) 或 ets:delete(TableId).
创建一个表
ets:new(Name,[Opt]) -> TableId : Name是一个原子,[Opt]是一列选项分为:
- set|ordered_set|bag|duplicate_bag : 创建一个指定类型的ETS表
- private 创建一个私有表,只有主管进程才能读取和写入它
- public 创建一个公共表,任何知道此表标识符的进程都能读取和写入它
- protected 创建一个受保护表,任何知道此表标识符的进程都能读取它,但只有主管进程才能写入它
- named_table 如果设置了此选项,Name就可以被用于后续的表操作
- {keypos,K} 用K作为键的位置,通常键的位置是1。基本上唯一需要使用这个选项的场合是保存Erlang记录(它其实是元组的另一种形式),并且记录的第一个元素包含记录名的时候。
下面我们来看一个例子
est_test.erl
-module(ets_test).
-export([start/0]).
start()->
lists:foreach(fun test_ets/1,[set,ordered_set,bag,duplicate_bag]).
test_ets(Mode)->
TableId=ets:new(test,[Mode]),
ets:insert(TableId,{a,1}),
ets:insert(TableId,{b,2}),
ets:insert(TableId,{a,1}),
ets:insert(TableId,{a,3}),
List=ets:tab2list(TableId),
io:format("~-13w => ~p~n",[Mode,List]),
%这里是格式化输出
ets:delete(TableId).
输出运行
1>ets_test:start().
set => [{b,2},{a,3}]
ordered_set => [{a,3},{b,2}]
bag => [{b,2},{a,1},{a,3}]
duplicate_bag => [{b,2},{a,1},{a,1},{a,3}]
这里我们就可以直观的发现几种类型的表有什么区别
DETS(简单涉及)
如果在打开DETS文件后没有关闭, 则Erlang将自动进行修复, 而修复可能会耗费大量的时间, 因此应在使用完后关闭DETS文件。
打开一个DETS表时, 要给出一个全局性的名字, 以便于多个进程用相同的名字和属性来共享这个表。
来个例子看看
lib_filenames_dets.erl
-module(lib_filenames_dets).
-export([open/1, close/0, test/0, filename2index/1, index2filename/1]).
open(File) ->
io:format("dets opened:~p~n", [File]),
Bool = filelib:is_file(File),
%% 使用?MODULE宏获取文件名作为TableName
case dets:open_file(?MODULE, [{file, File}]) of
{ok, ?MODULE} ->
case Bool of
true ->void;
%% 插入键为free, 值为1的记录, 用于统计表中记录的总数
false ->ok = dets:insert(?MODULE, {free, 1})
end,
true;
{error, _Reason} ->
io:format("cannot open dets table~n"),
exit(eDetsOpen)
end.
close() ->dets:close(?MODULE).
%% 将二进制格式的文件名插入到dets表
filename2index(FileName) when is_binary(FileName) ->
%% 插入前先查询文件名是否已经存在
case dets:lookup(?MODULE, FileName) of
[] ->
%% 不存在则先查询最大索引, 然后将索引-文件名, 文件名-索引, free-最大索引插入到dets表
[{_, Free}] = dets:lookup(?MODULE, free),
ok = dets:insert(?MODULE, [{Free, FileName}, {FileName, Free}, {free, Free+1}]),
Free;
%% 存在则直接返回索引值
[{_, N}] ->N
end.
%% 根据索引值查询文件名
index2filename(Index) when is_integer(Index) ->
case dets:lookup(?MODULE, Index) of
[] ->error;
[{_, Bin}] ->Bin
end.
test() ->
%% 每次测试都重建dets表
file:delete("./filenames.dets"),
open("./filenames.dets"),
%% 使用正则查找当前目录下的所有erl文件添加到dets表中
F = lib_files_find:files(".", ".*[.](erl).*$", true),
lists:foreach(fun(I) ->filename2index(list_to_binary(I)) end, F).
运行结果
1> c(lib_filenames_dets).
{ok,lib_filenames_dets}
2> lib_filenames_dets:test().
dets opened:"./filenames.dets"
ok
3> lib_filenames_dets:index2filename(1).
<<"./lib_trigrams.erl">>
4> lib_filenames_dets:index2filename(2).
<<"./lib_files_find.erl">>
5> lib_filenames_dets:index2filename(3).
<<"./lib_filenames_dets.erl">>
6> lib_filenames_dets:index2filename(4).
<<"./ets_test.erl">>
7> lib_filenames_dets:filename2index(list_to_binary("./ets_test.erl")).