Java - Hashmap retreival sequence

时间:2017-03-09 09:59:43

标签: java collections hashmap

import java.util.HashMap;
import java.util.Map.Entry;
public class TestString {
    public static void main(String[] args) {
        System.gc();

        String str = "deepak";
        int length = str.length();
        System.out.println("str " + str + " length " + length);

        HashMap<Character,Integer> map = new HashMap<Character,Integer>();
        for(int i=0; i<length; i++){
            char ch = str.charAt(i);
            if(map.containsKey(ch)){
                Integer val = map.get(ch);
                map.put(ch, val+1);
            }else{
                map.put(ch, 1);
            }
        }

        for (Entry<Character, Integer> entry : map.entrySet())
        {
            int hashCode = entry.hashCode();
            char key = entry.getKey();
           // int hash = hash();
            System.out.println("hashcode  " + hashCode + " hashcode of key>> " + entry.getKey().hashCode() + " key : " + key);
        }
        System.out.println(">>> " + map);
    }
}
  

输出:
  str deepak长度6

     

哈希码113键的哈希码&gt;&gt; 112键:p

     

哈希码96键的哈希码&gt;&gt; 97键:a

     

哈希码101键的哈希码&gt;&gt; 100键:d

     

哈希码103键的哈希码&gt;&gt; 101键:e

     

哈希码106密钥的哈希码&gt;&gt; 107键:k

     

>>> {p = 1,a = 1,d = 1,e = 2,k = 1}

任何人都可以帮助我理解程序和输出中的两件事:

  1. 地图对象打印的数据,如何在内部决定序列? 例如。它是打印序列p,a,d,e,k。

  2. entry.hashcode()和entry.key()。hashcode()有什么区别? 请参考输出来解释差异。

3 个答案:

答案 0 :(得分:0)

  1. 据我所知,HashMap并没有保证Entrys或键的顺序。如果您想订购它们,您将需要一个TreeMap或链接的地图。 See here.。 HashMap根本没有订单。

  2. 我已经评论了Java API的链接,这很好地解释了它。但我会解释它对你的输出的影响:

  3.   

    哈希码113键的哈希码&gt;&gt; 112键:p

    这意味着:条目HashCode是113.您的密钥的HashCode是112.您不打印条目的HashCode,即1.条目的HashCode使用密钥的HashCode和值的HashCode。那些HashCodes将被xored。

答案 1 :(得分:0)

迭代顺序未指定,从技术上讲,它是关键哈希码,实际地图实现,地图容量以及某些情况下特定地图实例的历史记录的函数。

您可以通过将代码更改为

来轻松显示对地图容量的依赖性
String str = "deepak";
int length = str.length();
System.out.println("str " + str + " length " + length);

HashMap<Character,Integer> map = new HashMap<>(100);
for(int i=0; i<length; i++) {
    char ch = str.charAt(i);
    map.merge(ch, 1, Integer::sum);
}

for(Entry<Character, Integer> entry: map.entrySet()) {
    int hashCode = entry.hashCode();
    Character key = entry.getKey();
    System.out.printf("hashcode %3d, hashcode of key %3d, key : %s%n",
                      hashCode, key.hashCode(), key);
}
map = new HashMap<>(map);
System.out.println(">>> " + map);

在我的环境中,打印

str deepak length 6
hashcode  96, hashcode of key  97, key : a
hashcode 101, hashcode of key 100, key : d
hashcode 103, hashcode of key 101, key : e
hashcode 106, hashcode of key 107, key : k
hashcode 113, hashcode of key 112, key : p
>>> {p=1, a=1, k=1, d=1, e=2}

显示具有不同容量的地图如何展示不同的迭代次序。

密钥和Map.Entry实例是不同的对象,因此,它们具有不同的哈希码并不奇怪。 Map.Entry的哈希码为well specified

  

映射条目e的哈希码定义为:

(e.getKey()==null   ? 0 : e.getKey().hashCode()) ^
(e.getValue()==null ? 0 : e.getValue().hashCode())
     

根据e1.equals(e2)的一般合同的要求,这可确保e1.hashCode()==e2.hashCode()隐含任意两个条目e1e2的{​​{1}}。

这符合Object.hashCode视图的预期行为。如果您有两个MapMapa,那么

    如果两张地图都有相同的密钥,则
  • b为真。
  • a.keySet().equals(b.keySet())如果两个地图具有相同的键,则每个地图都映射到相同的值,换句话说,两个地图都相等。< / p>

    这甚至是specified in Map.equals

      

    如果给定对象也是一个映射,并且两个映射表示相同的映射,则返回a.entrySet().equals(b.entrySet())。更正式地说,如果true,则两个地图m1m2代表相同的映射。

  • 由于键和条目不同,m1.entrySet().equals(m2.entrySet())只有a.keySet().equals(a.entrySet())为空时才会为真。

答案 2 :(得分:0)

有点不清楚你在这里要证明什么或者问,但是一个更简单的例子怎么样:

 HashMap<String, Integer> map = new HashMap<>();
    map.put("HZV", 1);
    map.put("CJX", 2);

    System.out.println(map); // {CJX=2, HZV=1}

    for (int i = 0; i < 16; ++i) {
        map.put("" + i, i);
    }

    for (int i = 0; i < 16; ++i) {
        map.remove("" + i);
    }

    System.out.println(map); // {HZV=1, CJX=2}

注意上面示例中的两个打印件如何存储相同的内容,地图中的条目是相同的;唯一的区别是它们的顺序不同

这是因为内部地图存储中的buckets数量增加了,因为条目为re-hashed,并且它们移动到其他存储桶。从地图检索条目时,显而易见there is no guaranteed sequence

这里的其他答案,已经指出,地图的规范也说明了。

在jdk-9不可变地图中可以看到更好:

Map<String, Integer> map2 = Map.of("1", 1, "2", 2, "3", 3);
System.out.println(map2);

map2的输出因运行而异!这样的输出完全合法(在不同的VM中运行两次):

{1=1, 3=3, 2=2}

{1=1, 2=2, 3=3}

这正是因为Map没有订单,所以他们在Map中实现了随机化模式。

修改

一点点解释。

我会尝试,但这是一个很重要的主题。因此,HashMap将使用buckets来存储它的键/值对。存储桶实际上是Node s(红色/黑色TreeNode或LinkedNode)的数组,具体取决于大小。条目使用密钥和模数的hashCode转到某个存储桶。好吧不是直接模块,而是使用& operator的技巧(搜索两个哈希vs modulo的力量)。重新调整大小(实际上是内部数组加倍 - 从而触发重新哈希)是在某些条件下完成的,大多数明显的是在达到load_factor时(但这不是唯一的) )。因此,如果您声明:new HashMap(16),则加载因子为0.75,因此当您添加13条目时,内部数组将重新调整大小为32个条目。这也将触发密钥的重新哈希(在引擎盖下还有一个在选择存储桶时要考虑的位 - 因此entires可能会移动 - 就像我提供的示例中一样)。这不依赖于JVM,而是依赖于实现。 这是jdk-8和jdk-9中此刻的行为。但无论如何你不应该关心,而是依赖于Map提供的一般合同。