web-dev-qa-db-fra.com

Plusieurs index pour une collection Java - la solution la plus basique?

Je recherche la solution la plus simple pour créer plusieurs index sur une collection Java.

Fonctionnalité requise:

  • Lorsqu'une valeur est supprimée, toutes les entrées d'index associées à cette valeur doivent être supprimées.
  • La recherche d'index doit être plus rapide que la recherche linéaire (au moins aussi rapide qu'une TreeMap).

Conditions latérales:

  • Aucune dépendance sur les grandes bibliothèques (comme Lucene). Pas de bibliothèques peu communes ou pas bien testées. Pas de base de données.
  • Une bibliothèque comme Apache Commons Collections, etc.
  • Mieux encore, si cela fonctionne avec JavaSE (6.0) seul.
  • Edit: Pas de solution auto-implémentée (merci pour les réponses suggérant ceci - il est bon de les avoir ici pour qu'elles soient complètes, mais j'ai déjà une solution très similaire à celle de Jay) Chaque fois que plusieurs personnes découvrent qu'elles ont implémenté la même chose, cela devrait faire partie d'une bibliothèque commune.

Bien sûr, je pourrais écrire un cours qui gère plusieurs cartes moi-même (ce n’est pas difficile, mais c’est comme réinventer la roue). J'aimerais donc savoir si cela peut être fait sans, tout en obtenant un usage simple, similaire à l'utilisation d'un seul fichier Java.util.Map indexé. 

Merci chris

Mettre à jour

On dirait bien que nous n'avons rien trouvé. J'aime toutes vos réponses - les versions développées par vous-même, les liens vers des bibliothèques de type base de données.

Voici ce que je souhaite vraiment: disposer des fonctionnalités de (a) Apache Commons Collections ou (b) de Google Collections/Guava. Ou peut-être une très bonne alternative.

Est-ce que d'autres personnes manquent cette fonctionnalité dans ces bibliothèques? Ils fournissent toutes sortes de choses telles que MultiMaps, MulitKeyMaps, BidiMaps, ... Je pense que cela rentrerait bien dans ces bibliothèques - ça pourrait s'appeler MultiIndexMap. Qu'est-ce que tu penses?

46
Chris Lercher

Chaque index sera fondamentalement une Map séparée. Vous pouvez (et devriez probablement) résumer ceci derrière une classe qui gère les recherches, l’indexation, les mises à jour et les suppressions pour vous. Ce ne serait pas difficile de faire cela de façon assez générique. Mais non, il n’existe pas de classe standard dans cette classe, bien qu’elle puisse être facilement construite à partir des classes Java Collections.

19
cletus

Jetez un coup d’œil à CQEngine (moteur de recherche de collection) , c’est un choix parfait pour ce type de besoin, étant fondé sur une IndexedCollection.

Voir également la question connexe Comment interrogez-vous les collections d'objets en Java (critères/type SQL)? pour plus de fond.

12
npgall

Ma première pensée serait de créer une classe pour la chose indexée, puis de créer plusieurs HashMap pour contenir les index, avec le même objet ajouté à chaque HashMaps. Pour un ajout, vous devez simplement ajouter le même objet à chaque HashMap. Une suppression nécessiterait de rechercher dans chaque HashMap la référence à l'objet de destination. Si les suppressions doivent être rapides, vous pouvez créer deux HashMaps pour chaque index: un pour index-valeur et l'autre pour valeur-index. Bien sûr, je mettrais tout ce que vous faites dans une classe avec une interface clairement définie.

Cela ne semble pas être difficile. Si vous connaissez les nombres et les types d'index et la classe du widget à l'avance, ce serait assez simple, par exemple:

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

  }
}

Si vous voulez le rendre générique et accepter n'importe quel objet, avoir un nombre variable et des types d'index, etc., c'est un peu plus compliqué, mais pas beaucoup.

7
Jay

Vous avez beaucoup d'exigences très contraignantes qui semblent être très spécifiques à vos besoins. La plupart des choses que vous dites non viables sont dues au fait que beaucoup de gens ont exactement les mêmes besoins, ce qui définit fondamentalement un moteur de base de données. C'est pourquoi ce sont de "grandes" bibliothèques. Vous dites "pas de base de données" mais chaque système d'indexation est essentiellement une "base de données" de termes et de documents. Je dirais qu'une collection est une "base de données". Je dirais jetez un oeil à Space4J

