Java数据结构 Hashmap

Hash表有:

布隆过滤器、bitmap、HashSet、HashMap、ConcurrentHashMap

JDK的HashMap

HashMap不能由get是否为空来判断是否存在某个键

如果key存在,那么覆盖,不存在则存入整个键值对。

HashMap的一些参数

默认初始化长度为16

默认负载因子0.75

当table被使用75%以上时会二倍扩容,是效率和空间的折中,比如ArrayList扩容1.5因为扩容代价比较小,HashMap需要reHash等等。

数组中链表超过8转红黑树,为什么是8?

  • 这一段注释的内容和目的都是为了解释在java8 HashMap中引入Tree Bin(也就是放入数据的每个数组bin从链表node转换为red-black tree node)的原因
  • TreeNode虽然改善了链表增删改查的性能,但是其节点大小是链表节点的两倍
  • 虽然引入TreeNode但是不会轻易转变为TreeNode(如果存在大量转换那么资源代价比较大),根据泊松分布来看转变是小概率事件,性价比是值得的
  • 泊松分布是二项分布的极限形式,两个重点:事件独立、有且只有两个相互对立的结果
  • 泊松分布是指一段时间或空间中发生成功事件的数量的概率
  • 对HashMap table[]中任意一个bin来说,存入一个数据,要么放入要么不放入,这个动作满足二项分布的两个重点概念
  • 对于HashMap.table[].length的空间来说,放入0.75*length个数据,某一个bin中放入节点数量的概率情况如上图注释中给出的数据(表示数组某一个下标存放数据数量为0~8时的概率情况)
    • 举个例子说明,HashMap默认的table[].length=16,在长度为16的HashMap中放入12(0.75*length)个数据,某一个bin中存放了8个节点的概率是0.00000006
    • 扩容一次,16*2=32,在长度为32的HashMap中放入24个数据,某一个bin中存放了8个节点的概率是0.00000006
    • 再扩容一次,32*2=64,在长度为64的HashMap中放入48个数据,某一个bin中存放了8个节点的概率是0.00000006

所以,当某一个bin的节点大于等于8个的时候,就可以从链表node转换为treenode,其性价比是值得的。

理解:
  1. 链表占据的空间小一半:在1-8的时候使用链表,那么时间复杂度其实是O1的,不仅数据结构占用空间是红黑树的一半;
  2. 红黑树在数量较少时插入耗时:链表插入简单,根据编写人员给的数据,数据少的时候冲突时的插入发生概率较大,红黑树会有平衡,改色等操作,远远没有链表来的快速。
  3. 但是在大量数据冲突的时候,链表的时间复杂度就会达到O(N),尤其在Hash函数较差的时候,所以使用红黑树来有效降低高数据的查询O(logN)。
/* 默认参数,在Hash函数理想的情况下, 每个位置上Hash冲突发生的数据个数。
* 0:    0.60653066
* 1:    0.30326533
* 2:    0.07581633
* 3:    0.01263606
* 4:    0.00157952
* 5:    0.00015795
* 6:    0.00001316
* 7:    0.00000094
* 8:    0.00000006*/
关于两个Node结构空间的源码
//摘自ConcurrentHashMap源码,上方是Node,下方是TreeNode
//看起来没差多少,但是TreeNode其实是继承了Node的,所以父类的成员变量也是有的。
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;

TreeNode<K,V> parent;  // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;    // needed to unlink next upon deletion
boolean red;

hash的处理

hashCode

hashCode 表示对象在 hash 表中的位置,对于同一个对象来说,多次调用,返回相同的 hashCode。

  • 如果 Object.equal () 相等,Object.hashCode () 也必然相等。重写时也建议保证此特性。
  • 如果 Object.equal () 不相等,这并不要求 Object.hashCode () 也返回不同值。如果真出现这种情况,最好优化代码,充分利用 hash 表的性能。

重写hashCode:如果你重载了equals,比如说是基于对象的内容实现的,而保留hashCode的实现不变,那么很可能某两个对象明明是“相等”,而hashCode却不一样。

确定位置

数组结构内通过(n-1)&hash取余,取hash值对于当前容量的余。

Key–Hash—取模—>存储

判断key有三个部分
((k = p.key) == key || (key != null && key.equals(k)))
时间复杂度

O1,On,Ologn(红黑树)

Hashmap缺点:

速度快;但是存不了大数据,线程不安全的,扩容的时候会用空间的操作,不支持多线程,主要是put的时候。

HashTable:

线程安全。继承自Dictionary类实现了Map。方法是同步的,而HashMap中的方法缺省情况下是非同步的。

key和value都不能有null值。

ConcurrentHashMap:

分段锁(JDK1.7),默认16个分段锁。
JDK1.8 使用CAS加synchronized

Hash(散列函数)

数组是简单的Hash,但没有数组就没有散列表,数组的散列函数为:y=x。

  1. 直接寻址法
  2. 余数法
  3. 数字分析法
  4. 平方取中法
  5. 折叠法
  6. 随机数法
Java中的Hash函数
int hash(Object key){
    int h = key.hashCode();
    return (h^(h>>>16))&(capacity - 1);//cap为散列表大小
}

Hash冲突

两种方法解决Hash冲突

探测(线性寻址)+链表(拉链)
开放寻址:

如果出现散列冲突,就重新探测一个空闲位置,放入。

Hash函数不变,在寻找对应值时,额外进行比较,如果值不对应或者有delete标志,再向下探测

装不下:扩容

链路地址(更常用):

寻址后找到的是链表,即不存值,存链表指针 。

链表插入、删除快,但是查询慢。二叉搜索树可能会退化成链表,所以加入了红黑树。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值