mirror of
https://github.com/doocs/advanced-java.git
synced 2024-12-24 03:50:08 +08:00
docs: correct the solution of using Trie Tree in big-data/find-common-urls.md (#309)
This commit is contained in:
parent
ef7ed71dd5
commit
fd9820d035
@ -20,12 +20,46 @@
|
||||
|
||||
接着遍历 a<sub>i</sub>( `i∈[0,999]` ),把 URL 存储到一个 HashSet 集合中。然后遍历 b<sub>i</sub> 中每个 URL,看在 HashSet 集合中是否存在,若存在,说明这就是共同的 URL,可以把这个 URL 保存到一个单独的文件中。
|
||||
|
||||
#### 2. 前缀树
|
||||
|
||||
一般而言,URL 的长度差距不会不大,而且前面几个字符,绝大部分相同。这种情况下,非常适合使用**字典树**(trie tree) 这种数据结构来进行存储,降低存储成本的同时,提高查询效率。
|
||||
#### 2. 前缀树是否可行?
|
||||
|
||||
> 一般而言,URL 的长度差距不会不大,而且前面几个字符,绝大部分相同。这种情况下,非常适合使用**字典树**(trie tree) 这种数据结构来进行存储,降低存储成本的同时,提高查询效率。
|
||||
>
|
||||
> 由 [@ChunelFeng](https://github.com/ChunelFeng) 反馈。[#212](https://github.com/doocs/advanced-java/issues/212)
|
||||
|
||||
有 issue 提到是否可以直接使用 trie 树,我们来推算一下方案是否可行。
|
||||
|
||||
Background:4G 的机器,解析 320G 的数据。
|
||||
|
||||
首先题目提到 64B 大小,按一个字符一个字节来算的话就是 64 位,也就是 64 个字符组成(这里考虑极端情况)。Trie 树的定义这里就不提了,见[字典树](https://doocs.github.io/leetcode/tags/#%E5%A4%9A%E7%BA%BF%E7%A8%8B)。
|
||||
|
||||
- **树高**:URL 的长度就是最后的树的能达到的高度,及最高 64 层树。
|
||||
|
||||
- **节点下的子节点数量**:因为是 URL,除了数字和字符之外还有`%`, `/`, `:`etc。这里为了简化计算和方便理解只使用英文字符+数字总数=62+10=72。也就是说一个结点下的子节点最多有 72 个,及占 72B。
|
||||
|
||||
- **节点总大小**:因为样本 320G 足够大,我们考虑坏情况下,最后的树是 64 层高的满 72 叉树。最后的内存占用为节点数量\*节点大小:
|
||||
|
||||
- 节点数量:满 N = 64 层,K = 72 叉,树节点数量计算公式(等比数列求和)为:`1 * (1-72^64)/ (1 - 72) = (72^64 - 1)/ (71)`,估算为`71^63`个节点。
|
||||
|
||||
- 节点大小:每个节点占用大小为 1B
|
||||
- 当前字符 1B
|
||||
- 当前位置是否存在值 1b(常见 Bool 值大小),这里按最小大小来。
|
||||
|
||||
所以总大小为:`71^63 * 1B/ 1000 ≈ 71^60 KB ≈ 71 ^ 63 GB`,这个值已经很大了,况且在方案推算过程中按照最小原则考虑。
|
||||
|
||||
---
|
||||
|
||||
写到这里有些读者可能就被吓到了,怎么出来这么多数据,trie 树不是有压缩的作用嘛?
|
||||
|
||||
这里得到的结果是不正确的,因为 320G 的数据,生成的节点数也最多只会是 320G(路径完全不重复),因为无法构成上面讨论的满 N 叉树的情况,填满了其中一点而已。但是对于单机来说此部分数据单机已无法解析,并且方案不具有扩展性和稳定性 。所以单纯的 trie 树太依赖数据分布,如果你的大部分 URL 是相同的压缩度很高,单机 4G 解析 320G 有可能,但是对于面试官想听到的应用场景来说这个概率太小了。所以还是得借助分治法的方式降低内存。
|
||||
|
||||
在方案一上进行优化:在上一个方法中使用 HaseSet 来判重,hash 效率比访问 trie 树,hash 效率更高,trie 可以降低内存。
|
||||
|
||||
所以在面试中可以提到 trie 方案,在这个场景中解析 320G 离线数据为时间不敏感类型,所以可以牺牲速度来换取空间。
|
||||
|
||||
- 方案一提到单文件大小为 300MB+,使用 trie 树之后这 300MB+会有所压缩比如到 200MB+,考虑到机器性能利用率,在一些 CPU 充足的场景中可以考虑引入并发,因为压缩率 1/3,其余空间可以腾出来加载更多文件做并发提升处理速度。
|
||||
|
||||
- 如果 CPU 不充足比如单核,可以考虑增加切分的文件大小,原先的 1000 个文件,降为 200 个也就是文件大小扩大两倍,之前 300MB,现在 300\*5 = 1.5G,在 4G 的机器上可以打满内存,减少 IO 次数,减少内核态到用户态的拷贝,也可以提一嘴 DMA。
|
||||
|
||||
### 方法总结
|
||||
|
||||
#### 分治策略
|
||||
@ -35,4 +69,4 @@
|
||||
|
||||
#### 前缀树
|
||||
|
||||
1. 利用字符串的公共前缀来降低存储成本,提高查询效率。
|
||||
1. 利用字符串的公共前缀牺牲速度,来降低内存占用。
|
||||
|
Loading…
Reference in New Issue
Block a user