【性能优化】3.1 [外存查找] 二分法
3.1 二分法
如果文件中存储的外存数据表中的记录对被查找键有序,只要这种文件格式可以支持较随意的分段,我们就可以实施外存二分法查找,以避免顺序遍历整个文件。
先将文件分成两段,取出第二段的第一条记录,和查找键对比,根据对比结果再决定是在前一半还是后一半继续查找,这时要把文件分成 4 段,取第 2 段或第 4 段中的第 1 条记录再继续对比,继而分成 8 段,…,最终找到目标值。
有重复值的情况会更为复杂,向后可以直接继续遍历,向前则要继续在前半部分做二分直到找到第一个出现查找值为止,要比内存情况麻烦得多。
二分法还适用于区间式的查找,即找出被查找键在某个范围内的记录。只要找到第一个起始查找值,然后一直向后遍历直到把所有的终止查找值都找出来就可以了,不需要两边找。
这个过程并不难理解,但实现起来却较为繁琐。SPL 做了封装,可以直接使用:
A |
B |
|
1 |
=file("data.txt") |
|
2 |
for 1000 |
=10000.sort(rand()).to(9000).sort() |
3 |
>A1.export@at(B2.new(A2*10000+~-1:ID,…)) |
|
4 |
=A1.iselect@t(123456,ID) |
|
5 |
=A1.iselect@t(123456:234567,ID) |
A1-B3 生成了 900 万行 ID 有序的文本文件。使用 iselect 函数即可在有序的文件中用二分法找到目标值,也支持查找值在某个区间的形式。读者可以对比使用游标遍历的速度。
集文件也可以用这个函数。
A |
|
1 |
=file("data.btx")) |
2 |
=A1.export@b(file("data.txt").cursort@t()) |
3 |
=A1.iselect@b(123456,ID) |
4 |
=A1.iselect@b(123456:234567,ID) |
组表当然也可以,不过将使用另一个函数。
A |
B |
|
1 |
=file("data.ctx") |
=file("data.btx").cursor@b() |
2 |
=A1.create(#ID,…) |
>A2.append(B1) |
3 |
=A1.open() |
|
4 |
=A3.find(123456) |
|
5 |
=A3.cursor(;ID>=123456 && ID<=234567) |
用 find 找时,要求被查找键是组表的主键,即在组表中唯一。如果区间式的查找(会返回多个目标值),则直接生成游标上加条件即可,SPL 会根据键的有序性自动跳到合适的数据块读数据,而不会真地遍历。
请教一下,为什么针对不同文件设计不同函数,这不会增加使用难度吗?文本和集文件只是选项的区别还能理解,组表则完全不同。
另外,文本和集文件查找为什么不沿用 select 函数,比如 f.cursor().select@b() 代表二分法查找,而是单造一个 iselect 呢?
组表要复杂得多,本身有有序的维和键,要支持列存的分块存储机制也麻烦很多,还有专业的索引机制,这样能获得高得多的性能,但接口也相应复杂化。而文本和集文件就是简单的顺序存储。
游标的定义就是只能顺序读取,不可以跳着读,而二分法是需要跳的。select@b 就不能建立在游标上,当然可以造个 f.select@b,也就是 iselect 了。
游标上定义运算,并不知道游标的来源,游标数据可能来源于文件、可能数据库、可能多个基础游标组合计算而来。只要这个游标对象能支持 fetch 动作,就可以在后面附加各种运算,比如 cs.select,它并不要求游标一定要来源于文件,来源于数据库也是一样算的。
iselect 似乎没生效?
iselect 是否生效,要看 B4/B5 读出的数。
A7 中新产生了游标,还是会再次从头读数。
A7 是为了说明这个文件是有数据的,且包含查找值。现在 B4/B5 执行后没有结果,截图是执行整个脚本的效果,可以看到 A4-B5 都是白色的。
抱歉,这个代码又写错了,示意性的代码没有实际跑。iselect要加@t ,表示文本有标题,区间过滤不需要用[]。已修改了。谢谢指出。
里面有个…,是要自己补充一下了,不然读出来的字段很怪
感谢蒋总回复。函数帮助里是正确的,我也应该先查一下😄