Entiendo que el conjunto devuelto por el método keySet () de un mapa no garantiza ningún orden en particular.
Mi pregunta es, ¿garantiza el mismo orden en múltiples iteraciones? Por ejemplo
Map<K,V> map = getMap();
for( K k : map.keySet() )
{
}
...
for( K k : map.keySet() )
{
}
En el código anterior, asumiendo que el mapa no se modifica, la iteración sobre los conjuntos de claves estará en el mismo orden. Usando jdk15 de Sun se hace iterate en el mismo orden, pero antes de que dependen de este comportamiento, me gustaría saber si todos los JDK harán lo mismo.
EDITAR
Veo por las respuestas que no puedo depender de eso. Demasiado. Tenía la esperanza de no tener que construir una nueva colección para garantizar mi pedido. Mi código necesitaba iterar, hacer algo de lógica y luego iterar nuevamente con el mismo orden. Simplemente crearé una nueva ArrayList a partir del keySet que garantizará el orden.
.keySet()
y.values()
es consistente. Desafortunadamente, la respuesta aceptada es incorrecta. @karoberts - ¿puedes echar un vistazo?Respuestas:
Si no se indica que está garantizado en la documentación de la API, no debería depender de él. El comportamiento puede incluso cambiar de una versión del JDK a la siguiente, incluso del JDK del mismo proveedor.
Podrías obtener fácilmente el conjunto y luego ordenarlo tú mismo, ¿verdad?
fuente
Puede usar un LinkedHashMap si desea un HashMap cuyo orden de iteración no cambie.
Además, siempre debe usarlo si recorre la colección. Iterar sobre el entrySet o keySet de HashMap es mucho más lento que sobre LinkedHashMap.
fuente
Map es solo una interfaz (en lugar de una clase), lo que significa que la clase subyacente que lo implementa (y hay muchas) podría comportarse de manera diferente, y el contrato para keySet () en la API no indica que se requiera una iteración consistente.
Si está buscando una clase específica que implementa Map (HashMap, LinkedHashMap, TreeMap, etc.), entonces podría ver cómo implementa la función keySet () para determinar cuál sería el comportamiento al verificar la fuente, tendría que realmente eche un vistazo de cerca al algoritmo para ver si la propiedad que está buscando se conserva (es decir, un orden de iteración consistente cuando el mapa no ha tenido inserciones / eliminaciones entre iteraciones). La fuente de HashMap, por ejemplo, está aquí (abra JDK 6): http://www.docjar.com/html/api/java/util/HashMap.java.html
Podría variar mucho de un JDK a otro, por lo que definitivamente no confiaría en él.
Dicho esto, si un orden de iteración consistente es algo que realmente necesita, es posible que desee probar un LinkedHashMap.
fuente
La API para Map no garantiza ningún orden en absoluto, incluso entre múltiples invocaciones del método en el mismo objeto.
En la práctica, me sorprendería mucho si el orden de iteración cambiara para múltiples invocaciones posteriores (suponiendo que el mapa en sí no cambiara en el medio), pero no debería (y según la API no puede) confiar en esto.
EDITAR: si desea confiar en que el orden de iteración sea consistente, entonces desea un SortedMap que proporcione exactamente estas garantías.
fuente
Solo por diversión, decidí escribir un código que puedes usar para garantizar un orden aleatorio cada vez. Esto es útil para que pueda detectar casos en los que depende del orden pero no debería hacerlo. Si desea depender del orden, como han dicho otros, debe usar un SortedMap. Si solo usa un mapa y confía en el orden, entonces el siguiente RandomIterator lo detectará. Solo lo usaría en el código de prueba, ya que utiliza más memoria que no hacerlo.
También puede ajustar el mapa (o el conjunto) para que devuelvan el RandomeIterator que luego le permitirá usar el bucle for-each.
import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; public class Main { private Main() { } public static void main(final String[] args) { final Map<String, String> items; items = new HashMap<String, String>(); items.put("A", "1"); items.put("B", "2"); items.put("C", "3"); items.put("D", "4"); items.put("E", "5"); items.put("F", "6"); items.put("G", "7"); display(items.keySet().iterator()); System.out.println("---"); display(items.keySet().iterator()); System.out.println("---"); display(new RandomIterator<String>(items.keySet().iterator())); System.out.println("---"); display(new RandomIterator<String>(items.keySet().iterator())); System.out.println("---"); } private static <T> void display(final Iterator<T> iterator) { while(iterator.hasNext()) { final T item; item = iterator.next(); System.out.println(item); } } } class RandomIterator<T> implements Iterator<T> { private final Iterator<T> iterator; public RandomIterator(final Iterator<T> i) { final List<T> items; items = new ArrayList<T>(); while(i.hasNext()) { final T item; item = i.next(); items.add(item); } Collections.shuffle(items); iterator = items.iterator(); } public boolean hasNext() { return (iterator.hasNext()); } public T next() { return (iterator.next()); } public void remove() { iterator.remove(); } }
fuente
Estoy de acuerdo con LinkedHashMap. Solo pongo mis hallazgos y experiencia mientras enfrentaba el problema cuando intentaba ordenar HashMap por claves.
Mi código para crear HashMap:
HashMap<Integer, String> map; @Before public void initData() { map = new HashMap<>(); map.put(55, "John"); map.put(22, "Apple"); map.put(66, "Earl"); map.put(77, "Pearl"); map.put(12, "George"); map.put(6, "Rocky"); }
Tengo una función showMap que imprime las entradas del mapa:
public void showMap (Map<Integer, String> map1) { for (Map.Entry<Integer, String> entry: map1.entrySet()) { System.out.println("[Key: "+entry.getKey()+ " , "+"Value: "+entry.getValue() +"] "); } }
Ahora, cuando imprimo el mapa antes de ordenar, imprime la siguiente secuencia:
Map before sorting : [Key: 66 , Value: Earl] [Key: 22 , Value: Apple] [Key: 6 , Value: Rocky] [Key: 55 , Value: John] [Key: 12 , Value: George] [Key: 77 , Value: Pearl]
Que es básicamente diferente al orden en el que se colocaron las claves del mapa.
Ahora, cuando lo ordeno con claves de mapa:
List<Map.Entry<Integer, String>> entries = new ArrayList<>(map.entrySet()); Collections.sort(entries, new Comparator<Entry<Integer, String>>() { @Override public int compare(Entry<Integer, String> o1, Entry<Integer, String> o2) { return o1.getKey().compareTo(o2.getKey()); } }); HashMap<Integer, String> sortedMap = new LinkedHashMap<>(); for (Map.Entry<Integer, String> entry : entries) { System.out.println("Putting key:"+entry.getKey()); sortedMap.put(entry.getKey(), entry.getValue()); } System.out.println("Map after sorting:"); showMap(sortedMap);
la salida es:
Sorting by keys : Putting key:6 Putting key:12 Putting key:22 Putting key:55 Putting key:66 Putting key:77 Map after sorting: [Key: 66 , Value: Earl] [Key: 6 , Value: Rocky] [Key: 22 , Value: Apple] [Key: 55 , Value: John] [Key: 12 , Value: George] [Key: 77 , Value: Pearl]
Puede ver la diferencia en el orden de las claves. El orden ordenado de las claves está bien, pero el de las claves del mapa copiado está nuevamente en el mismo orden del mapa anterior. No sé si esto es válido para decirlo, pero para dos hashmap con las mismas claves, el orden de las claves es el mismo. Esto implica para la declaración que el orden de las claves no está garantizado, pero puede ser el mismo para dos mapas con las mismas claves debido a la naturaleza inherente del algoritmo de inserción de claves si se implementa HashMap de esta versión de JVM.
Ahora, cuando uso LinkedHashMap para copiar entradas ordenadas a HashMap, obtengo el resultado deseado (que era natural, pero ese no es el punto. El punto está relacionado con el orden de las claves de HashMap)
HashMap<Integer, String> sortedMap = new LinkedHashMap<>(); for (Map.Entry<Integer, String> entry : entries) { System.out.println("Putting key:"+entry.getKey()); sortedMap.put(entry.getKey(), entry.getValue()); } System.out.println("Map after sorting:"); showMap(sortedMap);
Salida:
Sorting by keys : Putting key:6 Putting key:12 Putting key:22 Putting key:55 Putting key:66 Putting key:77 Map after sorting: [Key: 6 , Value: Rocky] [Key: 12 , Value: George] [Key: 22 , Value: Apple] [Key: 55 , Value: John] [Key: 66 , Value: Earl] [Key: 77 , Value: Pearl]
fuente
Hashmap no garantiza que el orden del mapa se mantenga constante en el tiempo.
fuente
No tiene por qué serlo. La función keySet de un mapa devuelve un conjunto y el método iterador del conjunto dice esto en su documentación:
"Devuelve un iterador sobre los elementos de este conjunto. Los elementos se devuelven sin ningún orden en particular (a menos que este conjunto sea una instancia de alguna clase que proporcione una garantía)".
Entonces, a menos que esté usando una de esas clases con garantía, no hay ninguna.
fuente
Map es una interfaz y no define en la documentación que el orden debe ser el mismo. Eso significa que no puede confiar en el pedido. Pero si controla la implementación de Map devuelta por getMap (), entonces puede usar LinkedHashMap o TreeMap y obtener el mismo orden de claves / valores todo el tiempo que itera a través de ellos.
fuente
Lógicamente, si el contrato dice "ningún pedido en particular está garantizado", y dado que "el pedido que salió una vez" es un pedido en particular , la respuesta es no, no puede depender de que salga de la misma manera dos veces.
fuente
tl; dr Sí.
Creo que el orden de iteración para
.keySet()
y.values()
es consistente (Java 8).Prueba 1 : cargamos a
HashMap
con claves aleatorias y valores aleatorios. Repetimos sobre estoHashMap
usando.keySet()
y cargamos las claves y sus valores correspondientes a aLinkedHashMap
(mantendrá el orden de las claves y valores insertados). Luego comparamos el valor.keySet()
de ambos Mapas y.values()
de ambos Mapas. Siempre resulta ser lo mismo, nunca falla.public class Sample3 { static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; static SecureRandom rnd = new SecureRandom(); // from here: https://stackoverflow.com/a/157202/8430155 static String randomString(int len){ StringBuilder sb = new StringBuilder(len); for (int i = 0; i < len; i++) { sb.append(AB.charAt(rnd.nextInt(AB.length()))); } return sb.toString(); } public static void main(String[] args) throws Exception { for (int j = 0; j < 10; j++) { Map<String, String> map = new HashMap<>(); Map<String, String> linkedMap = new LinkedHashMap<>(); for (int i = 0; i < 1000; i++) { String key = randomString(8); String value = randomString(8); map.put(key, value); } for (String k : map.keySet()) { linkedMap.put(k, map.get(k)); } if (!(map.keySet().toString().equals(linkedMap.keySet().toString()) && map.values().toString().equals(linkedMap.values().toString()))) { // never fails System.out.println("Failed"); break; } } } }
Prueba 2 : A partir de aquí ,
table
es una matriz deNode<K,V>
clases. Sabemos que iterar una matriz dará el mismo resultado cada vez./** * The table, initialized on first use, and resized as * necessary. When allocated, length is always a power of two. * (We also tolerate length zero in some operations to allow * bootstrapping mechanics that are currently not needed.) */ transient Node<K,V>[] table;
La clase responsable de
.values()
:final class Values extends AbstractCollection<V> { // more code here public final void forEach(Consumer<? super V> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.value); } if (modCount != mc) throw new ConcurrentModificationException(); } } }
La clase responsable de
.keySet()
:final class KeySet extends AbstractSet<K> { // more code here public final void forEach(Consumer<? super K> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.key); } if (modCount != mc) throw new ConcurrentModificationException(); } } }
Mire cuidadosamente ambas clases internas. Son prácticamente iguales excepto:
if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.key); <- from KeySet class // action.accept(e.value); <- the only change from Values class } if (modCount != mc) throw new ConcurrentModificationException(); }
Se repiten en la misma matriz
table
para admitir.keySet()
enKeySet
clase y.values()
enValues
clase.Prueba 3: esta respuesta también establece explícitamente: entonces, sí, keySet (), values () y entrySet () devuelven valores en el orden que usa la lista vinculada interna.
Por lo tanto, los
.keySet()
y.values()
son consistentes.fuente
También puede almacenar la instancia de Set devuelta por el método keySet () y puede usar esta instancia siempre que necesite el mismo pedido.
fuente
iterator()
devolver un iterador con un orden de iteración diferente, incluso en el mismo conjunto?