web-dev-qa-db-fra.com

Le moyen le plus rapide de vérifier si une liste <String> contient une chaîne unique

En gros, j'ai environ 1 000 000 chaînes, pour chaque demande, je dois vérifier si une chaîne appartient ou non à la liste.

La performance m'inquiète, alors quelle est la meilleure méthode? ArrayList? Hacher?

62
Ben

Votre meilleur choix est d'utiliser un HashSet et de vérifier si une chaîne existe dans l'ensemble via la méthode contains(). Les HashSets sont conçus pour un accès rapide via les méthodes Object hashCode() et equals(). La Javadoc pour HashSet indique:

Cette classe offre des performances constantes dans le temps pour les opérations de base (ajout, suppression, contenu et taille),

HashSet stocke les objets dans des compartiments de hachage , ce qui signifie que la valeur renvoyée par la méthode hashCode déterminera dans quel compartiment un objet est stocké. De cette manière, le La quantité d'égalité vérifie que HashSet doit effectuer via la méthode equals() est réduite aux seuls objets du même compartiment de hachage.

Pour utiliser efficacement HashSets et HashMaps, vous devez vous conformer aux contrats equals et hashCode décrits dans le javadoc . Dans le cas de Java.lang.String, ces méthodes ont déjà été implémentées.

96
krock

En général, un hachage vous donnera de meilleures performances, puisqu'il n'a pas besoin d'examiner chaque élément et de comparer, comme le fait un tableau, mais compare généralement au plus quelques éléments, où les codes de hachage sont égaux.

Toutefois, pour les chaînes 1M, les performances de hashSet peuvent ne pas être optimales. De nombreuses erreurs de cache ralentiront la recherche dans l'ensemble. Si toutes les chaînes ont la même probabilité, cela est inévitable. Cependant, si certaines chaînes sont plus souvent demandées que d'autres, vous pouvez alors placer les chaînes communes dans un petit hashSet, et le vérifier avant de rechercher le plus grand jeu. Le petit hachage doit être dimensionné pour tenir dans le cache (quelques centaines de K au plus, par exemple). Les impacts sur le petit hashset seront alors très rapides, tandis que les hits sur le plus gros hashset se dérouleront à une vitesse limitée par la bande passante mémoire.

11
mdma

Avant d'aller plus loin, veuillez considérer ceci: Pourquoi êtes-vous inquiet pour la performance? À quelle fréquence ce chèque est-il appelé?

En ce qui concerne les solutions possibles:

  • Si la liste est déjà triée, vous pouvez utiliser Java.util.Collections.binarySearch qui offre les mêmes caractéristiques de performance qu'un Java.util.TreeSet.

  • Sinon, vous pouvez utiliser un Java.util.HashSet that comme caractéristique de performance de O (1). Notez que le calcul du code de hachage pour une chaîne qui n'en a pas encore calculée est une opération O(m) avec m = string.length(). N'oubliez pas non plus que les tables de hachage ne fonctionnent correctement que jusqu'à ce qu'elles atteignent un facteur de charge donné, c'est-à-dire qu'elles utiliseront plus de mémoire que les listes simples. Le facteur de charge par défaut utilisé par HashSet est 0,75, ce qui signifie qu'en interne un objet HashSet pour 1e6 utilisera un tableau avec des entrées 1.3e6.

  • Si le hachage ne fonctionne pas pour vous (p. Ex. Parce qu'il y a beaucoup de collisions de hachage, parce que la mémoire est serrée ou qu'il y a beaucoup d'insertions), alors envisagez d'utiliser un Trie . La recherche dans un Trie a une complexité dans le pire des cas de O(m) où m = string.length(). Un Trie a également quelques avantages supplémentaires qui pourraient vous être utiles: par exemple, il peut vous donner le ajustement le plus proche pour une chaîne de recherche. Mais gardez à l’esprit que le meilleur code n’est pas le code. Par conséquent, implémentez votre propre implémentation de Trie uniquement si les avantages sont supérieurs aux coûts.

  • Pensez à utiliser une base de données si vous souhaitez des requêtes plus complexes, par exemple. correspond à une sous-chaîne ou une expression régulière.

8
nd.

Je voudrais utiliser un Set, dans la plupart des cas HashSet est bien. 

5
unbeli

Après avoir exécuté l'exercice, voici mes résultats.

private static final int TEST_CYCLES = 4000;
private static final long Rand_ELEMENT_COUNT = 1000000l;
private static final int Rand_STR_LEN = 20;
//Mean time
/*
Array list:18.55425
Array list not contains:17.113
Hash set:5.0E-4
Hash set not contains:7.5E-4
*/

Je crois que les chiffres parlent d'eux-mêmes. Le temps de recherche de l'ensemble de hachage est beaucoup, beaucoup plus rapide.

2
awiebe

Avec un si grand nombre de cordes, je pense immédiatement à un Trie . Cela fonctionne mieux avec un ensemble de caractères plus limité (tels que des lettres) et/ou lorsque le début de plusieurs chaînes se chevauchent.

2
ILMTitan

Si vous avez une telle quantité de chaînes, la meilleure opportunité est d'utiliser une base de données. Recherchez MySQL.

1
oopbase

Ce n'est peut-être pas nécessaire pour votre cas, mais je pense qu'il est utile de savoir qu'il existe des algorithmes probabilistes peu encombrants. Par exemple Filtre Bloom .

1
simplylizz

Parfois, vous voulez vérifier si un objet est dans la liste/set et en même temps vous voulez que la liste/set soit ordonnée. Si vous souhaitez également extraire facilement des objets sans utiliser une énumération ou un itérateur, vous pouvez envisager d'utiliser à la fois un ArrayList<String> et un HashMap<String, Integer>. La liste est soutenue par la carte.

Exemple d'un travail que j'ai fait récemment:

public class NodeKey<K> implements Serializable, Cloneable{
private static final long serialVersionUID = -634779076519943311L;

private NodeKey<K> parent;
private List<K> children = new ArrayList<K>();
private Map<K, Integer> childrenToListMap = new HashMap<K, Integer>();

public NodeKey() {}

public NodeKey(Collection<? extends K> c){
    List<K> childHierarchy = new ArrayList<K>(c);
    K childLevel0 = childHierarchy.remove(0);

    if(!childrenToListMap.containsKey(childLevel0)){
        children.add(childLevel0);
        childrenToListMap.put(childLevel0, children.size()-1);
    }

    ...

Dans ce cas, le paramètre K serait un String pour vous. La carte (childrenToMapList) stocke Strings insérée dans la liste (children) en tant que clé et les valeurs de la carte correspondent à la position d'index dans la liste.

La liste et la carte ont pour but de pouvoir récupérer les valeurs indexées de la liste, sans avoir à effectuer une itération sur un HashSet<String>.

0
ghostNet

Non seulement pour String, vous pouvez utiliser Set dans tous les cas où vous avez besoin d'éléments uniques.

Si le type d'éléments est primitif ou wrapper, vous ne vous en souciez peut-être pas. Mais s'il s'agit d'une classe, vous devez remplacer deux méthodes:

  1. hashCode ()
  2. équivaut à()
0
Truong Ha