Soru Her bir girişte 'Harita' içinde verimli bir şekilde yineleme nasıl yapılır?


Bir nesneyi uygularsam Map Java arayüzünde ve içinde bulunan her bir çiftin üzerinde yineleme yapmak istiyorum, haritadan geçmenin en etkili yolu nedir?

Öğelerin sıralanması, arabirim için sahip olduğum belirli harita uygulamasına mı bağlı olacak?


2529
2017-09-05 21:12


Menşei


Java 8'de Lambda Expression'u kullanarak: stackoverflow.com/a/25616206/1503859 - Nitin Mahesh


Cevaplar:


Map<String, String> map = ...
for (Map.Entry<String, String> entry : map.entrySet())
{
    System.out.println(entry.getKey() + "/" + entry.getValue());
}

4093
2017-09-05 21:15



Bunu yaparsanız, Giriş, Harita'da yuvalanmış bir Sınıf olduğu için çalışmaz. java.sun.com/javase/6/docs/api/java/util/Map.html - ScArcher2
İthalatı "import java.util.Map.Entry;" şeklinde yazabilirsiniz. ve işe yarayacak. - jjujuma
@Pureferret Bir yineleyici kullanmak isteyebilirsiniz tek nedeni onun aramak gerekiyorsa remove yöntem. Eğer durum buysa, bu diğer cevap nasıl yapacağınızı gösterir. Aksi takdirde, yukarıdaki yanıtta gösterildiği gibi gelişmiş döngü gitmenin yoludur. - assylias
Map.Entry formunun, iç sınıfın geçerli ad alanına içe aktarılmasından daha net olduğuna inanıyorum. - Josiah Yoder
Kullanabileceğinizi unutmayın map.values() veya map.keySet() Sadece değerler veya anahtarlar arasında geçiş yapmak istiyorsanız. - dguay


Diğer cevapları özetlemek ve bildiğim şeyle birleştirmek için bunu yapmanın 10 ana yolunu buldum (aşağıya bakın). Ayrıca, bazı performans testleri yazdım (aşağıdaki sonuçlara bakınız). Örneğin, bir haritanın tüm anahtarlarının ve değerlerinin toplamını bulmak istiyorsak, şunları yazabiliriz:

  1. kullanma yineleyici ve Map.Entry

    long i = 0;
    Iterator<Map.Entry<Integer, Integer>> it = map.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry<Integer, Integer> pair = it.next();
        i += pair.getKey() + pair.getValue();
    }
    
  2. kullanma her biri için ve Map.Entry

    long i = 0;
    for (Map.Entry<Integer, Integer> pair : map.entrySet()) {
        i += pair.getKey() + pair.getValue();
    }
    
  3. kullanma her biri için Java 8'den

    final long[] i = {0};
    map.forEach((k, v) -> i[0] += k + v);
    
  4. kullanma anahtar seti ve her biri için

    long i = 0;
    for (Integer key : map.keySet()) {
        i += key + map.get(key);
    }
    
  5. kullanma anahtar seti ve yineleyici

    long i = 0;
    Iterator<Integer> itr2 = map.keySet().iterator();
    while (itr2.hasNext()) {
        Integer key = itr2.next();
        i += key + map.get(key);
    }
    
  6. kullanma için ve Map.Entry

    long i = 0;
    for (Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator(); entries.hasNext(); ) {
        Map.Entry<Integer, Integer> entry = entries.next();
        i += entry.getKey() + entry.getValue();
    }
    
  7. Java 8'i Kullanma Akış API'sı

    final long[] i = {0};
    map.entrySet().stream().forEach(e -> i[0] += e.getKey() + e.getValue());
    
  8. Java 8'i Kullanma Akış API'si paralel

    final long[] i = {0};
    map.entrySet().stream().parallel().forEach(e -> i[0] += e.getKey() + e.getValue());
    
  9. kullanma IterableMap arasında Apache Collections

    long i = 0;
    MapIterator<Integer, Integer> it = iterableMap.mapIterator();
    while (it.hasNext()) {
        i += it.next() + it.getValue();
    }
    
  10. kullanma MutableMap Eclipse (CS) koleksiyonları

    final long[] i = {0};
    mutableMap.forEachKeyValue((key, value) -> {
        i[0] += key + value;
    });
    

