【SDU Chart Team - Core】DOM节点哈希

DOM节点哈希

在项目实现过程中,经常涉及到基于内容进行比较的情况。所谓基于内容比较,其实就是推导出的HTML一致。

原始方法

显然计算一遍HTML是十分耗时的:对于最自然的,自顶向底的过程,遍历子树的复杂度是 O ( n ) O(n) O(n) 的, n n n为子树大小。当引入缓存机制后,计算自底向顶,需要遍历祖先,而且每个祖先需要重新使用儿子的缓存值,所以复杂度是 O ( m h ) O(mh) O(mh) 的, m m m为子节点个数, h h h为高度。

然后进一步考虑比较的开销,对于生成字符串,其长度正比于子树大小,因此复杂度都是 O ( n ) O(n) O(n) 的。

最后将节点内部属性纳入考虑的范畴,假设每个点有 a a a 个属性,那么复杂度分别乘上了常数 a a a

结论:更新的复杂度为 O ( a n ) O(an) O(an),比较的复杂度为 O ( a n ) O(an) O(an)

哈希树

有没有一个办法,使用某个 O ( 1 ) O(1) O(1) 的量表达整棵树。显然,可以维护一颗哈希树,一个节点的哈希需要表示自身、所有子节点。所以定义如下式子计算一个节点的哈希:(加号表示字符串连接)

h i = h a s h ( h a s h ( ∑ k ∈ A i v k ) + h a s h ( ∑ ( i , j ) ∈ E h j ) ) h_i=hash(hash(\sum_{k\in A_i}v_{k})+hash(\sum_{(i,j)\in E}h_j)) hi=hash(hash(kAivk)+hash((i,j)Ehj))

为了进一步降低复杂度,将此式分为三个部分,分别更新:

h a t t r i b u t e = h a s h ( ∑ k ∈ A i v k ) h a s h i n n e r = h ( ∑ ( i , j ) ∈ E h j ) h a s h i = h o u t e r = h a s h ( h a t t r i b u t e + h i n n e r ) h_{attribute}=hash(\sum_{k\in A_i}v_{k})\\hash_{inner}=h(\sum_{(i,j)\in E}h_j)\\hash_i=h_{outer}=hash(h_{attribute}+h_{inner}) hattribute=hash(kAivk)hashinner=h((i,j)Ehj)hashi=houter=hash(hattribute+hinner)

复杂度分析

第一个部分表示对所有节点内的属性进行哈希,需要遍历全部属性,复杂度为 O ( a ) O(a) O(a);第二个部分表示对儿子的哈希和进行哈希,复杂度为 O ( m ) O(m) O(m);第三个部分是相加,可近似认为是 O ( 1 ) O(1) O(1)

注意到哈希的更新显然需要自底向上进行,这与缓存更新相同,因此遍历所有组件的复杂度是 O ( h ) O(h) O(h)。每个父亲只需要更新儿子的哈希和,所以复杂度也是 O ( m ) O(m) O(m)。最坏情况下 O ( m h ) = O ( n ) O(mh)=O(n) O(mh)=O(n),此时整个树退化成链;但在随机情况满叉树中 O ( m h ) = O ( log ⁡ n ) O(mh)=O(\log n) O(mh)=O(logn)

结论:更新复杂度是 O ( a m h ) ≈ O ( a log ⁡ n ) O(amh)\approx O(a \log n) O(amh)O(alogn),比较的复杂度是 O ( 1 ) O(1) O(1)

尽管最坏情况下更新仍然需要遍历全部节点,但每个节点仅需提供一个哈希值,开销也是远小于字符串的。并且哈希的比较是个常数过程,对设计中出现的密集的比较过程,其优化效果十分明显。

应用

核心中多处使用了比较:

  1. Remove中基于内容的移除
  2. Dom Diff算法
  3. 提交算法

在Dom Diff算法和提交算法中,哈希树是降低复杂度的核心设计之一。Dom Diff算法大量使用了三个部分的哈希,来贪心地得到出最小更新开销的解。

部分代码

  • Outer Hash

    void SVGElement::update_outer_hash() {
        // has raw html
        if (_raw_HTML != STR_NULL) {
            _attribute_hash = _inner_hash = 0;
            _outer_hash = str_hash(_raw_HTML);
        }
        // update outer hash by attribute hash and inner hash
        else {
            std::stringstream ss;
            ss << get_tag() << ",";
            ss << _attribute_hash << ",";
            ss << _inner_hash;
            _outer_hash = str_hash(ss.str());
        }
    
        // update parent element
        if (auto sp = _parent_element.lock()) {
            sp->update_inner_hash();
        }
    }
    
  • Attribute Hash

    void SVGElement::update_attribute_hash() {
        // update attribute hash
        if (_raw_HTML == STR_NULL) {
            _attribute_hash = str_hash(get_attributes());
        }
    
        // update outer hash
        update_outer_hash();
    }
    
  • Inner Hash

    void SVGElement::update_inner_hash() {
        // inner string + (inner elements)
        if (_raw_HTML == STR_NULL) {
            std::stringstream ss;
            ss << get_raw_HTML() << ",";
            for (auto p : _inner_elements) ss << p->get_outer_hash() << ",";
            _inner_hash = str_hash(ss.str());
        }
    
        // update outer hash
        update_outer_hash();
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值