web-dev-qa-db-fra.com

HashMap en Java, 100 millions d'entrées

Je veux stocker 100 millions de termes et leurs fréquences (dans une base de données texte) dans un HashMap <String, Double>. Il me donne une erreur "Mémoire insuffisante". J'ai essayé d'augmenter l'espace de tas à -Xmx15000M. Cependant, il dure une demi-heure, puis jette à nouveau la même exception. La taille du fichier à partir de laquelle j'essaie de lire les mots et les fréquences est de 1,7 Go. 

Toute aide serait très appréciée.

Merci :-) 

27
ablimit

Pour un traitement de texte comme celui-ci, la réponse est généralement une arborescence plutôt qu'une hashmap, si vous pouvez vivre avec des temps de recherche plus longs. Cette structure est assez efficace en termes de mémoire pour les langues naturelles, où de nombreux mots ont des chaînes de départ communes.

Selon l’entrée, un arbre de Patricia pourrait être encore meilleur.

(En outre, s’il s’agit bien de mots d’une langue naturelle, êtes-vous sûr de vouloir réellement 100 000 000 entrées? La majorité des mots couramment utilisés sont étonnamment faibles, les solutions commerciales (prédiction de mots, correction orthographique) utilisent rarement plus de 100 000 mots, quelle que soit la langue. .)

16
Christoffer

Votre problème est que 1,7 Go de texte brut correspond à plus de 1 500 Mo, même sans la surcharge ajoutée par les objets chaîne individuels. Pour les mappages énormes, vous devez utiliser une base de données ou une mappe sauvegardée sur fichier, ceux-ci utiliseront de la mémoire disque au lieu de tas.

Mettre à jour

Je ne pense pas que l'allocation de 15 Go pour le tas est possible pour la plupart des jvms. Cela ne fonctionnera pas avec un JVM 32 bits et je ne pense pas qu'un JVM 64 bits fonctionnerait non plus.  15 Go de mémoire devraient fonctionner sur un JVM 64 bits lorsque suffisamment de RAM est disponible.

11
josefx

Avec 100 millions de termes, vous avez presque certainement dépassé la limite de ce qui devrait être stocké en mémoire. Stockez vos termes dans une base de données quelconque. Utilisez une base de données commerciale ou écrivez quelque chose qui vous permet d'accéder au fichier pour obtenir les informations souhaitées. Si le format de fichier que vous avez ne vous permet pas d'accéder rapidement au fichier, convertissez-le en un fichier compatible. Par exemple, attribuez à chaque enregistrement une taille fixe et calculez instantanément le décalage du fichier pour n'importe quel numéro d'enregistrement. Le tri des enregistrements vous permettra alors de faire une recherche binaire très rapidement. Vous pouvez également écrire du code pour accélérer énormément l'accès aux fichiers sans avoir à stocker l'intégralité du fichier en mémoire.

5
DJClayworth

Un fichier de 1,7 Go est un fichier relativement petit pour le faire et le stocker dans la RAM. Je le fais avec des fichiers beaucoup plus gros et les stocke en mémoire sans problème. Une base de données peut être utilisée, mais peut être excessive ou peut être parfaite en fonction de ce que vous envisagez de faire avec les données. 

Comme d'autres l'ont dit, avec le langage naturel, il y aura probablement un nombre relativement petit de valeurs uniques, de sorte que la carte ne deviendra pas aussi grande. Je ne voudrais pas utiliser Java.util.HashMap, car il est très inefficace en termes de mémoire d’utilisation, en particulier lors du stockage de valeurs primitives telles que ints. Java.util.HashMap stocke les primitives en tant qu'objets. Il stocke également chaque valeur à l'intérieur d'un objet HashMap.Entry qui gaspille de la mémoire. En raison de ces deux facteurs, Java.util.HashMap utilise beaucoup plus de mémoire que des alternatives telles que Trove , Fastutil et d’autres:

Comme mentionné précédemment, plusieurs implémentations de cartes ne rencontrent pas ces problèmes. Étant donné que vous stockez des nombres dans votre carte, un avantage supplémentaire est que vous obtiendrez un gain de performances car il n'est pas nécessaire de basculer constamment entre les objets et les primitives (c.-à-d. Boxing/unboxing) lorsque vous insérez de nouvelles valeurs dans la carte ou mettez à jour d'anciens valeurs. Vous trouverez une référence de divers hashmaps primitifs mieux adaptés à de grandes quantités de données sur ce message du Guide d'optimisation des performances Java :

5
Jay Askren

Si vous voulez juste un magasin KeyValue (Map) léger, je voudrais utiliser Redis. Il est très rapide et a la capacité de conserver les données s’il en a besoin. Le seul inconvénient est que vous devez exécuter le magasin Redis sur une machine Linux. 

Si vous êtes limité à Windows, MongoDB est une bonne option si vous pouvez l’exécuter en 64 bits.

4
Joshua

Vous pouvez également essayer de multiplier les doublons. 

