八股
为什么 JDK 要同时提供 equals 和 hashcode 两个方法?
hashmap, hashset 的插入流程。
hashcode 可能会冲突,equals 检查是否一致。
String、StringBuffer、StringBuilder 的区别
String 不可变,每次操作生成新对象。
StringBuffer、StringBuilder 每次都是对对象本身进行操作;区别是前者是线程安全的,后者不安全仅提升 10% - 15% 的性能。
String 不可变的原因
内部是由字符数组维护的。
- 被 final 和 private 修饰不可指向其他对象,也不能被外部访问,String 类也没有提供修改这个字符串的方法。
- String 类被 final 修饰,不可被继承避免了子类破坏结构。
字符串拼接用 “+” 还是 StringBuilder?
直接使用 “+” 的方式,其实底层创建了 StringBuilder 进行优化。
但是如果循环进行拼接应显式创建一个 StringBuilder 来复用,否则会造成多次创建。
字符串常量池
是 JVM 为了提升性能和减少内存消耗针对字符串专门开辟的一块区域。
当创建一个字符串对象时,会先检查常量池内是否存在,如果没有就创建 2 个字符串对象,一个给当前引用,另一个的引用保存到字符串常量池中。
String.valueOf
会先从常量池中找,没有再 new String()
而 new String()
是直接创建 1 个新的,只有常量池中没有才会给常量池也创建一个。
所以这个常量池是专门为字符串常量建立的缓存。
String#intern
类似 new String()
会创建 1 / 2 个对象,其中一个保存到常量池,并且返回常量池的引用,确保这个引用是唯一的
String 类型的变量和常量做 “+” 运算时发生了什么。
“常量折叠”:对于在编译时期就可以确定的值,会提前进行计算。
而引用的值是无法直接确定的,因此会创建一个新的对象,后续进行计算。
发生常量折叠的情况:
- 基本数据类型(
byte
、boolean
、short
、char
、int
、float
、long
、double
)、字符串的常量
无法测试区分常量相加和变量相加创建的变量地址是否一致,因为无法直接比较基本类型的地址。
- final 修饰的基本数据类型和字符串的变量。
- 字符串通过 “+”拼接得到的字符串、基本数据类型之间算数运算(加减乘除)、基本数据类型的位运算(<<、>>、>>> )
简言之,通过 final 修饰或者其他确定的直接运算,就会被折叠。
Exception 和 Error 有什么区别
Exception 表示程序本身能处理的异常,可以捕获。
Error 程序无法处理,JVM 一般会选择直接终止。
如:虚拟机错误(Virtual MachineError)
finally 和 try 中同时存在 return 会怎么样?
最后 return 的值会变成 finally 的。
因为执行到 try 的 return 时会将返回值暂存到一个本地变量,如果 finally 也有 return 会覆盖掉之前的值。
try-with-resources Java7
用于实现了 AutoCloseable
或者 Closeable
的对象。
在作用域结束后会自动关闭声明的资源。
通过 ;可以声明多个资源
异常使用有哪些需要注意的地方?
不要把异常定义为静态变量,因为这样会导致异常栈信息错乱。每次手动抛出异常,我们都需要手动 new 一个异常对象抛出。
算法题
两数之和
从一个数组中找到和为 target 的两个数的位置,时间 O(n)。
普通思路:两两遍历 O(n^2^)
优化:如果第一层遍历确定了一个数,那么第二层仅是为了找 target - x 是否存在,因此可以借助哈希表优化索引时间为 O(1),key:元素值,value:索引位置。
数组内存在重复值问题:一边遍历一边建立哈希表,即使当前元素的值重复了也不会匹配同一个元素;
如果提前建立哈希表,数组内重复的值无法作为唯一 key,当 target 需要由类似 3+3=6 组成时,就会找不到索引。
接雨水
给出一个数组 height,其中 n 个元素代表 n 个高度为 height[n],底部为 1 的柱子,试求出所有柱子构成的“容器”能够储存的雨水量总和。
类似 “盛最多水的容器” 一题,能够承载雨水量由最短高度决定。
解法:一个柱子能够装的水等于两条“边”的最短边 - 本身高度,遍历每根柱子,依次求出左右两边的最长边,求出所有柱子的储水量相加即为结果。
求 左右 最长边时包括自身,确保不会比自身还短,导致“漏掉”。
普通思路:遍历数组,求每个柱子的左右 max 然后计算,统计所有柱子储水量之和 O(n^2^)。
优化(动态规划): 和哈希表类似,可以提前算出每个点的左右 max,这样就避免了每次计算的重复寻找 max,使得计算过程为 O(1),最终时间 O(n),空间 O(n)。
再优化(双指针):从左右边界开始,由于每个柱子都可能作为边界,只要 height[l] < height[r] 那么就一定有 left_max < right_max 的情况发生,所以此时直接用 left_max - 自身高度 得到此刻的储水量,将空间缩减为了 O(1)。
相当于抽象了整体,选出只需要“处理”一个方向的情况进行计算,不需要管另一边了。
当动态规划中的数据只需要用到 1 次时,可以考虑转化为双指针。
什么是动态规划?
在动态中规划逻辑路线,根据状态变化能够做出新的决策;
MySQL 分组
一个完整的分组流程。
选择表 emp;
分组 job;
筛选 having avg(sal) > 2000;
展示 job,avg(sal)