您需要查看 Boon。 :)
http://rick-hightower.blogspot.com/2013/11/what-if-java-collections-and-java.html
您可以添加 n 个搜索索引和查找索引。它还允许您有效地查询原始属性。
这是一个来自 wiki 的示例(我是作者)。
repoBuilder.primaryKey("ssn")
.searchIndex("firstName").searchIndex("lastName")
.searchIndex("salary").searchIndex("empNum", true)
.usePropertyForAccess(true);
您可以通过提供一个 true 标志作为 searchIndex 的第二个参数来覆盖它。
注意 empNum 是一个可搜索的唯一索引。
如果在运行时查询一组复杂的 Java 对象很容易呢?如果有一个 API 可以让你的对象索引(实际上只是 TreeMaps 和 HashMaps)保持同步呢?那么你将拥有 Boon 的数据仓库。本文展示了如何使用 Boon 的数据存储库实用程序来查询 Java 对象。这是第一部分。可以有很多很多部分。 :)
Boon 的数据仓库使对集合进行基于索引的查询变得更加容易。
为什么选择 Boon 数据回购
Boon 的数据存储库允许您至少在查询集合时将 Java 集合更像数据库一样对待。 Boon 的数据存储库不是内存数据库,不能替代将对象排列成针对应用程序优化的数据结构。
如果您想花时间提供客户价值并构建您的对象和类,并为您的数据结构使用 Collections API,那么 DataRepo 适合您。这并不排除打破 Knuth 的书籍并提出优化的数据结构。它只是帮助让平凡的事情变得简单,这样你就可以花时间让困难的事情成为可能。
因需要而生
这个项目是出于需要。我正在做一个项目,该项目计划在内存中存储大量域对象以提高速度,有人问了一个我忽略的非常重要的问题。我们将如何查询这些数据。我的回答是我们将使用 Collections API 和 Streaming API。然后我试着这样做......嗯......
我也厌倦了在大型数据集上使用 JDK 8 流 API,而且速度很慢。 (Boon 的数据仓库适用于 JDK7 和 JDK8)。这是一个线性搜索/过滤器。这是设计使然,但对于我所做的,它不起作用。我需要索引来支持任意查询。
Boon 的数据仓库增强了流式 API。
Boon 的数据仓库并没有试图替换 JDK 8 流 API,事实上它可以很好地配合它。 Boon 的数据存储库允许您创建索引集合。索引可以是任何东西(它是可插入的)。
目前,Boon 的数据回购索引基于 ConcurrentHashMap 和 ConcurrentSkipListMap。
根据设计,Boon 的数据存储库与标准集合库一起使用。没有计划创建一组自定义集合。如果愿意的话,应该可以插入 Guava、Concurrent Trees 或 Trove。
它为此提供了一个简化的 API。它允许线性搜索以获得完成感,但我建议主要使用它来使用索引,然后将流式 API 用于其余部分(为了类型安全和速度)。
步步为营前的潜行
假设您有一个创建 200,000 个员工对象的方法,如下所示:
List<Employee> employees = TestHelper.createMetricTonOfEmployees(200_000);
所以现在我们有 200,000 名员工。让我们搜索它们...
首先将员工包装在可搜索的查询中:
employees = query(employees);
现在搜索:
List<Employee> results = query(employees, eq("firstName", firstName));
那么上面和流API的主要区别是什么?
employees.stream().filter(emp -> emp.getFirstName().equals(firstName)
使用 Boon 的 DataRepo 大约快 20,000%!啊 HashMaps 和 TreeMaps 的力量。 :)
有一个 API 看起来就像您的内置集合。还有一个 API 看起来更像是 DAO 对象或 Repo 对象。
使用 Repo/DAO 对象的简单查询如下所示:
List<Employee> employees = repo.query(eq("firstName", "Diana"));
一个更复杂的查询应该是这样的:
List<Employee> employees = repo.query(
and(eq("firstName", "Diana"), eq("lastName", "Smith"), eq("ssn", "21785999")));
或者这个:
List<Employee> employees = repo.query(
and(startsWith("firstName", "Bob"), eq("lastName", "Smith"), lte("salary", 200_000),
gte("salary", 190_000)));
甚至这个:
List<Employee> employees = repo.query(
and(startsWith("firstName", "Bob"), eq("lastName", "Smith"), between("salary", 190_000, 200_000)));
或者如果你想使用 JDK 8 流 API,这适用于它而不是反对它:
int sum = repo.query(eq("lastName", "Smith")).stream().filter(emp -> emp.getSalary()>50_000)
.mapToInt(b -> b.getSalary())
.sum();
如果员工数量很大,上述操作会快得多。它将缩小以史密斯开头且薪水超过 50,000 的员工的范围。假设您有 100,000 名员工,只有 50 名名为 Smith,所以现在您通过使用有效地从 100,000 名员工中抽出 50 名员工的索引快速缩小到 50 名,然后我们只过滤 50 名员工而不是全部 100,000 名员工。
以下是线性搜索与索引搜索(以纳秒为单位)的数据存储库的基准测试:
Name index Time 218
Name linear Time 3709120
Name index Time 213
Name linear Time 3606171
Name index Time 219
Name linear Time 3528839
最近有人对我说:“但是使用流式 API,您可以并行运行过滤器。
让我们看看数学是如何成立的:
3,528,839 / 16 threads vs. 219
201,802 vs. 219 (nano-seconds).
索引获胜,但这是一个照片完成。不是! :)
它只快了 9,500%,而不是快了 40,000%。这么近.....
我添加了更多功能。他们大量使用索引。 :)
repo.updateByFilter(values(value("firstName", "Di")),
和(eq(“名字”,“戴安娜”),
eq(“姓氏”,“史密斯”),
eq("ssn", "21785999") ) );
以上将等价于
更新员工 e
SET e.firstName='Di'
WHERE e.firstName = '戴安娜'
和 e.lastName = 'Smith'
和 e.ssn = '21785999'
这允许您在多条记录上一次设置多个字段,因此如果您要进行批量更新。
所有基本类型都有重载方法,所以如果你有一个值要更新从过滤器返回的每个项目:
repo.updateByFilter("firstName", "Di",
and( eq("firstName", "Diana"),
eq("lastName", "Smith"),
eq("ssn", "21785999") ) );
以下是一些基本的选择功能:
List <Map<String, Object>> list =
repo.query(selects(select("firstName")), eq("lastName", "Hightower"));
您可以选择任意多的选项。您还可以将列表重新排序:
List <Map<String, Object>> list =
repo.sortedQuery("firstName",selects(select("firstName")),
eq("lastName", "Hightower"));
您可以选择相关属性的属性(即employee.department.name)。
List <Map<String, Object>> list = repo.query(
selects(select("department", "name")),
eq("lastName", "Hightower"));
assertEquals("engineering", list.get(0).get("department.name"));
上面会尝试使用类的字段。如果要使用实际属性(emp.getFoo() 与 emp.foo),则需要使用 selectPropertyPath。
List <Map<String, Object>> list = repo.query(
selects(selectPropPath("department", "name")),
eq("lastName", "Hightower"));
请注意,select("department", "name") 比 selectPropPath("department", "name") 快得多,这在紧密循环中可能很重要。
默认情况下,所有搜索索引和查找索引都允许重复(主键索引除外)。
repoBuilder.primaryKey("ssn")
.searchIndex("firstName").searchIndex("lastName")
.searchIndex("salary").searchIndex("empNum", true)
.usePropertyForAccess(true);
您可以通过提供一个 true 标志作为 searchIndex 的第二个参数来覆盖它。
注意 empNum 是一个可搜索的唯一索引。
如果您愿意或需要,您甚至可以将简单的搜索返回为地图:
List<Map<String, Object>> employees = repo.queryAsMaps(eq("firstName", "Diana"));
我不确定这是功能还是错误功能。我的想法是,一旦您处理数据,您需要以一种不会将数据消费者与您的实际 API 联系起来的方式呈现该数据。拥有 String / 基本类型的 Map 似乎是实现这一目标的一种方法。
请注意,映射转换的对象深入如下:
System.out.println(employees.get(0).get("department"));
产量:
{class=Department, name=engineering}
这对于工具的调试和临时查询很有用。我正在考虑添加支持以轻松转换为 JSON 字符串。
添加了查询集合属性的功能。这应该适用于您喜欢的深度嵌套的集合和数组。再读一遍,因为它是一个真正的 MF 实现!
List <Map<String, Object>> list = repo.query(
selects(select("tags", "metas", "metas2", "metas3", "name3")),
eq("lastName", "Hightower"));
print("list", list);
assertEquals("3tag1", idx(list.get(0).get("tags.metas.metas2.metas3.name3"), 0));
上面的打印出来是这样的:
list [{tags.metas.metas2.metas3.name3=[3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3]},
...
我创建了几个关系类来测试这个:
public class Employee {
List <Tag> tags = new ArrayList<>();
{
tags.add(new Tag("tag1"));
tags.add(new Tag("tag2"));
tags.add(new Tag("tag3"));
}
...
public class Tag {
...
List<Meta> metas = new ArrayList<>();
{
metas.add(new Meta("mtag1"));
metas.add(new Meta("mtag2"));
metas.add(new Meta("mtag3"));
}
}
public class Meta {
...
List<Meta2> metas2 = new ArrayList<>();
{
metas2.add(new Meta2("2tag1"));
metas2.add(new Meta2("2tag2"));
metas2.add(new Meta2("2tag3"));
}
}
...
public class Meta2 {
List<Meta3> metas3 = new ArrayList<>();
{
metas3.add(new Meta3("3tag1"));
metas3.add(new Meta3("3tag2"));
metas3.add(new Meta3("3tag3"));
}
public class Meta3 {
...
您也可以按类型搜索:
List<Employee> results = sortedQuery(queryableList, "firstName", typeOf("SalesEmployee"));
assertEquals(1, results.size());
assertEquals("SalesEmployee", results.get(0).getClass().getSimpleName());
上面找到了所有具有简单类名 SalesEmployee 的员工。它也适用于完整的类名,如下所示:
List<Employee> results = sortedQuery(queryableList, "firstName", typeOf("SalesEmployee"));
assertEquals(1, results.size());
assertEquals("SalesEmployee", results.get(0).getClass().getSimpleName());
您也可以按实际班级搜索:
List<Employee> results = sortedQuery(queryableList, "firstName", instanceOf(SalesEmployee.class));
assertEquals(1, results.size());
assertEquals("SalesEmployee", results.get(0).getClass().getSimpleName());
您还可以查询实现某些接口的类:
List<Employee> results = sortedQuery(queryableList, "firstName",
implementsInterface(Comparable.class));
assertEquals(1, results.size());
assertEquals("SalesEmployee", results.get(0).getClass().getSimpleName());
您还可以索引嵌套字段/属性,它们可以是集合字段或属性非集合字段,只要您愿意,嵌套深度就可以:
/* Create a repo, and decide what to index. */
RepoBuilder repoBuilder = RepoBuilder.getInstance();
/* Look at the nestedIndex. */
repoBuilder.primaryKey("id")
.searchIndex("firstName").searchIndex("lastName")
.searchIndex("salary").uniqueSearchIndex("empNum")
.nestedIndex("tags", "metas", "metas2", "name2");
以后可以使用nestedIndex进行搜索。
List<Map<String, Object>> list = repo.query(
selects(select("tags", "metas", "metas2", "name2")),
eqNested("2tag1", "tags", "metas", "metas2", "name2"));
使用nestedIndex 的安全方法是使用eqNested。如果你有这样的索引,你可以使用 eq、gt、gte 等:
List<Map<String, Object>> list = repo.query(
selects(select("tags", "metas", "metas2", "name2")),
eq("tags.metas.metas2.name2", "2tag1"));
您还可以添加对子类的支持
List<Employee> queryableList = $q(h_list, Employee.class, SalesEmployee.class,
HourlyEmployee.class);
List<Employee> results = sortedQuery(queryableList, "firstName", eq("commissionRate", 1));
assertEquals(1, results.size());
assertEquals("SalesEmployee", results.get(0).getClass().getSimpleName());
results = sortedQuery(queryableList, "firstName", eq("weeklyHours", 40));
assertEquals(1, results.size());
assertEquals("HourlyEmployee", results.get(0).getClass().getSimpleName());
数据仓库在其 DataRepoBuilder.build(...) 方法中具有类似的功能,用于指定子类。这允许您在同一个 repo 或可搜索集合中无缝查询字段形式的子类和类。