Apong's Blog

当你快坚持不住的时候,困难也快坚持不住了

0%

每日杂记 day3

八股

为什么 JDK 要同时提供 equals 和 hashcode 两个方法?

hashmap, hashset 的插入流程。

hashcode 可能会冲突,equals 检查是否一致。

String、StringBuffer、StringBuilder 的区别

String 不可变,每次操作生成新对象。

StringBuffer、StringBuilder 每次都是对对象本身进行操作;区别是前者是线程安全的,后者不安全仅提升 10% - 15% 的性能。

String 不可变的原因

内部是由字符数组维护的。

  1. 被 final 和 private 修饰不可指向其他对象,也不能被外部访问,String 类也没有提供修改这个字符串的方法。
  2. String 类被 final 修饰,不可被继承避免了子类破坏结构。

字符串拼接用 “+” 还是 StringBuilder?

直接使用 “+” 的方式,其实底层创建了 StringBuilder 进行优化。

但是如果循环进行拼接应显式创建一个 StringBuilder 来复用,否则会造成多次创建。

字符串常量池

是 JVM 为了提升性能和减少内存消耗针对字符串专门开辟的一块区域。

当创建一个字符串对象时,会先检查常量池内是否存在,如果没有就创建 2 个字符串对象,一个给当前引用,另一个的引用保存到字符串常量池中。

String.valueOf 会先从常量池中找,没有再 new String()

new String() 是直接创建 1 个新的,只有常量池中没有才会给常量池也创建一个。

所以这个常量池是专门为字符串常量建立的缓存。

String#intern

类似 new String() 会创建 1 / 2 个对象,其中一个保存到常量池,并且返回常量池的引用,确保这个引用是唯一的

String 类型的变量和常量做 “+” 运算时发生了什么。

“常量折叠”:对于在编译时期就可以确定的值,会提前进行计算。

而引用的值是无法直接确定的,因此会创建一个新的对象,后续进行计算。

发生常量折叠的情况:

  • 基本数据类型( bytebooleanshortcharintfloatlongdouble)、字符串的常量

无法测试区分常量相加和变量相加创建的变量地址是否一致,因为无法直接比较基本类型的地址。

  • 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)