Java HashMap keySet () 반복 순서가 일치합니까?


81

지도의 keySet () 메서드에서 반환 된 Set이 특정 순서를 보장하지 않는다는 것을 이해합니다.

제 질문은 동일 하게 보장 됩니까? 여러 반복에 걸쳐 순서를 ? 예를 들면

Map<K,V> map = getMap();

for( K k : map.keySet() )
{
}

...

for( K k : map.keySet() )
{
}

위의 코드에서 맵이 수정 되지 않았다고 가정 하면 keySet에 대한 반복 순서가 동일합니다. Sun의 jdk15 IT 사용 않습니다 같은 순서로 반복 처리를,하지만이 동작에 의존하기 전에, 나는 모두의 JDK가 같은 행동을 할 것인지 알고 싶습니다.

편집하다

나는 그것에 의지 할 수없는 대답에서 봅니다. 너무 나쁘다. 주문을 보장하기 위해 새로운 컬렉션을 만들 필요가없는 상황에서 벗어나고 싶었습니다. 내 코드는 반복하고 논리를 수행 한 다음 동일한 순서로 다시 반복해야했습니다. 순서를 보장 할 keySet에서 새로운 ArrayList를 생성합니다.


2
getMap () 메서드에서 반환되는 Map 구현을 제어합니까?
Andrey Adamovich 2009

2
당신은 확실히 할 수 있습니다 자신의 콜렉션을 만들지 않고 일관된 순서를 얻을. 다른 사람들이 언급했듯이 SortedMap을 참조하십시오. 따라서 getMap () 메서드가 대신 SortedMap을 반환하면 호출자는 일관된 순서를 기대할 수 있습니다.
PSpeed ​​2009

대답은 순서 것을 증명 .keySet()하고 .values()일치한다. 불행히도 허용되는 대답은 잘못되었습니다. @karoberts-좀 봐 주실 래요?
Harshal Parekh

답변:


52

API 문서에 보증이 명시되어 있지 않다면 의존해서는 안됩니다. 동작은 동일한 공급 업체의 JDK에서도 JDK의 한 릴리스에서 다음 릴리스로 변경 될 수 있습니다.

당신은 쉽게 세트를 얻은 다음 직접 정렬 할 수 있습니다.


3
다른 사람이 언급했듯이 getMap ()에서 반환되는 Map 인스턴스를 제어 할 수 있다면 SortedMap을 반환 할 수 있습니다. 이 경우 Map 대신 getMap ()에서 SortedMap을 명시 적으로 반환하고 싶을 것입니다.
Ken Liu

4
해시 맵과 HashSet의 반복 순서는 제 7 및 자바 자바 사이에서 변경
user100464

@KenLiu 안녕하세요 저는 Java를 처음 접했습니다. SortedMap을 얻는 방법에 대한 예제를 제공 할 수 있습니까? 감사합니다.
Cecilia

일관성이 없음을 증명할 수 있습니까? javadoc에서 "보증"이라는 단어를 언급하지 않는다고해서 일관성이 없다는 의미는 아닙니다.
Harshal Parekh

이 대답은 틀 렸습니다. 일관성이 있습니다. 나는 여기서 그것을 증명했다 .
Harshal Parekh

58

LinkedHashMap을 사용할 수 있습니다.반복 순서가 변경되지 않는 HashMap을 원하는 경우 .

또한 컬렉션을 반복하는 경우 항상 사용해야합니다. HashMap의 entrySet 또는 keySet에 대한 반복은 LinkedHashMap보다 훨씬 느립니다.


9

Map은 (클래스가 아니라) 인터페이스 일뿐입니다. 즉,이를 구현하는 기본 클래스가 다르게 동작 할 수 있으며 API의 keySet () 계약은 일관된 반복이 필요함을 나타내지 않습니다.

Map을 구현하는 특정 클래스 (HashMap, LinkedHashMap, TreeMap 등)를보고 있다면 소스를 확인하여 동작이 무엇인지 결정하기 위해 keySet () 함수를 구현하는 방법을 볼 수 있습니다. 알고리즘을 자세히 살펴보고 찾고있는 속성이 유지되는지 확인합니다 (즉, 맵에 반복 사이에 삽입 / 제거가없는 경우 일관된 반복 순서). 예를 들어 HashMap의 소스는 다음과 같습니다 (open JDK 6) : http://www.docjar.com/html/api/java/util/HashMap.java.html

JDK마다 크게 다를 수 있으므로 확실히 의존하지 않을 것입니다.

즉, 일관된 반복 순서가 정말로 필요한 경우 LinkedHashMap을 사용해 볼 수 있습니다.


Set 클래스는 그 자체로 요소에 대한 순서가 없으며 고유하다는 것만 보장합니다. 따라서 .keySet ()를 요청하면 보장 된 순서가없는 Set의 인스턴스를 반환합니다. 순서를 원하면 직접 수행하고 정렬해야합니다 (Collections.sort 또는 SortedSet 구현을 사용하여)
Matt

7

지도의 API는 보증하지 않습니다 어떤 경우에도 동일한 개체에 대한 방법의 여러 호출 사이, 어떠한 순서.

실제로는 여러 후속 호출에 대해 반복 순서가 변경되면 매우 놀라 울 것입니다 (맵 자체가 그 사이에 변경되지 않았다고 가정).하지만 API에 따라 이에 의존해서는 안됩니다.

편집-일관된 반복 순서에 의존하려면 정확히 이러한 보장을 제공 하는 SortedMap 을 원합니다 .


