如果keySet()维护HashMap的顺序,为什么我们需要LinkedHashMap?

dgu*_*091 3 java collections hashmap linkedhashmap keyset

public class HashMapKeySet {

public static void main(String[] args) {
    Map<HashCodeSame,Boolean> map=new HashMap();

    map.put(new HashCodeSame(10),true);
    map.put(new HashCodeSame(2),false);

    for(HashCodeSame i:map.keySet())
        System.out.println("Key: "+i+"\t Key Value: "+i.getA()+"\t Value: "+map.get(i)+"\t Hashcode: "+i
                .hashCode());

    System.out.println("\nEntry Set******");
    for(Map.Entry<HashCodeSame, Boolean> i:map.entrySet())
        System.out.println("Key: "+i.getKey().getA()+"\t Value: "+i.getValue()+"\t Hashcode: "+i.hashCode());

    System.out.println("\nValues******");
    for(Boolean i:map.values())
        System.out.println("Key: "+i+"\t Value: "+map.get(i)+"\t Hashcode: "+i.hashCode());

}

static class HashCodeSame{

    private int a;

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }

    HashCodeSame(int a){
        this.a=a;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        HashCodeSame that = (HashCodeSame) o;

        return a == that.a;

    }

    @Override
    public int hashCode() {
        return 1;
    }
}
Run Code Online (Sandbox Code Playgroud)

}

如果您在上面的示例中可以看到,我已经明确地使hashcode()在所有情况下返回1,以检查当hashmap中的key.hashcode()发生冲突时会发生什么.会发生什么,为这些Map.Entry对象维护链接列表,例如

1(key.hashcode())将链接到<2,false>将链接到<10,true>

(正如我所理解的那样,在真值之后输入错误值).

但是当我执行keySet()时,先返回true,然后返回false,而不是先返回false.

所以,我在这里假设,因为keySet()是一个集合并设置维护顺序,我们在迭代时得到true和false.但是,再说一遍,为什么我们不说hashmap维护顺序,因为检索的唯一方法是按顺序.或者为什么我们使用LinkedHashMap?

 Key: DS.HashMapKeySet$HashCodeSame@1    Key Value: 10   Value: true     Hashcode: 1
Key: DS.HashMapKeySet$HashCodeSame@1     Key Value: 2    Value: false    Hashcode: 1

Entry Set******
Key: 10  Value: true     Hashcode: 1230
Key: 2   Value: false    Hashcode: 1236

Values******
Key: true    Value: null     Hashcode: 1231
Key: false   Value: null     Hashcode: 1237
Run Code Online (Sandbox Code Playgroud)

现在,当我添加chsnge时,hashcode方法返回一个类似的东西

@Override
    public int hashCode() {
        return a;
    }
Run Code Online (Sandbox Code Playgroud)

我得到了逆序.另外还有

    map.put(new HashCodeSame(10),true);
    map.put(new HashCodeSame(2),false);
    map.put(new HashCodeSame(7),false);
    map.put(new HashCodeSame(3),true);
    map.put(new HashCodeSame(9),true);
Run Code Online (Sandbox Code Playgroud)

收到的输出是,

    Key: DS.HashMapKeySet$HashCodeSame@2     Key Value: 2    Value: false    Hashcode: 2
Key: DS.HashMapKeySet$HashCodeSame@3     Key Value: 3    Value: false    Hashcode: 3
Key: DS.HashMapKeySet$HashCodeSame@7     Key Value: 7    Value: false    Hashcode: 7
Key: DS.HashMapKeySet$HashCodeSame@9     Key Value: 9    Value: true     Hashcode: 9
Key: DS.HashMapKeySet$HashCodeSame@a     Key Value: 10   Value: true     Hashcode: 10

Entry Set******
Key: 2   Value: false    Hashcode: 1239
Key: 3   Value: false    Hashcode: 1238
Key: 7   Value: false    Hashcode: 1234
Key: 9   Value: true     Hashcode: 1222
Key: 10  Value: true     Hashcode: 1221

Values******
Key: false   Value: null     Hashcode: 1237
Key: false   Value: null     Hashcode: 1237
Key: false   Value: null     Hashcode: 1237
Key: true    Value: null     Hashcode: 1231
Key: true    Value: null     Hashcode: 1231
Run Code Online (Sandbox Code Playgroud)

