暂不公开2023 年 10 月 31 日
面试总结
最近面试的遗留问题做探讨记录
注意: 加密文章暂不公开,访问需输入认证码
面试遗留问题
Company B
总结:1)关注点很基础,但提出的问题部分的确自己在工作中没有深究。如果和校园招聘对比的话,本次面试更相当于校园招聘。2)面试时间定在晚上7点,所以我也Get到部分信息,之前刚好有收集这方面信息。
- java 类加载机制 classLoader
- hashMap hashTable
- Python tuple List
- python. == is区别
- 内存溢出-循环/大变量 / 垃圾回收算法
- String StringBuffer StringBuilder
- 数据库索引哪些字段可以加索引 数据库事务使用
- 单链表排序题目
1. java 类加载机制 classLoader
.class文件如何在jvm中表达,class中的函数、变量运行时诞生都是通过加载机制来实现的。
如何在jvm中表达:.class经过加载(本地文件,网络文件流)、连接后会在内存中生成两部分 - 方法区的class入口以及堆区的class实例数据
所谓的加载,就是让jvm正确找到你的class然后在内存中表现出来,可被调用。
加载机制的重点是:BootStrapClassLoader/ExtClassLoader/AppClassLoader/自定义的ClassLoader, jvm通过这些loader来加载,
机制是“双亲委派机制”, 一个类需要加载时,先向上寻求帮助是否可以加载到(AppClassLoader -> ExtClassLoader (parent of AppClassLoader) -> BootStrapClassLoader (parent of ExtClassLoader)),若可以则加载结束,否则再通过AppClassLoader或者自定义加载
2. hashMap hashTable区别
hashMap非线程安全,hashTable线程安全
因此在多线程场景用hashTable, 单线程用hashMap
不过,hashTable好久不用啦(遗留类),当前多线程场景更多(几乎全部)使用concurrentHashMap,性能更好。
ps
: concurrentHashMap将hash表分最多16个不同的段(分段锁segment),一种操作在不同的段之间是可并发的,意味着一次可以有16个线程并发操作;
而hashTable一次锁(syncronized关键字)住全部hash表,意味着一次只能有一个线程操作,并发性能差3. Python tuple List区别
两者都算作基本数据类型吧,世纪工作中还是List用的比较多,更符合广义上的数组,概念直白,从其他语言迁移过来几乎无差别。
不同的是:tuple是不可变的,List是可变的
意思是:
tuple中的元素不能直接修改,但是tuple内部元素支持可变元素存储,你可以改变tuple里的可变元素的值
list中元素可以直接索引访问去修改
t = (1,2,3)
t[0]
1
t[0] = 1
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[4], line 1
----> 1 t[0] = 1
TypeError: 'tuple' object does not support item assignment
t = ([1,2,3], 2, 3)
t[0]
[1, 2, 3]
t[0].append(4)
t[0]
[1, 2, 3, 4]
4. python == 和 is的区别
is判断引用是否相等;==判断值或者引用对象的值是否相等
ps: 可以饮用c的概念 -> is就是指针的地址比较,并非指针指向的对象的值比较;==就是指针指向的对象的值比较
5. 内存溢出-循环/大变量 / 垃圾回收算法
从实际工作角度来看:常见的内存溢出就是堆内存溢出(OutOfMemoryError - Heap Space)
java进程启动时又启动参数会配置整个进程的内存(未配置则为默认值,可以查看对应参数说明了解),否则进程跑飞直接将
整个虚拟机搞挂就不好玩了。
常见的原因(我自己处理过的)90%以上都是代码层面处理粗糙导致内存溢出,例如:
- 循环操作变量例如hashMap,大量数据存储在内存中,超出进程配置溢出。
- 文件操作如可能的解压zip炸弹,没有安全防护,直接“暴击”,导致内存溢出
- 某些变量使用后未能及时释放(例如文件流等,没有使用try-with-resources,遗忘关闭)导致随着程序的运行时间越久所积累增长的 微小部分慢慢放大,导致溢出
这里谈一下处理方式:
首先进程配置好溢出后产生dump文件(.hprof), 内存溢出产生时第一时间保存dump快照溢出文件,留存OutOfMemoryError
报错详情;然后,使用MAT或者擅长的分析工具分析内存占用,占用最大的部分就是,逐层分析,一般来说会定位到某一个具体的类继而某个函数或者变量
然后,修复后(看看资源是否正确关闭、存储是否不合理导致增长等)观察运行是否还会溢出。
最后,如果未分析到不合适或者不正确的函数,确认程序运行正确符合预期但是就是溢出了,此刻需要进行JVM参数调优例如根据虚拟机内存以及业务上下文给出符合
的参数值,然后修改重启进程,观察运行是否还会溢出。
6. String Stringbuffer Stringbuilder区别
若使用的字符串是不变的(不改变的字符串常量)使用String,否则使用Stringbuffer/Stringbuilder。
因为,string不可变;Stringbuffer Stringbuilder则是可变字符串。
业务中常见的字符串场景就是“+”,即字符串拼接,这里3者的区别就很好玩了:
- 纯粹用string拼接,性能是最差的(如果拼接的是字符串变量),例如下方 每一次都相当于编译器去新生成stringbuffer or stringbuilder中间构建一次,再拼接上去
String result = "";
for (String s : array) {
result = result + s;
}
- 使用stringbuilder/stringbuffer来拼接,区别是这样的:
首先方法一致都是
.append()
,不同的是:stringbuffer是线程安全的,stringbuilder是非线程安全的,如果你的字符串场景是并发场景则要使用stringbuffer 倘若开发人员无法区分字符串是否用于并发的上下文,则推荐使用stringbuffer. 相同情况下,stringbuilder相比于stringbuffer带来性能提升是10%到15%,但是要冒 可能的并发风险,因此仍推荐使用stringbuffer
7. 数据库索引哪些字段可以加索引 数据库事务使用
工作中数据库表的索引字段一般在建立时凭借业务上下文就确立了是否建立索引、哪些字段建立索引,之后很少改动。
除了通过主键确定数据外,为了加快查询,对一些热字段(查询过程中总业务总是要查询的字段)建立一个“目录”,类比于图书目录,这样查询过程中相当于先找
目录,再通过目录直达对应章节,提升查询效率
哪些字段:就是业务所需的热字段
ps: 至于索引的实现(所类比的目录)则就是另外一个话题了。
8. 单链表排序题目
老生常谈的一个题目,不过在面试做题时一方面对在线IDE不熟悉以及没有自动提示,另一方面是业务层面不常直接使用链表数据结构,
所以没能在给定时间完成实现。
不过,针对链表及单链表排序这个话题,在学习C语言是就已经学习过了。
主要玩的就是一个指针。
排序方法,先说第一种(初学时就能想到)- 所谓“空间换时间”:
直接遍历链表拿到数据数组,数组进行冒泡排序或者其他排序方法,然后将排序后的数组初始化为新链表
第二种:原地排序 - 所谓“时间换空间”
新指针指向链表头,每次遍历链表找出当前需要的节点(例如降序,就是找比排序后链表尾节点小的最大节点),然后剔除节点插入至排序后链表(尾插即可)
public class Solution {
static class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
/**
* 初始化一个单链表
* @param array 数据源
* @return 链表 ListNode
*/
public static ListNode initList(int [] array) {
ListNode head, p;
head = p = null;
if (array == null) return head;
for (int i = 0; i < array.length; i++) {
ListNode temp = new ListNode(array[i]);
temp.next = null;
if (i == 0) {
head = temp;
p = head;
} else {
p.next = temp;
p = p.next;
}
}
return head;
}
/**
* 遍历单链表
* @param head
*/
public static void printNodeList(ListNode head) {
if (head == null) return;
ArrayList<Integer> data = new ArrayList<>();
ListNode p = head;
while (p != null) {
data.add(p.val);
p = p.next;
}
System.out.println(data.toString());
}
/**
* 单链表排序 - 原地排序
* @param head
* @return
*/
public static ListNode sortList(ListNode head) {
if (head == null) return null;
ListNode p, t;
p = head;
t = null;
while (p != null) {
ListNode maxNode = findMaxNode(p);
if (maxNode == p) {
p = p.next;
}
if (t == null) {
t = maxNode;
head = t;
} else {
t.next = maxNode;
t = t.next;
}
}
return head;
}
private static ListNode findMaxNode(ListNode head) {
int minVal = Integer.MIN_VALUE;
ListNode minNode = null;
ListNode before = null;
ListNode p,q;
p = q = head;
while (p != null) {
if (p.val > minVal) {
minVal = p.val;
minNode = p;
before = q;
}
q = p;
p = p.next;
}
// 剔除minNode
if (minNode != head) {
before.next = minNode.next;
}
return minNode;
}
public static void main(String[] args) {
int [] array = {1,5,3,6,7,9,12};
ListNode head = initList(array);
printNodeList(head);
printNodeList(sortList(head));
}
}
---
[1, 5, 3, 6, 7, 9, 12]
[12, 9, 7, 6, 5, 3, 1]
Company H
总结:1)面试流程很规范。2)面试定于周内早上10点。3)面试过程和之前都不太一样,但是这个不一样的确是我认为的工作后面试。前期英文面试;后期围绕技术基础、技术生态、职业规划等,面试很饱满。
- java stream使用flatmap/reduce/…
- CompletableFuture/ThreadLocal使用
- try-catch-finally中return的执行
- 一个线程中收集另一个线程的执行结果
- arraylist & vector
- Optional的使用
- Spring: 从容器中 - 一个class如何生成并使用2个不同的bean
- Mybatis分页使用
1. java stream使用flatmap/reduce/…
业务场景中使用stream编程,解读不同的关键字函数扮演的什么功能:
使用stream来编程是针对流式数据更友好,管道的思想去处理流式数据,那么用stream是很nice的
常用方法如下:
stream()
parallelStream() - 并行流(可以利用多核并行加速处理)
filter() - 过滤,可通过加入Predicate条件来过滤
findAny()
findFirst()
sort() - 排序
forEach() - 遍历
map() - 对值做处理、映射成新的集合
reduce() - 数据做收集合并
flatMap() - 多个流合并为一个流 “拍扁”
collect(Collectors.toList())
distinct - 去重,包括下面关键字,可以按照SQL语句来理解
limit - 截取
count - 计数
min - 比较器
max - 比较器
summaryStatistics - 做统计
参考:Lambda表达式
2. CompletableFuture/ThreadLocal使用 & 一个线程中收集另一个线程的执行结果
- CompletableFuture: 可以收集异步线程的执行结果(不用像Future那样需要等待),通过回调函数来通知执行结果。
- ThreadLocal: 多线程场景(例如微服务启动时,将文件推送至oss存储,多线程并发执行,直至所有成功),如果在一个线程的执行流程中有处用到同一个变量,则可以使用ThreadLocal将变量共享至该线程内部(可以理解为线程内全局),而不同线程之间不会影响(各自线程持有变量的副本)。ThreadLocal生命的变量,存储在ThreadLocalMap,相当于有一个线程<->变量的映射,这也是线程之间ThreadLocal可以隔离的原因。
3. try-catch-finally中return的执行
首先要明确的一点是:finally中的语句块在整个try-catch执行完后一定会执行,不存在try中return”所谓提前返回的干扰”
- return在try中
return前语句先执行,然后return的数据暂存,执行finally中语句,最后执行try中return暂存的数据
- return在catch中
先执行try中抛异常前代码,然后跑出异常后catch中return前语句先执行,然后return的数据暂存,执行finally中语句,最后执行catch中return暂存的数据
以上两种情况很清晰,需要注意的是:暂存的数据是引用类型还是基本数据类型,引用类型真正的值(即暂存值)可能会在finally中被修改,而基本类型不会
- return在finally中
先执行try或这catch中语句块(根据异常决定即可),然后执行finally中语句块,再顺序执行return即可,此处return会覆盖try/catch中的return。因此不建议finally中有return,导致异常无法捕获
try (xxxx) {
code; // D
return variable; // A
} catch (xxxx) {
code; // E
return variable; // B
} finally (xxxx) {
code; // F
return variable // C
}
4. Spring: 从容器中 - 一个class如何生成并使用2个不同的bean
Spring在初始化时(ApplicationContext在启动时初始化所有bean)会生成对应class的一个实例,启动后使用该bean的都是这同一个实例。
要想同一个class生成两个不同的bean,可以通过:
- @Scope注解,将bean注解为prototype类型的bean(ConfigurableBeanFactory.SCOPE_PROTOTYPE)。这样在获取bean实例时,均是新生成的实例。
- @Bean/@Qualifier注解,对bean起个别名,这样容器在引用时不会冲突,可以生成不同实例但是是同一个类。
# @scope方式
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Data
public class SameBean {
private int id;
private String name;
}
SameBean sameBean = context.getBean(SameBean.class);
SameBean sameBean1 = new SameBean();
// 从容器中获取一直是同一个bean
System.out.println(sameBean);
// 通过new方式生成的并未在容器中管理,所以是新的实例
System.out.println(sameBean1);
// @scope注解为prototype类型后,每次从容器中拿都是新实例
SameBean sameBean2 = context.getBean(SameBean.class);
System.out.println(sameBean2);
# @bean设置别名
@Configuration
public class SameBeanConfig {
@Bean("SameBeanNum1")
public SameBean createSameBean1() {
return new SameBean();
}
@Bean("SameBeanNum2")
public SameBean createSameBean2() {
return new SameBean();
}
}
// qualifier别名设置后
SameBean sameBean3 = (SameBean) context.getBean("SameBeanNum1");
SameBean sameBean4 = (SameBean) context.getBean("SameBeanNum2");
System.out.println(sameBean3);
System.out.println(sameBean4);
参考:定制bean
5. arraylist & vector 区别
ArrayList和Vector实现了相同的接口,几乎一样。区别是:Vector是线程安全的。
6. Optional的使用
主要针对的使用场景:参数需要提前判断是否是null后才可使用。 - 使用Optinal即可。
常用方法如下:
- get()方法返回对应值对象
- isPresent()方法返回true代表值非null
- of()方法为非null值创建Optional对象
- ofNullable()方法为null值可以创建Optional对象(空对象)
参考:Optional
7. Mybatis分页使用
使用分页时:分页插件传递固定参数即可,即第几页,每页几个,剩下的计算交给Mybatis解决。
参考示例:示例
版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
作者: Henry He 发表日期:2023 年 10 月 31 日 上次更新:2023 年 11 月 15 日