Perfomance testleri (mod = Ortalama Zaman, sistem = Windows 8.1 64 bit, Intel i7-4790 3.60 GHz, 16 GB)

  1. Küçük harita için (100 element), skor 0.308 en iyisidir

    Benchmark                          Mode  Cnt  Score    Error  Units
    test3_UsingForEachAndJava8         avgt  10   0.308 ±  0.021  µs/op
    test10_UsingEclipseMap             avgt  10   0.309 ±  0.009  µs/op
    test1_UsingWhileAndMapEntry        avgt  10   0.380 ±  0.014  µs/op
    test6_UsingForAndIterator          avgt  10   0.387 ±  0.016  µs/op
    test2_UsingForEachAndMapEntry      avgt  10   0.391 ±  0.023  µs/op
    test7_UsingJava8StreamApi          avgt  10   0.510 ±  0.014  µs/op
    test9_UsingApacheIterableMap       avgt  10   0.524 ±  0.008  µs/op
    test4_UsingKeySetAndForEach        avgt  10   0.816 ±  0.026  µs/op
    test5_UsingKeySetAndIterator       avgt  10   0.863 ±  0.025  µs/op
    test8_UsingJava8StreamApiParallel  avgt  10   5.552 ±  0.185  µs/op
    
  2. 10000 elemanlı harita için 37.606 skoru en iyisidir

    Benchmark                           Mode   Cnt  Score      Error   Units
    test10_UsingEclipseMap              avgt   10    37.606 ±   0.790  µs/op
    test3_UsingForEachAndJava8          avgt   10    50.368 ±   0.887  µs/op
    test6_UsingForAndIterator           avgt   10    50.332 ±   0.507  µs/op
    test2_UsingForEachAndMapEntry       avgt   10    51.406 ±   1.032  µs/op
    test1_UsingWhileAndMapEntry         avgt   10    52.538 ±   2.431  µs/op
    test7_UsingJava8StreamApi           avgt   10    54.464 ±   0.712  µs/op
    test4_UsingKeySetAndForEach         avgt   10    79.016 ±  25.345  µs/op
    test5_UsingKeySetAndIterator        avgt   10    91.105 ±  10.220  µs/op
    test8_UsingJava8StreamApiParallel   avgt   10   112.511 ±   0.365  µs/op
    test9_UsingApacheIterableMap        avgt   10   125.714 ±   1.935  µs/op
    
  3. 100000 elemanlı harita için 1184.767 skoru en iyisidir.

    Benchmark                          Mode   Cnt  Score        Error    Units
    test1_UsingWhileAndMapEntry        avgt   10   1184.767 ±   332.968  µs/op
    test10_UsingEclipseMap             avgt   10   1191.735 ±   304.273  µs/op
    test2_UsingForEachAndMapEntry      avgt   10   1205.815 ±   366.043  µs/op
    test6_UsingForAndIterator          avgt   10   1206.873 ±   367.272  µs/op
    test8_UsingJava8StreamApiParallel  avgt   10   1485.895 ±   233.143  µs/op
    test5_UsingKeySetAndIterator       avgt   10   1540.281 ±   357.497  µs/op
    test4_UsingKeySetAndForEach        avgt   10   1593.342 ±   294.417  µs/op
    test3_UsingForEachAndJava8         avgt   10   1666.296 ±   126.443  µs/op
    test7_UsingJava8StreamApi          avgt   10   1706.676 ±   436.867  µs/op
    test9_UsingApacheIterableMap       avgt   10   3289.866 ±  1445.564  µs/op
    

Grafikler (harita büyüklüğüne bağlı olarak perfomance testleri)

Enter image description here

Tablo (harita boyutuna bağlı olarak perfomance testleri)

          100     600      1100     1600     2100
test10    0.333    1.631    2.752    5.937    8.024
test3     0.309    1.971    4.147    8.147   10.473
test6     0.372    2.190    4.470    8.322   10.531
test1     0.405    2.237    4.616    8.645   10.707
test2     0.376    2.267    4.809    8.403   10.910
test7     0.473    2.448    5.668    9.790   12.125
test9     0.565    2.830    5.952   13.220   16.965
test4     0.808    5.012    8.813   13.939   17.407
test5     0.810    5.104    8.533   14.064   17.422
test8     5.173   12.499   17.351   24.671   30.403

Bütün testler açık GitHub.


677
2018-02-22 16:37



