在尽可能短的篇幅里,将所有List、Map、Set、Queue的特征与实现方式捋一遍。适合所有"精通Java"其实还不那么自信的人阅读。 List ArrayList 按数组下标访问元素--get(i)/set(i,e) 的性能很高,这是数组的基本优势。 LinkedList 按下标访问元素--get(i)/set(i,e) 要悲剧的遍历链表将指针移动到位(如果i > size/2)会从末尾移起。 CopyOnWriteArrayList 因为对快照的修改对读操作来说不可见,所以只有写锁没有读锁,加上复制的成本,典型的适合读多写少的场景。 如果更新频率较高,或数组较大时,还是Collections.synchronizedList(list),对所有操作用同一把锁来保证线程安全更好。 增加了addIfAbsent(e)方法,会遍历数组来检查元素是否已存在,性能可想像的不会太好。 补充 没有按元素值排序的SortedList的实现,在线程安全类中也没有实现无锁算法的ConcurrentLinkedList,凑合着用Set与Queue中等价的类时,会缺少一些List的特有方法。 Map HashMap 插入元素时,如果两条Key落在同一个桶(比如哈希值1和17取模16后都属于第一个哈希桶),Entry用一个next属性实现多个Entry以单向链表方式的存放,后入桶的Entry将next指向桶当前的Entry。 查找哈希值为17的key时,先定位到第一个哈希桶,然后以链表遍历桶里所有元素,逐个比较其key值。 当Entry数量达到桶数量的75%时,会成倍扩容桶数组,并重新分配所有原来的Entry,所以这里也需要一个合理的预估值。 取模用位运算(hash & (arrayLength-1))会比较快,所以数组的大小永远是2的N次方, 你随便给一个初始值比如17会转为32。默认初始值是16。 iterator()时顺着哈希桶数组来遍历,看起来是个乱序。 在JDK8里,新增默认为8的閥值,当一个桶里的Entry超过8个,就不以单向链表而以红黑树来存放以加快查找速度。 LinkedHashMap 实现上是在Entry上再增加属性before/after指针,插入时把自己加到Header Entry的前面去,如果所有读写访问都要排序,还要把前后Entry的before/after拼接起来以在链表中删除掉自己。 TreeMap 支持SortedMap接口,如firstKey(),lastKey()取得最大最小的key,或sub(fromKey, toKey), tailMap(fromKey)剪取Map的某一段。 ConcurrentHashMap 支持ConcurrentMap接口,如putIfAbsent(key,value)与相反的replace(key,value)与以及实现CAS的replace(key, oldValue, newValue)。 没有读锁是因为put/remove动作对数据结构的改变最终是个原子动作(put是一个对数组元素/Entry 指针的赋值操作;remove是一个对 entry.next 的赋值操作,rehash是一个对数组引用的赋值操作),因此read不会看到一个更新动作的中间状态。 ConcurrentSkipListMap 补充 Set Set几乎都是内部用一个Map来实现, 因为Map里的KeySet就是一个Set,而value全部使用同一个Object。 HashSet:内部是HashMap。 CopyOnWriteArraySet:内部用CopyOnWriteArrayList 实现的并发优化的Set,利用其addIfAbsent()方法实现元素去重,如前所述该方法的性能很一般,同时继承CopyOnWriteArrayList的其他优缺点。 补充:好像少了个ConcurrentHashSet,本来也该有一个内部用 ConcurrentHashMap的简单实现,但JDK偏偏没提供。Jetty就自己封了一个,Guava则直接用 java.util.Collections.newSetFromMap(new ConcurrentHashMap()) 实现。 Queue Queue是在两端出入的List,所以也可以用数组或链表来实现。 --普通队列-- LinkedList ArrayDeque 普通数组只能快速在末尾添加元素,为了支持FIFO,从数组头快速取出元素,就需要使用循环数组:有队头队尾两个下标:弹出元素时,队头下标递增;加入元素时,如果已到数组空间的末尾,则将元素循环赋值到数组[0](如果此时队头下标大于0,说明队头弹出过元素,有空位),同时队尾下标指向0,再插入下一个元素则赋值到数组[1],队尾下标指向1。如果队头与队尾的下标重合,说明数组所有空间已用完,进行双倍的扩容。 PriorityQueue ConcurrentLinkedQueue PriorityBlockingQueue DelayQueue BlockingQueue的队列长度受限,用以保证生产者与消费者的速度不会相差太远。队列长度设定后不可改变。
LinkedBlockingQueue ArrayBlockingQueue 补充 |
小黑屋|在路上
( 蜀ICP备15035742号-1 )
GMT+8, 2025-8-23 04:34
Copyright 2015-2025 djqfx