本文主要是介绍金九银十面试季 —— 2020 年10 月,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
十月共面试 4 家。基本流程大同小异,本文整理了 4 家公司的面试题以供参考,其中高频问题将重点标出。
答案正在整理完善,持续更新中… (欢迎在评论区留下自己的答案和见解)
图示 | 提问次数 |
---|---|
★ | 1 家公司提问 |
★★ | 2 家公司提问 |
★★★ | 3 家公司提问 |
★★★★ | 4 家公司提问 |
文章目录
- HR 面(初面)
- 自我介绍 ★★★
- 您对我们公司了解多少? 我们公司是 XXX 性质,做 XXX 项目的,办公地点在 XXX,可以接受吗? ★★★
- 期望薪资 ★★★★
- 离职原因 ★★★
- 技术面(二面)
- 初面(基础篇)
- 1. Java 创建对象有几种方法?★
- 2. 自动拆装箱的原理?Integer 类型的值如何比较大小?★
- 3. Java 中对象如何排序?(自定义排序规则)★
- 4. try/catch/finally 的执行顺序?如果 try 中执行了 return,finally 中的代码会继续执行吗?如何执行?★
- 5. ArrayList 和 LinkedList 的区别。 ★★★
- 6. Array 和 List 之间如何转换?★
- 7. ArrayList 去重该如何操作?★
- 8. HashMap 线程是否安全?如何保证其线程安全?★★
- 9. 线程池的创建方式有几种?有哪些使用场景?★★★
- 10. 注解是如何自定义?★
- 11. Java 8 的新特性有哪些?★
- 12. Java 8 对 HashMap 的优化有哪些?★★
- 13. MySQL 和 Oracle 的区别★
- 14. 分组排序及排名函数的使用★
- 15. Oracle 中分页的实现,rownum 的特点★★
- 16. Oracle 中表的内联、外联、自联★
- 复面(高级篇)
- 17. 最近开发的项目及使用的技术。 ★★★
- 18. 最有难度的项目或者最有成就感的项目。★★★
- 19. Spring MVC 的工作流程★
- 20. 项目中大数据量的优化处理★★★★
- 21. 并发与多线程的使用★★★★
- 22. SQL 的优化★★★★
- 23. 用户单位★
- 24. 项目中遇到的技术性问题★★
- 25. 项目中遇到的业务性问题及和用户如何沟通解决★★
- 26. 详细介绍一下 MyBatis 的一、二级缓存★★
- 27. SpringBoot 中如何使用 ES?ES 的常用方法有哪些?★
- 28. Maven 中 dependencies 和 dependencyManager 的区别?★
- 29. 介绍一下 XX 系统的基本业务流程
- 30. @Autowired 和 @Resource 的区别★★
- 31. 数据库索引的创建原则,什么情况下会导致索引失效?如何判断索引是否有效?★★
- 32. Oracle 排序的实现原理★
- 33. MyBatis 中 insert 后如何返回主键?批量 Insert 可以返回主键集合吗?如何返回?★★
- 34. MyBatis 中 # 和 $ 符号的区别★★★
- 35. Hibernate 和 MyBatis 的区别★
- 36. MyBatis 的常用方法和常用标签,及其使用场景★
- 37. SpringBoot 基本配置★★★
- 38. SpringBoot 的常用注解和常用方法★★
- 39. Redis 的基本数据类型有哪几种?★★★
- 40. ES 的常用方法★
- 41. MyBatis 的分页原理★
- 42. Oracle 中 union 和 union all 的区别,哪一个会自动排序,其原理是什么?★★
- 43. Nignx 的部署步骤★
- 44. IO 流的常用类★
- 45. Ajax 的优缺点及回调函数★
- 46. Linux 的常用命令★
- 47. IDEA 的常用快捷键★
- 48. Redis是否了解?其常用方法有哪些?★★
- 49. Spring 的特性★★
- 50. MyBatis 的工作原理★★
- 51. MyBatis 的 Executor 执行器原理★
- 52. MyBatis 的延迟加载原理及使用场景★
- 53. MyBatis 传参问题★
- 54. SpringBoot 的安全配置有哪些?Security 及 OAuth2 的使用方法★★★
- 55. GC 的工作原理★★
- 终面
- 期望薪资
- 婚否
- 是否考虑搬家?
- 对短期出差有什么看法?
- 未来打算在 SZ 定居吗?
HR 面(初面)
初面一般是对个人情况的一个基本了解,不牵扯到技术问题。
通常情况下,是简历被选中后,HR 打电话过来了解情况并记录,然后回去反馈到部门,由招聘部门根据HR的反馈结果决定是否安排面试。
自我介绍 ★★★
几乎所有的公司面试都必备的一项环节。其中外企和部分大厂可能会要求英文的自我介绍。
简单的介绍一下自己的背景(专业、学历)及目前的工作状况(工作地点、职务、最近项目)等。
很多人对面试的自我介绍一关嗤之以鼻,认为既然给了你简历,你还要我介绍什么,我要介绍的都写在简历里,你到底有没有看我简历……
我想说的是,其实自我介绍是很重要也是必不可少的一关。首先,面试官会通过你的自我介绍来初步判断你简历的真实性(如果描述的信息和简历中相差太大时,面试官可能会追问直到你解释合理或者承认造假为止,简历造假是非常严重的事,可能会被面试公司拉入黑名单),其次,也是通过自我介绍来过渡一下面试的气氛,使你逐渐进入状态。再者,通过自我介绍也可以部分体现出求职者的一个基本礼仪、素质等,给面试官留下的一个第一印象。
您对我们公司了解多少? 我们公司是 XXX 性质,做 XXX 项目的,办公地点在 XXX,可以接受吗? ★★★
这种问题以前倒是很少碰到,个人觉得挺好的,先聊一聊公司文化,确定可以接受再继续进行,毕竟大家时间都是很宝贵的。
一般情况下,既然投递了简历,肯定都会多多少少了解一下公司的,基本上都会回答可以接受。当然,如果 HR 没有介绍到的方面,也可以追问了解一下公司的一些制度,确认是否能接受(比如:上下班时间,办公地点,加班制度,出差频率等)。站在求职者的角度,还是建议大家给予肯定回答,毕竟参加面试也是一种经验积累嘛。
期望薪资 ★★★★
现在公司都比较直接……一面就直接问期望薪资
还是要认真提前考虑一下的,如果提太高,可能就没有面试的机会了(比如你应聘中级,提个高级的薪资),如果提太低,终面谈薪资的时候就会有点吃亏(虽然终面谈薪资的时候还会问一次期望薪资,但也不好变化太大了吧)。一般是涨薪 40% 吧,当然你觉得实力足够,直接要求翻倍都不是问题。
我开始的时候还是有点怂,面第一家的时候只涨了 20 - 30%,然后面完了觉得还不错,后面三家问的时候我就涨了 50%,然后就进入了被面试官吊打的心酸历程……o(╥﹏╥)o
所以,个人感觉,这个薪资和未来的技术面试难度应该还是有很强的关联的。
离职原因 ★★★
也是一个几乎所有公司都会问的问题。
我个人主要是因为:技术瓶颈,感觉在公司许长时间没有技术上的增长,有点枯燥,而且公司的开发框架比较旧,多次尝试升级都因一些业务原因失败,想更换一个工作环境试试。(当然语言要好好组织一下,这里最忌讳的就是说前公司/领导的坏话,尤其是那种和前公司不欢而散的)
技术面(二面)
技术面通常分两个环节:初面和复面。一般初面是考察Java基础、框架基础使用等;复面是考察框架、原理、优化、业务等方面。技术面如果通过,基本上就稳了。
这里重点提一下: 面试官所问的问题,基本上都是和你的回答相关的!如下情景(其中注意黑体字部分和面试官下次提问的关联):
面试官:请先进行一个简单的自我介绍
回答:面试官你好,我叫XXX,XXXX年X月毕业于XX大学,XXXX年入职XX公司担任XX一职,期间主要负责Java语言开发,先后使用XX框架开发了XX系统(挑选两三个熟悉的系统)等,对XX框架/技术有了解较深,最近正在进行的项目是XX,使用的是XX框架/技术。因XX原因想要更换工作环境,很荣幸能得到这次机会(最后这句英文面试比较常说,可能是比较偏书面语吧~!)。
面试官:(如果是复面,很高概率会问到上个回答中粗体部分的两种框架和项目相关问题,然后每次回答时预留一个"彩蛋",引导面试官提问)
面试官:(如果是初面,只会问一些基础知识,一般除了应届生,其他求职者几乎不会会在自我介绍中专门提出对某方面的基础知识,所以基础知识的第一个问题一般由面试官发出,当然也可以尝试在自我介绍时略微"暗示"一下)
面试官:简单说一下 ArrayList 和 LinkedList 的区别
回答:ArrayList 是基于动态数组实现的集合,LinkedList 是基于链表实现的集合,LinkedList 不支持高效随机访问,所以查询速率要低于 ArrayList,但因为其元素使用指针连接,而 ArrayList 增删元素时可能需要扩容和复制操作,所以 LinkedList 增删操作的整体效率高于 ArrayList,另外这二者是非线程安全的。
面试官:你说这两个都是非线程安全,那能说说 Java 中还有其他常用的集合实现类吗?有没有线程安全的?
回答:Java 的集合主要分两种:Collection(存储元素) 和 Map(存储键值对)。Collection 又分三种类型:List、Set、Queue。刚才所说的 ArrayList 和 LinkedList 都是 List 接口的实现类,除此之外,Set 又有 HashSet 、 TreeSet 、SortedSet、LinkedHashSet 等实现类,Map 又有 HashMap、HashTable、TreeMap、LinkedHashMap等实现类。其中 HashTable 就是线程安全的,另外,Vector 也是一个线程安全的集合,只不过因为加锁导致性能降低明显,现在已经被弃用了。现在需要线程安全时,通常调用 Collections 工具类的 synchronized 方法将对应的集合包装成线程安全的集合,如 synchronizedList、synchronizedSet、synchronizedMap 等。
面试官:你了解 HashMap 吗?能简单的说一下 HashMap 的实现吗?
回答:HashMap 是一个由链表+数组构成的散列桶,存储的内容是键值对,主要由 put 方法存储元素和 get 方法获取元素。存储元素时,通过调用元素的键的 hashCode 方法计算 hash 值找到对应的存储位置将其存储,如果两个不同元素计算出来的 hash 值相同,则称之为哈希碰撞,就会将这两个元素放入同一个桶中,也就是以链表的形式将元素链接到该数组元素的后面。这种存储方式结合了数组的线性查找和链表的寻址修改,使得查询和修改效率都大幅提升,另外,Java 8 中对 HashMap 进行了进一步的优化,在数组长度大于 64 且链表长度大于 8 时,将自动将链表转换为红黑树结构存储,存取效率进一步提升。
面试官:刚才听到你谈到 Java 8 中的 HashMap 优化,那 Java 8 中还有什么新特性你了解过吗?
回答:Java 8 中的新特性还是很多的,平时开发中用的最频繁的就是 stream 流和 Lambda 表达式,这两个特性在对数据处理这一块十分友好,减少了非常多的代码量,而且用起来很方便,另外还有一些譬如 Optional、默认方法、组合式异步编程、时间API等特性都为开发提供了极大的方便。
面试官:你们平时开发的系统主要都是什么类型的呢?关于数据都是怎样处理的呢?
回答:我们的系统大多是报表系统和可视化系统,这类系统对数据的处理逻辑比较繁琐,一般我们对于一些简单的数据处理(通常是一些临时所需,或者数据量极小的数据)会在 Java 代码中使用 stream 流去操作,对于一些数据比较大,或者计算逻辑比较复杂,我们通常会在数据库中采用存储过程处理,这样比较方便后期维护,因为维护存储过程不会影响到系统的线上运行,并且存储过程可以定时 Job 去后台计算,不会占用 Web 系统的资源,我们一般用的是 Oracle。
面试官:你能说说 Oracle 中分页是如何实现的吗?
回答:我通常是采用 rownum 来实现分页的,先将数据排序并将 rownum 取别名,然后外层查询获取小于等于 n 的数据,再在外层套一层查询筛选大于等于 m 的数据。
面试官:rownum 为什么要嵌套这么多层查询呢?不能直接使用 <= n and >=m 或者 between and 吗?
回答:
rownum 是一个序列,它是在读取数据时实时变化的,第一条数据为 1,第二条数据为 2,如果筛选数据时,第一条数据不满足条件,会被去掉,此时第二条数据的 rownum 会变为 1,所以,如果采用 rownum > 2 或者 between 2 and 10 之类的语句,是永远无法满足条件的。所以使用 rownum 分页,只能为 rownum 命别名,然后再外层对其进行筛选。
在回答一个问题时,去刻意提到下一个知识点,百分之七十的概率会引导面试官的提问。但是通常面试官不会连续追问超过三次,面试官不可能完全按照你的节奏,所以这只是这段时间面试得出来的一个小经验,必要的知识储备才是重中之重。
以下,将遇到的一些面试题分类列出,答案会陆续更新,欢迎大家参考补充。
初面(基础篇)
1. Java 创建对象有几种方法?★
class Person implements Serializable, Cloneable {String name;public Person(){}public Person(String name){this.name = name;}
}
Java 创建对象有 4 中方法。分别是:
-
new 关键字。最基本的创建对象方法。
Person p1 = new Person(); Person p2 = new Person("Ambrose");
-
反射。
Person p3 = (Person) Class.forName("com.example.demo.Person").newInstance(); // 或者 Constructor<?>[] constructor = Person.class.getDeclaredConstructors(); Person p4 = (Person) constructor[0].newInstance(); // 无参构造 Person p5 = (Person) constructor[1].newInstance("Ambrose"); // 带参构造
-
反序列化。需要对象实现
Serializable
接口Person person = new Person("Petter"); byte[] bytes = SerializationUtils.serialize(person); // 序列化 Person p6 = (Person) SerializationUtils.deserialize(bytes); // 反序列化
-
克隆。clone 即拷贝,从一个对象中复制一个新对象,但不会调用任何构造方法,且需要对象实现
Cloneable
接口,并且clone
方法是protected
修饰的,需要合适的使用环境,如果需要扩展其使用范围,可以复写clone
方法的权限为public
java Person p7 = (Person) p1.clone();
2. 自动拆装箱的原理?Integer 类型的值如何比较大小?★
自动装箱指的就是 Java 中自动将原始数据类型转换成对应的引用数据类型(包装类型)。如 int 装箱为 Integer。其底层是通过自动调用
Integer
类的valueOf
方法来实现的。
Integer i1 = 100; // 实际上是: Integer i1 = Integer.valueOf(100);
自动拆箱指的就是 Java 中自动将引用数据类型(包装类型)转换为对应的基本数据类型。如 Integer 拆箱为 int。其底层通过自动调用包装类型的 xxxValue() 方法来实现的。
int i2 = i1; // 实际上是: int i2 = i1.intValue();
而关于
Integer
类型的值的比较,看如下一段代码:
int i1 = 100;int i2 = 300;Integer i3 = new Integer(100);Integer i4 = new Integer(100);Integer i5 = new Integer(300);Integer i6 = new Integer(300);Integer i7 = 100;Integer i8 = 100;
Integer i9 = 300;Integer i10 = 300;System.out.println(i1 == i3); // true
System.out.println(i2 == i5); // trueSystem.out.println(i3 == i4); // false
System.out.println(i5 == i6); // falseSystem.out.println(i7 == i8); // true
System.out.println(i9 == i10); // false
分析以上三种情况,可得知:
- int 类型和 Integer 类型使用 == 比较时,只要值相等即返回 true;(因为 int 类型和 Integer 类型比较,会自动拆箱成 int 类型比较)
- 两个通过 new 关键字创建的 Integer 类型使用 == 比较时,会返回 false;(因为 new 关键字重新开辟内存空间,而 == 比较的是他们的内存地址,两个 Integer 的内存地址是不同的)
- 两个自动装箱的 Integer 对象使用 == 比较时,可能返回 false,也可能返回 true;具体原因可以通过查看 Integer 的 valueOf 方法的源码得知,如果传入的值存在于缓存区中,则直接返回,如果不存在,则通过 new 关键字创建 Integer 对象。而默认的缓存区的值范围是 -128 ~ 127,也就是说,两个自动装箱的 Integer 对象在使用 == 比较时,默认情况下,如果数值在 -128 ~ 127 之间,则会返回 true,否则返回 false。
3. Java 中对象如何排序?(自定义排序规则)★
Java 中对象的排序一般有两种方法:
-
通过
Collections.sort()
函数排序。查看sort()
方法源码,可以看到该方法又分两种写法:-
public static <T extends Comparable<? super T>> void sort(List<T> list)
该方法支持的数据类型要求实现
Comparable
接口,并复写compareTo
方法用来定义排序规则@Data public class Person implements Comparable {String name;public Person(){}public Person(String name){this.name = name;}@Overridepublic int compareTo(Object o) {return this.name.toCharArray()[0];} }
List<Person> persons = new ArrayList<>(); persons.add(new Person("Ambrose")); // ... Collections.sort(persons);
-
public static <T> void sort(List<T> list, Comparator<? super T> c)
该方法支持在使用
sort
方法时直接定义自定义排序规则,常用于临时排序或者数据类型有多种排序规则不便于直接定义在类中的场景。List<Person> persons = new ArrayList<>(); persons.add(new Person("Ambrose")); // ... Collections.sort(persons, new Comparator<Person>() {@Overridepublic int compare(Person o1, Person o2) {return o1.getName().compareTo(o2.getName());}});
-
-
通过
Stream
流的sorted()
函数排序(需要 Java 8 支持)List<Person> persons = new ArrayList<>(); persons.add(new Person("Ambrose")); // ... // 可以在 comparing 方法中自定义排序规则 persons.stream().sorted(Comparator.comparing(Person::getName));
4. try/catch/finally 的执行顺序?如果 try 中执行了 return,finally 中的代码会继续执行吗?如何执行?★
首先,try/catch/finally 代码块的执行顺序是:先执行 try 代码块,如果遇到异常,则执行 catch 代码块,无论是否遇到异常,最终都执行 finally 代码块,然后结束方法。
在这个过程中,如果 try 代码块中有 return 语句,那 finally 代码块是否会继续执行?
答案是:会的。但是,有一点要注意。try 代码块执行时,如果遇到 return 语句,整个方法即将结束,后续语句不再执行,需要 return 的结果会保存但不立即返回,等待 finally 代码块执行完成后,再返回之前保存的结果。
虽然这么回答是没有问题的,但并不完美。准确来讲,应该分三种情况去回答:基本类型、引用类型、finally
中返回
-
基本类型
返回结果为基本类型时,很明显,刚才的回答是完全正确的,finally 语句会执行,但不会影响返回结果。
public int testFinally(){int i = 1;try {return i;}catch (Exception e){return 0;}finally {System.out.println("finally running");i = i + 1;} }
finally running 1
-
引用类型
返回结果为引用类型时,就不一样了。return 语句执行后,依然会保存但不立即返回,但是切记,这里暂存的是引用类型的地址,finally 语句中如果修改了该引用类型的内容,不会影响返回的引用地址,但是会影响到该地址中的内容!!!
public Person testFinally(){Person person = new Person();try {person.setName("Ambrose");return person;}catch (Exception e){return null;}finally {person.setName("Loser");} }
Person(name=Loser)
-
finally
中返回这种情况也很容易理解。try 代码块中的 return 不立即返回,继续执行 finally 语句,但当 finally 语句中也存在 return 语句时,finally 中的 return 会抢先返回结果并造成整个方法提前结束,不给 try 代码块中的 return 返回结果的机会。
public int testFinally(){int i = 1;try {return i;}catch (Exception e){return 0;}finally {System.out.println("finally running");i = i + 1;return i;} }
finally running 2
5. ArrayList 和 LinkedList 的区别。 ★★★
ArrayList 和 LinkedList 都实现了 List 接口。ArrayList 是基于动态数组的数据结构实现,以 O(1) 的时间复杂度对元素进行随机访问,访问元素的效率优于 LinkedList,但增删操作涉及到元素重排,内存扩展,数组复制等操作,效率较低;LinkedList 是基于链表的数据结构实现,查找元素的时间复杂度是 O(n),但它增删元素时只需要修改元素的指向地址,效率较高,不过也正因此,每个元素都需要存储前后元素的地址,所以 LinkedList 需要的内存空间也更多。
6. Array 和 List 之间如何转换?★
List 转换为 Array 通常使用 List 的 toArray 方法。
List<String> list = new ArrayList<>();list.add("H");list.add("e");list.add("l");list.add("l");list.add("o");// Object[] objs = list.toArray(); // 无参,返回 Object 数组。不建议// String[] res = list.toArray(new String[list.size()]); // 带参,返回泛型数组。建议
Array 转换为 List 通常有两种做法:
-
使用
java.util.Arrays
的asList()
方法。但是它返回的是java.utils.Arrays.ArrayList
,并非java.utils.ArrayList
,因此,它的返回结果支持 set()、get()、contains() 等但不支持 add()、remove() 等方法。如需增删元素,则需要重新初始化一个java.utils.ArrayList
集合将其加入,然后再进行增删操作。String[] strs = {"W", "o", "r", "l", "d"};List<String> list = Arrays.asList(strs);list.add("error"); // unsupportedOperationExceptionList<String> newList = new ArrayList<>(list);list.add("correct");
-
使用
Collections.addAll()
方法String[] strs = {"W", "o", "r", "l", "d"};List<String> list = new ArrayList<>();Collections.addAll(list, strs);
7. ArrayList 去重该如何操作?★
ArrayList 去重一般有两种操作:遍历 List 使用 contains 方法判断、利用 Set 集合的元素不可重复性。
-
使用
contains
方法判断List<Person> list = new ArrayList<>();list.add(new Person("Ambrose"));// ...List<Person> newList = new ArrayList<>();for (Person person:list){if (!newList.contains(person))newList.add(person);}
-
利用 Set 集合的元素不可重复性。
List<Person> list = new ArrayList<>(); list.add(new Person("Ambrose")); // ...List<Person> newList = new ArrayList(new HashSet<>(list));
但是需要注意的一点:HashSet 集合判定元素是否重复是用
equals
和hashcode
方法,所以如果元素是对象,一定要先重写其equals
和hashcode
方法;另外 HashSet 这种方式是不保证元素的顺序一致。
8. HashMap 线程是否安全?如何保证其线程安全?★★
HashMap 是线程不安全的。为什么说 HashMap 线程不安全,首先,put 元素时,如果两个线程向同一个桶中插入元素,则有可能会出现覆盖数据的风险;另外,get 元素,桶在扩重新计算大小时(resize )有可能会产生循环链表,造成死循环。
保证 HashMap 的线程安全通常有 3 中做法:使用 HashTable 线程安全类(性能差,已被淘汰,不建议使用)、使用 Collections.synchronizedMap 方法同步加锁(对象锁保护线程安全,全表加锁,性能差,不推荐使用)、使用 ConcurrentHashMap 类(分段锁。主存是 Segment 数组,继承了 ReetrantLock 锁,操作灵活,性能较好,且在 Java 8 中进一步优化,推荐使用)
9. 线程池的创建方式有几种?有哪些使用场景?★★★
常用的线程池有 5 种。
线程池名称 | 示例 | 使用场景及说明 |
---|---|---|
newFixedThreadPool | ExecutorService fixed = Executors.newFixedThreadPool(10); | 固定线程数量。超出指定数量后,新任务将在阻塞队列排队等待,直到有可能线程资源 |
newCachedThreadPool | ExecutorService cached = Executors.newCachedThreadPool(); | 缓存线程池。线程会在 keepAliveTime 时间超过默认的 60s 之后终止并从缓存中移除。适用于执行时间很短的大量任务需求场景。 |
newScheduledThreadPool | ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(10); // 定时任务 延迟时间 时间单位 scheduled.schedule(() -> System.out.println(), 3, TimeUnit.SECONDS); | 调度任务线程池。给定延迟后或定期执行任务 |
newSingleThreadExecutor | ExecutorService single = Executors.newSingleThreadExecutor(); | 单线程池。线程池保证永远有且只有一个线程可用,如果该线程异常,则立即启动一个新线程替代之 |
newWorkStealingPool | ExecutorService single = Executors.newWorkStealingPool(); | JDK 1.8 新增,足够大小线程池。自动根据当前操作系统资源计算最合理的线程数量,提高并发效率。 |
10. 注解是如何自定义?★
自定义注解是通过
@interface
关键字实现,与定义接口类似。
public @interface TestTag {
}
如上不包含任何成员变量的注解称为 标记注解, 注解可以包含成员变量,成员变量以无参形式声明,可以定义默认值,不定义默认值时,使用注解时必须对其赋值。
public @interface TestTag {String name;String value defalut "Defalut";
}
有 4 个元注解 专门负责注解其他注解,分别是:
Target
、Retention
、Inherited
、Documented
。
- Target:指定注解支持的范围。
- ElementType.Type:类、接口、枚举
- ElementType.FIELD:属性
- ElementType.METHOD:方法
- ElementType.PARAMETER:参数
- ElementType.CONSTRUCTOR:构造器
- ElementType.LOCAL_VARIABLE:局部变量
- ElementType.ANNOTATION_TYPE:注解
- ElementType.PACKAGE:包
- Retention:注解保留时间长短。
- SOURCE:源文件保留
- CLASS:类文件保留
- RUNTIME:运行时保留
- Inherited:自动继承。被标记了该注解的注解标记的类的子类也继承标记了该注解的注解
- Documented:文档化。被标记了该注解的注解标记的类或方法将可以被 javadoc 文档化
@Target(ElementType.METHOD, ElementType.FIELD)
@Retention(Retention.RUNTIME)
@Inherited
@Documented
public @interface TestTag {String name;String value defalut "Defalut";
}
注解定义完成后,接下来需要解析注解,即定义注解的作用逻辑。
通过反射机制,标记在类上的注解,可以从Class
对象上获取,标记在方法上的注解,可以从Method
对象上获取,标记在属性上的注解,可以从Field
对象上获取。
通常还会有几个关于注解的常用方法:isAnnotationPresent:判断是否配置了某个注解;getAnnotation:获取该元素上指定注解;getAnnotations:获取该元素上所有注解。
11. Java 8 的新特性有哪些?★
Java 8 是 Java 更新的一个主要版本。提供的新特性非常多,我个人使用频率最高的就是Stream流、Lambda表达式和方法引用,另外Optional解决空指针异常、Date Time API日期时间处理的加强版、接口的默认方法的定义也都是非常常用的特性,除此之外,Java 8 还提供了新的 js 引擎、类依赖分析器等等。
12. Java 8 对 HashMap 的优化有哪些?★★
Java 8 对 HashMap 的优化主要从 3 方面:
- 引入 红黑树 结构,当链表长度超过 8 时,链表会自动转换为红黑树结构,避免了单链表过长的情况,利用红黑树快速增删改查的特点提升 HashMap 的性能;
- 扩容机制优化。JDK 1.8 对 HashMap 使用了 2 次幂扩展(即原来长度的 2 倍),扩容时无需计算 hash 值,只需要看原 hash 新增的位数为 1 还是 0 即可,省去了计算 hash 的时间。
- 解决 resize 时死循环导致的线程安全问题。JDK 1.8 之前,HashMap 的链表才用头插法 resize,在多线程环境下有可能会产生死循环问题,JDK 1.8 采用 tail 指向尾指针,解决了该隐患,但线程依然是不安全的。
13. MySQL 和 Oracle 的区别★
MySQL 和 Oracle 的区别还是比较多的,这里只提几点具有代表性的:
- MySQL 是开源中小型数据库,占用空间小;Oracle 是收费大型数据库,占用空间大;
- MySQL 以表级锁为主,Oracle 以行级锁为主,所以 Oracle 对大数据和并发支持要更好;
- MySQL 默认不支持事务,需要借助 innodb 存储引擎才可以支持,Oracle 默认支持事务;
- MySQL 默认自动提交,Oracle 默认不自动提交;
- MySQL 用户与主机关联,容易被仿冒主机 IP,Oralce 权限和安全管理比较完善;
- MySQL 主键可以设置自增,Oracle 没有自动增长类型
- MySQL 默认 repeatable read 隔离级别,Oracle 默认 read commited 隔离级别
- MySQL 默认提交 SQL,如果过程中主机异常,可能会丢失数据,Oracle 的提交 SQL 线写入日志保存在磁盘,如果过程中主机异常,可以恢复数据;
- MySQL 逻辑备份需要锁表,影响正常使用,Oracle 逻辑备份不需要锁定数据。
14. 分组排序及排名函数的使用★
15. Oracle 中分页的实现,rownum 的特点★★
16. Oracle 中表的内联、外联、自联★
复面(高级篇)
17. 最近开发的项目及使用的技术。 ★★★
18. 最有难度的项目或者最有成就感的项目。★★★
19. Spring MVC 的工作流程★
- 用户发起请求到 DispatcherServlet【前端控制器】,DispatcherServlet 过滤请求(哪些请求能访问 Servlet,哪些不能访问 Servlet。即 url-pattern)并加载 springmvc.xml 配置文件;
- DispatcherServlet 找到 HandlerMapping【处理映射器】,完成 url 到 controller 的映射;
- HandlerMapping 携带拦截器,返回 Handler【处理器】;
- DispatcherServlet 获取到 Handler,找到 HandlerAdapter【处理器适配器】,访问 Handler;
- 执行 Handler;
- 执行完成后,返回 ModelAndView 给 HandlerAdapter;
- HandlerAdapter 接收 ModelAndView 返回给 DispatcherServlet;
- DispatcherServlet 请求 ViewResolver 【视图解析器】解析视图,找到 jsp 页面生成视图对象;
- 返回视图对象到 DispatcherServlet
- 将 ModelAndView 数据放置到 request 域中,加载数据;
- 返回数据,渲染视图;
20. 项目中大数据量的优化处理★★★★
21. 并发与多线程的使用★★★★
22. SQL 的优化★★★★
23. 用户单位★
24. 项目中遇到的技术性问题★★
25. 项目中遇到的业务性问题及和用户如何沟通解决★★
26. 详细介绍一下 MyBatis 的一、二级缓存★★
> MyBatis 的一级缓存基于 SqlSession,默认是开启的,在 SqlSession 对象中使用 HashMap 存储,不同的 SqlSession 之间的缓存是互不影响的。如果 SqlSession 执行了 DML 操作并提交,MyBatis 会清空 SqlSession 中的一级缓存,避免出现脏读。> MyBatis 的二级缓存基于 mapper,默认是关闭的,需要在 mybatis-config.xml 中配置,也是使用 HashMap 存储,但是多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的,其作用于是 mapper 下的同一个 namespace,不同 SqlSession 执行多次同一个 namespace 下的 SQL,第二次会从二级缓存中获取
27. SpringBoot 中如何使用 ES?ES 的常用方法有哪些?★
28. Maven 中 dependencies 和 dependencyManager 的区别?★
> `dependencyManager` 是一个管理器,其下声明的 `dependency` 并不会去直接去下载 jar 包> `dependencies`:其下声明 `dependency` 的依赖,Maven 会直接去下载相关 jar 包,如果 `dependency `声明了版本,则以它为准,如果没有声明 `version`, Maven 就会倒退到 `dependencyManagement` 中查找 该 artifactId 对应的 version,如果有,则继承,如果无,则报错
29. 介绍一下 XX 系统的基本业务流程
30. @Autowired 和 @Resource 的区别★★
@Autowired
是 Spring 提供的注解,默认按照类型自动注入,默认情况下要求依赖对象必须存在,如果允许 null 值,需要设置 required 属性为 false。如果想按照名称 (byName) 来装配,可以使用@Qualifier
注解一起使用
public class UserService {@Autowried(required=false)@Qualifier("userDao")private UserDao userDao;
}
@Resource
是 J2EE 提供的注解,默认按照名称自动注入。它有两个属性:name 和 type。Spring 会将 name 属性解析为 bean 的名字,type 属性解析为 bean 的类型。如果使用 name 属性,则使用 byName 策略注入,如果使用 type 属性则使用 byType 策略注入,如果都不使用则通过反射机制使用默认的 byName 策略注入
public class UserService {@Resource(name="userDao")private UserDao userDao; // 用于字段上// 或者@Resource(name="userDao")public void setUserDao(UserDao userDao) { // 用于属性的setter方法上this.userDao = userDao;}
}
31. 数据库索引的创建原则,什么情况下会导致索引失效?如何判断索引是否有效?★★
我使用的是 Oracle 数据库。Oracle 数据库的索引使用
CREATE INDEX 索引名 ON 表名(字段名)
基本语法创建。
创建索引的原则一般有以下几点:
- 唯一。唯一性索引一般基于 Hash 算法,可以快速。唯一的定位某条数据;
- 常用。为经常需要排序、分组和联合操作的字段建立索引;
- 查询。为经常作为查询筛选条件的字段建立索引;
- 数量。索引的数量要合理。数据更新时需要不断计算添加索引,如果索引数量太多会导致更新速率低;
- 数据量。索引列尽量数据量少,否则占用磁盘空间大,影响查询效率,如果数据量大不可避免,可以使用字段前缀作为索引;
- 区分度。尽量选择区分度高的列作为索引;
- 扩展。扩展现有索引,联合索引的查询效率要高于多个独立索引;
- 清理。及时删除不用或者不常用的索引;
导致索引失效的情况常见的有以下几种(测试时尽量多几条数据,数据量太少偶尔会导致不使用索引):
- 不包含 Where 条件
- 条件中包含 OR 关键字的大多数情况(除非 or 中包含的每一列都加了索引)
- 条件中包含 LIKE 关键字且占位符在前端(即 %LIKE)
- 条件中包含隐式转换(如 id 在表中定义为 varchar 类型,查询条件中写了 number 类型。 where id = 123,此时会导致隐式的类型转换,相当于 where to_number(id) = 123)
- 条件中包含 IS NULL 或者 IS NOT NULL 或者 <>、!= 关键字
- 条件中对索引列进行计算
判断索引是否有效一般使用 SQL 语句
select status from 表名 where index_name='索引名'
,如果结果为 VALID 则说明索引有效
32. Oracle 排序的实现原理★
33. MyBatis 中 insert 后如何返回主键?批量 Insert 可以返回主键集合吗?如何返回?★★
insert 单条记录时返回主键有两种方法:
- 在
insert
标签中设置属性useGeneratedKeys
的值为true
,属性keyProperty
的值为数据库记录主键名,属性keyColumn
的值为 Java Bean 中主键属性;然后在执行添加操作后,直接读取 Java 对象的属性即可。(仅支持可自增主键的数据库:如 MySQL)
<mapper namespace="xxx.mapper"><!-- 插入数据:返回记录主键id值 --><insert id="xxx" parameterType="XXX" useGeneratedKeys="true" keyProperty="id" keyColumn="id" >insert xxx</insert>
</mapper>
- 使用
selectKey
标签内执行 SQL 语句获取主键
<insert id="xxx" parameterType="XXX"><!-- resultType: 返回类型 --><!-- order: 获取主键 SQL 在 Insert 之前还是之后,MySQL 一般写 AFTER,因为它是自增 --><!-- keyProperty: 返回的主键存放于哪一列 --><selectKey resultType="INTEGER" order="BEFORE" keyProperty="id">SELECT LAST_INSERT_ID()</selectKey>insert xxx
</insert>
批量 insert 记录时返回主键(与 insert 单条记录配置大体相同,需要 MyBatis 3.2 以上),只需要将 parameterType 属性的值修改为
java.util.List
<insert id="xxx" useGeneratedKeys="true" keyProperty="id">insert into xxx (xxx) values<foreach item="item" collection="list" separator=",">(#{item.xxx})</foreach>
</insert>
34. MyBatis 中 # 和 $ 符号的区别★★★
#{}
传参,传入的数据会被当做字符串,自动加上双引号,在预编译处理阶段,将参数部分用占位符代替,然后在 DBMS 中进行参数替换,可以有效的防止 SQL 注入攻击;
${}
传参,MyBatis 不会进行特殊处理,只进行简单的字符串替换,一般用于传入数据库名、表名、动态列名等
35. Hibernate 和 MyBatis 的区别★
二者都作为非常流行的 ORM 框架,其区别从以下几方面来看:
- Hibernate 具有优秀的映射机制,无需关心 SQL 的实现,适用于侧重业务逻辑,SQL 逻辑简单的场景;MyBatis 需要手动编写 SQL 实现,有极高的灵活性,适用于处理复杂的 SQL 逻辑及优化;
- Hibernate 与数据库的关联在 XML 中,无需 SQL,对数据库没有具体要求,扩展性和移植性极高,MyBatis 的所有 SQL 都依赖于数据库手动编写,扩展性和移植性较差;
- 二者的除了默认的二级缓存机制都可以自定义缓存方案。但 Hibernate 的二级缓存配置在 SessionFactory 生成的配置文件中,然后再在具体的表对象中配置;而 MyBatis 的二级缓存直接在具体的表对象中进行详细配置,不同的表可以定义不同的缓存机制,并且可以在 namespace 中共享相同的缓存配置。正因为如此,MyBatis 在自定义二级缓存时要特别注意脏数据的处理,而 Hibernate 良好的管理机制可以避免二级缓存出现脏数据。
36. MyBatis 的常用方法和常用标签,及其使用场景★
MyBatis 的常用标签大致分以下几类:
- sql 语句
- select 查询语句
- updata 更新语句
- delete 删除语句
- insert 插入语句
- sql 拼接
- if 条件判断
- foreach 常用与构建 in 语句或者批量操作
- choose when 按顺序判断,选择第一个满足条件的语句(类似于 switch case)
- 配置属性与结果集关系
- resultMap
- 配置关联关系
- collection
- association
- 格式化
- where 多个 if 条件添加查询条件时,不知道哪个该加 AND,哪个不该加,就用 where,它会自动处理
- set 多个 if 条件插入字段时,不知道哪个后面需要加逗号,哪个不该加,就用 set,它会自动处理
- trim 忽略前后空格,可以指定忽略前后缀
- 常量及引用
- sql 定义一段 SQL 用于复用
- include 用于引用 sql 标签定义的常量
37. SpringBoot 基本配置★★★
SpringBoot 项目下创建名为
xxxApplicatioin
的入口类(一般会自动创建),入口类中创建main
入口方法,方法中使用SpringApplication.run(xxx.class, args);
启动 SpringBoot 项目;
入口类上有一个@SpringBootApplication
注解,是 SpringBoot 核心注解,是一个组合注解,包含:@SpringBootConfiguration
标注当前类为配置类,并将当前类中以 @Bean 注解标记的方法实例纳入 Spring 容器、EnableAutoConfiguration
完成依赖的自动配置、ComponentScan
自动扫描该类下所有同级包及子包中的 Bean。所以建议入口类文件放置于最外层包(groupId + artifactId)下、
SpringBoot 使用全局配置文件
application.properties
或application.yml
,可存在于 4 个路径,优先级依次为:
- 根目录下 config 文件夹中
- 根目录下
- classpath 下 config 文件夹中
- classpath 下
在
application.properties
配置文件中,配置 tomcat 端口号、访问路径、编码、全局常量属性等。
启动 SpringBoot 应用,访问即可
38. SpringBoot 的常用注解和常用方法★★
常用注解有以下几类:
- SpringBootApplication 核心注解
- EnableAutoConfiguration 自动配置注解。开启后,SpringBoot 根据当前类下的包或类来配置 SpringBean
- Configuration 配置注解。标记配置类
- ImportResource 加载配置文件注解。加载 xml、properties 配置文件,无法加载 yml 文件
- ComponentScan 组件扫描。开启后,SpringBoot 扫描到类并加入上下文。默认装配了 @Controller、@Service、@Repository、@Component 注解
- Repository DAO 组件注解。标记数据访问组件,会被 ComponentScan 扫描到
- Service Service 组件注解。标记服务层组件,会被 ComponentScan 扫描到
- RestController REST 风格控制器注解。标记控制层,相当于 @Controller + @ResponseBody
- ResponseBody 返回数据体组件。开启后,返回结果不会被解析为路径,直接写入 http body 中,如果是 JSON,则返回 JSON 数据
- RequestMapping 请求地址映射注解。 用于方法时表示请求的路径,用于类时表示类中所有请求方法的父路径
- Bean Bean对象注解。产生 Bean 对象,交给 Spring 管理,产生 Bean 对象的方法 Spring 只会调用一次,随后将其放入 IOC 容器中
- Autowried 自动装配注解。默认按照类型注入。
- Resource 自动装配注解。默认按照名称注入。
- RequestParam 请求参数注解。用于请求方法的参数前。
- Component 通用组件注解。用于无法用 Service 和 Repository 注解标记的组件
39. Redis 的基本数据类型有哪几种?★★★
Redis 的基本数据类型有 5 种:String、Hash、List、Set、zset
40. ES 的常用方法★
41. MyBatis 的分页原理★
42. Oracle 中 union 和 union all 的区别,哪一个会自动排序,其原理是什么?★★
union 对两个结果集进行并集操作,不包括重复行,同时进行默认排序。它会将两次查询的结果合并后重查并排序。效率较低;
union all 对两个结果集进行并集操作,不做其他任何操作,效率较高,但可能出现重复数据。
另外这里再扩展两个:
minus 对两个结果集进行差集操作,去重,默认排序
intersect 对两个结果集进行交集操作,去重,默认排序
43. Nignx 的部署步骤★
44. IO 流的常用类★
Java 的 IO 流主要分 2 种:字符流 和 字节流。
字符流又分为读取流(Reader) 和 写入流(Writer),字节流又分为 输入流(InputStream) 和 输出流(OutputStream)。
这 4 个流又分为很多个子类。比较重要的几个如下:
输入流 | 输出流 | |
---|---|---|
字符流 | InputStreamReader(BufferedReader) | OutputStreamWriter(BufferedWriter) |
字节流 | FileInputStream(BufferedInputStream) | FileOutputStream(BufferedOutputStream) |
45. Ajax 的优缺点及回调函数★
Ajax 的优点
- 无刷新更新数据
- 异步通信减少响应时间,不打断用户操作
- 前后端负载均衡
- 界面与应用分离
Ajax 的缺点
- 破坏了浏览器机制,back 和 history 功能失效
- 增加安全隐患。暴露更多的信息,有 XSS,SQL 注入等风险
- 对移动设备不友好
- 破坏了异常处理机制
ajax 的回调函数有 5 个:
- beforeSend:发送请求之前
- error:请求出错
- success:请求成功
- complete:请求完成
- dataFilter:请求成功之后,传入返回数据和类型,会返回处理后的数据给 success 回调函数
46. Linux 的常用命令★
- ls:显示目录
- mkdir:创建目录
- cd:切换目录
- echo:创建带内容的文件
- cat:查看文件内容
- cp:复制
- mv:移动
- rm:删除
- find:搜索文件
- more:显示更多
- whoami:当前用户
- ifconfig:网络配置
- ping:测试网络连通
- netstat:网络状态
- clear:清屏
- kill:杀死进程
- tar:压缩
- shutdown -r:关机重启
- | :管道符
- :q:退出
- :q!:强制退出
- :wq:保存退出
47. IDEA 的常用快捷键★
以下快捷键是自己比较常用,部分快捷键进行过自定义调整,不具普遍性。
- shift+ F6:重命名
- alt + enter:自动修复
- ctrl + w:自动选择代码块
- ctrl + [ 或 ctrl + ]:自动跳转对应的代码块起始/结束
- tab:代码补全
- ctrl + shift + u:切换大小写
- f2:跳转到下一个报错位置
- shift + f2:跳转到上一个报错位置
- alt + shift + f:格式化
- ctrl + f 或 ctrl + shift + f 或 shift + shift:搜索
- ctrl + alt + t:自动包裹(surround with)
- alt + insert:常用功能(构造方法、重写方法、产生 getter&setter 等)
- ctrl + z:撤销(undo)
- ctrl + y:重做(redo)
- ctrl + r:替换
- ctrl + /:注释
- ctrl + x:剪切整行
- ctrl + shift + j:将选中内容合并到一行
- ctrl + shift + backspace:返回上个修改位置
48. Redis是否了解?其常用方法有哪些?★★
Redis 所有的数据都在内存中,所以运算速度极快,而且单线程避免了上下文切换的性能损耗。它使用多路复用将所有事件排入队列中进行依次分配来实现并发连接。
Redis 提供了 2 种不同的持久化方式:RDB(数据文件) 和 AOF(更新日志),可以通过 redis.conf 配置文件进行修改。
满足持久化条件时,会进行持久化保存,当 redis 启动时,会先解析日志文件,恢复数据,然后加载持久化保存的数据。
这两种持久化方式:
- RDB 模式默认开启,在指定时间间隔内生成数据集的时间点快照;
- AOF 模式默认关闭,持久化记录服务器执行的所有写操作命令,在服务器启动时,通过重新执行这些命令来还原数据。
关于 Redis 的常用方法,其实最常用的也就存值、取值、删除值的方法。只是不同的数据类型,对命名有所区分,另外各数据类型也会有一些特定的方法。
String:get(key)、set(key, val)、delete(key)、rename(oldKey, newKey)、keys(pattern) 、hasKey()等
Hash:get(key, field)、put(key, hashKey, val)、putAll(key, maps)、keys(pattern) 、hasKey()等
List:index(key, index)、range(key, start, end)、leftPush(key, value)、leftPushAll(key, values)、rightPush(key, value)、rightPush(key, values)、set(key, index, value)、leftPop(key)、rightPop(key)、remove(key, index, value)、trim(key, start, end)等
Set:add(key, value)、remove(key, values)、pop(key)、interset(key, otherKey)、union(key, otherKeys)、difference(key, otherKeys) 等
49. Spring 的特性★★
Spring 的核心特性 IOC(控制反转) 和 AOP (面向切面)
一个项目中需要用到很多类来描述它们特有的功能,通过类与类之间的相互协作来完成特定的业务逻辑。而 IOC 设计思想,则是将这些相互依赖的对象的创建、关联等工作交给 Spring 容器,对象本身只需要关注自身业务逻辑,由 Spring 容器来控制对象如何获取外部资源。
面向切面编程实际上就一个模块分离的思想。一个关注点的模块,可以横向切分很多个对象。系统本身就是由很多组件组成,而组件除了负责特定功能外,往往还承担一些额外功能,而 AOP 做的,就是分离这些功能。
换句话说,通常系统的业务功能实际上分为核心业务功能和非核心业务功能(辅助功能,如日志、性能统计等)。AOP 就是将这些与业务无关却被共同调用的模块进行抽离封装,已减少系统代码冗余,降低模块间耦合度。
AOP 常用的术语:
- 通知
- Before(前置通知):方法被调用前
- After(后置通知):方法被调用后
- After-returning(返回通知):方法成功执行后
- After-throwing(异常通知):方法抛出异常后
- Around(环绕通知):方法调用前和调用后
- 连接点:应用执行过程中能够插入切面的点
- 切点:定义了切面在何处要织入的一个或多个连接点
- 切面:通知和切点的结合。通知和切点共同定义切面
- 引入:允许向现有类添加新方法或属性
- 织入:将切面应用到目标对象,并创建新的代理对象的过程,一般进行织入有几个生命周期:
- 编译期:目标类编译时织入,需要特定编译器
- 类加载期:目标类加载到 JVM 时织入,需要特定编译器
- 运行期:应用运行到某时刻时织入,AOP 会在此时为目标对象动态创建代理对象。
50. MyBatis 的工作原理★★
- 程序开始运行,需要读取 mybatis 全局配置文件:
mybatis-config.xml
,获取数据库连接, MyBatis 运行环境等;- 配置文件中加载 SQL 映射文件;
- 然后,通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory;
- 创建 SqlSession 会话对象,该对象包含了执行 SQL 语句的所有方法;
- 创建 Executor 执行器,根据 SqlSession 传递的参数动态生成需要执行的 SQL 语句,同时查询及维护缓存数据;
- 在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数对应信息的封装,用于存储映射的 SQL 语句 id、参数等信息。
- 输入参数映射
- 输出结果映射
51. MyBatis 的 Executor 执行器原理★
52. MyBatis 的延迟加载原理及使用场景★
53. MyBatis 传参问题★
54. SpringBoot 的安全配置有哪些?Security 及 OAuth2 的使用方法★★★
55. GC 的工作原理★★
终面
期望薪资
婚否
是否考虑搬家?
对短期出差有什么看法?
未来打算在 SZ 定居吗?
这篇关于金九银十面试季 —— 2020 年10 月的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!