@Viacheslav: çok güzel cevap. Sadece Java8 apis'in nasıl durduğunu merak ederek, referans noktanızda lambdaları yakalayarak ... (ör. long sum = 0; map.forEach( /* accumulate in variable sum*/); yakalar sum uzun, hangisinden daha yavaş olabilir stream.mapToInt(/*whatever*/).sum Örneğin. Tabii ki, her zaman yakalama durumundan kaçınamazsınız, ancak bu tezgah için makul bir ek olabilir. - GPI
8 testin yanlış. senkronizasyon olmadan farklı threadlardan aynı değişkene erişir. Değişmek AtomicInteger sorunu çözmek için. - talex
@ZhekaKozlov: akıl almaz büyük hata değerlerine bakın. Bir test sonucunu düşünün x±e aradan sonuç olduğunu ima eder x-e için x+e, bu yüzden en hızlı sonuç (1184.767±332.968) aralıkları 852 için 1518ikinci en yavaş ise (1706.676±436.867) arasında çalışır 1270 ve 2144Sonuç olarak, sonuçlar hala önemli ölçüde örtüşüyor. Şimdi en yavaş sonuca bak, 3289.866±1445.564arasındaki fark, 1844 ve 4735 ya sen bilmek Bu test sonuçlarının anlamsız olduğu. - Holger
Ne dersin map.entrySet().stream().[parallel().]mapToInt(e -> e.getKey() + e.getValue()).sum();? - glglgl
3 ana uygulamayı karşılaştırmaya ne dersiniz: HashMap, LinkedHashMap ve TreeMap? - Thierry


Java 8'de yeni lambda özelliklerini kullanarak temiz ve hızlı bir şekilde yapabilirsiniz:

 Map<String,String> map = new HashMap<>();
 map.put("SomeKey", "SomeValue");
 map.forEach( (k,v) -> [do something with key and value] );

 // such as
 map.forEach( (k,v) -> System.out.println("Key: " + k + ": Value: " + v));

Türü k ve v derleyici tarafından çıkarılacak ve kullanılmasına gerek yoktur Map.Entry Artık.

Tereyağından kıl çeker gibi!


227
2017-10-21 10:15



Bir harita ile ne yapmak istediğinize bağlı olarak, döndürülen girişlerde akış API'sini de kullanabilirsiniz. map.entrySet().stream()  docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html - Vitalii Fedorenko
Lambda ifadenizin dışında bildirilen son olmayan değişkenlere forEach () içinden başvurmak istiyorsanız bu işe yaramaz. - Chris
@Chris Doğru. Kullanmaya çalışırsanız işe yaramaz etkin olmayan final lambda dışından değişkenler. - Austin Powers


Evet, sipariş belirli Harita uygulamasına göre değişir.

@ ScArcher2 daha zarif Java 1.5 sözdizimine sahiptir. 1.4'de, böyle bir şey yapardım:

Iterator entries = myMap.entrySet().iterator();
while (entries.hasNext()) {
  Entry thisEntry = (Entry) entries.next();
  Object key = thisEntry.getKey();
  Object value = thisEntry.getValue();
  // ...
}

196
2017-09-05 21:15



(For Iterator entries = myMap.entrySet (). İterator (); entries.hasNext ();) {...} Bu sözdizimi ile 'giriş' kapsamı yalnızca for döngüsüne indirilir. . - HanuAthena
@jpredham Kullanmakta haklısın for olarak yapılandır for (Entry e : myMap.entrySet) koleksiyonu değiştirmenize izin vermeyecektir, ancak @HanuAthena örneğinin çalışması gerektiği anlamına gelir, çünkü size Iterator kapsamında. (Bir şey özlemediğim sürece ...) - pkaeding
IntelliJ bana hata veriyor Entry thisEntry = (Entry) entries.next();: tanımıyor Entry. Bu başka bir şey için sözde kod mu? - JohnK
@JohnK'yi içe aktarmayı deneyin java.util.Map.Entry. - pkaeding
Bu çözümle, anahtar ve değer türü önemli değil. Teşekkür ederim. - Andreas L.


Bir harita üzerinde yinelemek için tipik kod:

Map<String,Thing> map = ...;
for (Map.Entry<String,Thing> entry : map.entrySet()) {
    String key = entry.getKey();
    Thing thing = entry.getValue();
    ...
}

HashMap Kanonik harita uygulamasıdır ve garanti vermez (ya da üzerinde herhangi bir mutasyon işlemi yapılmazsa siparişi değiştirmemelidir). SortedMap anahtarların doğal sıralamasına dayalı girişleri döndürür, veya Comparator, sağlanırsa. LinkedHashMap Girdilerin nasıl yapıldığına bağlı olarak giriş sırasına veya erişim düzenine geri döner. EnumMap girişleri doğal sırayla döndürür.

