综合1-20
1.说说 Java 中 HashMap 的原理?
展开
思路:数据结构,底层实现,hash到数组位置,扩容
- HashMap是基于哈希表的数据结构,底层实现是桶数组,存储的单元是KV节点,通过对Key的hash,生成index,将节点散列到数组的对应位置。 当有hash冲突的时候,会在对应的位置上生成一个链表,当超过一定阈值的时候,会转化成红黑树。
- 扩容的负载因子默认0.75,初始数组大小16,链表数化的阈值是链表长度大于8并且数组长度大于64。
- 通过hash值计算数组位置一般是用取模的方式,在hashmap中,length为n的2次方,所以 h & (length -1) 相当于h%length,这种与运算提高了运算效率。
2.Java 中 ConcurrentHashMap 1.7 和 1.8 之间有哪些区别?
展开
思路:锁粒度,size计算,扩容
- 同步锁的粒度不同,1.7在段上加锁。1.8是桶级别的锁,并且插入的时候先用CAS尝试插入空的table槽,当插入node节点的时候才会加同步锁。
- size的计算方法不同,1.7先尝试无锁统计所有 Segment 的 size,如果失败,锁住所有 Segment 重算。1.8使用 CounterCell 数组分段计数,类似 LongAdder,性能更高。
- 扩容机制不同,1.7分段扩容,每个段之间互不影响,扩容的时候会锁段。1.8是全局扩容,将扩容任务分成多端,线程之间合作扩容。
3.为什么 JDK 1.8 对 HashMap 进行了红黑树的改动?
展开
思路:时间复杂度,散列不均
当链表长度过长的时候,获取链表节点的操作会从hash表的O(1)变成遍历链表的O(n),因此要转化成红黑树,红黑树是一种平衡树,插入、查找、删除的时间复杂度是O(log n),要链表效率高
4.JDK 1.8 对 HashMap 除了红黑树还进行了哪些改动?
展开
- put插入链表方式:从头插法改为尾插法,需要遍历到尾部节点,性能有所下降,但是扩容时不会形成环,因为尾插法不会修改原节点的next,原节点本身是一个竞态变量。
- 如何形成环:假设线程1取出一个节点,然后这个节点已经在另一个节点做了扩容,它在新桶的头部,这时候线程1要把这个节点的next指向新桶,就是它自身。
- hash的计算:将高 16 位与低 16 位异或,进一步打散哈希值,增强哈希值的均匀性,减少冲突。
- 扩容机制的优化:1.8利用位运算优化迁移逻辑,避免逐个重新计算哈希值。
5.Java 中有哪些集合类?请简单介绍
展开
- List:ArrayList,LinkedList
- Set:HashSet,TreeSet,LinkedHashSet
- Map:HashMap,ConcurrentHashMap,TreeMap,LinkedHashMap,HashTable
- Queue:LinkedList,PriorityQueue
6.MySQL 索引的最左前缀匹配原则是什么?
展开
- 当使用联合索引进行条件查询的时候,查询条件必须从最左边的列开始依次匹配。当遇到索引字段的缺失,或者对索引字段进行范围查询的时候,会导致后面的索引不能完全利用。
- 举个例子,联合索引是(a,b,c),那么如果where条件中只有ac,那c的索引就用不上,但是可以用到索引下推。
7.数据库的脏读、不可重复读和幻读分别是什么?
展开
- 脏读,读到其他事务未提交的修改数据
- 不可重复读,多次读取同一行数据时,由于其他事务提交的修改,导致结果不一致
- 幻读,在当前事务中,执行相同范围查询时,由于其他事务提交的插入或删除,导致结果集不一致
8.MySQL 的存储引擎有哪些?它们之间有什么区别?
展开
- InnoDB,支持事务,行级锁和外键,支持聚簇索引,有日志系统,支持崩溃恢复,适合高并发读写场景
- MyISAM,不支持事务,表级锁,适合只读或读多写少的场景