使用Java和SQLite的递归数据处理性能

时间:2009-04-04 10:18:59

标签: java android sqlite recursion

如果您的答案与Java / SQLite无关,我很乐意阅读。

环境

我使用以下方案将项目存储在数据库中:

###################
#       Item      #    
###################
#      _id        #    This is the primary key
#    parent_id    #    If set, it the ID of the item containing this item
#      date       #    An ordinary date
#  geocontext_id  #    Foreign key to a pair of named coordinates
###################

###################
#   Geocontext    #    
###################
#       _id       #    This is the primary key
#       name      #    Way for the user to label a pair of coordinates (e.g : "home", "work")
#         x       #    One of the coordinate
#         y       #    The other one
###################

问题

我必须根据geocontext和日期过滤项目。如果项目都在同一级别,那将是一件容易的事,但诀窍在于它是递归的。 E.G:

root
      |_item 1
      |_item 2 
      |      |_item 4
      |      |_item 5
      |             |_item 6
      |_item 3
      |      |_item 8
      |             |_item 10
      |_item 11
      |       |_item 12
      |_item 7

递归深度没有明确的限制。

现在,如果我们在任何节点并使用日期“4月1日”过滤,我们不仅必须看到节点中直接包含的与日期匹配的项目,而且我们必须看到包含的项目与日期匹配的商品

E.G:我们在“第2项”中,如果“第6项”与日期匹配,那么我们认为“第5项”也与日期相符,我们必须保留它。如果我们在根,则必须显示第2项。

geocontext也是如此,但它更难,因为:

  • 它存储在另一张表中。
  • 匹配上下文是一项代价高昂的数学计算。

当然,强制匹配会导致软件速度变慢,用户体验也很差。

注意:我不需要显示树。我显示了树中过滤数据的列表。我们必须只看到顶级元素的平面列表。根据所有孩子的层次结构,挑战在于决定是否显示每个元素。

我是如何解决它的

我认为通过使用更多表来缓存平面数据我可以缓解一些问题:

###################
# Geocontex_cache #    
###################
#     item_id     #     I can Join the items table on this field
#     child_id    #     I can delete / update a child, and so delete / update the cache
#  geocontext_id  #     I can delete / update a geocontext, and so delete / update the cache
#        x        #      Here, I can brute force :-)
#        y        # 
###################

###################
#    Date_cache   #    
###################
#     item_id     #     
#     child_id    #    
#       date      #    
###################

这似乎很合理,但我还没有尝试过。然而,它应该有以下缺点:

  • 我将昂贵的流程转移到了get / set / create / delete方法 必须管理缓存日期。 这将是一个麻烦的代码 写作和维护。五个深度 等级项目将使一个过程变为三角形 将递归五个父母。

  • 数据库的大小可以 变得巨大。五级深度 item store缓存了五个数据 父母。不知道它是否相关, 因为这是一个单用户应用程序与 手动输入。我认为没有人 会插入更多的1000件物品 超过10级的深度。

现在好消息是我们离开了    金字塔的底部到顶部,而不是    另一种方式,所以它没有    看起来很恐怖。当我愿意    必须处理父项    删除,这将是另一个不错的    头疼,但我把它保存到另一个    问题; - )。

现在我的问题

您将如何以最佳方式存储数据并处理过滤?

可选:

我应该定义一个明确的递归深度限制吗? 我应该使用SQL还是Java执行过滤? SQL肯定会更快,但在Java中更容易匹配geocontext。

当我在Android平台上工作时,我有以下限制:

  • Java是唯一可用的语言, 而不是整个标准的lib。

  • SQLite是唯一可用的DBMS。

  • 性能和记忆很重要 的问题。如果你必须选择, 电池寿命因此 绩效是首要任务。

  • Exotics外部库可能无法使用 使用。

P.S:我挖到了SO并发现了一些有趣的信息(特别是What is the most efficient/elegant way to parse a flat table into a tree?)。这是一个暗示,但不是问题解决者。

4 个答案:

答案 0 :(得分:5)

1)首先,让我们看一下将所有内容放入内存中。这是简单,灵活,最重要的是快速解决方案。缺点包括你必须在启动时将所有内容读入内存(给用户一个漂亮的加载栏,他们甚至都不会注意到),并且可能需要做一些额外的工作来确保一切都反映到磁盘上用户认为是,所以数据不会丢失。

