HashMap conCurrentHashmap Linkedhashmap Linkedhashset Arraymap SparseArray
Posted danfengw
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HashMap conCurrentHashmap Linkedhashmap Linkedhashset Arraymap SparseArray相关的知识,希望对你有一定的参考价值。
集合
集合分类
Java集合大致可以分为Set、List、Queue和Map四种体系。
其中Set代表无序、不可重复的集合;List代表有序、重复的集合;而Map则代表具 有映射关系的集合。Java 5 又增加了Queue体系集合,代表一种队列集合实现。
集合介绍
Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java 集合框架的根接口。
ArrayList、Vector 采用的是数组,其中Vector 是线程安全的,不合适高并发,LinkedList通过链表实现。
HashMap介绍
HashSet:底层采用的是HashMap
hashMap(java8 之前):数组+链表,后面改为 数组+链表+红黑树
HashMap
https://zhuanlan.zhihu.com/p/76735726
HashMap增删改查
增:HashMap的put方法的逻辑
(1)如果HashMap未被初始化过,则初始化
(2)对Key求Hash值,然后再计算下标
(3)如果没有碰撞,直接放入桶中
(4)如果碰撞了,以链表方式链接到后面
(5)如果链表长度超过阈值,就把链表转换成红黑树
(6)如果链表长度低于6,就把红黑树转回链表
(7)如果节点已经存在就替换旧值
(8)如果桶满了(容量16*加载因子0.75),就需要resize(扩容2倍后重排)
public V put(K key, V value)
// (2)对Key求Hash值,然后再计算下标
return putVal(hash(key), key, value, false, true);
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict)
Node<K,V>[] tab; Node<K,V> p; int n, i;
//(1)如果HashMap未被初始化过,则初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// (3)如果没有碰撞,直接放入桶中
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else
Node<K,V> e; K k;
//(7)如果节点已经存在就替换旧值
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//当前链表是否已经树化,树化就
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else
//(4)如果碰撞了,以链表方式链接到后面
//(5)如果链表长度超过阈值,就把链表转换成红黑树
for (int binCount = 0; ; ++binCount)
if ((e = p.next) == null)
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
//(7)如果节点已经存在就替换旧值
if (e != null) // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
++modCount;
//(8)如果桶满了(容量16*加载因子0.75),就需要resize(扩容2倍后重排)
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
查:HashMap的get方法
(1)通过hash值找到,链表/树 的第一个位置的元素,如果是则直接返回,否则就继续
(2)遍历链表/树,找到就直接返回,找不到则返回空
public V get(Object key)
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
final Node<K,V> getNode(int hash, Object key)
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null)
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null)
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
while ((e = e.next) != null);
return null;
HashMap其他问题
HashMap 如何有效减少碰撞
二次hash 、扰动函数、链表法
(1)扰动函数:促使元素位置分布均匀,减少碰撞机率
(2)使用final对象,并采用合适的equals和hashCode方法
1 hashMap底层实现原理
数组加原理 数组过多会转成树的方式
2 hashMap 怎么扩容的
根据扩容因子 和 所能承载的表的容器量 2的倍数扩容,在1.7 的时候扩容之后需要重新计算对应的index下标,1.8的时候是通过与原来容器长度做&运算,来获取元素应该放置的位置。
并且扩容时 1.7是先扩容再放元素 1.8是先放元素再扩容
HashMap扩容会存在的问题
多线程环境下,调整大小会存在条件竞争,容易造成死锁
rehasing是个耗时的过程
为什么长度最开始是16呢?
因为必须是2的幂
hashMap是线程安全的吗?想要线程安全需要用什么?
concurrentHashMap
hashMap 1.7 版本与1.8的差异
(1)结构上: 1.7 :数组+链表 1.8: 数组+链表+红黑树
(2)扩容:1.7:先扩容再放元素,需要重新计算index 1.8:先放元素再扩容,通过与原来容器长度做&运算来计算
(3)插入元素方式:1.7 是头插入 1.8:是尾插入
为什么推荐用SpareArray代替HashMap
SparseArray
(1)内部使用双数组,分别存储 Key 和 Value,Key 是 int[],用于查找 Value 对应的 Index,来定位数据在 Value 中的位置。
(2)使用二分查找来定位 Key 数组中对应值的位置,所以 Key 数组是有序的。
(3)使用数组就要面临删除数据时数据搬移的问题,所以引入了 DELETE 标记。
LinkedHashMap
原理
双向链表
两种有序
插入顺序和访问顺序
如何用它实现LRU
public class LruLinkedHashMap<K,V> extends LinkedHashMap<K,V>
private int size;
public LruLinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder)
super(initialCapacity,loadFactor,accessOrder);
size=initialCapacity;
@Override
protected boolean removeEldestEntry(Entry<K, V> eldest)
if (size()>size)
return true;
return false;
ConCurrentHashMap
ConCurrentHashMap:put方法
(1)判断Node[]数组是否初始化,没有就进行初始化
(2)通过hash定位数组的索引坐标,是否有Node节点,没有则用CAS进行添加(链表的头节点),添加失败则进入下一次循环
(3)检查到内部正在扩容,就帮助它一起扩容
(4)如果f!=null 就使用synchronized 锁住f元素(链表/红黑树的的头元素),如果是Node(链表结构)则执行链表的添加操作,如果是TreeNode(树型结构)则执行树的添加操作
(5)判断链表长度已经达到临界值8,就将链表转换为树结构。
如何优化Hashtable
通过锁细粒度化,将整锁拆解成多个锁进行优化,早期的conCurrentHashMap也是通过这种分段锁的方式优化的。
ConCurrentHashMap 锁优化
早起conCurrentHashMap通过分段锁的方式进行了优化,后续(现在)采用的是,CAS+synchronized使锁更细化。
synchronized只锁住链表或者红黑树的首节点
(1)首先使用无锁操作CAS插入头节点,失败则循环重试
(2)若头节点已存在,则尝试获取头节点的同步锁synchronized,在进行操作
ConCurrentHashMap 的size()和mappingCount 方法的异同,两者计算是否准确
多线程环境下如何进行扩容?
HashMap 、Hashtable、ConcurrentHashMap 区别
LinkedHashMap一般什么情况下用到
需要记录顺序的时候使用LinkedHashMap
相比较hashMap它是有序的,
LinkedHashMap的扩容
LinkedHashMap的迭代器遍历顺序
get set 去值的时候,头连表与尾连标的移动,扩容与删除
实现Lru
ArrayMap SpareArray
ArrayMap:要求key是泛型
SpareArray:要求key 是int
为什么性能管理的比hashMap好?
遍历
并发集合类
ArrayList copyOnWriteArrayList (相对与arrayList解决了什么问题?读锁与写锁的配合,锁的是什么对象,所以才更适合于什么场景),
阻塞队列 LinkedBlockqueue (Lru) ArrayBlockQueue
HashMap两个键的hashcode相同如何获取对应的内容呢?
HashMap中get方法如何实现的
HashMap的增删查改
concurrentHashMap 锁的颗粒度是怎样的
HashMap 复杂度是怎样的
HashMap与HashTable
HashSet 与 HashMap的区别(hashSet 底层是hashMap实现的,hashSet 不能为Null)
BlockingQueue
提供可阻塞的入队 和 出队操作
如果队列满了,入队操作则会被阻塞,直到有空间可用,如果队列空了,出队操作被阻塞,直到有元素可用。
主要用于生产者-消费者模式,在多线程场景时生产者线程在队列尾部添加元素而消费者线程则在队列头部消费元素,通过这种方式将任务的生产和消费进行隔离的目的
以上是关于HashMap conCurrentHashmap Linkedhashmap Linkedhashset Arraymap SparseArray的主要内容,如果未能解决你的问题,请参考以下文章
ConcurrentHashMap以及HashMap,HashTable的区别
HashMap和ConcurrentHashMap的区别,HashMap的底层源码。
HashMap线程不安全 | 线程安全的 ConcurrentHashMap