zoukankan      html  css  js  c++  java
  • 《Erlang程序设计》第十五章 ETS和DETS:大数据的存储机制

    第十五章 ETS和DETS:大数据的存储机制

    第十五章 ETS和DETS:大数据的存储机制

    ETS和DETS都提供"键-值"搜索表, 只不过ETS驻留在内存而DETS驻留在磁盘, 因此ETS高效但数据存储是临时的, DETS数据存储是持久的且节省内存但比较低效。

    15.1 表的基本操作

    创建和打开表

    ets:new或dets:open_file

    插入表

    insert(TableName, X)

    查找元组

    lookup(TableName, Key)

    释放表

    ets:delete(TableId)或dets:close(TableId)

    15.2 表的类型

    set表

    每个元组的键值不能相同

    ordered set表

    排序的set表

    bag表

    多个元组可以有相同的键值, 但不能有两个完全相同的元组

    duplicate bag表

    多个元组可以有相同的键值, 且同一个元组可以在表中出现多次

    代码示例

    -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}]
    ok
    

    15.3 ETS表的效率考虑

    set表保证键值不相同, 因此相对耗费空间;ordered_set保证了排序, 因此相对耗费时间。
    因为bag表插入数据时需要比较是否存在相同的键值, 因此如果大量元组存在相同键值, 使用bag表将会非常低效。
    ETS表隶属于创建它的进程, 随进程消亡而消亡, 因此无需考虑垃圾回收。
    尽可能的使用二进制数据。

    15.4 创建ETS表

      创建ETS表的函数原型

    @spec ets:new(Name, [Opt]) -> TableId
    

      其中[Opt]的取值:

    # 表类型
    set | ordered_set | bag | duplicate_bag
    
    # 权限
    private      只有所有者进程可以读写
    public       所有可以获取TableId的进程都可以读写
    protected    所有可以获取TableId的进程都可以读, 但只有所有者进程可以写
    
    # 命名
    named_table  是否可以使用Name来操作表
    
    # 指定键的位置
    {keypos, K}  通常是第一个位置, 当需要存储记录时, 第一个位置是记录的名字, 因此需要指定键的位置 
    
    # 默认选项为[set, protected, {keypos, 1}]
    

    15.5 ETS程序示例

    15.5.1 三字索引迭代器

    %% 从压缩文件中读取英文单词, 每个单词都使用F函数处理
    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).
    
    %% 单词匹配, 遇到换行符则添加空格
    get_next_word([$
    , $
    |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.
    

    15.5.2 构造表

    %% 分别创建两者类型的ets表
    make_ets_ordered_set() ->make_a_set(ordered_set, "trigramsOS.tab").
    make_ets_set()         ->make_a_set(set, "trigramsS.tab").
    
    %% 根据指定类型创建ets表, 导入数据后转成文件保存
    make_a_set(Type, FileName) ->
        Tab = ets:new(table, [Type]),
        %% 声明遍历英文单词时所使用的函数
        %% 这里将每个单词插入到ets表
        F = fun(Str, _) ->ets:insert(Tab, {list_to_binary(Str)}) end,
        for_each_trigram_in_the_english_language(F, 0),
        %% 转换为文件, 获取记录数后在内存中删除ets表
        ets:tab2file(Tab, FileName),
        Size = ets:info(Tab, size),
        ets:delete(Tab),
        Size.
    
    make_mod_set() ->
        %% 使用sets存储单词
        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)]).
    

    15.5.3 构造表有多快

    %% 统计有多少个单词
    how_many_trigrams() ->
        %% 遇到一个单词就将累加器增加1
        F = fun(_, N) ->1 + N end,
        for_each_trigram_in_the_english_language(F, 0).
    
    make_tables() ->
        %% 统计有多少个三字索引
        {Micro1, N} = timer:tc(?MODULE, how_many_trigrams, []),
        io:format("Counting - No of trigrams=~p time/trigram=~p~n", [N, Micro1/N]),
    
        %% 统计排序的ets表平均占用存储和时间
        {Micro2, Ntri} = timer:tc(?MODULE, make_ets_ordered_set, []),
        FileSize1 = filelib:file_size("trigramsOS.tab"),
        io:format("Ets ordered Set size=~p time/trigram=~p~n", [FileSize1/Ntri, Micro2/N]),
    
        %% 统计普通ets表平均占用存储和时间
        {Micro3, _} = timer:tc(?MODULE, make_ets_set, []),
        FileSize2 = filelib:file_size("trigramsS.tab"),
        io:format("Ets set size=~p time/trigram=~p~n", [FileSize2/Ntri, Micro3/N]),
    
        %% 统计使用set方式平均占用存储和时间
        {Micro4, _} = timer:tc(?MODULE, make_mod_set, []),
        FileSize3 = filelib:file_size("trigrams.set"),
        io:format("Module sets size=~p time/trigram=~p~n", [FileSize3/Ntri, Micro4/N]).
    

      运行结果:

    1> c(lib_trigrams).
    {ok,lib_trigrams}
    2> lib_trigrams:make_tables().
    Counting - No of trigrams=3357707 time/trigram=0.1504470163715893
    Ets ordered Set size=19.029156153630503 time/trigram=0.6226862558287546
    Ets set size=19.028408559947668 time/trigram=0.40671625010758833
    Module sets size=9.433978132884777 time/trigram=1.728039700903027
    ok
    

    共有3 357 707个三字索引, 处理每个三字索引平均时间为0.15微秒;
    ordered_set类型的ETS表平均每个三字索引占用19字节耗费0.62微秒;
    普通类型的ETS表平均每个三字索引占用19字节耗费0.41微秒;
    而使用Erlang的集合模块平均存储占用9字节耗费1.73微秒

    15.5.4 访问表有多快

    lookup_all_ets(Tab, L) ->
        lists:foreach(fun({K}) ->ets:lookup(Tab, K) end, L).
    
    time_lookup_module_sets() ->
        %% 读取文件转成二进制数据
        {ok, Bin} = file:read_file("trigrams.set"),
        %% 二进制数据转成列表
        Set = binary_to_term(Bin),
        Keys = sets:to_list(Set),
        Size = length(Keys),
        {M, _} = timer:tc(?MODULE, lookup_all_set, [Set, Keys]),
        io:format("Module set lookup=~p micro seconds~n", [M/Size]).
    
    %% 遍历集合
    lookup_all_set(Set, L) ->
        lists:foreach(fun(Key) ->sets:is_element(Key, Set) end, L).
    

      运行结果:

    1> lib_trigrams:timer_tests().
    Ets ordered Set lookup=0.45313522100738246 micro seconds
    Ets set lookup=0.1906363891225119 micro seconds
    Module set lookup=0.31632557704887393 micro seconds
    ok
    

    15.5.5 胜出的是…

    %% 查询某个字符串是否为单词
    %% 表中存储时已经添加了空格, 因此这里要补全
    is_word(Tab, Str) ->is_word1(Tab, "s" ++ Str ++ "s").
    
    %% 查询符合[A, B, C]格式的字符串是否为单词
    is_word1(Tab, [_, _, _] = X) ->is_this_a_trigram(Tab, X);
    is_word1(Tab, [A, B, C|D])   ->
        case is_this_a_trigram(Tab, [A, B, C]) of
            true  ->is_word1(Tab, [B, C|D]);
            false ->false
        end;
    is_word1(_, _) ->false.
    
    %% 查询ETS表中是否存在此索引
    is_this_a_trigram(Tab, X) ->
        case ets:lookup(Tab, list_to_binary(X)) of
            [] ->false;
            _  ->true
        end.
    
    %% 找到与源码文件同目录下的数据文件并转换成ETS表
    open() ->
        {ok, I} = ets:file2tab(filename:dirname(code:which(?MODULE)) ++ "/trigramsS.tab"),
        I.
    
    close(Tab) ->ets:delete(Tab).
    

      运行结果

    1> Tab = lib_trigrams:open().
    16402
    2> lib_trigrams:is_word(Tab, "example").
    true
    

    15.6 DETS

    如果在打开DETS文件后没有关闭, 则Erlang将自动进行修复, 而修复可能会耗费大量的时间, 因此应在使用完后关闭DETS文件。
    打开一个DETS表时, 要给出一个全局性的名字, 以便于多个进程用相同的名字和属性来共享这个表。
    代码示例:

    -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")).
    4
    

    15.7 我们没有提及的部分

    Date: 2013-12-07 10:30:38 CST

    Author: matrix

    Org version 7.8.11 with Emacs version 24

    Validate XHTML 1.0
     
  • 相关阅读:
    智能视频分析
    基于libuv的TCP设计(三)
    视频质量诊断----画面抖动检测
    视频质量诊断----画面冻结检测
    视频质量诊断----PTZ云台运动检测
    视频质量诊断----模糊检测
    视频质量诊断----信号丢失检测
    视频质量诊断----遮挡检测
    视频质量诊断----条纹噪声检测
    视频质量诊断----雪花噪声检测
  • 原文地址:https://www.cnblogs.com/scheme/p/3462508.html
Copyright © 2011-2022 走看看