java HashMap在不发生冲突的情况下get(key)时间复杂度是o(1)

map是一种key、value形式的键值对,将hash表和map结合即形成了HashMap。 在Java中HashMap的数据是以Entry数组的形式存放的,HashMap通过对key进行hash运算得到一个数组下标,然后将数据存放到Entry数组对应的位置,又因为不同的key进行hash运算可能会得到一个相同的数组下标,为了解决碰撞覆盖冲突,所以Entry本身又是一个链表的结构,即以后不同的key相同数组下标的数据的next会被赋值为已存在Entry链表,新的Entry会替换数组值。

今天上课,老师布置了一道java题,是统计文件中的重复单词;我想到用hashMap的方法,觉得这样的话时间复杂度为O(n);

但是被老师否决了,说是还需要遍历;后来我仔细看了一下hashmap的源代码,觉得老师的说法并不是正确的,应该就是o(n);

因为hashMap本身就是用空间来换时间的方法;它可以根据key,生成相应的hashcode,然后根据hashcode,利用indexof直接定位到链表的某个位置;

当然这是和其中的hash因子有关的,如果产生冲突必然要进行再处理;但是基于本题,冲突的事情应该是不需要考虑的;

老师的意思是现将文件中的所有单词进行排序,然后在进行统计;这样的方法最低的时间复杂度也是需要o(nlogn)的;所以我觉得还是hashMap效率更高!

最后附上hashMap的源代码,如有大神有更完美的见解,欢迎赐教,本菜虚心学习之!

源码如下:

HashMap 的put方法的源码解析

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public V put(K key, V value) {
         if (key ==  null )
             return putForNullKey(value);  // HashMap接收key为null的数据
         int hash = hash(key.hashCode());  //对key的hashCode再进行hash运算
         int i = indexFor(hash, table.length); //根据hash值和entry数组的大小计算出新增数据应该存放的数组位置
         for (Entry<k,v> e = table[i]; e !=  null ; e = e.next) {
              // for循环遍历找到的数组下标的entry,如果hash值和key都相等,则覆盖原来的value值       
             Object k;
             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                 V oldValue = e.value;
                 e.value = value;
                 e.recordAccess( this );
                 return oldValue;
             }
         }
 
         modCount++;
         //如果上面for循环没有找到相同的hash和key,则增加一个entry
         addEntry(hash, key, value, i);
         return null ;
     }</k,v>

?
1
2
3
4
5
6
7
void addEntry( int hash, K key, V value,  int bucketIndex) {
     Entry<k,v> e = table[bucketIndex];  //找到下标的entry
         //new 一个新的entry,赋值给当前下标数组
         table[bucketIndex] =  new Entry<k,v>(hash, key, value, e);
         if (size++ >= threshold)
             resize( 2 * table.length);
     }</k,v></k,v>
?
1
2
3
4
5
6
Entry( int h, K k, V v, Entry<k,v> n) {
             value = v;
             next = n;  //即将原来数组下标对应的entry赋值给新的entry的next
             key = k;
             hash = h;
         }</k,v>

    (1)hash值相同且key相等数据将被覆盖。

    (2)添加新的entry时,将已存在的数据下标的entry(可能是null)赋值给新entry的next,新entry将替换原数组下标的值。

HashMap的get方法源码解析 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public V get(Object key) {
         //key为null时特别处理
         if (key ==  null )
             return getForNullKey();
         int hash = hash(key.hashCode());
         //indexFor(hash, table.length) 根据hash值和数组长度计算出下标,然后遍历Entry链表
         for (Entry<k,v> e = table[indexFor(hash, table.length)];
              e !=  null ;
              e = e.next) {
             Object k;
             if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                 return e.value;
         }
         return null ;
     }</k,v>

总结

  • 一个对象当HashMap的key时,必须覆盖hashCode()和equals()方法,hashCode()的返回值尽可能的分散。
  • 当HashMap的entry的数组足够大,key的hash值足够分散时,即是可以实现一个entry数组下标最多只对应了一个entry,此时get方法的时间复杂度可以达到O(1)。
  • 在数组长度和get方法的速度上要达到一个平衡。数组比较长碰撞出现的概率就比较小,所以get方法获取值时就比较快,但浪费了比较多的空间;当数组长度没有冗余时,碰撞出现的概率比较大,虽然节省了空间,但会牺牲get方法的时间。
  • HashMap有默认的装载因子loadFactor=0.75,默认的entry数组的长度为16。装载因子的意义在于使得entry数组有冗余,默认即允许25%的冗余,当HashMap的数据的个数超过12(16*0.75)时即会对entry数组进行第一次扩容,后面的再次扩容依次类推。
  • HashMap每次扩容一倍,resize时会将已存在的值从新进行数组下标的计算,这个是比较浪费时间的。在平时使用中,如果能估计出大概的HashMap的容量,可以合理的设置装载因子loadFactor和entry数组初始长度即可以避免resize操作,提高put的效率。
  • HashMap不是线程安全的,多线程环境下可以使用Hashtable或ConcurrentHashMap。
以上源码部分借用了open开发经验库,感谢之!

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值