Je dirais que si vous ne trouvez pas ce que vous recherchez, démarrez un projet sur GitHub et collez-le vous-même et partagez les résultats.

5
user177800

Google CollectionsLinkedListMultimap

A propos de votre première exigence

  • Lorsqu'une valeur est supprimée, toutes les entrées d'index associées à cette valeur doivent être supprimées.

Je pense qu'il n'y a ni une bibliothèque ni une aide qui la supporte.

Voici comment j'ai fait en utilisant 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

Pour obtenir votre première exigence, un assistant peut jouer un bon travail

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 collections est

  • poids léger
  • Pris en charge par Joshua Block (Effective Java)
  • Fonctionnalités intéressantes comme ImmutableList, ImmutableMap, etc.
4
Arthur Ronald

Vous devez vérifier Boon. :)

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

Vous pouvez ajouter un nombre n d'index de recherche et d'index de recherche. Il vous permet également d'interroger efficacement les propriétés primitives. 

Voici un exemple tiré du wiki (je suis l'auteur).

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

Vous pouvez remplacer cela en fournissant un indicateur true comme second argument de searchIndex.

Notez que empNum est un index unique interrogeable.

Et s'il était facile d'interroger un ensemble complexe d'objets Java au moment de l'exécution? Et si une API maintenait la synchronisation de vos index d’objets (en fait, il ne s’agissait que de TreeMaps et de HashMaps)? Eh bien, vous auriez le rapport de données de Boon. Cet article explique comment utiliser les utilitaires de dépôt de données de Boon pour interroger des objets Java. Ceci est la première partie. Il peut y avoir beaucoup, beaucoup de parties. :) Le référentiel de données de Boon facilite beaucoup les requêtes d'index sur les collections . Pourquoi le référentiel de données de Boon

Le référentiel de données de Boon vous permet de traiter les collections Java plus comme une base de données, du moins lorsqu'il s'agit d'interroger les collections. Le référentiel de données de Boon n'est pas une base de données en mémoire et ne peut pas remplacer l'organisation de vos objets dans des structures de données optimisées pour votre application. Si vous souhaitez passer votre temps à fournir de la valeur client, à construire vos objets et vos classes et à utiliser l'API de collections pour vos structures de données, DataRepo est fait pour vous. Cela n'empêche pas de sortir les livres de Knuth et de proposer une structure de données optimisée. Cela aide simplement à garder les choses banales faciles afin que vous puissiez passer votre temps à rendre les choses difficiles possibles . Nées du besoin

Ce projet est né d'un besoin. Je travaillais sur un projet qui prévoyait de stocker rapidement une grande collection d'objets de domaine en mémoire. Quelqu'un a posé une question très complexe que j'ai oubliée. Comment allons-nous interroger ces données. Ma réponse était que nous allons utiliser l'API Collections et l'API Streaming. Ensuite, j'ai essayé de faire cela ... Hmmm ... Je me suis aussi fatigué d'utiliser l'API de flux JDK 8 sur un grand ensemble de données, et c'était lent. (Le dépôt de données de Boon fonctionne avec JDK7 et JDK8). C'était une recherche/filtre linéaire. C'est par conception, mais pour ce que je faisais, cela n'a pas fonctionné. Il me fallait des index pour prendre en charge des requêtes arbitraires .. Le référentiel de données de Boon augmente l'API de diffusion en continu.

Le référentiel de données de Boon ne tente pas de remplacer l'API de flux JDK 8 et, en fait, il fonctionne bien. Le référentiel de données de Boon vous permet de créer des collections indexées. Les index peuvent être n'importe quoi (il est enfichable) . À l'heure actuelle, les index de référentiels de données de Boon sont basés sur ConcurrentHashMap et ConcurrentSkipListMap . De par leur conception, le référentiel de données de Boon fonctionne avec des bibliothèques de collections standard. Il n'est pas prévu de créer un ensemble de collections personnalisées. On devrait pouvoir brancher Goyave, Arbres Concurrents ou Trove si on le souhaite .. Il fournit une API simplifiée pour le faire. Il permet la recherche linéaire pour un sentiment d'achèvement, mais je recommande de l'utiliser principalement pour utiliser des index, puis d'utiliser l'API de transmission en continu pour le reste (pour le type sécurité et la vitesse).

aperçu avant l'étape par étape

Disons que vous avez une méthode qui crée 200 000 objets employés comme ceci:

 List<Employee> employees = TestHelper.createMetricTonOfEmployees(200_000);

