赞
踩
建议:集合的话主要看以下几个就可以了(工作两年以上),应届生的话尽量都看,集合的源码去看一下不多的。
概述:开发中基本用不上这个集合,因为我们取数据的时候一般用 ArrayList。我们一般也不会对其进行增删,更多的是修改。
特点:采用的是链表的数据结构,更适合删除和添加较多的场景。链表物理存储上是非连续的,各元素之间的逻辑顺序是通过链表的指针地址实现的。
采用的是数组的数据结构,相比较LinkedList更适合在查询较多的场景下使用(因为每当向数组中添加元素时,都会去检查添加后元素的个数是否会超出当前数组的长度,如果超出,就会扩容,扩容是通过复制引用的方式来实现的,所以不适合在增删比较多的情况下会比LinkedList慢),默认大小是10,扩容是原数组的1.5倍(原数组的长度+原数组长度右移一位)。
数组是通过一块连续的内存空间存储的,并且每个节点的大小相同,所以可以通过索引快速查找。
基于Hashmap实现,key就是存入的值,Hashmap中不允许key重复,value就是一个伪值(new Object)。
集合结束后,一般也会问Map,所以HashMap和ConcurrentHashMap一定要会。
kv键值对的形式,主要的数据结构是数组+链表(红黑树),默认大小是16,负载因子是0.75,扩容的话是原大小的两倍(这样的操作有利于key在数组中的均匀分布)。key在数组中的 index 是通过 (key.hasCode & 数组长度 - 1)算出来的。1.7会出现死循环的问题,因为1.7还用的头插法,所以两个线程在同时操作的时候可能会出现问题。1.8改成了尾链法,解决了这个问题,不过在高并发的情况下还是去使用ConcurrentHashMap。
下面两个图就解释了HashMap的基础结构
最重要的是putValue这个方法, 这个方法先是判断这个index的位置有没有值,没有的话直接将value值插入,如果有的话通过equal()方法是否相同,相同的话覆盖,不同的话使用尾插法将value插入,当链表的长度大于8并且数组大于64的时候会将链表转化外红黑树,这样的操作时为了加快查询速度(将查询速度从O(n)优化到O(logn)),插入成功会检查是否需要扩容。
(不重要)jdk1.7及之前,是通过Segment分段锁来实现的,将一个数组分成几段来实现,当某个段很大的时候性能会下降。
(重要!)jdk1.8之后,是通过synchronizd和CAS来保证线程安全的,进一步实现锁细化的概念,在put和transfer的操作会加锁,具体的话是通过Synchronized锁住Node,用CAS来替换值。
如果容量大于数组很多的话再加上散列算法不是很优秀的情况下容易出现链表过长的情况,这样会降低查询速度
同时由于负载因子的存在,容量就不会等于数组长度的情况
只要插入的位置扩容线程还未迁移到(还没轮到),就可以插入,当迁移到该插入的位置时,就会阻塞等待插入操作完成再继续迁移 。
get操作不会阻塞,因为迁移期间是用的类似复制引用的方式,所以原hash桶上的数据不会收到影响,因此会正常访问原hash桶上的数据,如果查询的桶被迁移了,就会通过ForwardingNode的指针去查已扩容的新数组
因为Node成员的value使用volatile修饰的
不可以,因为源码中是这样判断的,进行put()操作的时候如果key为null或者value为null,会抛出NullPointerException空指针异常。
如果ConcurrentHashMap中存在一个key对应的value是null,那么当调用map.get(key)的时候,必然会返回null,那么这个null就有两个意思:这个key从来没有在map中映射过,也就是不存在这个key;这个key是真实存在的,只是在设置key的value值的时候,设置为null了;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。