저를 5 초로 때려서 당신이 그것에 의지 할 수 있더라도 당신이해야할지 의문이 든다고 덧붙일 것입니다. 나는 그것이 매우 연약 해 보이기 때문에 왜 이것에 의존해야하는지 궁금합니다.
PSpeed ​​2009

5

재미로 매번 무작위 순서를 보장하는 데 사용할 수있는 코드를 작성하기로 결정했습니다. 이것은 주문에 의존하지만 그렇게해서는 안되는 경우를 포착 할 수 있도록 유용합니다. 다른 사람들이 말한 것보다 순서에 의존하려면 SortedMap을 사용해야합니다. Map을 사용하고 순서에 의존하는 경우 다음 RandomIterator를 사용하면이를 파악할 수 있습니다. 더 많은 메모리를 사용하기 때문에 테스트 코드에서만 사용합니다.

또한 Map (또는 Set)을 래핑하여 RandomeIterator를 반환하도록 할 수 있습니다. 그러면 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();
    }
}

4

LinkedHashMap에 동의합니다. 키로 HashMap을 정렬하려고 할 때 문제에 직면했을 때 내 발견과 경험을 넣었습니다.

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");

}

지도 항목을 인쇄하는 함수 showMap이 있습니다.

public void showMap (Map<Integer, String> map1) {
    for (Map.Entry<Integer,  String> entry: map1.entrySet()) {
        System.out.println("[Key: "+entry.getKey()+ " , "+"Value: "+entry.getValue() +"] ");

    }

}

이제 정렬하기 전에지도를 인쇄하면 다음과 같은 순서로 인쇄됩니다.

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] 

기본적으로 맵 키를 넣은 순서와 다릅니다.

이제 맵 키로 정렬 할 때 :

    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);

출력은 다음과 같습니다.

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] 

키 순서의 차이를 확인할 수 있습니다. 정렬 된 키 순서는 괜찮지 만 복사 된 맵의 키 순서는 이전 맵의 순서와 동일합니다. 이것이 유효한지 모르겠지만 동일한 키를 가진 두 개의 해시 맵의 경우 키 순서가 동일합니다. 이것은 키의 순서가 보장되지는 않지만이 JVM 버전의 HashMap이 구현 된 경우 키 삽입 알고리즘의 고유 한 특성으로 인해 동일한 키를 가진 두 개의 맵에 대해 동일 할 수 있음을 의미합니다.

이제 LinkedHashMap을 사용하여 정렬 된 항목을 HashMap에 복사 할 때 원하는 결과를 얻습니다 (자연 스럽지만 포인트는 아닙니다. 포인트는 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);

산출:

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] 

3

Hashmap은 맵의 순서가 시간이 지남에 따라 일정하게 유지된다는 것을 보장하지 않습니다.


2

그럴 필요는 없습니다. 맵의 keySet 함수는 Set을 반환하고 세트의 반복기 메서드는 설명서에서 다음과 같이 말합니다.

"이 세트의 요소에 대한 반복자를 리턴합니다. 요소는 특정 순서없이 리턴됩니다 (이 세트가 보증을 제공하는 일부 클래스의 인스턴스가 아닌 경우)."

따라서 이러한 클래스 중 하나를 보증과 함께 사용하지 않는 한 아무것도 없습니다.


2

맵은 인터페이스이며 문서에서 순서가 동일해야한다고 정의하지 않습니다. 즉, 주문에 의존 할 수 없습니다. 그러나 getMap ()에서 반환 된 Map 구현을 제어하는 ​​경우 LinkedHashMap 또는 TreeMap을 사용하여 반복 할 때마다 동일한 순서의 키 / 값을 얻을 수 있습니다.


2

tl; dr 예.


나는에 대한 반복 순서를 생각 .keySet()하고 .values()(자바 8) 일치한다.

증명 1 : HashMap임의의 키와 임의의 값으로 a 를 로드 합니다. 우리는 이것을 HashMap사용하여 반복 .keySet()하고 키를로드하고 이는 a에 해당하는 값입니다 LinkedHashMap(삽입 된 키와 값의 순서를 유지합니다). 그런 다음 .keySet()지도와 .values()두 지도를 비교합니다 . 항상 똑같이 나오고 결코 실패하지 않습니다.

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;
            }
        }
    }
}

증명 2 : 여기 에서는 클래스 table의 배열입니다 Node<K,V>. 배열을 반복하면 매번 같은 결과를 얻을 수 있습니다.

/**
 * 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;

담당 클래스 .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();
        }
    }
}

담당 클래스 .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();
        }
    }
}

두 내부 클래스를주의 깊게 살펴보십시오. 다음을 제외하고는 거의 동일합니다.

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

그들은 같은 배열에 반복 table지원 .keySet()KeySet클래스 .values()Values클래스입니다.


증명 3 : 이 대답 은 또한 명시 적으로 명시합니다. 따라서 예, keySet (), values ​​() 및 entrySet ()은 내부 연결 목록이 사용하는 순서대로 값을 반환합니다.

따라서, 상기 .keySet()와는 .values()일치한다.


1

논리적으로 계약서에 "특정 주문이 보장되지 않는다"고 말하고 "한 번에 나온 주문"이 특정 주문 이기 때문에 대답은 '아니오'이므로 동일한 방식으로 두 번 나오는 것에 의존 할 수 없습니다.


0

keySet () 메서드에서 반환 한 Set 인스턴스를 저장할 수도 있으며 동일한 순서가 필요할 때마다이 인스턴스를 사용할 수 있습니다.


1
되어 불구하고, 보장? 아니면 iterator()동일한 세트에서도 반복 순서가 다른 반복자 를 반환하기 위해 각 호출을 할 수 있습니까?
Matt Leidholm 2017
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.