HashMap底层在1.7是数组+链表 在1.8后是数组+链表+红黑树
具体而言对于 put(key,value)操作来说 我们会计算出key的哈希值 然后通过数组长度-1 && key 计算出该存储的下标位置如果 如果说发现该下标有了数据了 那么代表此时发生了哈希冲突 对于put操作来说会将其依次匹配该下标位置的链表元素 如果说equals(key)相同就将value覆盖 如果遍历完了链表还没有相同的key 就插入到链表尾节点
对于扩容机制来说 如果说数组的大小达到了负载因子 也就是0.75 那么此时就会将数组大小翻倍里面的元素进行重新计算放到新数组当中 称为rehash
Java 中 ConcurrentHashMap 1.7 和 1.8 之间有哪些区别?
1.7的时候核心思想是采取分段锁 每个 Segment 内部维护一个 HashEntry<K,V>[] table 数组,相当于一个小的 HashMap segment继承ReentranLock 不同线程访问不同的段 互相之间不影响 每次都会锁住整个segment桶数组 + 其下的所有链表
对于1.8来说 锁的颗粒度更精细了 锁细化到每个槽位 每次插入的时候如果下标位置没有节点先通过CAS插入新的节点 如果有节点通过synchronized将这个节点上锁 执行后续操作 别的线程就无法访问这个节点以及后面的链表 或 红黑树了 每次锁住的都是头节点synchronized(头节点)
为什么 JDK 1.8 对 HashMap 进行了红黑树的改动?
因为HashMap 1.7以及之前 如果说冲突过多链表过长 就会从o(1) 退化到 o(n) 所以1.8在链表长度大于8并且数组长度大于64的时候转换为红黑树 如果说数组长度较低的话本身hash冲突就比较激烈所以规定了必须要数组长度大于64才可以
JDK 1.8 对 HashMap 除了红黑树还进行了哪些改动?
1.7使用的是头插法 1.8后使用尾插法 因为头插法会有循环链表的问题
对于扩容机制来说也是同理 JDK 1.7 :扩容后,每个元素必须调用函数重新计算在新数组中的下标位置。
对于1.8来话说利用了2的幂次方的特性 扩容后小标只有两种可能 意识原位置 二是 一道道原位置加旧数组长度 低位保持不变 高位集体搬家
List 有序可重复 按照下标访问 ArrayList底层是数组 增删较慢但是查询较快 LinkedList底层是链表增删较快但是查询较慢 不过也是增删较快也是针对于头节点和尾节点来说
Set set是不允许有重复元素 HashSet基于哈希表 key用来存储元素 value存储固定值 LinkedHashSet用链表维护顺序 TreeSet基于红黑树 元素按大小排序
Map HashMap基于哈希表键 LinkedHashMap 底层基于哈希表和双向链表 维护了插入顺序和存储顺序一致 HashMap在并发编程下线程有问题ConcurrentHashMap则是更好的选择