Nous avons donc maintenant 200 000 employés. Fouillons-les ...

Commencez par insérer les employés dans une requête interrogeable:

   employees = query(employees);

Maintenant chercher:

  List<Employee> results = query(employees, eq("firstName", firstName));

Alors, quelle est la principale différence entre ce qui précède et l’API de flux?

  employees.stream().filter(emp -> emp.getFirstName().equals(firstName)

Un facteur environ 20 000% plus rapide pour utiliser le DataRepo de Boon! Ah la puissance de HashMaps et TreeMaps. :) Il existe une API qui ressemble à vos collections intégrées. Il existe également une API qui ressemble davantage à un objet DAO ou à un objet Repo.

Une requête simple avec l'objet Repo/DAO ressemble à ceci:

  List<Employee> employees = repo.query(eq("firstName", "Diana"));

Une requête plus impliquée ressemblerait à ceci:

  List<Employee> employees = repo.query(
      and(eq("firstName", "Diana"), eq("lastName", "Smith"), eq("ssn", "21785999")));

Ou ca:

  List<Employee> employees = repo.query(
      and(startsWith("firstName", "Bob"), eq("lastName", "Smith"), lte("salary", 200_000),
              gte("salary", 190_000)));

Ou même ceci:

  List<Employee> employees = repo.query(
      and(startsWith("firstName", "Bob"), eq("lastName", "Smith"), between("salary", 190_000, 200_000)));

Ou si vous souhaitez utiliser l'API de flux JDK 8, cela ne fonctionne pas avec:

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

Ce qui précède serait beaucoup plus rapide si le nombre d'employés était assez important. Cela limiterait les employés dont le nom commençait par Smith et touchait un salaire supérieur à 50 000. Supposons que vous aviez 100 000 employés et seulement 50 nommés Smith. Maintenant, vous réduisez rapidement à 50 en utilisant l’indice qui tire effectivement 50 employés de 100 000, puis nous ne filtrons que 50 personnes au lieu de 100 000.

Voici un point de repère exécuté à partir du dépôt de données d'une recherche linéaire par rapport à une recherche indexée en nanosecondes:

Name index  Time 218 
Name linear  Time 3709120 
Name index  Time 213 
Name linear  Time 3606171 
Name index  Time 219 
Name linear  Time 3528839

Quelqu'un m'a récemment dit: "Mais avec l'API de streaming, vous pouvez utiliser le filtre en parallèle).

Voyons comment le calcul tient le coup:

3,528,839 / 16 threads vs. 219

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

Les index gagnent, mais c'était une photo finish. NE PAS! :) 

Il était seulement 9 500% plus rapide au lieu de 40 000% plus rapide. Si proche .....J'ai ajouté quelques fonctionnalités supplémentaires. Ils font un usage intensif des index. :).

Repo.updateByFilter (valeurs (valeur ("prénom", "Di")), et (eq ("prénom", "Diana"), eq ("prénom", "Smith"), eq ("ssn", "21785999")));

ce qui précède équivaudrait à

UPDATE Employé e SET e.firstName = 'Di' WHERE e.firstName = 'Diana' et e.lastName = 'Smith' et e.ssn = '21785999'

Cela vous permet de définir plusieurs champs à la fois sur plusieurs enregistrements, donc si vous effectuez une mise à jour en bloc.

Il existe des méthodes surchargées pour tous les types de base, donc si vous avez une valeur à mettre à jour pour chaque élément renvoyé par un filtre:.

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"));

  List <Map<String, Object>> list = repo.query(
          selects(select("department", "name")),
          eq("lastName", "Hightower"));

  assertEquals("engineering", list.get(0).get("department.name"));
.

  List <Map<String, Object>> list = repo.query(
          selects(selectPropPath("department", "name")),
          eq("lastName", "Hightower"));
.

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

  List<Map<String, Object>> employees = repo.queryAsMaps(eq("firstName", "Diana"));

  System.out.println(employees.get(0).get("department"));

{class=Department, name=engineering}

  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());

  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");

  List<Map<String, Object>> list = repo.query(
          selects(select("tags", "metas", "metas2", "name2")),
          eqNested("2tag1", "tags", "metas", "metas2", "name2"));
.

  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());

The data repo has a similar feature in its DataRepoBuilder.build(...) method for specifying subclasses. This allows you to seemless query fields form subclasses and classes in the same repo or searchable collection.

4
RickHigh

J'ai écrit une interface de table qui inclut des méthodes telles que 

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()

