一:ETS
1.表的类型
set | table中的每一个Value(Tuple)都是唯一,并且一个Key只能对应一个Value |
ordered_set | 同set,唯一不同的是table中的Key是有序的 |
bag | table中的每一个Value都是唯一,但是一个key可以对应多个Value |
duplicate_bag | table中每一个key可以对应多个Value,并且允许Value重复 |
%% ets_test.erl
-module(ets_test).
-export([start/0]).
start() ->
lists:foreach(fun test_ets/1,
[set, ordered_set, bag, duplicate_bag]).
test_ets(Mode) ->
Tableld = 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}]
2.创建一个 ETS 表
ets:new 的参数如下:-spec 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记录(它其实是元组的另一种形式),并且记录的第一个元素包含记录名的时候。
3.ETS示例程序
%% lib_trigrams.erl
for_each_trigram_in_the_english_language(F, A0) ->
{ok,Bin0} = file:read_file("354984si.ngl.gz"),
Bin = zlib:gunzip(Bin0),
scan_word_list(binary_to_list(Bin), F, A0).
scan_word_list([], _, A) ->
A;
scan_word_list(L, F, A) ->
{word, L1} = get_next_word(L, []),
A1 = scan_trigrams([$\s | Word], F, A),
scan_word_list(L1, F, A1).
%% 扫描单词,寻找\r\n。
%% 第二个参数是(反转的)单词,
%% 所以必须在找到\r\n或扫描完字符时把它反转回来
get_next_word([$\r,$\n|T], L) -> {reverse([$\s|L]), T};
get_next_word([H|T], L) -> get_next_word(T, [H|L]);
get_next_word([], L) -> {reverse([$\s|L]), []}.
scan_trigrams([X, Y, Z], F, A) ->
F([X, Y, Z], A);
scan_trigrams([X, Y, Z|T], F, A) ->
A1 = F([X, Y, Z], A),
scan_trigrams([Y, Z|T], F, A1);
scan_trigrams(_, _, A) ->
A.
%% lib_trigrams.erl
make_ets_ordered_set() -> make_a_set(ordered_set, "trigramsos.tab").
make_ets_set() -> make_a_set(set, "trigramss.tab").
make_a_set(Type, FileName) ->
Tab = ets:new(table, [Type]),
F = fun(Str, _) -> ets:insert(Tab, {list_to_binary(Str)}) end,
for_each_trigram_in_the_english_language(F,0),
ets:tab2file(Tab, FileName),
Size = ets:info(Tab, size),
ets:delete(Tab),
Size.
%% lib_trigrams.erl
make_mod_set() ->
D = sets:new(),
F = fun(Str, Set) -> sets:add_element(list_to_binary(Str), Set) end,
D1 = for_each_trigram_in_the_english_language(F, D),
file:write_file("trigrams.set", [term_to_binary(D1)]).
二:gen_server
1.gen_server入门
(1)确定回调模块名:我们将制作一个简单的支付系统。把这个模块称为my_bank(我的银行)。
(2)编写接口方法:我们将定义五个接口方法,它们都在my_bank模块里,分别是:
start()
打开银行。
stop()
关闭银行。
new account(Who)
创建一个新账户。
deposit (Who,Amount)
把钱存人银行。
withdraw(Who,Amount)
把钱取出来(如果有结余的话)。
每个函数都正好对应一个gen_server方法调用,代码如下:
%% my_bank.erl
start() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
stop() -> gen_server:call(?MODULE, stop).
new_account(Who) -> gen_server:call(?MODULE, {new, Who}).
deposit(Who, Amount) -> gen_server:call(?MODULE, {add, Who, Amount}).
withdraw(Who, Amount) -> gen_server:call(?MODULE, {remove, Who, Amount}).
%% gen_server_template.mini
-module().
%% gen_server迷你模板
-behaviour(gen_server).
-export([start_link/0]).
%% gen_server回调
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
init([]) -> {ok, State}.
handle_call(_Request, _From, State) -> {reply, Reply, state}.
handle_cast(_Msg, State) -> {noreply, State}.
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, state) -> ok.
code_change(OldVsn, State, Extra) -> {ok,State}.
handle_call({new, who}, From, State) ->
Reply = ...,
Statel = ...,
{reply, Reply, Statel};
handle_call({add, Who, Amount}, From, State) ->
Reply = ...,
Statel = ...,
{reply, Reply, Statel};
handle_call({remove, Who, Amount}, From, State) ->
Reply = ...,
Statel = ...,
{reply, Reply, State1};
%% my_bank.erl
init([]) -> {ok, ets:new(?MODULE,[])}.
handle_call({new, Who}, From, Tab) ->
Reply = case ets:lookup(Tab, Who) of
[] -> ets:insert(Tab, {who, 0}),
{welcome, who};
[_] -> {who, you_already_are_a_customer}
end,
{reply, Reply, Tab};
handle_call({add, Who, X}, From, Tab) ->
Reply = case ets:lookup(Tab,Who) of
[] -> not_a_customer;
[{Who, Balance}] ->
NewBalance = Balance + X,
ets:insert(Tab, {Who, NewBalance}),
{thanks, Who, your_balance_is, NewBalance}
end,
{reply, Reply, Tab};
handle_call({remove, Who, X}, _From, Tab) ->
Reply = case ets:lookup(Tab, Who) of
[] -> not_a_customer;
[{Who, Balance}] when X = Balance ->
NewBalance = Balance - X,
ets:insert(Tab,{Who,NewBalance}),
{thanks, Who, your_balance_is, NewBalance};
[{Who, Balance}] ->
{sorry, Who,you_only_have, Balance, in_the_bank}
end,
{reply, Reply, Tab};
handle_call(stop, _From, Tab) ->
{stop, normal, stopped, Tab}.
handle_cast(_Msg, State) -> {noreply, State}.
handle_info(_Info, State) -> {noreply, State}.
terminate(Reason, State) -> ok.
code_change(_oldVsn, State, _Extra) -> {ok, State}.
就是这样,我们的任务完成了。下面来访问一下银行:
1> my_bank:start().
{0k, <0.33.0>}
2> my_bank:deposit("joe", 10).
not_a_customer
3> my_bank:new_account("joe").
{welcome, "joe"}
4> my_bank:deposit("joe", 10).
{thanks, "joe", your_balance_is, 10}
5> my_bank:deposit("joe", 30).
{thanks, "joe", your_balance_is, 40}
6> my_bank:withdraw("joe", 15).
{thanks, "joe", your_balance_is, 25}
7> my_bank:withdraw("joe", 45).
{sorry, "joe", you_only_have, 25, in_the_bank}
2.gen_server 的回调结构
(1)启动服务器:gen_server:start_link(Name, Mod, InitArgs, Opts)这个调用是所有事物的起点。它会创建一个名为Name的通用服务器,回调模块是Mod,Opts则控制通用服务器的行为。在这里可以指定消息记录、函数调试和其他行为。通用服务器通过调用Mod:init(InitArgs)启动。下面展示了一个init的模板项
%% -----------------------------------------------------
%% @private
%% @d0C
%% 品初始化服务器
%%
%% @spec init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop,Reason}
%% @end
%% ----------------------------------------------------
init([]) ->
{ok, #state{}}.
在通常的操作里,只会返回{ok, State}。如果返回{ok, State},就说明我们成功启动了服务器,它的初始状态是State。
(2)调用服务器:要调用服务器,客户端程序需要执行gen_server:call(Name, Request)。它最终调用的是回调模块里的handle_call/3。handle_call/3的模板项如下:
%% -----------------------------------------------------------------
%% @private
%% @d0C
%% 处理调用消息
%%
%% @spec handle_call(Request, From, State) ->
%% {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% @end
%% ----------------------------------------------------------------
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
(3) 调用和播发:我们已经见过了gen_server:call和handle_call之间的交互,它的作用是实现远程过程调用。gen_server:cast(Name, Msg)则实现了一个播发(cast),也就是没有返回值的调用(实际上就是一个消息,但习惯上称它为播发来与远程过程调用相区分)。对应的回调方法是handle_cast,它的模板项如下:
%% ------------------------------------------------------------------
%% @private
%% @d0C
%% 处理播发消息
%%
%% @spec handle_cast(Msg,State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% @end
%% -----------------------------------------------------------------
handle_cast(Msg, State) ->
{noreply, State}.
(4) 发给服务器的自发性息:回调函数handle_info(Info, State)被用来处理发给服务器的自发性消息。自发性消息是一切未经显式调用gen_server:call或gen_server:cast而到达服务器的消息。举个例子,如果服务器连接到另一个进程并捕捉退出信号,就可能会突然收到一个预料之外的{'EXIT', Pid, What}消息。除此之外,系统里任何知道通用服务器PID的进程都可以向它发送消息。这样的消息在服务器里表现为info值。handle_info的模板项如下:
%% ------------------------------------------------------------------
%% @private
%% @doc
%% 处理所有非调用/播发的消息
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% @end
%% ------------------------------------------------------------------
handle_info(Info, State) ->
{noreply, State}.
%% -------------------------------------------------------------------
%% @private
%% @doc
%% 这个函数是在某个gen_server即将终止时调用的。
%% 它应当是Module:init/1的逆操作,并进行必要的清理。
%% 当它返回时,<mod>gen_server</mod>终止并生成原因Reason。
%% 它的返回值会被忽略。
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%% -------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%% ----------------------------------------------------------------
%% @private
%% @doc
%% 在代码更改时转换进程状态
%%
%% @spec code_change(oldVsn, State, Extra) -> {ok, NewState}
%% @end
%% ----------------------------------------------------------------
code_change(_OldVsn, State, Extra) ->
{ok, State}.
3.填写 gen_server 模板
%% my_bank.erl
-module(my_bank).
-behaviour(gen_server).
-export([start/0]).
%%gen_server回调函数
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-compile(export_all).
-define(SERVER, ?MODULE).
start() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
stop() -> gen_server:call(?MODULE, stop).
new_account(Who) -> gen_server:call(?MODULE, {new, Who}).
deposit(Who, Amount) -> gen_server:call(?MODULE, {add, Who, Amount}).
withdraw(Who, Amount) -> gen_server:call(?MODULE, {remove, Who, Amount}).
init([]) -> {ok, ets:new(?MODULE, [])}.
handle_call({new, Who}, From, Tab) ->
Reply = case ets:lookup(Tab, Who) of
[] -> ets:insert(Tab, {Who,0}),
{welcome, Who};
[_] -> {who, you_already_are_a_customer}
end,
{reply, Reply, Tab};
handle_call({add, Who, X}, From, Tab) ->
Reply = case ets:lookup(Tab, Who) of
[] -> not_a_customer;
[{Who, Balance}] ->
NewBalance = Balance + X,
ets:insert(Tab, {who, NewBalance}),
{thanks, Who, your_balance_is, NewBalance}
end,
{reply, Reply, Tab};
handle_call(remove, Who, X}, From, Tab) ->
Reply = case ets:lookup(Tab,Who) of
[] -> not_a_customer;
[{Who,Balance}] when X =< Balance ->
NewBalance = Balance - X,
ets:insert(Tab, {Who, NewBalance}),
{thanks, Who, your_balance_is, NewBalance};
[{Who, Balance}] ->
{sorry, Who, you_only_have, Balance, in_the_bank}
end,
{reply, Reply, Tab};
handle_call(stop, _From, Tab) ->
{stop, normal, stopped, Tab}.
handle_cast(_Msg, State) -> {noreply, State}.
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.