HashMap
HashMap中的属性:
1.capacity:容量,Node数组的大小(桶的数量),值为2的N次幂 2.threshold:扩容阈值,等于capacity * load factor 当size大于此值时进行扩容 3.loadFactor:负载因子,默认为0.75f 4.size属性是指map中的键值对个数 5.initialCapacity hashMap初始化时设置的容量大小(Node数组的长度),initialCapacity = (需要存储的元素个数 / 负载因子) + 1,如果可以确定要存储的键值对个数可以通过该公式计算出 容量,然后threshold如果暂时无法确定初始值大小,请设置为16(即默认值)。如果,需要放置1024个元素,由于没有设置容量初始大小, 随着元素的不断增加,容量7次被迫扩大,resize需要重建hash,严重影响性能。 官方的建议是initailCapacity设置成2的n次幂;。
HashMap常见问题:
阿里巴巴开发规范中,推荐用户在初始化HashMap时,应指定集合初始值大小。 一、原因 当我们new一个HashMap没有对其容量进行初始化的时候,系统会默认创建一个16大小的集合。当我们使用的集合太小时,就会造成内存的浪费,而当HashMap的容量超过临界值时,HashMap就会扩容到下一个2的指数幂(2->4,4->8,8->16)。扩容(resize)时,HashMap会重新建立hash表,重新计算没个元素的位置,这是很消耗资源的。 二、合理的设置 当我们使用HashMap(int initialCapacity)来初始化容量的时候,jdk会默认帮我们计算出一个相对合理的值当做初始容量。当HashMap的容量值超过了临界值(threshold)时就会扩容, threshold = HashMap的容量值0.75,比如初始化容量为8的HashMap当大小达到80.75=6时将会扩容到16。当我们设置HashMap的初始化容量是遵循expectedSize(预期大小)/0.75+1,比如expectedSize是6时 6/0.75+1=9,此时jdk处理后会被设置成16,大大降低了HashMap被扩容的几率。 当我们通过HashMap(int initialCapacity)设置初始容量时,HashMap不一定会采取我们设置的初始容量,会根据计算得到一个新的值(2的指数幂),以保证hash的效率。 PS.为什么容量一定要是2的指数幂? 简答:1.节约空间 2.让元素分布均匀,put和get获取map数组小标方式 h & (length-1),h:为插入元素的hashCode,length:为map长度;&:与操作,如果length为2的次幂 则length-1 转化为二进制必定是11111……的形式,再与h的二进制与操作效率会非常的快,而且空间不浪费; 如果length不是2的次幂,比如length为15,则length-1为14,对应的二进制为1110,再与h与操作,最后一位都为0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率, 减慢了查询的效率!这样就会造成空间的浪费。
ArrayList
初始时构造可以设置(initialCapacity参数)集合的大小,来避免多次的扩容浪费性能。 由数组组成,新增的时候如果空间不够 1.5倍数进行扩容,原数组数据通过Arrays.copyWrite到新的数组中 遍历的时候不能做修改操作,否则会抛异常,遍历的时候会记录当前的modCount,以为做修改操作会改变modCount值,比较发现改变抛出异常!
CopyOnWriteArrayList
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加, 而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后, 再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。 所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
当每次添加一个元素时才动态扩展数组大小为1.每次扩展为size()+1; 这里不管数组大小是否足够,每次都会新建一个数组,大小为原数组+1。 注意到没,每次添加元素时都会复制原数组,在原数组上+1,进行添加元素。 在添加的时候加锁,但是读取元素时读取的还是老数组,所以读不用加锁,不影响读的问题。但是缺点也很明显,就是在某一个时刻内存中会多了一个数组,随着list的数组量多时,在添加一个元素时,内存会一倍的被占用。
数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。 读的时候不需要加锁,因为读取到时候读的是array数组。如果这个时候有数据添加或者删除,操作的都是新数组,并不会影响到我们读。
Set和List的重要区别:不重复
HashSet 基于HashMap实现、非线程安全,初始化时初始化一个hashMap,存储key是传入的value,value是object CopyOnWriteArraySet 基于CopyOnWriteArrayList 线程安全 ConcurrentSkipListSet 基于ConcurrentSkipListMap 线程安全,有序,查询快
|