在这个分析中,我对Android / Dalvik做了一些通用的假设我真的不太了解,所以希望它有点准确:)记住G1有192MB的RAM。此外,您的上述假设最多约为1000项。

Object superclass ~ 8 bytes
parent/child pointer ~ 4 bytes
date (long) ~ 8 bytes
name (non interned string avg 32 chars) ~ 64 bytes
x point (int) ~ 4 bytes
y point (int) ~ 4 bytes

Total = 92 bytes + possible memory alignment + fudge factor = 128 bytes
1000 items = 125kB
10000 items = 1.22MB

注意:我意识到虽然一个孩子只能有一个指针,但父母可以有多个孩子。但是,parent-gt;子指针的数量是(elements-1),因此parent->子指针的平均成本是(elements-1)/ elements~1个元素或4个字节。这假定子结构不分配未使用的内存,例如LinkedList(而不是ArrayList)

2)我的书呆子说这将是一个有趣的地方来描绘一个B +树,但我认为这对你现在想要的东西来说太过分了:)但是,无论你解决什么解决方案如果你没有将所有内容保存在内存中,你肯定会想要尽可能多地在内存中缓存树的顶层。这可能会大幅减少磁盘活动量。

3)如果您不想全部记忆,另一种可能的解决方案可能如下。 Bill Karwin建议使用相当优雅的RDBMS structure called a Closure Table来优化基于树的读取,同时使写入更复杂。将它与顶级缓存相结合可能会给你带来性能上的好处,尽管我会在接受它之前测试它:

评估视图时,请使用内存中的任何内容来评估尽可能多的子项。对于那些不匹配的子节点,使用闭包表和平面表之间的SQL连接以及相应的where子句来查找是否存在任何匹配的子节点。如果是这样,您将在结果列表中显示该节点。

希望这一切都有意义,似乎它可以满足您的需求。

答案 1 :(得分:2)

我听了Soonil并尝试了«封闭表»。我添加了下表:

################
#   Closure    #
################
# ancestor_id  #
#   item_id    #
################

如果像我一样,你之前从未使用过那个模型,那就是这样:

为层次结构中的每个直接或间接关系添加一行。如果C是B的孩子,而B是A的孩子,那么你就得到了:

ancestor    item
   B         C
   A         B
   A         C      # you add the indirect relationship   
   A         A
   B         B
   C         C      # don't forget any item is in relation with himself 

然而,通过这种方案,您缺少一个重要信息:直接关系是什么?如果您只想要一个项目的直接孩子怎么办?

为此,您可以在闭包表中添加一个带有bool的列is_direct,或者您可以将列parent_id保留在item表中。这就是我所做的,因为它阻止我重写我之前的许多代码。

好的部分是我现在可以在一个查询中检查项目是否与日期或地理文本匹配。

E.G,如果我正在浏览第4项中包含的所有项目,并且只想获得匹配或包含与日期D匹配的子项的那些项目:

SELECT ti.parent_id, ti.id, ti.title 
FROM item AS di                                  # item to filter with the date
              JOIN closure AS c                  # closure table
                  ON (di.id = c.item_id) 
              JOIN item AS ti 
                  ON (c.ancestor_id = ti.id)     # top item to display
WHERE di.date = D                                # here you filter by date   
AND ti.parent_id = 4                             # here you ensure you got only the top items

所以我可以丢弃所有*_cache表。我仍然有很多工作要做一个 UPDATE / DELETE / CREATE ,但是一切都是集中的,大部分是程序性的,而不是递归的。很酷。

唯一的痛苦是我必须递归地向其所有祖先添加一个项目。但是获得祖先是一个查询镜头,所以这是非常合理的。当然封闭表占用了很多空间,但在我的情况下我只是不在乎。如果你正在寻找穿孔,不要忘记索引...

喜欢这个SQL技巧,非常感谢!第一眼看起来有点棘手,但是一旦你完成它就显而易见了。

答案 2 :(得分:1)

这可能是offtopic但是..你考虑过使用序列化吗?

Google协议缓冲区可用于以非常有效的方式(时间和空间)序列化数据,然后您必须创建合适的树结构(查看任何CS书籍)以帮助进行搜索。

我提到了协议缓冲区,因为它们可能是Android上的Google库。

只是一个想法。

答案 3 :(得分:-1)

AFAICT您可以在SQLite中使用分层查询(google for“CONNECT BY”“START WITH”)...