Java Hashmap Keyset()反復順は一貫していますか?
質問
MAPのKeySet()メソッドから返されたセットは、特定の順序を保証しないことを理解しています。
私の質問は、それが保証されていることです 同じ 複数の反復を注文します。例えば
Map<K,V> map = getMap();
for( K k : map.keySet() )
{
}
...
for( K k : map.keySet() )
{
}
上記のコードでは、マップが いいえ 修正された場合、キーセットを介した反復は同じ順序で行われます。 SunのJDK15を使用してください します 同じ順序で反復しますが、この動作に依存する前に、すべてのJDKが同じことをするかどうかを知りたいと思います。
編集
答えから、私はそれに依存することはできないとわかります。残念な。注文を保証するために新しいコレクションを構築する必要がないことで逃げたいと思っていました。私のコードは、繰り返し、いくつかのロジックを実行し、同じ注文で再び繰り返す必要がありました。 Keysetから新しいArrayListを作成するだけで、注文を保証します。
解決
APIドキュメントで保証されていると述べられていない場合は、依存してはいけません。動作は、同じベンダーのJDKからであっても、JDKの1つのリリースから次のリリースに変化する可能性があります。
セットを簡単に入手して、自分で並べ替えることができますよね?
他のヒント
使用できます LinkedHashmap 反復順序が変更されないハッシュマップが必要な場合。
さらに、コレクションを繰り返す場合は、常に使用する必要があります。 HashmapのエントリセットまたはKeysetを繰り返して、Linkedhasmapを超えるよりもはるかに遅くなります。
マップは(クラスではなく)インターフェイスのみです。つまり、それを実装する基礎となるクラス(および多くがあります)が異なる動作をする可能性があり、APIのkeyset()の契約は一貫した反復が必要であることを示していません。
マップ(Hashmap、Linkedhashmap、Treemapなど)を実装する特定のクラスを見ている場合、Keyset()関数をどのように実装してソースをチェックアウトすることで動作が何であるかを判断する方法を見ることができます。アルゴリズムを実際によく見て、探しているプロパティが保存されているかどうかを確認します(つまり、マップに反復間に挿入/削除がなかった場合の一貫した反復順序)。たとえば、ハッシュマップのソースはここにあります(JDK 6を開く): http://www.docjar.com/html/api/java/util/hashmap.java.html
JDKから次のJDKまで大きく異なる可能性があるため、間違いなく頼りません。
そうは言っても、一貫した反復順序が本当に必要なものである場合、LinkedHashmapを試してみてください。
MAPのAPIは保証されません どれか 同じオブジェクト上のメソッドの複数の呼び出しの間でさえ、何でも注文します。
実際には、複数の後続の呼び出しで反復順序が変更された場合(マップ自体が間に変化しないと仮定して)、私は非常に驚くでしょう - しかし、あなたはこれに依存するべきではありません(そしてAPIによると)。
編集 - 一貫性がある反復順に頼りたい場合は、 sortedmap これらの保証を正確に提供します。
楽しみのために、私はあなたが毎回ランダムな注文を保証するために使用できるいくつかのコードを書くことにしました。これは、注文に依存しているケースをキャッチできるように便利ですが、そうすべきではありません。注文に依存したい場合は、他の人が言ったように、sortedMapを使用する必要があります。マップを使用して、たまたま注文に依存している場合、次のランダムタイターを使用するとそれがキャッチされます。テストコードでのみ使用します。これは、より多くのメモリを使用しているよりも多くのメモリを使用しているからです。
また、マップ(またはセット)をラップして、randomeiteratorを返すこともできます。
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();
}
}
Hashmapは、マップの順序が時間の経過とともに一定のままであることを保証しません。
そうである必要はありません。マップのキーセット関数はセットを返し、セットのイテレーターメソッドはドキュメントにこれを示しています。
「このセットの要素上でイテレーターを返します。要素は特定の順序で返されます(このセットが保証を提供するクラスのインスタンスである場合を除きます)。」
したがって、保証付きのクラスのいずれかを使用していない限り、何もありません。
マップはインターフェイスであり、ドキュメントでは順序が同じであるべきであると定義していません。つまり、注文に頼ることはできません。ただし、getMap()によって返されたマップ実装を制御すると、LinkedHashMapまたはTreeMapを使用して、それらを繰り返して常に同じ順序のキー/値を取得できます。
論理的には、契約が「特定の順序が保証されていない」と書かれている場合、そして「一度に出てきた順序」は 特定の順序, 、答えはノーです。同じ方法で2回出てくることに依存することはできません。
Linkedhashmapのことに同意します。キーでハッシュマップをソートしようとしていたときに、問題に直面している間に私の発見と経験を置くだけです。
ハッシュマップを作成する私のコード:
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");
}
マップのエントリを印刷する関数ショーマップがあります。
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]
キーの順序の違いを見ることができます。キーの並べ替えられた順序は問題ありませんが、コピーされたマップのキーの順序は、以前のマップと同じ順序で再びあります。これが言うのが有効かどうかはわかりませんが、同じキーを持つ2つのハッシュマップの場合、キーの順序は同じです。これは、キーの順序は保証されていないが、このJVMバージョンのハッシュマップ実装の場合、キー挿入アルゴリズムの固有の性質のため、同じキーを持つ2つのマップで同じである可能性があることを声明に意味します。
linkedhashmapを使用してソートされたエントリをハッシュマップにコピーすると、希望の結果が得られます(これは自然でしたが、それはポイントではありません。ポイントはハッシュマップのキーの順序に関するものです)
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]
また、keyset()メソッドによって返されたセットインスタンスを保存することもでき、同じ注文が必要な場合はいつでもこのインスタンスを使用できます。