Par exemple, Cat = Chats = Chats = Chat 

ou

nager = nager = nager

essayez googler "Porter Stemmer" 

2
Ivan

D'autres réponses ont déjà indiqué que le problème réside dans l'utilisation de la mémoire. En fonction de votre domaine de problème, vous pouvez concevoir une classe de clés réduisant l’empreinte mémoire globale. Par exemple, si votre clé est composée de phrases en langage naturel, vous pouvez séparer et interner les mots qui la composent. par exemple.

public class Phrase {
  private final String[] interned;

  public Phrase(String phrase) {
    String[] tmp = phrase.split(phrase, "\\s");

    this.interned = new String[tmp.length];

    for (int i=0; i<tmp.length; ++i) {
      this.interned[i] = tmp[i].intern();
    }
  }

  public boolean equals(Object o) { /* TODO */ }
  public int hashCode() { /* TODO */ }
}

En fait, cette solution peut fonctionner même si les chaînes ne représentent pas le langage naturel, à condition qu'il existe un chevauchement significatif exploitable entre chaînes.

1
Adamski

Trove THashMap utilise beaucoup moins de mémoire. Reste à savoir si cela suffirait à réduire la taille. Vous avez besoin d’un autre endroit pour stocker cette information pour la récupération en plus strictement en mémoire 

1
AHungerArtist

Supprimez la HashMap et chargez toutes ces données dans HBase ou l'un des autres magasins de données NoSQL et écrivez vos requêtes en termes d'opérations MapReduce . C'est l'approche adoptée par Google Search et de nombreux autres sites traitant d'énormes quantités de données. Il s'est avéré à l'échelle pour atteindre une taille pratiquement infinie.

1
Barend

Envisagez de le remplacer par un cdb . Jusqu'à 4 Go et:

Une recherche réussie dans une base de données volumineuse ne nécessite normalement que deux accès au disque. Une recherche infructueuse n'en prend qu'une.

0
whiskeysierra

Il existe une offre intéressante de Terracotta - BigMemory qui semble être exactement ce que vous voulez. Je n'ai pas essayé moi-même et je ne connais pas les conditions de licence, etc.

0
maximdim

Verso de l'enveloppe: 1,7 Go/100 Mo = 18 octets en moyenne = par terme et par fréquence

Nous pouvons utiliser une table de hachage codée à la main avec deux tableaux logiques.

  1. L'un pour contenir les fréquences int (valeurs) et l'autre consiste à construire un tableau de caractères de style C pour simuler un tableau c à deux dimensions (un tableau de tableaux de caractères). donc on indexe par calcul. nous ne pouvons pas utiliser un tableau Java à deux dimensions car il est associé à une surcharge d’objet. Ce tableau de caractères peut contenir des tableaux de caractères de taille fixe pour représenter les clés. Nous calculons donc le hachage de la clé et le plaçons dans ce "tableau à deux dimensions" et si nous avons un conflit, il peut être résolu en sondant de manière linéaire. les paires clé et valeur sont liées par l'index commun des tableaux.

  2. Le hashmap doit utiliser un adressage ouvert car nous n'avons pas assez de mémoire pour le chaînage.

  3. On peut avoir 10 instances de cette hashmap basée sur la longueur des clés; ne peux pas être certain puisque je ne connais pas les caractéristiques des données.

  4. Espace utilisé = 2 puissance 29 pour int array + (2 puissance 4 (16 octets par chaîne) * 2 puissance 27) = 3,5 Go

  5. Si nous voulons des doubles fréquences plutôt que des ints, il se peut que nous devions réduire la taille des chaînes de manière appropriée.

0
Ranga Baireddy

En Java, la taille minimale d'un objet est de 16 octets avant que vous ne considériez le contenu qu'il contient.

1e8 éléments dans une carte de hachage ont une taille sous-estimée et de votre ordinateur.

Une chaîne est un objet contenant un tableau de caractères. Par exemple, les chaînes Mentionnées ci-dessus par plusieurs personnes peuvent être plus grandes qu'un objet double Par conséquent, vous aurez besoin de plus de mémoire pour le.

Notez que les programmes commencent à mal fonctionner lorsque vous approchez de la limite De votre ordinateur.

Si vous ne souhaitez pas utiliser une base de données comme suggéré ci-dessus, Vous pouvez envisager de coder et de compresser vos clés pour les transformer en nombres que vous pouvez toujours compter avec la fréquence de . codage basé sur la fréquence de mots dans ce premier codage et va de là ...

0
nichole

C'est un mauvais design. Avec 1,7 Go de données en mémoire sur une carte de hachage, j'aurais fait l'une des deux:

  1. Conservez toutes les données (fichier/base de données) et conservez le 1% supérieur ou quelque chose en mémoire. Utilisez un algorithme pour décider quels identifiants seront en mémoire et quand.

  2. Utilisez memcached . La meilleure façon de sortir. Un hashable distribué en mémoire. C'est exactement ce pour quoi les DHT sont utilisés.

0
zengr