Nous aimerions l'inclure dans une future version de Guava, mais je ne sais pas quand cela se produira . http://code.google.com/p/guava-libraries/issues/detail?id = 173

2
Jared Levy

Votre objectif principal semble être de supprimer l'objet de tous les index lorsque vous le supprimez.

La méthode la plus simple consiste à ajouter une autre couche d'indirection: vous stockez votre objet dans un Map<Long,Value> et utilisez une carte bidirectionnelle (que vous trouverez dans Jakarta Commons et probablement Google Code) pour vos index sous la forme Map<Key,Long>. Lorsque vous supprimez une entrée d'un index particulier, vous prenez la valeur Long de cet index et vous l'utilisez pour supprimer les entrées correspondantes de la carte principale et des autres index.

Une alternative à BIDIMap consiste à définir vos cartes "index" comme suit: Map<Key,WeakReference<Long>>; toutefois, cela nécessitera de mettre en œuvre une ReferenceQueue pour le nettoyage.


Une autre alternative consiste à créer un objet clé pouvant prendre un Tuple arbitraire, définir sa méthode equals() pour apparier n'importe quel élément du Tuple et l'utiliser avec une TreeMap. Vous ne pouvez pas utiliser HashMap, car vous ne pourrez pas calculer un hashcode basé sur un seul élément du tuple.

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()
   }
}
2
Anon

Utilisez PrefuseTables . Ils supportent autant d'indices que vous le souhaitez, sont rapides (les index sont des TreeMaps) et ont des options de filtrage Nice (filtres booléens? Aucun problème!). Aucune base de données requise, testée avec de grands ensembles de données dans de nombreuses applications de visualisation d'informations. 

Sous leur forme brute, ils ne sont pas aussi pratiques que les conteneurs standard (vous devez gérer des lignes et des colonnes), mais vous pouvez sûrement écrire une petite enveloppe autour de cela. De plus, ils se connectent parfaitement aux composants de l'interface utilisateur tels que les JTables de Swing.

1
tucuxi

Si vous souhaitez plusieurs index sur vos données, vous pouvez créer et gérer plusieurs mappages de hachage ou utiliser une bibliothèque telle que Data Store:

https://github.com/jparams/data-store

Exemple:

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");

Avec Data Store, vous pouvez créer des index insensibles à la casse et toutes sortes de choses intéressantes. Cela vaut la peine de vérifier. 

1
Syed Belal Jafri

Je ne suis pas sûr de comprendre la question, mais je pense que ce que vous demandez, ce sont plusieurs façons de mapper des clés uniques et différentes en valeurs et en un nettoyage approprié lorsqu'une valeur disparaît.

Je vois que vous ne voulez pas lancer le vôtre, mais il existe une composition assez simple de carte et de multi-carte (j'ai utilisé la multi-carte Guava ci-dessous, mais celle d'Apache devrait également fonctionner) pour faire ce que vous voulez. J'ai une solution rapide et sale ci-dessous (ignoré les constructeurs, car cela dépend du type de carte/carte sous-jacente que vous souhaitez utiliser):

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();
    }   

}

la suppression est O (nombre de clés), mais tout le reste est dans le même ordre qu'une implémentation de carte typique (une mise à l'échelle constante supplémentaire, car vous devez également ajouter des éléments à l'inverse).

Je viens d'utiliser des clés Object (cela devrait convenir avec les implémentations appropriées de equals() et hashCode() et distinction des clés) - mais vous pouvez également avoir un type de clé plus spécifique. 

0
Carl

En principe, une solution basée sur plusieurs cartes de hachage serait possible, mais dans ce cas, elles doivent toutes être mises à jour manuellement. Une solution intégrée très simple peut être trouvée ici: http://insidecoffe.blogspot.de/2013/04/indexable-hashmap-implementation.html

0
Jan Wiemer

regardons le projet http://code.google.com/p/multiindexcontainer/wiki/MainPage C’est une façon généralisée d’utiliser des cartes pour les getters JavaBean et d’effectuer des recherches sur les valeurs indexées ..__ pense que c'est ce que vous recherchez. Essayons.

0
Fekete Kamosh

Voici comment je parviens à cela. À l’heure actuelle, seuls les méthodes de travail, de suppression et d’obtention fonctionnent pour vous reposer, vous devez remplacer les méthodes souhaitées.

Exemple:

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"));

Sortie:

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

    }

}
0
H4SN