概述

这篇文章,我们打算探索一下java集合(collections)框架中map接口中hashmap的实现。map虽然是collctions框架的一部分,但是map并没有实现collection接口,而set和list是实现collection接口的。

简单来说,hashmap主要通过key存储value值,并且提供了添加,获取和操作存储value的方法。hashmap的实现基于hashtable。

hashmap内部呈现

key-value对在内部是以buckets的方式存储在一起,最终成为一个表。存储和检索操作的时间是固定的,也就是时间复杂度为o(1)。

这篇文章暂时不过于涉及hashmap的底层,我们先对hashmap有个整体认知。

put方法

map中通过put方法来存储一个value。

    /**
     * 建立键值对应关系,如果之前已经存在对应的key,
     * 返回之前存储的value,之前如果不存在对应的key,返回null
     */
    public v put(k key, v value) {
        return putval(hash(key), key, value, false, true);
    }

知识点一: 当map的调用put方法的时候,key对象被调用hashcode()方法,获得一个hash值供hashmap使用。
我们创建一个对象来证实一下。

public class mykey {
    private int id;
    
    @override
    public int hashcode() {
        system.out.println("调用 hashcode()");
        return id;
    }

    // constructor, setters and getters 
}

   @test
    public void mapkeytest(){
        hashmap<mykey,string> map = new hashmap<mykey, string>();
        string retv = map.put(new mykey(1),"value1");
    }

可以看到控制台的输出信息

调用 hashcode()

知识点二: hash()方法计算出的hash值可以标识它在buckets数组中的索引位置。

hashmap的hash()方法如下:可以与put方法进行关联。

static final int hash(object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashcode()) ^ (h >>> 16);
    }

hashmap有一个特点,它可以存储null的key和null的value。当key时null的时候,执行put方法,它会自动分配hash为0. 这也意味着key为null的时候没有hash操作,这样就避免了空指针异常。

get() 方法

为了获取存储在hashmap中的对象,我们需要知道与它对应的key。然后通过get方法把对应的key传到参数里。调用hashmap的get方法的时候,也会调用key对象的hashcode方法

    @test
    public void mapkeytest(){
        hashmap<mykey,string> map = new hashmap<mykey, string>();
        mykey key1 = new mykey(1);
        map.put(key1,"value1");
        string retv =  map.get(key1);
    }

控制台上可以看到两行输出

调用 hashcode()
调用 hashcode()

hashmap中的集合视图

hashmap提供了三种方式,让我们可以把key和value作为其它集合来使用。

set<k> keys = map.keyset()
collection<v> values = map.values()
set<entry<k, v>> entries = map.entryset();

注意: 在iteators创建完毕后,对map的任何结构修改,都会抛出一个异常。

@test
public void giveniterator_whenfailsfastonmodification_thencorrect() {
    map<string, string> map = new hashmap<>();
    map.put("name", "baeldung");
    map.put("type", "blog");
 
    set<string> keys = map.keyset();
    iterator<string> it = keys.iterator();
    map.remove("type");
    while (it.hasnext()) {
        string key = it.next();
    }
}

// 会抛出java.util.concurrentmodificationexception异常

hashmap中唯一允许的修改是在iterator中移除元素。

public void giveniterator_whenremoveworks_thencorrect() {
    map<string, string> map = new hashmap<>();
    map.put("name", "baeldung");
    map.put("type", "blog");
 
    set<string> keys = map.keyset();
    iterator<string> it = keys.iterator();
 
    while (it.hasnext()) {
        it.next();
        it.remove();
    }
 
    assertequals(0, map.size());
}

hashmap在iterator上的性能相比于linkedhashmap和treemap,性能非常糟糕。最差情况下为o(n),n为hashmap中条目的个数。

hashmap性能

hashmap的性能主要有两个参数影响,初始容量和负载因子。初始容量为map底层桶数组的长度,负载因子为当桶容量的长度为多大的时候,重新开辟新的空间。

    int threshold;
    final float loadfactor;

默认的初始容量为16,默认的负载因子为0.75. 我们也可以自定义它们的值。

map<string,string> hashmapwithcapacity=new hashmap<>(32);
map<string,string> hashmapwithcapacityandlf=new hashmap<>(32, 0.5f);

初始容量:

大的初始容量用于条目数较多,但是少量迭代(iteration)
小的初始容量用于条目数较少,但是多次迭代(iteration)

负载因子:
0.75是一个很折衷的方案了。在我们初始化hashmap的时候,初始容量和负载因子都应该考虑在内,比如为了减少重新hash的操作,初始容量乘以负载因子应该大于能存储的最大条目数,这样就不会发生重新hash的操作。

最后

hashmap内部有很多东西值得探索,这篇仅仅对hashmap做了一层表面的分析。接下来会深入分析。

到此这篇关于java之hashmap案例详解的文章就介绍到这了,更多相关java之hashmap内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!