发布于2021-05-29 22:29 阅读(1357) 评论(0) 点赞(26) 收藏(5)
Iterator
取出所有元素,再逐一遍历,还可以使用get(int index)
获取指定下标的元素。Iterator
接口取得所有元素,再逐一遍历各元素。iterator
不能使用for循环,因为每次for循环体内通过get(i)
取得某一元素时都要对list重新进行遍历,性能消耗极大。indexOf
等返回元素的索引,并利用其进行遍历,使用indexOf
对list进行了遍历,当结果为空时会遍历整个列表。区别:
synchronized
修饰,非线程安全的,默认容量是16,允许有空的键和值。newsize=oldsize<<1
,容量一定为2的n次幂(保证为偶数,降低哈希冲突)。index
方法:index=hash&(tab.length-1)
。synchroized
)住整个HashTable,效率低ConcurrentHashMap
做了相关优化。(tab.length << 1) + 1
(保证每次扩容结果均为奇数)。index = (hash & 0x7FFFFFFF) % tab.length
synchronized
修饰)hashcode
。// HashMap中重新计算hash值的算法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
HashMap:index = hash & (tab.length – 1)
HashTable:index = (hash & 0x7FFFFFFF) % tab.length
数据结构:ReentrantLock+Segment+HashEntry
,一个Segment
中包含一个HashEntry
数组,每个HashEntry
又是一个链表结构。
元素查询:二次hash,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部。
锁:Segment分段锁,Segment继承了ReentrantLock
,锁定操作的Segment,其他的Segment不受影响,并发度为Segment个数,可以通过构造函数指定,数组扩容不会影响其他的Segment。get
方法无需加锁,volatile
保证。
数据结构:Synchronized+CAS+Node+红黑树
,Node的val和next都是用volatile
修饰,保证可见性查找、替换、赋值操作都使用CAS。
锁:锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写操作、并发扩容。读操作无锁:Node的val和next使用volatile
修饰,读写线程对该变量互相可见;数组用volatile
修饰,保证扩容时被读线程感知。
synchronizedList
底层相当于把集合的set、add、remove
方法加上synchronized
锁。List<Object> list = Collections.synchronizedList(new ArrayList<>());
CopyOnWriteArrayList
,其底层也是对增删改方法进行加锁final ReentrantLock lock = this.lock;
ArrayList
,根据业务,对set、add、remove
方法进行加锁控制。capacityIncrement
,或该值不大于0时,每次扩容为原来的1倍,否则扩容为capacityIncrement
值。remove、add
等方法加上synchronized
关键字来实现;ArrayList是非线程安全集合。CopyOnWriteArrayList
底层并非动态扩容数组,不能动态扩容,其线程安全是通过加可重入锁ReentrantLock
来保证的。CopyOnWriteArrayList
添加元素的时候,线程获取锁的执行权后,add方法会新建一个容量为(旧数组容量+1)的数组,将旧数组数据拷贝到该数组中,并将新加入的数据放到新数组尾部。CopyOnWriteArrayList
适用于读多写少的情况下(读写分离),因为每次调用修改数组结构的方法都需要重新新建数组,性能低。Comparator
接口,能便捷的实现内部元素的各种排序TreeMap(Comparetor c)
,但是性能比HashMap差。
红黑树并不是一个完美平衡二叉查找树,上图中,根节点P的左子树显然比右子树高。但左子树和右子树的黑节点的层数是相等的,也就是说任意一个节点到叶节点的路径都包含数量相同的黑节点,我们称红黑树这种平衡为黑色完美平衡。
红黑树性质:
核心目的是为了使插入的节点均匀分布,减少hash冲突。
HashMap构造方法可以指定集合的初始化容量大小,如:
// 构造一个带指定初始容量和默认负载因子(0.75)的空 HashMap。
HashMap(int initialCapacity)
当向HashMap中添加一个元素的时候,需要根据key的hash值,去确定其在数组中的具体桶位(寻址算法)。HashMap为了存取高效,减少碰撞,就是要尽量把数据分布均匀,每个链表长度大致相同,这个实现的关键是把数据存到哪个链表中的算法。
这个算法实际就是取模运算:hash%tab.length
,而计算机中取余运算效率不如位移运算,所以在源码中做了优化,使用hash&(tab.length-1)
来寻找桶位,而实际上hash % length
等于 hash & ( length - 1)
的前提是length必须为2的n次幂。
原因总结:
index = hash % length
,然而计算机进行取模预算的效率远不如位运算,因此需要被改进成 hash & (length - 1)
的方式寻址。本质上,两种方式计算得到的结果是相同的,即:hash & (length - 1) = hash % length
。这种情况下,HashMap双参构造函数会通过tableSizeFor(initialCapacity)
方法,得到一个最接近length且大于length的2的n次幂。
HashMap中重新计算hash值的方式如下:
static final int hash(Object key) {
int h;
// 如果key为null,则hash值为0,
// 否则调用key的hashCode()方法计算出key的哈希值然后赋值给h,
// 然后与 h无符号右移16位后的二进制数进行按位异或 得到最终的hash值,
// 这样做是为了使计算出的hash更分散,
// 为什么要更分散呢?因为越分散,某个桶的链表长度就越短,之后生成的红黑树越少,检索效率越高!
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
将 key 的 hashCode 的高 16 位和 hashCode 低 16 位 进行异或(XOR)运算,最终得到新的 hash 值。
为什么要这样进行操作?
如果当哈希值的高位变化很大,低位变化很小,这样就很容易造成哈希冲突,所以这里把高低位都利用起来,从而解决了这个问题。
hash的基本概念就是把任意长度的输入,经过hash算法之后,映射成固定长度的输出。
在程序中可能会碰到两个value值经过hash算法计算之后,算出了同样的hash值,这种情况就叫hash冲突。
HashMap进行扩容时,会伴随一次重新hash分配,并且会遍历旧数组中所有的元素,并将其迁移到扩容后的新数组中,旧数组中的数据迁移有三种情况:
rehash
方式非常巧妙,因为每次扩容都是翻倍,与原来计算的(n - 1) & hash
的结果相比,只是多了一个bit位,所以节点要么就在原来的位置,要么就被分配到原位置+旧容量这个位置。原文链接:https://blog.csdn.net/m0_52675592/article/details/117354160
作者:小泽圈儿郎
链接:http://www.javaheidong.com/blog/article/207810/8bd9d1234d4f894e3328/
来源:java黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 java黑洞网 All Rights Reserved 版权所有,并保留所有权利。京ICP备18063182号-2
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!