(Güncelleme: Bence bu artık doğru değil.) Not, IdentityHashMap  entrySet Yineleyici şu anda aynı döndüren özel bir uygulamaya sahiptir. Map.Entry içindeki her öğe için örnek entrySet! Ancak, her zaman yeni bir yineleyici ilerler Map.Entry Güncellendi.


101
2017-09-05 21:26



EnumMap ayrıca IdentityHashMap ile birlikte bu tuhaf davranışa sahiptir. - Premraj
"LinkedHashMap, [...] erişim sırasındaki [...]" girişleri döndürür ... ... böylece öğelere eriştiğiniz sıraya erişirsiniz? Tatokolojik ya da bir digresyon kullanabilen ilginç bir şey. ;-) - jpaugh
@jpaugh Sadece doğrudan erişim LinkedHashMap sayın. Olanlar iterator, spliterator, entrySetvb. siparişi değiştirmeyin. - Tom Hawtin - tackline
Bu çok kullanışlı olabileceği gibi geliyor; ya da, dönüşümlü olarak kişinin kendini kirletmesi için harika bir yol. İlginç tidbit için teşekkürler! - jpaugh
1. gerçi → Eğer? 2. Son paragraf bir fırçadan yararlanabilir. - Peter Mortensen


Yineleyici ve jenerik kullanımı örneği:

Iterator<Map.Entry<String, String>> entries = myMap.entrySet().iterator();
while (entries.hasNext()) {
  Map.Entry<String, String> entry = entries.next();
  String key = entry.getKey();
  String value = entry.getValue();
  // ...
}

89
2017-08-18 17:34



Koymalısın Iterator for döngüsünde kapsamını sınırlamak için. - Steve Kuo
@SteveKuo "Kapsamını sınırlamak" ile ne demek istiyorsun? - StudioWorks
@StudioWorks for (Iterator<Map.Entry<K, V>> entries = myMap.entrySet().iterator(); entries.hasNext(); ) { Map.Entry<K, V> entry = entries.next(); }. Bu yapıyı kullanarak, değişkenin görünürlüğünü sınırlandırırız. entries döngü için. - ComFreek
@ComFreek Oh, anladım. Bunun çok önemli olduğunu bilmiyordum. - StudioWorks


Bu iki bölümden bir soru:

Bir haritanın girişleri üzerinde yineleme nasıl - @ ScArcher2 var cevap bu mükemmel.

Yineleme sırası nedir - eğer sadece kullanıyorsan Mapsonra kesinlikle konuşmak gerekirse sipariş garantisi yok. Bu nedenle, herhangi bir uygulama tarafından verilen siparişe güvenmemeniz gerekir. Ancak SortedMaparayüz uzatır Map ve tam olarak aradığınız şeyi sağlar - uygulamalar, tutarlı bir sıralama düzeni verir.

NavigableMap başka bir yararlı uzantısı - bu bir SortedMap Anahtar kümesindeki sıralı konumlarına göre giriş bulmak için ek yöntemlerle. Potansiyel olarak bu, ilk etapta yineleme ihtiyacını ortadan kaldırabilir. entry kullandıktan sonra higherEntry, lowerEntry, ceilingEntryveya floorEntry yöntemleri. descendingMap yöntem bile size açık bir yöntem sunar geçiş sırasını tersine çevirme.


76
2017-09-05 22:15





Harita üzerinde yinelemenin birkaç yolu vardır.

Burada, haritadaki bir milyonlarca değerli değer çiftini saklayarak harita üzerinde saklanan ortak bir veri seti için performanslarının karşılaştırılması ve harita üzerinde yineleme yapılacaktır.

1) kullanma entrySet() her döngü için

for (Map.Entry<String,Integer> entry : testMap.entrySet()) {
    entry.getKey();
    entry.getValue();
}

50 milisaniye

2) kullanma keySet() her döngü için

for (String key : testMap.keySet()) {
    testMap.get(key);
}

76 milisaniye

3) kullanma entrySet() ve yineleyici

Iterator<Map.Entry<String,Integer>> itr1 = testMap.entrySet().iterator();
while(itr1.hasNext()) {
    Map.Entry<String,Integer> entry = itr1.next();
    entry.getKey();
    entry.getValue();
}

50 milisaniye

4) kullanma keySet() ve yineleyici

Iterator itr2 = testMap.keySet().iterator();
while(itr2.hasNext()) {
    String key = itr2.next();
    testMap.get(key);
}

75 milisaniye

Sevk ettim this link.


62
2018-02-05 06:42



Çok güzel örnek! # 1 yolunu kullanmayı tercih ederim. - Phuong