2010-09-03 14 views
5

tengo un código que crea una lista, que se inicia con el tamaño de un mapa:¿Puede un mapa Java devolver un tamaño de -1?

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

que estoy viendo una excepción: java.lang.IllegalArgumentException: Capacidad ilegal: -1

Puede un mapa devuelve un tamaño de -1? Estoy mirando el código fuente para HashSet, que está respaldado por un HashMap. El código fuente de HashMap muestra las partes internas donde elementCount siempre se disminuye en una llamada a removeEntry(). Además, los métodos para HashMap.empty() responden en elementCount siendo == 0, que devolvería false si elementCount era -1.

¿Alguien ha topado con esto antes? Puedo codificarlo, pero se siente como un truco, lo que me hace pensar que estoy haciendo algo mal con el código actual.

EDITAR: Estaba tratando de simplificar el problema originalmente. El conjunto que estoy usando se define realmente como

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

EDITAR: La clave aquí puede estar en synchronizedSet. Desde el JavaDoc:

Es imperativo que el usuario sincronizar manualmente en el conjunto devuelto cuando se itera sobre ella:

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

no seguir este consejo podría obtener un comportamiento no determinista.

Comportamiento no determinista para mí podría incluir un tamaño de -1. Necesito volver atrás y asegurarme de que sincronizo correctamente al iterar sobre el conjunto, pero sospecho que este es el problema.

+2

A menos que estés poner algo en 'mKeys' en ese código se omite, se está cometiendo un error de todos modos : 'mKeys.size()' devuelve la cantidad de elementos en 'Set', no la cantidad de cubos. No es el "tamaño" de la estructura de datos, sino la cantidad de elementos que contiene. – Borealid

+2

¿Es posible que esté corrompiendo el mapa al acceder desde múltiples hilos sin sincronización? – finnw

+0

El código se simplifica de lo que estoy haciendo en realidad, ya que hay algo de sincronización. Actualizaré la pregunta con información más específica. Estoy casi seguro de que esto no es un problema con el ajuste en Integer.MAX_VALUE. – mmorrisson

Respuesta

5

De acuerdo con la documentación, HashMap devuelve el número de elementos en el mapa, sin otras condiciones. Un número negativo de elementos no tiene ningún sentido.

La única explicación posible que pensé fue un Mapa de tamaño Integer.MAX_VALUE + 1, que daría como resultado un tamaño negativo. Pero AbstractMap # size() precisa que si el tamaño del mapa es mayor que Integer.MAX_VALUE, se devuelve Integer.MAX_VALUE, por lo que este caso no puede suceder.

Además, he intentado su código en mi equipo de la siguiente manera:

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

puedo obtener el resultado esperado: 0.

Tal vez se trata de algo relacionado con el "...." parte de ¿tu codigo?

+0

Lovely qoute: '¿Tal vez es algo relacionado con el "...." parte de tu código?' –

0

Para HashMap, size() no debería devolver -1. Sin embargo tamaño() devuelve tamaño, un campo privado de HashMap que se mantiene indefinidamente en lugar de calcular el número de miembros cada vez que se llama (porque la tabla de entradas se almacena como una lista vinculada (NB no LinkedList)). Entonces, teóricamente podría haber ocurrido un error que causó que el tamaño no se sincronizara con el número real de entradas en la tabla (por ejemplo, en la línea de ejecución de las entradas Integer.MAX_VALUES). Sin embargo, es poco probable que esto ya no sea conocido.

+1

interesante. Me quedé perplejo al ver el tamaño "-1" de un conjunto en los registros ... tamaño = 0 [] tamaño = -1 donde dos subprocesos al mismo tiempo utilizan un iterador para eliminar elementos. no sabía que era posible llegar al tamaño negativo de una colección. –

3

El método HashSet size() puede devolver un número entero negativo en un entorno de múltiples hilos. La prueba 1 a continuación arrojará un montón de excepciones relacionadas con el tamaño negativo. La Prueba 2 es un enfoque seguro para subprocesos, una de las soluciones para evitar esto.

Editar: Nota: parece ser un problema en Java 1.6 y versiones anteriores. Cuando se prueba en Java 1.7, HashSet no devuelve un tamaño negativo. El tamaño no cambia si no se encontró el objeto y no se eliminó nada de un HashSet.

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

Prueba 2 - Hilo de seguridad

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

¿Por qué cambiar cómo se instancia el 'toStoreElements'? Hacer 'removeObject' sincronizado es suficiente no? – user368507

+0

Esto es solo un ejemplo, no una aplicación práctica. Puede sincronizar la manera en que es suficiente en su aplicación. –

Cuestiones relacionadas