2010-09-03 12 views
5

ho un po 'di codice che crea un elenco, inizializzato con le dimensioni di una mappa:Una mappa java può restituire una dimensione di -1?

private Set<String> mKeys = new HashSet<String>(64); 
.... 
List<String> keyList = new ArrayList<String>(mKeys.size()); 

sto vedendo un'eccezione: java.lang.IllegalArgumentException: Capacità illegale: -1

Can una mappa restituire una dimensione di -1? Sto guardando il codice sorgente di HashSet, che è supportato da una HashMap. Il codice sorgente di HashMap mostra gli interni dove elementCount viene sempre decrementato su una chiamata removeEntry(). Inoltre, i metodi per la risposta di HashMap.empty() su elementCount sono == 0, che restituirebbe false se elementCount era -1.

Qualcuno si è imbattuto in questo prima? Posso codificarlo, ma mi sembra un trucco, il che mi fa pensare che sto facendo qualcosa di sbagliato con il codice corrente.

EDIT: stavo cercando di semplificare il problema in origine. Il Set che sto utilizzando è in realtà definito come

private static Set<String> mKeys = Collections.synchronizedSet(new HashSet<String>(64)); 

MODIFICA: la chiave qui può essere nel set sincronizzato. Dal JavaDoc:

E 'indispensabile che l'utente sincronizzare manualmente sul set restituito quando l'iterazione su di esso:

Set s = Collections.synchronizedSet(new HashSet()); 
     ... 
synchronized(s) { 
    Iterator i = s.iterator(); // Must be in the synchronized block 
    while (i.hasNext()) 
     foo(i.next()); 
} 

La mancata osservanza di questi consigli può causare un comportamento non deterministico.

Il comportamento non deterministico per me potrebbe includere una dimensione di -1. Devo tornare indietro e assicurarmi di sincronizzare correttamente durante l'iterazione sul set, ma sospetto che questo sia il problema.

+2

A meno che non si sta mettendo qualcosa in 'mKeys' in quel codice omesso, si sta facendo un errore in ogni caso : 'mKeys.size()' restituisce il numero di elementi in 'Set', non il numero di bucket. Non è la "dimensione" della struttura dei dati, ma piuttosto il numero di elementi contenuti. – Borealid

+2

È possibile che si sta corrompendo la mappa accedendo da più thread senza sincronizzazione? – finnw

+0

Il codice è semplificato da quello che sto facendo in realtà, in quanto vi è un po 'di sincronizzazione. Aggiornerò la domanda con informazioni più specifiche Sono quasi sicuro che questo non è un problema con il wrapping su Integer.MAX_VALUE. – mmorrisson

risposta

5

In base alla documentazione, HashMap restituisce il numero di elementi nella mappa, senza altre condizioni. Un numero negativo di elementi non ha alcun senso.

L'unica spiegazione possibile che ho pensato è stata una mappa di dimensioni Integer.MAX_VALUE + 1, che risulterebbe in una dimensione negativa. Ma AbstractMap # size() precisa che se la dimensione Map è maggiore di Integer.MAX_VALUE, viene restituito Integer.MAX_VALUE, quindi questo caso non può accadere.

Inoltre, ho provato il codice sul mio computer come segue:

Set<String> mKeys = new HashSet<String>(64); 
System.out.println(mKeys.size()); 

ottengo il risultato atteso: 0.

forse è qualcosa legato alla "...." parte di il tuo codice?

+0

Lovely qoute: "Forse è qualcosa che riguarda la parte" .... "del tuo codice?" –

0

Per HashMap, size() non deve restituire -1. Comunque size() restituisce la dimensione, un campo privato di HashMap che viene mantenuto indipendentemente piuttosto che calcolando il numero di membri ogni volta che viene chiamato (perché la tabella delle voci è memorizzata come lista collegata (NB not LinkedList)). Quindi, teoricamente, si sarebbe potuto verificare un errore che ha causato la sincronizzazione della dimensione con il numero effettivo di voci nella tabella (ad esempio lungo le linee di esecuzione su voci Integer.MAX_VALUES). Tuttavia sembra improbabile che questo non sarebbe ben noto ormai.

+1

