Подтвердить что ты не робот

Несколько индексов для Java Collection - самое основное решение?

Я ищу наиболее базовое решение для создания нескольких индексов в коллекции Java.

Необходимая функциональность:

  • При удалении значения все записи индекса, связанные с этим значением, должны быть удалены.
  • Индексный поиск должен быть быстрее, чем линейный поиск (по крайней мере, так же быстро, как TreeMap).

Боковые условия:

  • Никаких зависимостей от больших (например, Lucene) библиотек. Нет необычных или не очень хорошо протестированных библиотек. Нет базы данных.
  • Библиотека, подобная коллекциям Apache Commons и т.д., будет в порядке.
  • Еще лучше, если он работает только с JavaSE (6.0).
  • Изменить: Нет самореализованного решения (спасибо за ответы, предлагающие это - хорошо, чтобы они были здесь для полноты, но у меня уже есть решение, очень похожее на Jay's) Всякий раз, когда несколько человек узнают, что они реализовали одно и то же, это должно быть частью некоторой общей библиотеки.

Конечно, я мог бы написать класс, который управляет несколькими Картами (это не сложно, но похоже, что вы изобретаете колесо). Поэтому я хотел бы знать, если это можно сделать без - при этом все еще простое использование, похожее на использование одной индексированной java.util.Map.

Спасибо, Крис

Update

Это выглядит так, как будто мы ничего не нашли. Мне нравятся все ваши ответы - саморазвивающиеся версии, ссылки на базы данных, подобные библиотекам.

Вот то, что я действительно хочу: Чтобы иметь функциональность в (а) коллекциях Apache Commons или (b) в Google Collections/Guava. Или, может быть, очень хорошая альтернатива.

Другие пропустили эту функцию в этих библиотеках? Они предоставляют всевозможные вещи, такие как MultiMaps, MulitKeyMaps, BidiMaps,... Я чувствую, что он будет хорошо вписываться в эти библиотеки - его можно было бы назвать MultiIndexMap. Как вы думаете?

4b9b3361

Ответ 1

Каждый индекс будет в основном отдельным Map. Вы можете (и, вероятно, должны) абстрагировать это за классом, который управляет поисками, индексацией, обновлениями и удалениями для вас. Было бы непросто сделать это в целом. Но нет, для этого нет стандартного класса, но его легко можно построить из классов Java Collections.

Ответ 3

Моя первая мысль заключалась в том, чтобы создать класс для индексируемой вещи, а затем создать несколько HashMap для хранения индексов, с тем же объектом, добавленным к каждому из HashMaps. Для добавления вы просто добавляете один и тот же объект к каждому HashMap. Для удаления требуется поиск в каждом HashMap для ссылки на целевой объект. Если удаление должно быть быстрым, вы можете создать два HashMaps для каждого индекса: один для индекса для значения, а другой для значения для индекса. Конечно, я бы обернул все, что вы делаете в классе с четко определенным интерфейсом.

Кажется, это не сложно. Если вы знаете числа и типы индексов и класс виджета спереди, это будет довольно легко, например:

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

У вас очень много суровых требований, которые, как представляется, очень специфичны для ваших нужд. Большинство вещей, о которых вы говорите, не являются жизнеспособными, потому что многие люди имеют одинаковые точные потребности, которые в основном определяют базовый движок базы данных. Вот почему они "большие" библиотеки. Вы говорите "нет базы данных", но в своей основе каждая система индексирования является "базой данных" терминов и документов. Я бы сказал, что коллекция - это "база данных". Я бы сказал, Space4J.

Я бы сказал, что если вы не найдете то, что ищете, начните проект на GitHub и займитесь его кодированием и делитесь результатами.

Ответ 5

Коллекции Google LinkedListMultimap

О вашем первом требовании

  • При удалении значения все записи индекса, связанные с этим значением, должны быть удалены.

Я думаю, что не поддерживается ни библиотека, ни помощник.

Вот как я это сделал, используя 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

Чтобы получить свое первое требование, Помощник может хорошо поработать

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 (Эффективная Java)
  • Хорошие функции как ImmutableList, ImmutableMap и т.д.

Ответ 6

Вам нужно проверить Бун.:)

http://rick-hightower.blogspot.com/2013/11/what-if-java-collections-and-java.html

Вы можете добавить n число индексов поиска и индексов поиска. Он также позволяет эффективно запрашивать примитивные свойства.

Вот пример из вики (я автор).

  repoBuilder.primaryKey("ssn")
          .searchIndex("firstName").searchIndex("lastName")
          .searchIndex("salary").searchIndex("empNum", true)
          .usePropertyForAccess(true);

Вы можете переопределить это, предоставив true флаг в качестве второго аргумента searchIndex.

Обратите внимание, что empNum - уникальный уникальный для поиска индекс.

Что делать, если было легко запросить сложный набор объектов Java во время выполнения? Что, если бы был API, который поддерживал ваши индексы объектов (на самом деле просто TreeMaps и HashMaps) в синхронизации.? Хорошо, тогда у вас будет репозиторий данных Boon. В этой статье показано, как использовать утилиты репозитория данных Boon для запроса объектов Java. Это часть первая. Может быть много, много частей.:) Boo data repo делает запросы, основанные на индексах, намного проще. Почему репозиторий данных Boon

