【问题标题】:Multiple indexes for a Java Collection - most basic solution?Java 集合的多个索引 - 最基本的解决方案?
【发布时间】: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?” stackoverflow.com/questions/822322/…。那里也没有明确的解决方案 - 看起来有些答案是使用带有复合键的地图,但那里的 OP 表示他希望能够通过单值键进行查找。尽管如此,在不太可能的情况下将问题联系起来似乎是一个好主意,稍后会出现可行的答案。此外,如果此处的 OP 决定自定义解决方案,则此相关问题提供了一些有关潜在实现的额外想法。
  • 删除某个值时,必须删除与该值关联的所有索引条目。查看! Boon Repo 就是这样做的。索引查找必须比线性搜索快(至少与 TreeMap 一样快)。 CHECK Boon Repo 就是这样做的!附带条件:不依赖于大型(如 Lucene)库。没有不常见或未经过良好测试的库。没有数据库。恩惠不依赖任何东西!查看! Boon Repo 做到了这一切。还有更多,它又小又紧。 :) richardhightower.github.io/site/Boon/Welcome.html

标签: java collections indexing


【解决方案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&lt;Long,Value&gt; 中,并使用双向映射(您可以在 Jakarta Commons 和可能的 Google 代码中找到)作为 @ 987654322@。当您从特定索引中删除条目时,您将从该索引中获取 Long 值,并使用它从主映射和其他索引中删除相应的条目。

        BIDIMap 的一种替代方法是将“索引”映射定义为Map&lt;Key,WeakReference&lt;Long&gt;&gt;;但是,这将需要您实现 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();
                      }
                  
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2018-09-13
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多