interessante. Sono rimasto perplesso vedendo la dimensione "-1" di un set nei registri ... size = 0 size = -1 size = -1 dove due thread utilizzano contemporaneamente un iteratore per rimuovere elementi. non sapevo che era possibile arrivare alla dimensione negativa di una collezione. –

3

Il metodo HashSet size() può restituire numero intero negativo in ambiente multi-thread. Il test 1 di seguito genererà una serie di eccezioni relative alla dimensione negativa. Il test 2 è un approccio sicuro per i thread, una delle soluzioni per evitarlo.

Edit: Nota: sembra essere un problema in Java 1.6 e versioni precedenti. Quando si esegue il test in Java 1.7, HashSet non restituisce le dimensioni negative. Le dimensioni non cambiano se l'oggetto non è stato trovato e non è stato rimosso nulla da un HashSet.

test 1

package test; 

import java.util.Collections; 
import java.util.HashSet; 
import java.util.Set; 

class TestHashSet1 { 
    private static int NUMBER_OF_THREADS = 5; 
    private static int COLLECTION_SIZE = 1000; 

    public static void main(String[] arg) { 
     final Set<Integer> toRemoveElement = new HashSet<Integer>(); 
     final Set<Integer> toStoreElements = new HashSet<Integer>(); 
     // populate collections for test 
     for (int i = 0; i < COLLECTION_SIZE; i++) { 
      Integer obj = new Integer(i); 
      toRemoveElement.add(obj); 
      toStoreElements.add(obj); 
     } 
     // two threads that will be using collection2 
     Runnable runnable = new Runnable() { 
      @Override 
      public void run() { 
       for (Integer o : toStoreElements) { 
        removeObject(toRemoveElement, o); 
       } 
      } 
     }; 
     Thread[] treads = new Thread[NUMBER_OF_THREADS]; 
     for (int i = 0; i < treads.length; i++) { 
      treads[i] = new Thread(runnable); 
     } 
     for (Thread t : treads) { 
      t.start(); 
     } 
    } 

    private static void removeObject(Set<Integer> toRemoveElement, Integer o) { 
     toRemoveElement.remove(o); 
     int size = toRemoveElement.size(); 
     if (size < 0) { 
      System.out.println(size); 
      try { 
       toRemoveElement.toArray(new Integer[toRemoveElement.size()]); 
      } catch (Exception e) { 
       e.printStackTrace(); 
      } 
     } 
    } 
} 

Test 2 - thread-safe

package test; 

import java.util.Collections; 
import java.util.HashSet; 
import java.util.Set; 

class TestHashSet2 { 
    private static int NUMBER_OF_THREADS = 5; 
    private static int COLLECTION_SIZE = 1000; 

    public static void main(String[] arg) { 
     final Set<Integer> toRemoveElement = Collections.synchronizedSet(new HashSet<Integer>()); // example of collection sync 
     final Set<Integer> toStoreElements = new HashSet<Integer>(); 
     // populate collections for test 
     for (int i = 0; i < COLLECTION_SIZE; i++) { 
      Integer obj = new Integer(i); 
      toRemoveElement.add(obj); 
      toStoreElements.add(obj); 
     } 
     // two threads that will be using collection2 
     Runnable runnable = new Runnable() { 
      @Override 
      public void run() { 
       for (Integer o : toStoreElements) { 
        removeObject(toRemoveElement, o); 
       } 
      } 
     }; 
     Thread[] treads = new Thread[NUMBER_OF_THREADS]; 
     for (int i = 0; i < treads.length; i++) { 
      treads[i] = new Thread(runnable); 
     } 
     for (Thread t : treads) { 
      t.start(); 
     } 
    } 

    synchronized private static void removeObject(Set<Integer> toRemoveElement, Integer o) { // example of method sync 
     toRemoveElement.remove(o); 
     int size = toRemoveElement.size(); 
     if (size < 0) { 
      System.out.println(size); 
      try { 
       toRemoveElement.toArray(new Integer[toRemoveElement.size()]); 
      } catch (Exception e) { 
       e.printStackTrace(); 
      } 
     } 
    } 
} 
+0

Perché modificare la modalità di istanziazione di 'toStoreElements'? Rendere "removeObject' sincronizzato è sufficiente no? – user368507

+0

Questo è solo un esempio, non un'applicazione pratica. Puoi sincronizzare il modo in cui è sufficiente nella tua applicazione. –

Problemi correlati