现在它又让我想知道,为什么订单会以有条理的方式进行.谁能详细解释一下hashmap的keySet(),entrySet()方法是如何工作的?

Stu*_*rks 6

HashMap 具有已定义的迭代顺序,并且LinkedHashMap 具有特定的迭代顺序.

难点HashMap在于,构建简单的示例很容易,其中迭代顺序是可预测的并且相当稳定,即使这不能保证.

例如,假设您这样做:

    Map<String, Boolean> map = new HashMap<>();
    String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    for (int i = 0; i < str.length(); i++) {
        map.put(str.substring(i, i+1), true);
    }
    System.out.println(map.keySet());
Run Code Online (Sandbox Code Playgroud)

结果是

[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]
Run Code Online (Sandbox Code Playgroud)

嘿! 这些都是有序的!好吧,原因是String的hashCode()函数非常糟糕,而且对于单字符字符串来说它非常糟糕.这是String的hashCode()规范.本质上它是一个加法和乘法,但对于单字符字符串,它只是它的Unicode值char.所以上面单字符串的哈希码是65,66,... 90.内部表HashMap总是2的幂,在这种情况下,它是64个条目长.使用的表项是密钥的hashCode()值右移16位并与其自身进行异或,以表大小为模.(请参阅此处的代码.)因此,这些单字符字符串最终位于HashMap表中的顺序存储区中,位于数组位置1,2,... 26.

密钥迭代按顺序通过桶进行,因此密钥最终按照它们放入的顺序出现.再次,这不能保证,因为各种实现的特性,它恰好以这种方式工作如上所述.

现在考虑HashCodeSame其中的hashCode()功能1次,每次返回.将一些这些对象添加到a HashMap将导致它们全部结束在同一个存储桶中,并且由于迭代按顺序遍历链接列表,它们将按顺序出现:

    Map<HashCodeSame, Boolean> map = new HashMap<>();
    for (int i = 0; i < 8; i++) {
        map.put(new HashCodeSame(i), true);
    }
    System.out.println(map.keySet());
Run Code Online (Sandbox Code Playgroud)

(我添加了一个toString()显而易见的方法.)结果是:

[HCS(0), HCS(1), HCS(2), HCS(3), HCS(4), HCS(5), HCS(6), HCS(7)]
Run Code Online (Sandbox Code Playgroud)

同样,密钥按顺序出现,因为实现的巧合,但出于不同的原因而不是上述.

可是等等!在JDK 8中,HashMap如果同一存储桶中出现过多条目,则会将存储桶从线性链表转换为平衡树.如果超过8个条目最终在同一个存储桶中,则会发生这种情况.我们试试看:

    Map<HashCodeSame, Boolean> map = new HashMap<>();
    for (int i = 0; i < 20; i++) {
        map.put(new HashCodeSame(i), true);
    }
    System.out.println(map.keySet());
Run Code Online (Sandbox Code Playgroud)

结果是:

[HCS(5), HCS(0), HCS(1), HCS(2), HCS(3), HCS(4), HCS(6),
HCS(18), HCS(7), HCS(11), HCS(16), HCS(17), HCS(15), HCS(13),
HCS(14), HCS(8), HCS(12), HCS(9), HCS(10), HCS(19)]
Run Code Online (Sandbox Code Playgroud)

底线是HashMap保持规定的迭代顺序.如果需要特定的迭代顺序,则必须使用LinkedHashMap或排序的映射,例如TreeMap.不幸的是,HashMap迭代顺序是相当稳定和可预测的,实际上,只是可预测到足以让人们认为它的顺序是明确定义的,而实际上并非如此.


为了帮助解决这个问题,在JDK 9中,新的基于哈希的集合实现将从运行到运行随机化它们的迭代顺序.例如:

    Set<String> set = Set.of("A", "B", "C", "D", "E",
                             "F", "G", "H", "I", "J");
    System.out.println(set);
Run Code Online (Sandbox Code Playgroud)

在JVM的不同调用中运行时,打印出以下内容:

[I, H, J, A, C, B, E, D, G, F]
[C, B, A, G, F, E, D, J, I, H]
[A, B, C, H, I, J, D, E, F, G]
Run Code Online (Sandbox Code Playgroud)

(迭代顺序是JVM的一个运行中的稳定.另外,现有的集合,如HashMap不能有自己的迭代顺序随机).