Java 集合的多个索引 - 最基本的解决方案?
Posted
技术标签:
【中文标题】Java 集合的多个索引 - 最基本的解决方案?【英文标题】:Multiple indexes for a Java Collection - most basic solution? 【发布时间】:2011-01-30 21:09:58 【问题描述】:我正在寻找在 Java 集合上创建多个索引的最基本解决方案。
所需功能:
删除某个值后,必须删除与该值关联的所有索引条目。 索引查找必须比线性搜索快(至少与 TreeMap 一样快)。附带条件:
不依赖于大型(如 Lucene)库。没有不常见或未经过良好测试的库。没有数据库。 像 Apache Commons Collections 这样的库就可以了。 如果它单独与 JavaSE (6.0) 一起使用,那就更好了。 编辑: 没有自行实现的解决方案(感谢建议的答案 - 为了完整性,将它们放在这里很好,但我已经有一个与 Jay 非常相似的解决方案)每当有几个人发现,他们实现了相同的东西,这应该是一些公共库的一部分。当然,我可以自己编写一个管理多个地图的类(这并不难,但感觉就像在重新发明***)。所以我想知道,如果它可以在没有的情况下完成 - 同时仍然获得类似于使用单个索引 java.util.Map 的简单用法。
谢谢,克里斯
更新
看起来我们好像什么都没找到。我喜欢你所有的答案 - 自行开发的版本,类似于数据库的库的链接。
这是我真正想要的:拥有 (a) Apache Commons Collections 或 (b) Google Collections/Guava 中的功能。或者也许是一个很好的选择。
其他人是否也错过了这些库中的这个功能?他们确实提供了各种各样的东西,比如 MultiMaps、MulitKeyMaps、BidiMaps……我觉得,它很适合这些库——它可以被称为MultiIndexMap
。你怎么看?
【问题讨论】:
我不完全理解您所说的“多个索引”是什么意思。您是否想要几种不同的查找方法 - 每个索引一种 - 或者您是否想要,例如HashMap 为那些具有相同哈希键的条目使用新的集合(例如哈希图)? 它应该类似于您可以对数据库表执行的操作:一个主键(例如user_id
),加上其他唯一索引(例如username
)。两者都可用于访问用户行。当用户被删除时,id 和用户名与条目一起从索引中删除。
您刚刚定义了一个内存数据库,您的要求表明您不想要它。
相关 - “如何实现具有多个键的 Map?” ***.com/questions/822322/…。那里也没有明确的解决方案 - 看起来有些答案是使用带有复合键的地图,但那里的 OP 表示他希望能够通过单值键进行查找。尽管如此,在不太可能的情况下将问题联系起来似乎是一个好主意,稍后会出现可行的答案。此外,如果此处的 OP 决定自定义解决方案,则此相关问题提供了一些有关潜在实现的额外想法。
删除某个值时,必须删除与该值关联的所有索引条目。查看! Boon Repo 就是这样做的。索引查找必须比线性搜索快(至少与 TreeMap 一样快)。 CHECK Boon Repo 就是这样做的!附带条件:不依赖于大型(如 Lucene)库。没有不常见或未经过良好测试的库。没有数据库。恩惠不依赖任何东西!查看! Boon Repo 做到了这一切。还有更多,它又小又紧。 :) richardhightower.github.io/site/Boon/Welcome.html
【参考方案1】:
每个索引基本上都是一个单独的Map
。您可以(并且可能应该)将其抽象为一个为您管理搜索、索引、更新和删除的类。相当通用地做到这一点并不难。但是不,没有标准的开箱即用的类,尽管它可以很容易地从 Java Collections 类构建。
【讨论】:
当索引值(地图的键)可以是双倍时,地图很棘手,例如不是唯一的。然后你需要里面有列表的地图。带有自定义比较器的 TreeSet 也存在同样的问题,这正是我所考虑的 - 在实现中也是 TreeMap :) 好答案。使用 createIndex/destroyIndex 创建一个库类并不难,在其中你将 createIndex 传递给一个除了实现 equals 和 hash 之外什么都不做的类。您必须存储曾经添加的所有内容的数组,以便当有人调用 createIndex 时,您可以快速将数组中的所有内容添加到该新哈希中。 destroyIndex 只会删除哈希。嗯,好像不值得。我想这就是为什么它从未完成过。 @cletus:也不在任何(小型、通用且经过良好测试的)库中? @extraneon:“一个键的多个值”功能由 Apache Commons Collections MultiMap commons.apache.org/proper/commons-collections/apidocs/org/…提供 集合(可能是 HashSet)比存储桶的列表更好。您不需要顺序,可能确实需要快速插入、删除和唯一性检查。【参考方案2】:看看CQEngine (Collection Query Engine),它非常适合这种要求,基于IndexedCollection
。
有关更多背景信息,另请参阅相关问题 How do you query object collections in Java (Criteria/SQL-like)?。
【讨论】:
不确定,如果它真的完全适合我的(旧的!)问题,但该项目看起来很有趣。【参考方案3】:我的第一个想法是为被索引的事物创建一个类,然后创建多个 HashMap 来保存索引,并将相同的对象添加到每个 HashMap。对于添加,您只需将相同的对象添加到每个 HashMap。删除需要在每个 HashMap 中搜索对目标对象的引用。如果需要快速删除,您可能需要为每个索引创建两个 HashMap:一个用于索引到值,另一个用于值到索引。当然,我会将你所做的任何事情都封装在一个具有明确定义接口的类中。
这似乎并不难。如果您预先知道索引的数量和类型以及小部件的类,那将非常简单,例如:
public class MultiIndex
HashMap<String,Widget> index1=new HashMap<String,Widget>();
HashMap<String,Widget> index2=new HashMap<String,Widget>();
HashMap<Integer,Widget> index3=new HashMap<Integer,Widget>();
public void add(String index1Value, String index2Value, Integer index3Value, Widget widget)
index1.put(index1Value, widget);
index2.put(index2Value, widget);
index3.put(index3Value, widget);
public void delete(Widget widget)
Iterator i=index1.keySet().iterator();
while (i.hasNext())
String index1Value=(String)i.next();
Widget gotWidget=(Widget) index1.get(index1Value);
if (gotWidget.equals(widget))
i.remove();
... similarly for other indexes ...
public Widget getByIndex1(String index1Value)
return index1.get(index1Value);
... similarly for other indexes ...
如果你想让它通用并接受任何对象,有可变数量和类型的索引等,它会稍微复杂一些,但并不多。
【讨论】:
【参考方案4】:您需要查看 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 或可搜索集合中无缝查询字段形式的子类和类。
【讨论】:
【参考方案5】:您有很多非常严格的要求,似乎对您的需求非常特别。您所说的大多数事情都不可行是因为很多人都有相同的确切需求,这些需求基本上定义了一个基本的数据库引擎。这就是为什么它们是“大型”图书馆。您说“没有数据库”,但每个索引系统的核心都是术语和文档的“数据库”。我认为集合是一个“数据库”。我想说看看Space4J。
我会说,如果您没有找到所需的内容,请在 GitHub 上开始一个项目,然后自己编写代码并分享结果。
【讨论】:
这是一个非常好的观点:我需要内存中数据库的部分功能!我不需要 ACID,我什至不需要多个表,我不需要使用舒适的查询语言来访问它。但我确实需要其中的一部分:多个唯一索引。 Space4J 有可能是目前最好的答案。我必须承认,我之前没有听说过那个项目 - 链接+1!但是,我确实认为这个问题很常见,就像在 SQL 中调用“CREATE UNIQUE INDEX ... ON ...”一样常见。 Space4J 有一个很好的索引方法,因为即使在内存中,当表开始增长时,您也可能需要索引搜索。看这里:forum.space4j.org/posts/list/5.page【参考方案6】:Google CollectionsLinkedListMultimap
关于您的第一个要求
删除某个值后,必须删除与该值关联的所有索引条目。我认为既没有库也没有 Helper 支持它。
这是我使用 LinkedListMultimap 所做的事情
Multimap<Integer, String> multimap = LinkedListMultimap.create();
// Three duplicates entries
multimap.put(1, "A");
multimap.put(2, "B");
multimap.put(1, "A");
multimap.put(4, "C");
multimap.put(1, "A");
System.out.println(multimap.size()); // outputs 5
要满足您的第一个要求,Helper 可以做得很好
public static <K, V> void removeAllIndexEntriesAssociatedWith(Multimap<K, V> multimap, V value)
Collection<Map.Entry<K, V>> eCollection = multimap.entries();
for (Map.Entry<K, V> entry : eCollection)
if(entry.getValue().equals(value))
eCollection.remove(entry);
...
removeAllIndexEntriesAssociatedWith(multimap, "A");
System.out.println(multimap.size()); // outputs 2
Google 集合是
轻量级 由 Joshua Block(Effective Java)支持 ImmutableList、ImmutableMap 等不错的功能【讨论】:
【参考方案7】:我编写了一个 Table 接口,其中包含
之类的方法V put(R rowKey, C columnKey, V value)
V get(Object rowKey, Object columnKey)
Map<R,V> column(C columnKey)
Set<C> columnKeySet()
Map<C,V> row(R rowKey)
Set<R> rowKeySet()
Set<Table.Cell<R,C,V>> cellSet()
我们希望将它包含在未来的 Guava 版本中,但我不知道什么时候会发生。 http://code.google.com/p/guava-libraries/issues/detail?id=173
【讨论】:
@Jared Levy:谢谢,很高兴听到 Guava 正在考虑这个话题!我正在考虑从 Apache Commons Collections 切换到 Guava :-) 你能稍微扩展一下表格界面的使用吗?乍一看,它看起来更像是提供类似于数据库表的复合主键所做的事情(您必须获得 both 键的部分才能访问该值)?还是它还提供多个索引(您选择 一个 键的一部分,然后只需要获得该部分的权限即可访问该值)? 访问表时可以指定 0、1 或 2 个键。不幸的是,我们可能需要一段时间才能将其开源。 这是一个很好的数据结构,我相信当递归应用时(表中的表),它会显示出它的优势!不过,它似乎与我在这个问题中寻找的结构不同。 Jay 的示例是我基本上要寻找的(多个 unique 索引;删除一个元素,它会从所有索引中删除)但我想在一个公共库中找到类似的东西,因为很多人似乎发现这非常正常,他们必须一遍又一遍地重新实现相同的东西。【参考方案8】:您的主要目标似乎是当您从一个索引中删除该对象时,您将从所有索引中删除它。
最简单的方法是添加另一层间接:您将实际对象存储在Map<Long,Value>
中,并使用双向映射(您可以在 Jakarta Commons 和可能的 Google 代码中找到)作为 @ 987654322@。当您从特定索引中删除条目时,您将从该索引中获取 Long
值,并使用它从主映射和其他索引中删除相应的条目。
BIDIMap 的一种替代方法是将“索引”映射定义为Map<Key,WeakReference<Long>>
;但是,这将需要您实现 ReferenceQueue
进行清理。
另一种选择是创建一个可以采用任意元组的键对象,定义其equals()
方法以匹配元组中的任何元素,并将其与TreeMap
一起使用。您不能使用HashMap
,因为您将无法仅根据元组的一个元素计算哈希码。
public class MultiKey
implements Comparable<Object>
private Comparable<?>[] _keys;
private Comparable _matchKey;
private int _matchPosition;
/**
* This constructor is for inserting values into the map.
*/
public MultiKey(Comparable<?>... keys)
// yes, this is making the object dependent on externally-changable
// data; if you're paranoid, copy the array
_keys = keys;
/**
* This constructor is for map probes.
*/
public MultiKey(Comparable key, int position)
_matchKey = key;
_matchPosition = position;
@Override
public boolean equals(Object obj)
// verify that obj != null and is castable to MultiKey
if (_keys != null)
// check every element
else
// check single element
public int compareTo(Object o)
// follow same pattern as equals()
【讨论】:
这些是编写实现时需要考虑的要点 (+1)。【参考方案9】:使用PrefuseTables。它们支持任意数量的索引,速度很快(索引是 TreeMaps),并且有很好的过滤选项(布尔过滤器?没问题!)。不需要数据库,在许多信息可视化应用程序中使用大型数据集进行了测试。
在原始形式中,它们不如标准容器方便(您需要处理行和列),但您肯定可以围绕它编写一个小包装器。此外,它们可以很好地插入到 UI 组件中,例如 Swing 的 JTables。
【讨论】:
【参考方案10】:如果您希望数据有多个索引,您可以创建和维护多个哈希映射或使用像 Data Store 这样的库:
https://github.com/jparams/data-store
例子:
Store<Person> store = new MemoryStore<>() ;
store.add(new Person(1, "Ed", 3));
store.add(new Person(2, "Fred", 7));
store.add(new Person(3, "Freda", 5));
store.index("name", Person::getName);
Person person = store.getFirst("name", "Ed");
使用数据存储,您可以创建不区分大小写的索引和各种很酷的东西。值得一试。
【讨论】:
【参考方案11】:我不确定我是否理解这个问题,但我认为您所要求的是多种方法来从不同的唯一键映射到值,并在值消失时进行适当的清理。
我看到您不想自己动手,但是有一个足够简单的 map 和 multimap 组合(我使用下面的 Guava multimap,但 Apache 也应该可以)来做您想做的事。我在下面有一个快速而肮脏的解决方案(跳过了构造函数,因为这取决于您要使用哪种底层地图/多重地图):
package edu.cap10.common.collect;
import java.util.Collection;
import java.util.Map;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.Multimap;
public class MIndexLookupMap<T> extends ForwardingMap<Object,T>
Map<Object,T> delegate;
Multimap<T,Object> reverse;
@Override protected Map<Object, T> delegate() return delegate;
@Override public void clear()
delegate.clear();
reverse.clear();
@Override public boolean containsValue(Object value) return reverse.containsKey(value);
@Override public T put(Object key, T value)
if (containsKey(key) && !get(key).equals(value)) reverse.remove(get(key), key);
reverse.put(value, key);
return delegate.put(key, value);
@Override public void putAll(Map<? extends Object, ? extends T> m)
for (Entry<? extends Object,? extends T> e : m.entrySet()) put(e.getKey(),e.getValue());
public T remove(Object key)
T result = delegate.remove(key);
reverse.remove(result, key);
return result;
public void removeValue(T value)
for (Object key : reverse.removeAll(value)) delegate.remove(key);
public Collection<T> values()
return reverse.keySet();
移除是 O(键的数量),但其他所有内容都与典型地图实现的顺序相同(一些额外的常量缩放,因为您还必须反向添加东西)。
我刚刚使用了Object
键(应该可以使用equals()
和hashCode()
的适当实现以及键区分)-但您也可以使用更具体的键类型。
【讨论】:
注意,这不会对每个键执行多个值,但可以直接使用 forward 作为多映射,并添加一个 get(Object...keys) 方法,该方法也执行一些集合交集操作。 此实现不支持通过entrySet()
、values()
和keySet()
进行修改。这些方法提供clear()
和iterator().remove()
,Entry 提供setValue(...)
。我可能还错过了一些。所有这些修改均未纳入索引。【参考方案12】:
让我们看看项目http://code.google.com/p/multiindexcontainer/wiki/MainPage 这是如何为 JavaBean getter 使用映射并在索引值上执行查找的通用方式。 我想这就是你要找的。让我们试一试。
【讨论】:
【参考方案13】:基本上基于多个哈希映射的解决方案是可能的,但在这种情况下,所有这些映射都必须手动保持最新。在这里可以找到一个非常简单的集成解决方案:http://insidecoffe.blogspot.de/2013/04/indexable-hashmap-implementation.html
【讨论】:
【参考方案14】:这是我实现这一点的方法,现在只有 put、remove 和 get 方法正在工作,您需要覆盖所需的方法。
例子:
MultiKeyMap<MultiKeyMap.Key,String> map = new MultiKeyMap<>();
MultiKeyMap.Key key1 = map.generatePrimaryKey("keyA","keyB","keyC");
MultiKeyMap.Key key2 = map.generatePrimaryKey("keyD","keyE","keyF");
map.put(key1,"This is value 1");
map.put(key2,"This is value 2");
Log.i("MultiKeyMapDebug",map.get("keyA"));
Log.i("MultiKeyMapDebug",map.get("keyB"));
Log.i("MultiKeyMapDebug",map.get("keyC"));
Log.i("MultiKeyMapDebug",""+map.get("keyD"));
Log.i("MultiKeyMapDebug",""+map.get("keyE"));
Log.i("MultiKeyMapDebug",""+map.get("keyF"));
输出:
MultiKeyMapDebug: This is value 1
MultiKeyMapDebug: This is value 1
MultiKeyMapDebug: This is value 1
MultiKeyMapDebug: This is value 2
MultiKeyMapDebug: This is value 2
MultiKeyMapDebug: This is value 2
MultiKeyMap.java:
/**
* Created by hsn on 11/04/17.
*/
public class MultiKeyMap<K extends MultiKeyMap.Key, V> extends HashMap<MultiKeyMap.Key, V>
private Map<String, MultiKeyMap.Key> keyMap = new HashMap<>();
@Override
public V get(Object key)
return super.get(keyMap.get(key));
@Override
public V put(MultiKeyMap.Key key, V value)
List<String> keyArray = (List<String>) key;
for (String keyS : keyArray)
keyMap.put(keyS, key);
return super.put(key, value);
@Override
public V remove(Object key)
return super.remove(keyMap.get(key));
public Key generatePrimaryKey(String... keys)
Key singleKey = new Key();
for (String key : keys)
singleKey.add(key);
return singleKey;
public class Key extends ArrayList<String>
【讨论】:
【参考方案15】:我多年前找到了这段代码(不记得在哪里)。只需使用它从不同的值构建您的密钥。
import java.util.Arrays;
public final class MultiKey
private static final int PRIME = 31;
private final Object[] values;
private final int hashCode;
/**
* Creates a new instance from the provided values. It is assumed that the
* values provided are good map keys themselves -- immutable, with proper
* implementations of equals() and hashCode().
*
* @param values
*/
public MultiKey(Object... values)
this.values = values;
hashCode = PRIME * Arrays.hashCode(this.values);
@Override
public int hashCode()
return hashCode;
@Override
public boolean equals(Object obj)
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final MultiKey other = (MultiKey) obj;
return Arrays.equals(values, other.values);
@Override
public String toString()
StringBuilder builder = new StringBuilder("MultiKey[");
boolean first = true;
for (Object o : values)
if (!first)
builder.append(", ");
builder.append(o);
first = false;
builder.append("]");
return builder.toString();
【讨论】:
以上是关于Java 集合的多个索引 - 最基本的解决方案?的主要内容,如果未能解决你的问题,请参考以下文章