32

假设您有一个包含数百个内存对象的集合,并且您需要查询此 List 以返回与某些 SQL 或 Criteria 之类的查询匹配的对象。例如,您可能有一个 Car 对象列表,并且您希望返回 1960 年代制造的所有汽车,车牌以 AZ 开头,按车型名称排序。

我知道JoSQL,有没有人使用过这个,或者对其他/本土解决方案有任何经验?

4

7 回答 7

27

正如其他答案中所讨论的,过滤是执行此操作的一种方法。

过滤虽然不可扩展。从表面上看,时间复杂度似乎是 O( n )(即,如果集合中的对象数量会增加,则已经无法扩展),但实际上因为需要根据查询对每个对象应用一个或多个测试,所以时间复杂度更准确地说,复杂度是 O( nt ),其中t是应用于每个对象的测试次数。

因此,随着向集合中添加额外的对象和/或查询中测试数量的增加,性能会下降。

还有另一种方法可以做到这一点,使用索引和集合论。

一种方法是在存储在您的集合中的对象中字段上构建索引,然后您将在查询中对其进行测试。

假设您有一个Car对象集合,并且每个Car对象都有一个字段color。假设您的查询相当于“ SELECT * FROM cars WHERE Car.color = 'blue'”。您可以在 上建立一个索引Car.color,它基本上看起来像这样:

'blue' -> {Car{name=blue_car_1, color='blue'}, Car{name=blue_car_2, color='blue'}}
'red'  -> {Car{name=red_car_1, color='red'}, Car{name=red_car_2, color='red'}}

然后给定一个查询WHERE Car.color = 'blue',可以在 O( 1 ) 时间复杂度内检索蓝色汽车的集合。如果您的查询中有其他测试,您可以测试该候选集中的每辆汽车,以检查它是否与您查询中的其余测试相匹配。由于候选集可能远小于整个集合,因此时间复杂度小于O( n )(在工程意义上,请参见下面的评论)。当将其他对象添加到集合中时,性能不会降低太多。但这仍然不完美,请继续阅读。

另一种方法,就是我所说的常设查询索引。解释一下:通过传统的迭代和过滤,对集合进行迭代并测试每个对象以查看它是否与查询匹配。所以过滤就像在一个集合上运行一个查询。一个常设查询索引将是另一种方式,其中集合改为在查询上运行,但对于集合中的每个对象仅运行一次,即使该集合可以被查询任意次数。

常设查询索引就像使用某种智能集合注册查询一样,当对象被添加到集合中或从集合中删除时,该集合将针对已注册的所有常设查询自动测试每个对象。如果一个对象与一个常设查询匹配,则该集合可以将其添加到/从专用于存储与该查询匹配的对象的集合中删除。随后,可以在 O( 1 ) 时间复杂度内检索与任何已注册查询匹配的对象。

以上信息取自CQEngine(集合查询引擎)。这基本上是一个 NoSQL 查询引擎,用于使用类似 SQL 的查询从 Java 集合中检索对象,而无需遍历集合的开销。它是围绕上述想法构建的,还有更多。免责声明:我是作者。它是开源的,位于 Maven 中心。如果您觉得有帮助,请点赞这个答案!

于 2012-07-29T21:02:32.197 回答
13

我在生产应用程序中使用了Apache Commons JXPath。它允许您将 XPath 表达式应用于 Java 中的对象图。

于 2008-09-18T15:29:50.050 回答
6

是的,我知道这是一篇旧帖子,但技术每天都在出现,答案会随着时间而改变。

我认为这是一个用 LambdaJ 解决的好问题。你可以在这里找到它: http ://code.google.com/p/lambdaj/

这里有一个例子:

寻找活跃客户//(可迭代版本)

List<Customer> activeCustomers = new ArrayList<Customer>();  
for (Customer customer : customers) {  
  if (customer.isActive()) {  
    activeCusomers.add(customer);  
  }  
}  

LambdaJ 版本

List<Customer> activeCustomers = select(customers, 
                                        having(on(Customer.class).isActive()));  

当然,有这种美感对性能有影响(有点……平均2次),但是你能找到更可读的代码吗?

它有很多功能,另一个例子可能是排序:

排序迭代

List<Person> sortedByAgePersons = new ArrayList<Person>(persons);
Collections.sort(sortedByAgePersons, new Comparator<Person>() {
        public int compare(Person p1, Person p2) {
           return Integer.valueOf(p1.getAge()).compareTo(p2.getAge());
        }
}); 

使用 lambda 排序

List<Person> sortedByAgePersons = sort(persons, on(Person.class).getAge()); 

更新:在 java 8 之后,您可以使用开箱即用的 lambda 表达式,例如:

List<Customer> activeCustomers = customers.stream()
                                          .filter(Customer::isActive)
                                          .collect(Collectors.toList());                                      
于 2014-03-06T16:52:58.750 回答
3

继续Comparator主题,您可能还想看看Google Collections API。特别是,它们有一个名为Predicate的接口,它的作用类似于Comparator,因为它是一个简单的接口,可以由过滤方法使用,例如Sets.filter。它们包括一大堆复合谓词实现,执行 AND、OR 等。

根据数据集的大小,使用这种方法可能比 SQL 或外部关系数据库方法更有意义。

于 2008-09-18T16:05:40.930 回答
2

如果你需要一个具体的匹配,你可以让类实现比较器,然后创建一个包含所有散列字段的独立对象,并使用它来返回匹配的索引。当您想在集合中找到多个(可能)对象时,您将不得不求助于像 JoSQL 这样的库(在我使用它的琐碎案例中效果很好)。

一般来说,我倾向于将 Derby 嵌入到我的小型应用程序中,使用 Hibernate 注释来定义我的模型类,并让 Hibernate 处理缓存方案以保持一切快速。

于 2008-09-18T15:18:30.280 回答
1

我会使用一个比较器,它需要一系列年份和车牌模式作为输入参数。然后只需遍历您的集合并复制匹配的对象。您最终可能会使用这种方法制作一整套自定义比较器。

于 2008-09-18T15:17:33.030 回答
0

Comparator选项还不错,特别是如果您使用匿名类(以免在项目中创建冗余类),但最终当您查看比较流程时,它几乎就像您自己循环整个集合一样,准确指定匹配项目的条件:

if (Car car : cars) {
    if (1959 < car.getYear() && 1970 > car.getYear() &&
            car.getLicense().startsWith("AZ")) {
        result.add(car);
    }
}

然后是排序......这可能在背后很痛苦,但幸运的是有类Collections及其sort方法,其中一个接收Comparator......

于 2008-09-18T15:45:33.303 回答