Boo data repo позволяет обрабатывать коллекции Java больше как база данных, по крайней мере, когда дело доходит до запросов к коллекциям. Boo data repo не является базой данных в памяти и не может заменить организацию ваших объектов в структуры данных, оптимизированные для вашего приложения. Если вы хотите потратить свое время на предоставление клиенту ценности и построение своих объектов и классов и использование API Collections для ваших структур данных, то DataRepo предназначен для вас. Это не исключает выхода из книг Кнута и оптимизации оптимизированной структуры данных. Это просто помогает сохранить мирские вещи, поэтому вы можете потратить свое время на то, чтобы сделать все возможное. Рожденный из необходимости

Этот проект вышел из-за необходимости. Я работал над проектом, который планировал хранить большую коллекцию доменных объектов в памяти для скорости, и кто-то задал все важные вопросы, которые я забыл. Как мы будем запрашивать эти данные. Мой ответ заключался в том, что мы будем использовать API Collections и Streaming API. Тогда я попытался это сделать... Хммм... Я также устал использовать API потока JDK 8 для большого набора данных, и он был медленным. (Репозиторий данных Boon работает с JDK7 и JDK8). Это был линейный поиск/фильтр. Это по дизайну, но для того, что я делал, это не сработало. Мне нужны индексы для поддержки произвольных запросов. Репо данных Boon расширяет API потоковой передачи.

Репозиторий данных Boon не пытается заменить API потока JDK 8, и на самом деле он хорошо работает с ним. Boo data repo позволяет создавать индексированные коллекции. Индексы могут быть любыми (он подключается). В настоящий момент индексы репо-данных 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)

Примерно на 20 000% быстрее использовать Boon DataRepo! А сила 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)));

Или, если вы хотите использовать API потока JDK 8, это работает с ним не против него:

  int sum = repo.query(eq("lastName", "Smith")).stream().filter(emp -> emp.getSalary()>50_000)
      .mapToInt(b -> b.getSalary())
      .sum();

Выше было бы намного быстрее, если бы число сотрудников было довольно большим. Это сузило бы сотрудников, чье имя началось со Смита и имело зарплату выше 50 000. Скажем, у вас было 100 000 сотрудников и только 50 названных Смитом, поэтому теперь вы быстро сокращаетесь до 50, используя индекс, который эффективно вытягивает 50 сотрудников из 100 000, тогда мы фильтруем только 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 вы можете запустить фильтр в parralel).

Посмотрите, как математика держится:

3,528,839 / 16 threads vs. 219

201,802 vs. 219 (nano-seconds).

Индексы выигрывают, но это был финал фотографии. НЕ!:)

Это было всего на 9500% быстрее, а не на 40 000% быстрее. Так близко.....

Я добавил еще несколько функций. Они интенсивно используют индексы.:)

repo.updateByFilter(значения (значение ( "firstName", "Di" )),               и (eq ( "firstName", "Diana" ),                       eq ( "lastName", "Smith" ),                       eq ( "ssn", "21785999" )));

Выше было бы эквивалентно

ОБНОВЛЕНИЕ Сотрудник e  SET e.firstName = 'Di' ГДЕ 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() vs. 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. Наличие карты строк/базовых типов, по-видимому, является способом достижения этого. Обратите внимание, что преобразование объекта в карту идет глубоко, как в:

  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(...) для указания подклассов. Это позволяет вам без видимых полей запроса формировать подклассы и классы в той же коллекции репозитория или поиска.

Ответ 7

Я написал интерфейс таблицы, который включает такие методы, как

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

Ответ 8

Ваша основная цель состоит в том, что вы удалите объект из всех индексов, когда вы удалите его из одного.

Самый простой способ - добавить еще один слой косвенности: вы сохраняете свой фактический объект в Map<Long,Value> и используете двунаправленную карту (которую вы найдете в Jakarta Commons и, возможно, Google Code) для своих индексов как Map<Key,Long>. Когда вы удаляете запись из определенного индекса, вы берете значение 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()
   }
}

Ответ 9

Используйте Prefuse Таблицы. Они поддерживают столько индексов, сколько хотите, быстрые (индексы - это TreeMaps) и имеют хорошие параметры фильтрации (логические фильтры - без проблем!). Никакой базы данных не требуется, протестировано с большими наборами данных во многих приложениях визуализации информации.

В своей исходной форме они не так удобны, как стандартные контейнеры (вам нужно иметь дело с строками и столбцами), но вы можете, конечно, написать небольшую обертку вокруг этого. Кроме того, они хорошо подключаются к компонентам пользовательского интерфейса, таким как Swing JTables.

Ответ 10

Я не уверен, что я понимаю этот вопрос, но я думаю, что вы запрашиваете несколько способов сопоставить различные уникальные ключи со значениями и соответствующую очистку, когда значение уходит.

Я вижу, что вы не хотите откатывать свои собственные, но там достаточно простой состав карты и multimap (я использовал мультимаплю Guava ниже, но 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() и разграничением ключей), но вы также можете иметь более конкретный тип ключа.

Ответ 11

позволяет посмотреть проект http://code.google.com/p/multiindexcontainer/wiki/MainPage Это обобщенный способ использования карт для гейтеров JavaBean и выполнения поиска по индексированным значениям. Я думаю, что это то, что вы ищете. Попробуем попробовать.

Ответ 12

В принципе, решение, основанное на нескольких хэш-картах, было бы возможно, но в этом случае все они должны быть обновлены вручную. Очень простое интегрированное решение можно найти здесь: http://insidecoffe.blogspot.de/2013/04/indexable-hashmap-implementation.html

Ответ 13

Вот как я достигаю этого, прямо сейчас только поместить, удалить и получить методы работают для отдыха, вам нужно переопределить нужные методы.

Пример:

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> {

    }

}