HashMap类(1)----了解属性和构造方法

2 篇文章 0 订阅
1 篇文章 0 订阅

在了解属性前你得首先你得知道它的数据结构是怎样的。

数据结构课程中讲过一个哈希表,奈何读的大学不咋滴,老师几乎略过了这章,就讲了下结构是这样的,

如何解决哈希冲突的俩种方法(实际是有4种的),以至于我后续都不知道原来哈希表就是hashmap,

hashmap我一直翻译成了哈希图(原谅当时的无知,那时是个老菜逼了)。

这是我从百度图片找到的一张图片。

我们会把第一排这12个格子叫成桶(实际是数组),上述就用到了一种解决哈希冲突的方法,我们俗称是拉链法,这也是hashmap使用的方法,因为这样才能存储更多的数据。另外三种有线性探测,再平方探测,和伪随机探测,这三种方法都不会出现这种拉链形状的链条,一旦12个格子满了就无法存储数据了。

 

hasmap的13个属性极其说明:

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
-> 数组默认初始容量:16
static final int MAXIMUM_CAPACITY = 1 << 30;
-> 数组最大容量2 ^ 30 次方
static final float DEFAULT_LOAD_FACTOR = 0.75f;
-> 默认负载因子的大小:0.75
static final int MIN_TREEIFY_CAPACITY = 64;
-> 树形最小容量:哈希表的最小树形化容量,超过此值允许表中桶转化成红黑树
static final int TREEIFY_THRESHOLD = 8;
-> 树形阈值:当链表长度达到8时,将链表转化为红黑树
static final int UNTREEIFY_THRESHOLD = 6;
-> 树形阈值:当链表长度小于6时,将红黑树转化为链表
transient int modCount; -> hashmap修改次数
int threshold; -> 可存储key-value 键值对的临界值 需要扩充时;值 = 容量 * 加载因子
transient int size; 已存储key-value 键值对数量
final float loadFactor; -> 负载因子
transient Set< Map.Entry< K,V >> entrySet; -> 缓存的键值对集合
transient Node< K,V>[] table; -> 链表数组(用于存储hashmap的数据)

hasmap的四个构造方法配合源码讲解:

//1、无参构造函数,默认桶大小为16,最常用的方法
Map<K,V> map = new HashMap<>();

//将扩容因子设置为默认大小
 public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}



//2、一个参数的构造函数,在已知数据量大的情况下需要自己定义桶的大小
Map<K,V> map = new HashMap<>(64);

//这里交给了另一个构造函数进行初始化
public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

//3、俩个参数的构造函数,在已知数据量大的情况下可以自己设置桶大小和扩容因子
Map<K,V> map = new HashMap<>(64,0.85f);

//对桶大小和扩容因子做个最大最小的判断,接下来我们着重讲下tableSizeFor()方法
public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
}


----------------------------------------------------------------------------------------


    /**
     * Returns a power of two size for the given target capacity.
     */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

我们以输入10为例,按照这个过程运算后会得到16.

 

你可以按照这个过程都试试,你会发现得到的值是当前值往上取的2的次方数,如17会取32,33会取64等等(不得不说这算法谁想出来的啊,真顶)。

//4、参数为map的构造函数
Map<String,String> map = new HashMap<>(64,0.85f);
Map<String,String> map1 = new HashMap<>(map);

//实质就是copy了这个map
 public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
}
//这个putMapEntries方法这里就不细讲了,不然一直能讲下去,有兴趣的可以去看看

 

也许有人会疑惑上述的tableSizeFor方法为什么存在,为什么需要把桶的数量维持在2的次方。接下来再发散讲解下。

当向HashMap中添加一 个元素的时候(可以去查看下put方法),需要根据key的hash值,去确定其在数组中的具体位置。

HashMap为了 存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现就在把数据存到哪个

链表中的算法。这个算法实际就是取模,hash%length, 计算机中直接求余效率不如位移运算。所以源码中做了优化,使用

hash&(length-1),而实际上hash%length等于hash&(length-1 )的前提是length是2的n次幕。

       另外还有一点就是如果桶长度不为2的次方数,hash&(length-1)算出来的值极其容易相等,及会产生很多碰撞。你可以

自己思考下,2的次方-1那就会后边全是1,在做&运算的时候得到的数会更不易相等。(这里没做图了,改天记得就补上)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值