Java基础相关

HashMap以及其子类关键性总结

2020-04-13  本文已影响0人  Sincerity_

HashMap

1.7中的HashMap

负载因子: 给定默认容量为16 负载因子为0.75

其实真正存放数据的是 Entry<K,V>[] table,Entry 是 HashMap 中的一个静态内部类,它有key、value、next、hash(key的hashcode)成员变量 多个Entry就构成hashMap的数据结构 数组+链表

put()

get()

1.8中的HashMap

当Hash冲突严重时,在桶上形成的链表越来越长,这样在查询时的效率就会越来越低,时间复杂度为o(N)

TREEIFY_THRESHOLD = 8 用于判断是否将链表转为红黑树的阈值

桶中存放的数据结构为Node

put()
get()
但是 HashMap 原有的问题也都存在,比如在并发场景下使用时容易出现死循环
HashMap何时扩容

当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值(默认12)---即大于当前数组的长度乘以加载因子的值的时候,就要自动扩容。

HashMap扩容算法

扩容(resize) 就是重新计算容量,数组无法自动扩容 方法就是使用一个新数组代替已有的容量小的数组 2倍扩容

Hashmap如何解决散列碰撞

HashMap是利用拉链法 处理hashcode的碰撞问题 在调用HashMap的put或者get方法时,都会调用Hashcode方法区查找相关的key 当有冲突时在调用equals方法

HashMap基于hashing原理 通过put和get方法存取对象,当我们将键值对传递给put方法时,他调用对象的hashCode方法计算Hashcode 知道哦啊哦哈系统位置来存储对象,当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。

HashMap使用链表来解决碰撞问题,当碰撞发生了,对象将会存储在链表的下一个节点中。hashMap在每个链表节点存储键值对对象。当两个不同的键却有相同的hashCode时,他们会存储在同一个bucket位置的链表中。键对象的equals()来找到键值对。

Hashmap底层为什么是线程不安全的?

ConcurrentHashMap

1.7 中ConcurrentHashMap

ConcurrentHashMap在jdk1.7中使用了分段锁,其中segment继承于ReentranLock ,不会像HashTable那样不管是put还是get都去做同步处理,理论上ConcurrentHashMap支持CurrencyLevel(Segment数组数量)的线程并发,当每个线程占用锁访问一个Segment时,不会影响到其他的Segment

数据结构和HashMap一致 数组+链表

分段锁

ConcurrentHashMap里面元素存放在table数组中,分段锁就是把这个table分成多段,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,在ConcurrentHashMap中使用了一个包含16个锁的数组,每个锁保护所有散列桶的1/16,其中第N个散列桶由第(N mod 16)个锁来保护。假设使用合理的散列算法使关键字能够均匀的分部,那么这大约能使对锁的请求减少到越来的1/16。也正是这项技术使得ConcurrentHashMap支持多达16个并发的写入线程。

put()

首先通过Key定位到Segment,之后再对应的Segment中具体的put元素

get()

1.8中的ConCurrentHashMap

在1.7中已经解决了HashMap的并发问题 ,并且支持N个Segment这个多次数的并发,但是任然存在1.7中HashMap的问题,查询遍历链表效率低, 和1.8中的HashMap结构类似, 其中抛弃可原有的分段锁,采用了CAS+Synchronized来保证并发的安全

CAS

如果Obj内的Value和Expect相同 就证明没有其他线程改变过这个变量,name就更新它为update 如果这一步的CAS没有成功,那么就采用自选的方式继续进行CAS操作

对CAS的理解,CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做

CAS ABA问题

数据结构和HashMap一致 数组+链表+红黑树

put()
get()

1.8 在 1.7 的数据结构上做了大的改动,采用红黑树之后可以保证查询效率(O(logn)),甚至取消了 ReentrantLock(可重入锁) 改为了 synchronized(独占锁),这样可以看出在新版的 JDK 中对 synchronized 优化是很到位的。


ArrayMap

ArrayMap利用两个数组,mHashes用来保存每一个key的hash值,mArrray大小为mHashes的2倍,依次保存key和value

mHashes[index] = hash;
mArray[index<<1] = key;
mArray[(index<<1)+1] = value;

当插入时,根据key的hashcode()方法得到hash值,计算出在mArrays的index位置,然后利用二分查找找到对应的位置进行插入,当出现哈希冲突时,会在index的相邻位置插入。


SparseArray

SparseArray比HashMap更省内存,在某些条件下性能更好,主要是因为它避免了对key的自动装箱(int转为Integer类型),它内部则是通过两个数组来进行数据存储的,一个存储key,另外一个存储value,为了优化性能,它内部对数据还采取了压缩的方式来表示稀疏数组的数据,从而节约内存空间,我们从源码中可以看到key和value分别是用数组表示:

private int[] mKeys; 
private Object[] mValues;

同时,SparseArray在存储和读取数据时候,使用的是二分查找法。也就是在put添加数据的时候,会使用二分查找法和之前的key比较当前我们添加的元素的key的大小,然后按照从小到大的顺序排列好,所以,SparseArray存储的元素都是按元素的key值从小到大排列好的。 而在获取数据的时候,也是使用二分查找法判断元素的位置,所以,在获取数据的时候非常快,比HashMap快的多。

假设数据量都在千级以内的情况下:
  1. 如果key的类型已经确定为int类型,那么使用SparseArray,因为它避免了自动装箱的过程,

  2. 如果key为long类型,它还提供了一个LongSparseArray来确保key为long类型时的使用

  3. 如果key类型为其它的类型,则使用ArrayMap。

上一篇 下一篇

猜你喜欢

热点阅读