2024 最新 Java 面试题汇总(附完整答案)
内卷可以说是 2024 年最火的一个词了。2024 年刚开始,LZ 在很多程序员网站看到很多 Java 程序员的 2023 年度总结都是:Java 越来越卷了(手动狗头),2024 年是被卷的一年。前有几百万毕业生虎视眈眈,后有在职人员带头“摸鱼”占着坑位,加上现在的经济形势让很多公司倒闭,市面上根本没那么多就业岗位。自然而然 HR 们就开始优中选优,计划内卷矛盾!
那么如何在众多的应聘者中脱颖而出呢?
答案之一就是要摸清现在互联网公司的面试套路! 我们以大厂为例,他们的面试题都长这样:
你来随意讲一个集合框架整体框架
为什么 hashmap 要在 8 的时候树化
你都了解什么设计模式,他们在 JDK 中如何体现的
线程池拒绝策略分别使用在什么场景
G1 两个 region 不是连续的,而且之间还有可达的引用,我现在要回收其中一个,另一个会被怎么处理
听说过 CMS 的并发预处理和并发可中断预处理吗
......
怎么样,是不是感觉与平时大家面试问的都是不太一样,不在是什么 Java 的面向对象,什么谈一下 Spring 的理解了(手动狗头)
另一方面就是要清楚面的岗位不同的职级对应需要掌握的技能有哪些了
还有就是深挖现在的高频面试题了,你还别说面试前临时抱佛脚,拿个高频面试手册系统查漏补缺一下多少是对你有帮助的!好巧不巧,这个 LZ 也有!前段时间我在阿里的一个好大哥分享了一份他们内部最新出品的 2023Java 面试突击手册,内容涵盖了:Java 基础,JVM,多线程,Mysql,Spring,Spring Boot,Spring Cloud,Dubbo,Mybatis,Redis,网络,Linux,MQ,Zookeeper,Netty,大数据,算法,项目,设计模式 19 个模块,足足有近 1700 页!LZ 大概也看了下,是由一些大厂高频 Java 面试题汇总而成,快过年了 LZ 也没啥好送的,借此机会,就把这份 Java 面试突击手册送给大家卷(手动狗头)。
Ps:由于篇幅限制,笔记无法全部为大家展示出来,就以截图主要内容的形式让大家参考啦,需要完整版的小伙伴可以点击>>传送门<<获取
目录
一.java 基础
1.八大基础类型
2.java 三大特性
3.重载和重写的区别
4.pubilc、protected、(dafault)不写、private 修饰符的作用范围
5.==和 equals 的区别
6.hashcode()值相同,equals 就一定为 true
7.为什么重写 equals(),就要重写 hashcode()?
8.short s = 1;s = s + 1;(程序 1)和 short s = 1; s += 1;(程序 2)是否都能正常运行
9.说出下面程序的运行结果,及原因
10.&和 &&的区别
11.String、StringBuffer、StringBuilder 的区别
12.String rap = new String("ctrl");创建了几个对象?
13.什么是反射
14.浅拷贝和深拷贝的区别
15.构造器能被重写吗
16.并发和并行
17.实例变量和类变量。
18.说出下面程序的运行结果,及原因
19.抽象类和接口的区别
20.Error 和 Exception 有什么区别
21.NoClassDefFoundError 和 ClassNotFoundException 区别
22.如果 try{} 里有一个 return 语句,那么 finally{} 里的代码会不会被执行,什么时候被执行,在 return 前还是后?
23.看一面代码执行结果是啥
24.final 关键字有哪些用法?
25.jdk1.8 的新特性
26.http 中重定向和转发的区别
27.get 和 post 请求的区别 delete、put
28.cookie 和 session 的区别
29.java 中的数据结构
30.什么是跨域?跨域的三要素
31.tomcat 三个默认端口及其作用
32.throw 和 throws 的区别?
33.说一下你熟悉的设计模式
34.实例化对象有哪几种方式
35.java 中什么样的类不能被实例化
36.序列化和反序列化
37.序列化的优点
38.你知道什么是单点登录吗?
39.实现单点登录的方式
40.sso(单点登录)与 OAuth2.0(授权)的区别?
41.如何防止表单提交
42.泛型是什么?有什么好处?
43.值传递和引用传递
二.java 集合
1.List、Set、Map 的区别
2.List、Set、Map 常用集合有哪些?
3.ArrayList 的初始容量是多少?扩容机制是什么?扩容过程是怎样?
4.什么是哈希表
5.什么是哈希冲突
6.解决哈希冲突
7.HashMap 的 hash()算法,为什么不是 h=key.hashcode(),而是 key.hashcode()^ (h>>>16)
8.为什么 HashMap 的初始容量和扩容都是 2 的次幂
9.HashMap 如果指定了不是 2 的次幂的容量会发生什么?
10.HashMap 为什么线程不安全
11.解决 Hashmap 的线程安全问题
12.ConcurrentHashMap 的原理
13.为什么用 synchronized 代替 ReentrantLock
14.HashMap 为什么使用链表
15.HashMap 为什么使用红黑树
16.HashMap 为什么不一上来就使用红黑树
17.说说你对红黑树的理解
18.为什么链表长度大于 8,并且表的长度大于 64 的时候,链表会转换成红黑树?
19.为什么转成红黑树是 8 呢?而重新转为链表阈值是 6 呢?
20.为什么负载因子是 0.75?
21.什么时候会扩容?
22.为什么不是满了扩容?
23.扩容过程
24.HashMap 和 Hashtable 的区别
25.集合为什么要用迭代器(Iterator)
三.多线程
1.线程是什么?多线程是什么?
2.守护线程和用户线程
3.线程的各个状态
4.线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等
5.wait()和 sleep()的区别?
6.为什么 wait()、notify()、notifyAll()方法定义在 Object 类里面,而不是 Thread 类?
7.start()和 run()的区别
8.实现多线程的方式
9.Runnable 和 Callable 的区别
10.线程池的好处
11.线程池的七大参数
12.线程池的执行过程
13.四大方法
14.四大拒绝策略
15.shutdown 和 shutdownNow 的区别?
16.什么是死锁?
17.造成死锁的四个必要条件
18.线程安全主要是三方面
19.volatile 和 synchronized 的区别
20.synchronized 和 lock 的区别
21.JMM(java 内存模型)
22.JMM 的约定
23.JMM 的八个命令
24.为什么要有 JMM,用来解决什么问题?
四.jvm
1.jvm 是什么?
2.jvm 的作用
3.java 文件的加载过程
4.jdk、jre、jvm 的区别
5.类加载器的作用
6.类加载器的类型
7.双亲委派机制的加载过程
8.双亲委派机制的优缺点
9.为什么要打破双亲委派机制
10.打破双亲委派机制的方式
11.jvm 的每个部分储存的都是什么
12.内存溢出(oom)和栈溢出
13.垃圾回收的作用区域
14.怎么判断对象是否可回收
15.四种引用类型 强引用 软引用 弱引用 虚引用
16.垃圾回收算法
17.轻 GC(Minor GC)和 重 GC(Full GC)
18.什么时候会发生重 GC
五.锁
1.悲观锁和乐观锁
2.悲观锁和乐观锁的场景
3.自旋锁和自适应自旋锁
4.无锁、偏向锁、轻量级锁、重量级锁
5.公平锁和非公平锁
6.可重入锁
7.独享锁和共享锁
8.互斥锁和读写锁
9.分段锁
10.锁优化技术
六.CAS 原理
七.Redis 高频面试题
八.常用八大排序算法和四大查找算法
九.数据库
十.Mybatis 常见面试题
十一.SpringMVC 常见面试题
十二.Spring 常见面试题
十三.SpringBoot 常见面试题
十四.springCloud 常见面试题
后续会持续更新,广告回来更精彩!如果想看原理可以看看我的另几篇文章
没有过多的讲解原理,你们只要背会了,就能快乐两年半。
一.java 基础
1.八大基础类型
数字型: 字节类型(byte)、短整型 short、整型 int、长整型 Long、单精度浮点数 float、双精度浮点数 double
字符型: 字符类型 char、
布尔型: 布尔类型 boolean、
2.java 三大特性
封装: 使用 private 关键字,让对象私有,防止无关的程序去使用。
继承: 继承某个类,使子类可以使用父类的属性和方法。
多态: 同一个行为,不同的子类具有不同的表现形式。
3.重载和重写的区别
重载: 发生在同一类中,函数名必须一样,参数类型、参数个数、参数顺序、返回值、修饰符可以不一样。
重写: 发生在父子类中,函数名、参数、返回值必须一样,访问修饰符必须大于等于父类,异常要小于等于父类,父类方法是 private 不能重写。
4.pubilc、protected、(dafault)不写、private 修饰符的作用范围
pubilc: 同类、同包子类、非同包子类、非同包类都可以使用。
protected: 同类、同包子类、非同包子类可以使用,非同包类不能。
(dafault)不写: 同类、同包子类可以使用,非同包子类、非同包类不能。
private: 只有同类可以。
5.==和 equals 的区别
==: 基础类型比较的值,引用类型比较的是地址值。
equals: 没有重写比较地址值是否相等,重写比较的内容是否相对。比如 String 类重写 equals,源码首先比较是否都是 String 对象,然后再向下比较。
6.hashcode()值相同,equals 就一定为 true
不一定,因为 "重地"和"通话"的 hashcode 值就相同,但是 equals()就为 false。
但是 equals()为 true,那么 hashcode 一定相同。
7.为什么重写 equals(),就要重写 hashcode()?
保证同一对象,如果不重写 hashcode,可能会出现 equals 比较一样,但是 hashcode 不一样的情况。
8.short s = 1;s = s + 1;(程序 1)和 short s = 1; s += 1;(程序 2)是否都能正常运行
程序 1 会编译报错,因为 s + 1 的 1 是 int 类型,因为类型不兼容。强制转换失败。
程序 2 可以正常运行,因为 java 在复合赋值解释是 E1 += E2,等价于 E1 = (T)(E1 + E2),T 是 E1 的类型,因此 s += 1 等价于 s = (short)(s + 1),所以进行了强制类型的转换,所以可以正常编译。
9.说出下面程序的运行结果,及原因
因为 Integer = a,相当于自动装箱(基础类型转为包装类),因为 Integer 引入了 IntegerCache 来缓存一定的值,IntegerCache 默认是 -128~127,所以 128 超过了范围,a 和 b 不是相同对象,c 和 d 是相同对象。
可以通过 jvm 启动时,修改缓存的上限。
10.&和 &&的区别
&&: 如果一边为假,就不比较另一边。具有短路行
&: 两边都为假,结果才为假,多用于位运算。
11.String、StringBuffer、StringBuilder 的区别
String: 适用于少量字符串。创建之后不可更改,对 String 的修改会生成新的 String 对象。
StringBuilder: 适用于大量字符串,线程不安全,性能更快。单线程使用
StringBuffer: 适用于大量字符串,线程安全。多线程使用,用 synchronized 关键字修饰。
12.String rap = new String(“ctrl”);创建了几个对象?
一个或两个,如果常量池存在,那就在堆创建一个实例对象,否则常量池也需要创建一个。
13.什么是反射
在运行过程中,对于任何一个类都能获取它的属性和方法,任何一个对象都能调用其方法,动态获取信息和动态调用,就是反射。
14.浅拷贝和深拷贝的区别
浅拷贝: 基础数据类型复制值,引用类型复制引用地址,修改一个对象的值,另一个对象也随之改变。
深拷贝: 基础数据类型复制值,引用类型在新的内存空间复制值,新老对象不共享内存,修改一个值,不影响另一个。
深拷贝相对浅拷贝速度慢,开销大。
15.构造器能被重写吗
不能,可以被重载。
16.并发和并行
并发: 一个处理器同时处理多个任务。(一个人同时吃两个苹果)
并行: 多个处理器同时处理多个任务。(两个人同时吃两个苹果)
17.实例变量和类变量。
类变量是被 static 所修饰的,没有被 static 修饰的叫实例变量也叫成员变量。同理也存在类对象和实例对象,类方法和实例方法。
18.说出下面程序的运行结果,及原因
①执行顺序是 父类静态代码块(父类静态变量) -> 子类静态代码块(子类静态变量) -> 父类非静态代码块 -> 父类构造方法 -> 子类非静态代码块 -> 子类构造方法
②静态代码块(静态变量)只执行一次。
19.抽象类和接口的区别
抽象类只能单继承,接口可以实现多个。
抽象类有构造方法,接口没有构造方法。
抽象类可以有实例变量,接口中没有实例变量,有常量。
抽象类可以包含非抽象方法,接口在 java7 之前所有方法都是抽象的,java8 之后可以包含非抽象方法。
抽象类中方法可以是任意修饰符,接口中 java8 之前都是 public,java9 支持 private。
扩展:普通类是亲爹,手把手教你怎么学,抽象类(多个类具有相同的东西,拿出来放抽象类)是师傅,教你一部分秘籍,然后告诉你怎么学。接口(规范了某些行为)是干爹,只给你秘籍,怎么学全靠你。
20.Error 和 Exception 有什么区别
Error: 程序无法处理,比较严重的问题,程序会立即崩溃,jvm 停止运行。
Exception: 程序本身可以处理(向上抛出或者捕获)。编译时异常和运行时异常
21.NoClassDefFoundError 和 ClassNotFoundException 区别
NoClassDefFoundError: 在打包时漏掉了某些类或者打包时存在,然后你把 target 里的类删除,然后 jvm 运行时找不到报错。
ClassNotFoundException: 在编译的时候某些类找不到,然后报错。
22.如果 try{} 里有一个 return 语句,那么 finally{} 里的代码会不会被执行,什么时候被执行,在 return 前还是后?
会执行,在 return 之前执行,如果 finally 有 return 那么 try 的 return 就会失效。
23.看一面代码执行结果是啥
因为在 return 前,jvm 会把 2 暂存起来,所以当 i 改变了,回到 try 时,还是会返回暂存的值。
24.final 关键字有哪些用法?
修饰类: 不能被继承。
修饰方法: 不能被重写。
修饰变量: 声明时给定初始值,只能读取不能修改。如果是对象引用不能改,但是对象的属性可以修改。
25.jdk1.8 的新特性
①lambda 表达式
②方法引用
③加入了 base64 的编码器和解码器
④函数式接口
⑤接口允许定义非抽象方法,使用 default 关键字即可
⑥时间日期类改进
26.http 中重定向和转发的区别
重定向发送两次请求,转发发送一次请求
重定向地址栏会变化,转发地址栏不会变化
重定向是浏览器跳转,转发是服务器跳转
重定向可以跳转任意网址,转发只能跳转当前项目
重定向会有数据丢失,转发不会数据丢失
27.get 和 post 请求的区别 delete、put
get 相对不安全,数据放在 url 中(请求行),post 放在 body 中(请求体),相对安全。
get 传送的数据量小,post 传送的数据量大。
get 效率比 post 高,是 form 的默认提交方法。
28.cookie 和 session 的区别
存储位置不同:cookie 放在客户端电脑,session 放在服务器端内存的一个对象
存储容量不同:cookie <=4KB,一个站点最多保存 20 个 cookie,session 是没有上限的,但是性能考虑不要放太多,而且要设置 session 删除机制
存储数据类型不同:cookie 只能存储 ASCll 字符串,session 可以存储任何类型的数据
隐私策略不同:cookie 放在本地,别人可以解析,进行 cookie 欺骗,session 放在服务器,不存在敏感信息泄露
有效期不同:可以设置 cookie 的过期时间,session 依赖于 jsessionID 的 cookie,默认时间为-1,只需要关闭窗口就会失效
29.java 中的数据结构
数组、链表、哈希表、栈、堆、队列、树、图
30.什么是跨域?跨域的三要素
跨域指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制
协议、域名、端口
注意:localhost 和 127.0.0.1 虽然都指向本机,但也属于跨域
31.tomcat 三个默认端口及其作用
8005:这个端口负责监听关闭 tomcat 的请求。
8009:接受其他服务器的请求
8080:用于监听浏览器发送的请求
32.throw 和 throws 的区别?
throw:抛出一个异常。
throws:声明一个异常。
33.说一下你熟悉的设计模式
单例模式: 保证被创建一次,节省系统开销。
工厂模式: 解耦代码。
观察者模式: 定义了对象之间的一对多的依赖,这样一来,当一个对象改变时,它的所有的依赖者都会收到通知并自动更新。
代理模式: 代理对象具备被代理对象的功能,并代替被代理对象完成相应操作,并能够在操作执行的前后,对操作进行增强处理。
模板模式: 较少代码冗余。例如:redis 模板。
34.实例化对象有哪几种方式
① new
② clone()
③ 反射
④先序列化在反序列化
35.java 中什么样的类不能被实例化
抽象类: abstract 关键字修饰的类。
36.序列化和反序列化
序列化: 把对象转为字节序列的过程,在传递和保存对象时,保证了对象的完整性和可传递性,便于在网络传输和保存在本地文件中。
反序列化: 把字节序列转为对象的过程,通过字节流的状态和信息描述,来重建对象。
37.序列化的优点
将对象转为字节流存储到硬盘上,当 JVM 噶了的话,字节流还会在硬盘上等待,等待下一次 JVM 的启动,把序列化的对象,通过反序列化为原来的对象,减少储存空间和方便网络传输(因为是二进制)。
38.你知道什么是单点登录吗?
单点登录(SSO:Single Sign On): 同一账号在多系统中,只登录一次,就可以访问其他系统。多个系统,统一登录。
列如:在一个公司下,有多个系统,比如淘宝和天猫,你登录上淘宝,就不用再去登录天猫了。
39.实现单点登录的方式
① Cookie: 用 cookie 为媒介,存放用户凭证。登录上父应用,返回一个加密的 cookie,访问子应用的时候,会对 cookie 解密校验,通过就可以登录。不安全和不能跨域免登。
② 分布式 session 实现: 用户第一次登录,会把用户信息记录下来,写入 session,再次登录查看 session 是否含有对应信息。session 系统不共享,使用缓存等方式来解决。
③重定向: 父应用提供一个 GET 方式的登录接口 A,用户通过子应用重定向连接的方式访问这个接口,如果用户还没有登录,则返回一个登录页面,用户输入账号密码进行登录,如果用户已经登录了,则生成加密的 token,并且重定向到子应用提供的验证 token 的接口 B,通过解密和校验之后,子应用登录当前用户,虽然解决了安全和跨域,但是没前两种简单。
40.sso(单点登录)与 OAuth2.0(授权)的区别?
单点登录: 就是一个公司多个子系统登录问题。
OAuth2.0: 是授权问题,比如微信授权问题。是一种具体的协议。
41.如何防止表单提交
①js 屏蔽提交按钮。
②给数据库添加唯一约束。
③利用 Session 防止表单重复提交。会有一个 token 标记,表单提交的时候拦截器会检查是否一致,不一致就不通过。
④使用 AOP 切入实现。自定义注解,然后新增切入点,然后每次都记录过期时间,然后做比较。
42.泛型是什么?有什么好处?
本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
好处:
①类型安全
②消除强制类型转换
③提高性能
④提高代码的复用性
43.值传递和引用传递
值传递: 函数调用时会把实际参数,复制一份到函数中,函数中对参数进行操作,并不会影响参数实际的值。
引用传递: 将实际参数的地址值传递到函数中,函数对参数进行操作,会影响到实际参数的值。
注意: java 中不存在引用传递(即使传的是对象,那也只是传递了对象的引用地址的副本,也属于值传递)。
二.java 集合
java 集合框架详解
HashMap 底层原理详解
1.List、Set、Map 的区别
List 集合有序、可重复的单例集合。
Set 集合无序、不可重复的单例集合。
Map 集合无序、k 不可重复,v 可重复的双例集合。
2.List、Set、Map 常用集合有哪些?
List
vector: 底层是数组,方法加了 synchronized 来保证线程安全,所以效率较慢,使用 ArrayList 替代。
ArrayList: 线程不安全,底层是数组,因为数组都是连续的地址,所以查询比较快。增删比较慢,增会生成一个新数组,把新增的元素和原有元素放到新数组中,删除会导致元素移动,所以增删速度较慢。
LinkedList: 线程不安全,底层是链表,因为地址不是连续的,都是一个节点和一个节点相连,每次查询都得重头开始查询,所以查询慢,增删只是断裂某个节点对整体影响不大,所以增删速度较快。
Set
HashSet: 底层是哈希表(数组+链表或数组+红黑树),在链表长度大于 8 时转为红黑树,在红黑树节点小于 6 时转为链表。其实就是实现了 HashMap,值存入 key,value 是一个 final 修饰的对象。
TreeSet: 底层是红黑树结构,就是 TreeMap 实现,可以实现有序的集合。String 和 Integer 可以根据值进行排序。如果是对象需要实现 Comparator 接口,重写 compareTo()方法制定比较规则。
LinkedHashSet: 实现了 HashSet,多一条链表来记录位置,所以是有序的。
Map<key,value>双例结构
TreeMap: 底层是红黑树,key 可以按顺序排列。
HashMap: 底层是哈希表,可以很快的储存和检索,无序,大量迭代情况不佳。
LinkedHashMap: 底层是哈希表+链表,有序,大量迭代情况佳。
3.ArrayList 的初始容量是多少?扩容机制是什么?扩容过程是怎样?
初始容量: 默认 10,也可以通过构造方法传入大小。
扩容机制: 原数组长度 + 原数组长度/2(源码中是原数组右移一位,也就相当于除以 2)
注意:扩容后的 ArrayList 底层数组不是原来的数组。
扩容过程: 因为 ArrayList 底层是数组,所以它的扩容机制和数组一样,首先新建一个新数组,长度是原数组的 1.5 倍,然后调用 Arrays.copyof()复制原数组的值,然后赋值给新数组。
4.什么是哈希表
根据关键码值(Key value)而直接进行访问的数据结构,在一个表中,通过 H(key)计算出 key 在表中的位置,H(key)就是哈希函数,表就是哈希表。
5.什么是哈希冲突
不同的 key 通过哈希函数计算出相同的储存地址,这就是哈希冲突。
6.解决哈希冲突
(1)开放地址法
如果发生哈希冲突,就会以当前地址为基准,再去寻找计算另一个位置,直到不发生哈希冲突。
寻找的方法有:① 线性探测 1,2,3,m
② 二次探测 1 的平方,-1 的平方,2 的平方,-2 的平方,k 的平方,-k 的平方,k<=m/2
③ 随机探测 生成一个随机数,然后从随机地址+随机数++。
(2)链地址法
冲突的哈希值,连到到同一个链表上。
(3)再哈希法(再散列方法)
多个哈希函数,发生冲突,就在用另一个算计,直到没有冲突。
(4)建立公共溢出区
哈希表分成基本表和溢出表,与基本表发生冲突的都填入溢出表。
7.HashMap 的 hash()算法,为什么不是 h=key.hashcode(),而是 key.hashcode()^ (h>>>16)
得到哈希值然后右移 16 位,然后进行异或运算,这样使哈希值的低 16 位也具有了一部分高 16 位的特性,增加更多的变化性,减少了哈希冲突。
8.为什么 HashMap 的初始容量和扩容都是 2 的次幂
因为计算元素存储的下标是(n-1)&哈希值,数组初始容量-1,得到的二进制都是 1,这样可以减少哈希冲突,可以更好的均匀插入。
9.HashMap 如果指定了不是 2 的次幂的容量会发生什么?
会获得一个大于指定的初始值的最接近 2 的次幂的值作为初始容量。
10.HashMap 为什么线程不安全
jdk1.7 中因为使用头插法,再扩容的时候,可能会造成闭环和数据丢失。
jdk1.8 中使用尾插法,不会出现闭环和数据丢失,但是在多线程下,会发生数据覆盖。(put 操作中,在 putVal 函数里) 值的覆盖还有长度的覆盖。
11.解决 Hashmap 的线程安全问题
(1)使用 Hashtable 解决,在方法加同步关键字,所以效率低下,已经被弃用。
(2)使用 Collections.synchronizedMap(new HashMap<>()),不常用。
(3)ConcurrentHashMap(常用)
12.ConcurrentHashMap 的原理
jdk1.7: 采用分段锁,是由 Segment(继承 ReentrantLock:可重入锁,默认是 16,并发度是 16)和 HashEntry 内部类组成,每一个 Segment(锁)对应 1 个 HashEntry(key,value)数组,数组之间互不影响,实现了并发访问。
jdk1.8: 抛弃分段锁,采用 CAS(乐观锁)+synchronized 实现更加细粒度的锁,Node 数组+链表+红黑树结构。只要锁住链表的头节点(树的根节点),就不会影响其他数组的读写,提高了并发度。
13.为什么用 synchronized 代替 ReentrantLock
①节省内存开销。ReentrantLock 基于 AQS 来获得同步支持,但不是每个节点都需要同步支持,只有链表头节点或树的根节点需要同步,所以使用 ReentrantLock 会带来很大的内存开销。
②获得 jvm 支持,可重入锁只是 api 级别,而 synchronized 是 jvm 直接支持的,能够在 jvm 运行时做出相应的优化。
③在 jdk1.6 之后,对 synchronized 做了大量的优化,而且有多种锁状态,会从 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁一步步转换。
AQS (Abstract Queued Synchronizer): 一个抽象的队列同步器,通过维护一个共享资源状态( Volatile Int State )和一个先进先出( FIFO )的线程等待队列来实现一个多线程访问共享资源的同步框架。
14.HashMap 为什么使用链表
减少和解决哈希冲突,把冲突的值放在同一链表下。
15.HashMap 为什么使用红黑树
当数据过多,链表遍历较慢,所以引入红黑树。
16.HashMap 为什么不一上来就使用红黑树
维护成本较大,红黑树在插入新的数据后,可能会进行变色、左旋、右旋来保持平衡,所以当数据少时,就不需要红黑树。
17.说说你对红黑树的理解
①根节点是黑色。
②节点是黑色或红色。
③叶子节点是黑色。
④红色节点的子节点都是黑色。
⑤从任意节点到其子节点的所有路径都包含相同数目的黑色节点。
红黑树从根到叶子节点的最长路径不会超过最短路径的 2 倍。保证了红黑树的高效。
18.为什么链表长度大于 8,并且表的长度大于 64 的时候,链表会转换成红黑树?
因为链表长度越长,哈希冲突概率就越小,当链表等于 8 时,哈希冲突就非常低了,是千万分之一,我们的 map 也不会存那么多数据,如果真要存那么多数据,那就转为红黑树,提高查询和插入的效率。
19.为什么转成红黑树是 8 呢?而重新转为链表阈值是 6 呢?
因为如果都是 8 的话,那么会频繁转换,会浪费资源。
20.为什么负载因子是 0.75?
加载因子越大,填满的元素越多,空间利用率越高,但发生冲突的机会变大了;
加载因子越小,填满的元素越少,冲突发生的机会减小,但空间浪费了更多了,而且还会提高扩容 rehash 操作的次数。
“冲突的机会”与“空间利用率”之间,寻找一种平衡与折中。
又因为根据泊松分布,当负载因子是 0.75 时,平均值时 0.5,带入可得,当链表为 8 时,哈希冲突发生概率就很低了。
21.什么时候会扩容?
元素个数 > 数组长度 * 负载因子 例如 16 * 0.75 = 12,当元素超过 12 个时就会扩容。
链表长度大于 8 并且表长小于 64,也会扩容
22.为什么不是满了扩容?
因为元素越多,空间利用率是高了,但是发生哈希冲突的几率也增加了。
23.扩容过程
jdk1.7: 会生成一个新 table,重新计算每个节点放进新 table,因为是头插法,在线程不安全的时候,可能会出现闭环和数据丢失。
jdk1.8: 会生成一个新 table,新位置只需要看(e.hash & oldCap)结果是 0 还是 1,0 就放在旧下标,1 就是旧下标+旧数组长度。避免了对每个节点进行 hash 计算,大大提高了效率。e.hash 是数组的 hash 值,,oldCap 是旧数组的长度。
24.HashMap 和 Hashtable 的区别
①HashMap,运行 key 和 value 为 null,Hashtable 不允许为 null。
②HashMap 线程不安全,Hashtable 线程安全。
25.集合为什么要用迭代器(Iterator)
更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。
如果不用迭代器,只能 for 循环,还必须知道集合的数据结构,复用性不强。
三.多线程
java 多线程及线程池原理讲解
1.线程是什么?多线程是什么?
线程: 是最小的调度单位,包含在进程中。
多线程: 多个线程并发执行的技术。
2.守护线程和用户线程
守护线程: jvm 给的线程。比如:GC 守护线程。
用户线程: 用户自己定义的线程。比如:main()线程。
拓展:
Thread.setDaemon(false)设置为用户线程
Thread.setDaemon(true)设置为守护线程
3.线程的各个状态
新建(New): 新建一个线程。
就绪(Runnable): 抢夺 cpu 的使用权。
运行(Running): 开始执行任务。
阻塞(Blocked): 让线程等待,等待结束进入就绪队列。
死亡(Dead): 线程正常结束或异常结束。
4.线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等
wait(): 线程等待,会释放锁,用于同步代码块或同步方法中,进入等待状态
sleep(): 线程睡眠,不会释放锁,进入超时等待状态
yield(): 线程让步,会使线程让出 cpu 使用权,进入就绪状态
join(): 指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
notify(): 随机唤醒一个在等待中的线程,进入就绪状态。
notifyAll(): 唤醒全部在等待中的线程,进入就绪状态。
5.wait()和 sleep()的区别?
① wait() 来自 Object,sleep()来自 Thread。
② wait()会释放锁,sleep()不会释放锁。
③ wait()只能用在同步方法或代码块中,sleep()可以用在任何地方。
④ wait()不需要捕获异常,sleep()需要捕获异常。
6.为什么 wait()、notify()、notifyAll()方法定义在 Object 类里面,而不是 Thread 类?
① 锁可以是任何对象,如果在 Thread 类中,那只能是 Thread 类的对象才能调用上面的方法了。
② java 中进入临界区(同步代码块或同步方法),线程只需要拿到锁就行,而并不关心锁被那个线程持有。
③ 上面方法是 java 两个线程之间的通信机制,如果不能通过类似 synchronized 这样的 Java 关键字来实现这种机制,那么 Object 类中就是定义它们最好的地方,以此来使任何 Java 对象都可以拥有实现线程通信机制的能力。
7.start()和 run()的区别
start()方法: 是启动线程,调用了之后线程会进入就绪状态,一旦拿到 cpu 使用权就开始执行 run()方法,不能重复调用 start(),否则会报异常。
run()方法: 就相当于一个普通的方法而已。直接调用 run()方法就还只有一个主线程,还是会顺序执行,也可以重复调用 run()方法。
8.实现多线程的方式
①继承 Thread 类。
②实现 Runnable 接口
③实现 Callable 接口
④线程池
9.Runnable 和 Callable 的区别
①Runnable 没有返回值,Callable 有返回值。
②Runnable 只能抛出异常,不能捕获,Callable 能抛出异常,也能捕获。
10.线程池的好处
① 线程是稀缺资源,使用线程池可以减少线程的创建和销毁,每个线程都可重复使用。
② 可以根据系统的需求,调整线程池里面线程的个数,防止了因为消耗内存过多导致服务器崩溃。
11.线程池的七大参数
corePoolSize: 核心线程数,创建不能被回收,可以设置被回收。
maximumPoolSize: 最大线程数。
keepAliveTime: 空闲线程存活时间。
unit: 单位。
workQueue: 等待队列。
threadFactory: 线程工程,用于创建线程。
handler: 拒绝策略。
12.线程池的执行过程
①接到任务,判断核心线程池是否满了,没满执行任务,满了放入等待队列。
②等待队列没满,存入队列,等待执行,满了去查看最大线程数。
③最大线程数没满,执行任务,满了执行拒绝策略。
13.四大方法
①ExecutorService executor = Executors.newCachedThreadPool(): 创建一个缓存线程池,灵活回收线程,任务过多,会 oom。
②ExecutorService executor = Executors.newFixedThreadPool(): 创建一个指定线程数量的线程池。提高了线程池的效率和线程的创建的开销,等待队列可能堆积大量请求,导致 oom。
③ExecutorService executor = Executors.newSingleThreadPool(): 创建一个单线程,保证线程的有序,出现异常再次创建,速度没那么快。
④ExecutorService executor = Executors.newScheduleThreadPool(): 创建一个定长的线程池,支持定时及周期性任务执行。
14.四大拒绝策略
①new ThreadPoolExecutor.AbortPolicy(): 添加线程池被拒绝,会抛出异常(默认策略)。
②new ThreadPoolExecutor.CallerRunsPolicy(): 添加线程池被拒绝,不会放弃任务,也不会抛出异常,会让调用者线程去执行这个任务(就是不会使用线程池里的线程去执行任务,会让调用线程池的线程去执行)。
③new ThreadPoolExecutor.DiscardPolicy(): 添加线程池被拒绝,丢掉任务,不抛异常。
④new ThreadPoolExecutor.DiscardOldestPolicy(): 添加线程池被拒绝,会把线程池队列中等待最久的任务放弃,把拒绝任务放进去。
15.shutdown 和 shutdownNow 的区别?
① shutdown 没有返回值,shutdownNow 会返回没有执行完任务的集合。
②shutdown 不会抛出异常,shutdownNow 会抛出异常。
③shutdown 会等待执行完线程池的任务在关闭,shutdownNow 会给所以线程发送中断信号,然后中断任务,关闭线程池。
16.什么是死锁?
各进程互相等待对方手里的资源,导致各进程都阻塞,无法向前推进的现象。
17.造成死锁的四个必要条件
互斥: 当资源被一个线程占用时,别的线程不能使用。
不可抢占: 进程阻塞时,对占用的资源不释放。
不剥夺: 进程获得资源未使用完,不能被强行剥夺。
循环等待: 若干进程之间形成头尾相连的循环等待资源关系。
18.线程安全主要是三方面
原子性: 一个或多个操作,要么全部执行,要么全部不执行(执行的过程中是不会被任何因素打断的)。
可见性: 一个线程对主内存的修改可以及时的被其他线程观察到。
有序性: 程序执行的顺序按照代码的先后顺序执行。
保证原子性
使用锁 synchronized 和 lock。
使用 CAS (compareAndSet:比较并交换),CAS 是 cpu 的并发原语)。
保证可见性
使用锁 synchronized 和 lock。
使用 volatile 关键字 。
保证有序性
使用 volatile 关键字
使用 synchronized 关键字。
19.volatile 和 synchronized 的区别
① volatile 仅能使用在变量级别的,synchronized 可以使用在变量、方法、类级别的
② volatile 不具备原子性,具备可见性,synchronized 有原子性和可见性。
③ volatile 不会造成线程阻塞,synchronized 会造成线程阻塞。
④ volatile 关键字是线程同步的轻量级实现,所以 volatile 性能肯定比 synchronized 要好。
20.synchronized 和 lock 的区别
① synchronized 是关键字,lock 是 java 类,默认是不公平锁(源码)。
② synchronized 适合少量同步代码,lock 适合大量同步代码。
③ synchronized 会自动释放锁,lock 必须放在 finally 中手工 unlock 释放锁,不然容易死锁。
21.JMM(java 内存模型)
java 内存模型,一个抽象的概念,不是真是存在,描述的是一种规则或规范,和多线程相关的规则。需要每个 JVM 都遵循。
22.JMM 的约定
①线程解锁前,必须把共享变量立即刷回主存。
②线程加锁前,必须读取主存中的最新值到工作内存中。
③加锁和解锁必须是同一把锁。
23.JMM 的八个命令
为了支持 JMM,定义了 8 条原子操作,用于主存和工作内存的交互。
lock(锁定): 作用于主内存的变量,把一个变量标识为一条线程独占状态。
unlock(解锁): 作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read(读取): 作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的 load 动作使用。
load(载入): 作用于工作内存的变量,它把 read 操作从主内存中得到的变量值放入工作内存的变量副本中。
use(使用): 作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎。
assign(赋值): 作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量。
store(存储): 作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以遍随后的 write 的操作。
write(写入): 作用于主内存的变量,它把 store 操作从工作内存中的一个变量的值传送到主内存的变量中。
24.为什么要有 JMM,用来解决什么问题?
解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。
四.jvm
JVM 详解
垃圾回收机制详解
1.jvm 是什么?
java 虚拟机,是实现 java 跨平台的核心组件。
2.jvm 的作用
java 中所有的类,必须被装载到 jvm 中才能使用,装载由类加载器完成,.class 这个类型可以在虚拟机运行,但不是直接和操作系统交互,需要 jvm 解释给操作系统,解释的时候需要 java 类库,这样就能和操作系统交互。
3.java 文件的加载过程
.java -> .class -> 类加载器 -> jvm
4.jdk、jre、jvm 的区别
jdk: 包含 java 运行环境和开发环境、jvm、java 类库。
jre: 包含 java 运行环境和 jvm、java 类库。
jvm: java 虚拟机,是跨平台的核心组件。
5.类加载器的作用
将.class 文件装载到 jvm 中,实质就是把文件从硬盘写到内存。
6.类加载器的类型
引导类加载器(Bootstrap ClassLoader): c++编写,jvm 自带的加载器,负责加载 java 核心类库,该加载器无法直接获取。
拓展类加载器(Extension ClassLoader): 加载 jre/lib/etc 目录下的 jar 包。
系统类加载器(Application ClassLoader): 加载当前项目目录下的类或 jar 包,最常用的加载器。
自定义加载器(Custom ClassLoader): 开发人员自定义的。需要继承 ClassLoader
7.双亲委派机制的加载过程
①接到类加载的请求。
②向上委托给父类加载器,直到引导类加载器。
③引导类加载器检查能否加载当前这个类,如果能,使用当前加载器,请求结束,如果不能,抛出异常,通知子加载器进行加载。
④重复③。
8.双亲委派机制的优缺点
优点:保证类加载的安全性,不管那个类被加载,都会被委托给引导类加载器,只有类加载器不能加载,才会让子加载器加载,这样保证最后得到的对象都是同样的一个。
缺点:子加载器可以使用父加载器加载的类,而父加载器不能使用子加载器加载的类。
9.为什么要打破双亲委派机制
子加载器可以使用父加载器加载的类,而父加载器不能使用子加载器加载的类。
例如:使用 JDBC 连接数据库,需要用到 com.mysql.jdbc.Driver 和 DriverManager 类。然而 DriverManager 被引导类加载器所加载,而 com.mysql.jdbc.Driver 被当前调用者的加载器加载,使用引导类加载器加载不到,所以要打破双亲委派机制。
10.打破双亲委派机制的方式
① 自定义类加载器,重写 loadclass 方法。
② 使用线程上下文类(ServiceLoader:使父加载器可以加载子加载器的类)。
11.jvm 的每个部分储存的都是什么
方法区(线程共享): 常量池、静态(static)变量以及方法信息(方法名、返回值、参数、修饰符等)等。
堆(线程共享): 是虚拟机内存中最大的一块,储存的是实例对象和数组。
本地方法栈(线程不共享): 调用的本地方法,被 native 修饰的方法,java 不能直接操作操作系统,所以需要 native 修饰的方法帮助。
虚拟机栈(线程不共享): 8 大基本类型、对象引用、实例方法。
程序计数器(线程不共享): 每个线程启动是都会创建一个程序计数器,保存的是正在执行的 jvm 指令,程序计数器总是指向下一条将被执行指令的地址。
12.内存溢出(oom)和栈溢出
内存溢出的原因:
(1)内存使用过多或者无法垃圾回收的内存过多,使运行需要的内存大于提供的内存。
(2)长期持有某些资源并且不释放,从而使资源不能及时释放,也称为内存泄漏。
解决:
(1)进行 jvm 调优。-Xmx:jvm 最大内存。-Xms:启动初始内存。-Xmn:新生代大小。 -Xss:每个虚拟机栈的大小。
(2)使用专业工具测试。
手动制造: 一直 new 对象就 ok。
栈溢出原有: 线程请求的栈容量大于分配的栈容量。
解决: (1)修改代码 (2)调优 -Xss
手动制造: 一直调用实例方法。
13.垃圾回收的作用区域
作用在方法区和堆,主要实在堆中的伊甸园区。年轻代分为(伊甸园区和幸存区)
14.怎么判断对象是否可回收
可达性分析算法: 简单来说就是一个根对象通过引用链向下走,能走到的对象都是不可回收的。可作为根对象有: 虚拟机栈的引用的对象,本地栈的引用的对象,方法区引用的静态和常量对象。
引用计数算法: 每个对象都添加一个计数器,每多一个引用指向对象,计数器就加一,如果计数器为零,那么就是可回收的。
15.四种引用类型 强引用 软引用 弱引用 虚引用
强引用: 基于可达性分析算法,只有当对象不可达才能被回收,否则就算 jvm 满了,也不会被回收,会抛出 oom。
软引用: 一些有用但是非必须的对象,当 jvm 即将满了,会将软引用关联对象回收,回收之后如果内存还是不够,会抛出 oom。
弱引用: 不论内存是否够,只要开始垃圾回收,软引用的关联对象就会被回收。
虚引用: 最弱的引用和没有一样,随时可能被回收。
16.垃圾回收算法
(1)标记-清除算法(适用老年代): 先把可回收的对象进行标记,然后再进行清除。
优点: 算法简单。
缺点: 产生大量的内存碎片,效率低。
(2)复制算法(适用年轻代): 把内存分成两个相同的块,一个是 from,一个是 to,每次只使用一个块,当一个块满了,就把存活的对象放到另一个块中,然后清空当前块。主要用在年轻区中的幸存区。
优点: 效率较高,没有内存碎片。
缺点: 内存利用率低。
(3)标记-整理算法(适用老年代): 标记-清除算法的升级版,也叫标记-压缩算法,先进行标记,然后让存活对象向一端移动,然后清除掉边界以外的内存。
有点: 解决了内存利用率低和避免了内存碎片。
缺点: 增加了一个移动成本。
17.轻 GC(Minor GC)和 重 GC(Full GC)
轻 GC: 普通 GC,当新对象在伊甸园区申请内存失败时,进行轻 GC,会回收可回收对象,没有被回收的对象进入幸存区,新对象分配内存极大部分都是在伊甸园区,所以这个区 GC 比较频繁。一个对象经历 15 次 GC,会进入老年区,可以设置。
重 GC: 全局 GC,对整个堆进行回收,所以要比轻 GC 慢,因此要减少重 GC,我们所说的 jvm 调优,大部分都是针对重 GC。
18.什么时候会发生重 GC
①当老年区满了会重 GC:年轻区对象进入或创建大对象会满。
②永久代满了会重 GC。
③方法区满了会重 GC。
④system.gc()会重 GC 。
⑤轻 GC 后,进入老年代的大小大于老年代的可用内存会,第一次轻 GC 进入老年代要 2MB,第二次的时候会判断是否大于 2MB,不满足就会重 GC。
五.锁
1.悲观锁和乐观锁
悲观锁: 在修改数据时,一定有别的线程来使用,所以在获取数据的时候会加锁。java 中的 synchronized 和 Lock 都是悲观锁。
乐观锁: 在修改数据时,一定没有别的线程来使用,所以不会添加锁。但是在更新数据的时候,会查看有没有线程修改数据。比如:版本号和 CAS 原理(无锁算法)。
2.悲观锁和乐观锁的场景
悲观锁: 更适合写操作多的场景,因为先加锁可以保证数据的正确。
乐观锁: 更适合读操作多的场景,因为不加锁会让读操作的性能提升。
3.自旋锁和自适应自旋锁
前言:因为线程竞争,会导致线程阻塞或者挂起,但是如果同步资源的锁定时间很短,那么阻塞和挂起的花费的资源就得不偿失。
自旋锁: 当竞争的同步资源锁定时间短,就让线程自旋,如果自旋完成后,资源释放了锁,那线程就不用阻塞,直接获取资源,减少了切换线程的开销。实现原理是 CAS。
缺点:占用了处理器的时间,如果锁被占用的时间短还好,如果长那就白白浪费了处理器的时间。所以要限定自旋次数(默认是 10 次,可以使用-XX:PreBlockSpin 来更改)没有成功获得锁,就应当挂起线程。
自适应自旋锁: 自旋次数不固定,是由上一个在同一个锁上的自旋时间和锁拥有者的状态决定。如果在同一个锁对象上,自旋刚刚获得锁,并且持有锁的线程在运行,那么虚拟机会认为这次自旋也可能成功,那么自旋的时间就会比较长,如果某个锁,自旋没成功获得过,那么可能就会直接省掉自旋,进入阻塞,避免浪费处理器时间。
4.无锁、偏向锁、轻量级锁、重量级锁
这四个锁是专门针对 synchronized 的,在 JDK1.6 中,对 synchronized 锁的实现引入了大量的优化,并且 synchronized 有多种锁状态。级别从低到高依次是:无锁、偏向锁、轻量级锁和重量级锁。锁状态只能升级不能降级。
无锁: 就是乐观锁。
偏向锁: 当只有一个线程访问加锁的资源,不存在多线程竞争的情况下,那么线程不需要重复获取锁,这时候就会给线程加一个偏向锁。(对比 Mark Word 解决加锁问题,避免 CAS 操作)
轻量级锁: 是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。(CAS+自旋)
重量级锁: 若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。(将除了拥有锁的线程以外的线程都阻塞)
5.公平锁和非公平锁
公平锁: 多个线程按照申请锁的顺序来获取锁。 Lock lock = new ReentrantLock(true); 默认是非公平锁,设置为 true 是公平锁。
优点:等待线程不会饿死。
缺点:CPU 唤醒线程得开销比非公平锁要大。
非公平锁: 多个线程获取锁的顺序并不是按照申请锁的顺序。 sybchronized 和 lock 都是非公平锁。
优点:减少唤醒线程得开销。
缺点:可能会出现线程饿死或者很久获得不了锁。
6.可重入锁
可重入锁: 也叫递归锁,同一个线程在外层方法获取了锁,在进入内层方法会自动获取锁。(前提:锁对象是同一个对象或者类)。ReentrantLock 和 synchronized 都是可重入锁。
7.独享锁和共享锁
独享锁: 独占锁是指锁一次只能被一个线程所持有。如果一个线程对数据加上排他锁后,那么其他线程不能再对该数据加任何类型的锁。获得独占锁的线程即能读数据又能修改数据。synchronized 和 Lock 的实现类就是独占锁。
共享锁: 共享锁是指锁可被多个线程所持有。如果一个线程对数据加上共享锁后,那么其他线程只能对数据再加共享锁,不能加独占锁。获得共享锁的线程只能读数据,不能修改数据。
8.互斥锁和读写锁
互斥锁: 是独享锁的实现,某一资源同时只允许一个访问者对其访问。具有唯一和排它性。
读写锁: 是共享锁的实现,读写锁管理一组锁,一个是只读的锁,一个是写锁。读锁可以在没有写锁的时候被多个线程同时持有,而写锁是独占的。写锁的优先级要高于读锁。并发度要比互斥锁高,因为可以拥有多个读锁。
9.分段锁
是锁的设计,不是具体的某一种锁,分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。
ConcurrentHashMap 的锁机制 jdk1.7 用的就是分段锁。
10.锁优化技术
锁粗化: 将多个同步块的数量减少,并将单个同步块的作用范围扩大,本质上就是将多次上锁、解锁的请求合并为一次同步请求。
锁消失: 锁消除是指虚拟机编译器在运行时检测到了共享数据没有竞争的锁,从而将这些锁进行消除。
六.CAS 原理
面试必问的 CAS,你懂多少?(多线程并发 46 道)
七.Redis 高频面试题
Redis 高频面试题共 42 道
八.常用八大排序算法和四大查找算法
八大排序算法
四大查找算法
推荐《leetcode 算法手册》已打包好
九.数据库
MySQL 常见面试题共 34 道
十.Mybatis 常见面试题
Mybatis 常见面试题共 14 道
十一.SpringMVC 常见面试题
SpringMVC 常见面试题
十二.Spring 常见面试题
Spring 常见面试题共 23 道
十三.SpringBoot 常见面试题
springBoot 常见面试题共 14 道
十四.springCloud 常见面试题
springCloud 常见面试题共 14 道
Ps:由于篇幅限制,笔记无法全部为大家展示出来,就以截图主要内容的形式让大家参考啦,需要完整版的小伙伴可以点击>>传送门<<获取
总结
每个程序员心中都有一个大厂梦,无论是阿里还是腾讯,对于程序员的技术要求都是比较高的,抛开学历不谈,你的技术水平是否达到了阿里的标准和要求?如果你与其他同行存在着较大的技术差距,而且还没有改变现状的想法,那么被市场所淘汰,只是时间问题。
所以,不管你是否想要进入大厂,或是想要拥有一份满意的薪资,都需要认真对待自己。
评论