1 前言
1.1 初衷
汇集国内各大厂的面试题,让小伙伴从容面对面试。
为了方便各位小伙伴能够有方向、高效的准备面试,将在这里将会实时汇总最新的、高质量的面试真题。
同时通过频率、难度以及重要级别去表明每一个问题的重要性。
1.2 数据
目前汇总数量为:
公司 | 数量 |
腾讯 | 15 |
最新面试题时间:2023-6-3
2 JAVA·基础
2.1 == 和 equals 区别
在 Java 中,== 和 equals() 都是用来比较两个对象是否相等的操作符,但它们的作用是不同的。
(1)== 运算符比较的是两个对象的引用是否相等,即它们是否指向同一个内存地址。如果两个对象的引用相同,则它们是相等的;否则,它们不相等。
(2)equals() 方法比较的是两个对象的内容是否相等。通常情况下,我们需要重写对象的 equals() 方法来比较对象的内容是否相等。
频率 | 难度 | 重要级别 |
1次 | ✰ | ✰ |
2.2 基本数据类型
基本数据类型:byte、short、int、long、float、double、char、boolean
频率 | 难度 | 重要级别 |
1次 | ✰ | ✰ |
2.3 集合
2.3.1 HashMap的使用
(1)语法
HashMap<keyType, valueType> mapName = new HashMap<>();
其中,keyType指定键的类型,valueType指定值的类型,mapName指定HashMap的名称。
(2)添加元素
mapName.put(key, value);
其中,key是要插入的键,value是要插入的值。
(3)获取元素
valueType value = mapName.get(key);
其中,key是要获取的元素的键,value是要获取的元素的值。
(4)删除元素
mapName.remove(key);
其中,key是要删除的元素的键。
(5)遍历HashMap
for (Map.Entry<keyType, valueType> entry : mapName.entrySet()) {
keyType key = entry.getKey();
valueType value = entry.getValue();
System.out.println("Key: " + key + ", Value: " + value);
}
2.3.1 HashMap的数据结构
JAVA8以前,是数组加链表,JAVA8以后,是数组+链表+红黑树。
频率 | 难度 | 重要级别 |
1次 | ✰ | ✰ |
2.3.2 HashMap的红黑树
(1)为什么使用红黑树?
HashMap 是 Java 中的一种散列表(Hash Table)实现,用于存储键值对。它提供了快速的查找、插入和删除操作,具有常数时间复杂度(O(1))的平均性能。然而,在某些情况下,HashMap 的性能可能会退化为线性时间复杂度(O(n))。
红黑树(Red-Black Tree)是一种自平衡二叉查找树,它通过保持树的平衡性来保证插入、删除和查找操作的时间复杂度为对数时间(O(log n))。红黑树的特点是具有良好的平衡性和可预测的性能。
HashMap 在内部使用一个数组来存储键值对,每个键值对存储在数组的一个位置上,该位置通过对键的哈希值进行计算得到。当发生哈希碰撞(即两个键的哈希值相同)时,HashMap 会使用链表或红黑树来解决冲突。
当 HashMap 中的链表长度超过阈值(默认为8)时,将链表转化为红黑树。这个阈值可以通过 TREEIFY_THRESHOLD 常量进行调整。
红黑树和链表之间的转化过程是为了在某些情况下提高 HashMap 的性能。当键值对数量较少时,使用链表可以减少额外的开销,而当键值对数量增加时,转化为红黑树可以提高查找和删除操作的效率。
(2)为什么是8时转化?
这是一个概率学的问题,链表长度达到8是一个很小概率的事情,所以以8为分界线,6转为链表,7不转化,8转为红黑树。
(3)为什么不开头用8?
不开头就用红黑树是因为红黑树的占用空间是链表空间的两倍,而且当链表长度较小的时候,使用链表的时间复杂度并不高,所以这是一个时间和空间复杂度的权衡值。
频率 | 难度 | 重要级别 |
1次 | ✰✰ | ★★✰ |
2.4 多线程
2.4.1 创建多线程的方式
在 Java 中,有几种方式可以创建多线程。下面是常见的几种方式:
(1)使用Thread创建
可以通过继承 Thread 类并重写其 run() 方法来创建线程。然后,通过创建 Thread 类的实例并调用 start() 方法来启动线程。
class MyThread extends Thread {
public void run() {
// 线程执行的代码
}
}
// 创建并启动线程
MyThread thread = new MyThread();
thread.start();
(2)实现 Runnable 接口
可以实现 Runnable 接口,并在实现类中实现 run() 方法。然后,通过创建 Thread 类的实例,将 Runnable 实例作为参数传递给 Thread 构造函数,并调用 start() 方法启动线程。
class MyRunnable implements Runnable {
public void run() {
// 线程执行的代码
}
}
// 创建并启动线程
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
(3)实现 Callable 和 Future
Callable 接口类似于 Runnable 接口,但是它可以返回一个结果并抛出异常。可以通过创建 Callable 实现类,并使用 ExecutorService 的 submit() 方法来提交 Callable 任务。提交后,将返回一个 Future 对象,可以使用它来获取任务的执行结果。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
class MyCallable implements Callable<String> {
public String call() {
// 线程执行的代码
return "Hello, World!";
}
}
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(1);
// 提交 Callable 任务并获取 Future 对象
Future<String> future = executor.submit(new MyCallable());
// 获取任务执行结果
try {
String result = future.get();
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
// 关闭线程池
executor.shutdown();
(4)使用线程池
可以使用 Executor 框架提供的线程池来管理和调度多个线程。通过创建一个 ThreadPoolExecutor 对象,并使用 submit() 或 execute() 方法提交任务。线程池会自动管理线程的创建、执行和回收。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交任务
executor.submit(new Runnable() {
public void run() {
// 线程执行的代码
}
});
// 关闭线程池
executor.shutdown();
频率 | 难度 | 重要级别 |
1次 | ✰ | ★ |
2.4.2 ThreadPoolExecutor线程池的核心参数
Java 线程池提供了一些核心参数,用于配置线程池的行为和性能。下面是 Java 线程池的一些核心参数:
(1)核心线程数(corePoolSize)
线程池中保持的最小线程数。即使线程处于空闲状态,也不会被回收,除非设置了 allowCoreThreadTimeOut 参数为 true。默认情况下,核心线程数为 0。
(2)最大线程数(maximumPoolSize)
线程池中允许的最大线程数。当工作队列已满且核心线程数已达到上限时,线程池会创建新的线程,直到线程数达到最大线程数。默认情况下,最大线程数为 Integer.MAX_VALUE。
(3)空闲线程存活时间(keepAliveTime)
当线程池中的线程数量超过核心线程数,并且这些线程处于空闲状态时,超过一定时间没有新的任务到达,这些空闲线程会被回收,直到线程数量不超过核心线程数。空闲线程存活时间通过 keepAliveTime 和 TimeUnit 进行定义,默认情况下为 60 秒。
(4)工作队列(workQueue)
用于存储等待执行的任务的阻塞队列。线程池会从队列中取出任务并执行。Java 提供了多种类型的阻塞队列,如 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue 等。默认情况下,使用的是无界队列 LinkedBlockingQueue。
(5) 线程工厂(threadFactory)
用于创建新线程的工厂。可以自定义线程工厂来设置线程的名称、优先级等属性。如果不指定线程工厂,则使用默认的 DefaultThreadFactory。
(6)拒绝策略(rejectedExecutionHandler)
当工作队列已满且线程池中的线程数量已达到最大线程数时,新提交的任务将如何处理的策略。常见的拒绝策略有 AbortPolicy(默认策略,直接抛出 RejectedExecutionException 异常)、CallerRunsPolicy(由提交任务的线程执行任务)、DiscardPolicy(直接丢弃新任务)、DiscardOldestPolicy(丢弃最旧的任务)。
频率 | 难度 | 重要级别 |
2次 | ✰✰ | ★✰ |
2.4.3 synchronized机制
(1)作用
synchronized 是 Java 中用于实现线程同步的关键字。它可以用于修饰方法或代码块,以保证在同一时间只有一个线程可以访问被synchronized 修饰的代码区域。synchronized 保证了多线程之间的互斥访问,从而避免了数据竞争和并发问题。
(2)语法
synchronized作用在方法上:
public synchronized void methodName() {
//需要同步的代码块
}
//示例:
public synchronized void printNum(int num) {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + num * i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized作用在代码块上:
synchronized (obj) {
//需要同步的代码块
}
// 示例
public void addCount() {
synchronized (this) {
count++;
System.out.println(Thread.currentThread().getName() + ": " + count);
}
}
其中obj为任意对象,多个线程访问同一个obj对象时,会对synchronized代码块进行同步。
(3)注意事项
- 尽量减小synchronized块的范围,以减小同步块的竞争。
- 尽量使用局部变量代替全局变量,在方法内部使用synchronized块进行同步。
- 避免在synchronized块中调用wait(),notify()方法,以避免线程间的死锁问题。
- 避免使用String类型作为synchronized块中的对象锁,因为String类型是不可变对象,可能会导致死锁问题。
频率 | 难度 | 重要级别 |
1次 | ✰✰ | ★✰ |
3 计算机网络
3.1 HTTP和HTTPS的区别
HTTP(Hypertext Transfer Protocol)和HTTPS(Hypertext Transfer Protocol Secure)的区别主要在安全性方面:
-
安全性:HTTPS是加密的,数据通过SSL/TLS协议进行传输,加密后的数据无法被第三方窃取和篡改,而HTTP是明文传输的,数据容易被窃取和篡改。
-
证书:HTTPS网站必须使用数字证书进行身份验证,证书由受信任的证书颁发机构(CA)颁发,能够保证网站的真实性和可信度,而HTTP没有身份验证过程,容易被攻击者伪造和篡改。
-
端口:HTTPS使用443端口进行传输,而HTTP使用80端口。
-
速度:由于加密和解密的过程比较耗费资源,所以HTTPS比HTTP要慢一些。
总之,HTTPS相对于HTTP更加安全,但是速度可能会稍慢一些。在需要保护用户隐私和防止数据被篡改的场合,推荐使用HTTPS。
频率 | 难度 | 重要级别 |
1次 | ✰ | ★ |
3.2 HTTPS连接建立的过程
HTTPS连接的建立需要依赖于公钥基础设施(PKI)和SSL/TLS协议,以下是HTTPS连接的基本建立过程:
-
客户端发送HTTPS连接请求给服务器
-
服务器把自己的公钥和数字证书发送给客户端
-
客户端验证证书的合法性,包括证书的有效期、颁发者是否可信等等。如果证书被验证为不合法,则客户端停止连接,否则进入下一步
-
客户端生成一个随机的对称密钥,并用服务器的公钥进行加密,然后发送给服务器。
-
服务器使用自己的私钥对客户端发送的密文进行解密,得到对称密钥。
-
服务器和客户端使用对称密钥加密后续的通信过程。
以上就是HTTPS连接的基本建立过程,通过使用公钥加密和私钥解密技术,保证了通信过程的安全性和完整性。同时,每个HTTP请求的数据都是独立加密的,即使被截获,也无法破解其他的请求数据。
频率 | 难度 | 重要级别 |
1次 | ✰✰ | ★✰ |
3.3 TCP和UDP
3.3.1 TCP和UDP的区别以及各自的适用场景
TCP和UDP是网络传输层协议,它们之间的主要区别在于:
-
可靠性:TCP是一种可靠的协议,它保证数据的可靠传输,确保数据按照正确的顺序到达目的地。UDP是一种不可靠的协议,它不对数据传输的可靠性进行保证。
-
连接:TCP是一种面向连接的协议,它在传输数据之前需要先建立连接,保持通信双方的状态信息。UDP没有连接的概念,直接将数据包发送出去。
-
延迟:由于TCP的可靠性和连接建立的过程,其延迟较高。UDP不需要进行连接建立和确认,因此延迟较低。
-
流量控制:TCP拥有流量控制和拥塞控制的机制,能够在网络繁忙时自动减少数据传输量,保证网络的可靠性和稳定性。UDP没有这样的机制,容易导致网络拥堵和丢包。
根据以上特点,TCP适用于需要可靠传输和顺序传输的应用,如电子邮件、文件传输、网页浏览等;而UDP适用于对实时性要求较高,即使有丢包也不影响使用的应用,如音频、视频、游戏等。
频率 | 难度 | 重要级别 |
1次 | ✰✰ | ★✰ |
3.3.2 TCP三次握手和四次挥手
TCP是一种可靠的传输协议,其在建立和断开连接时,采用了三次握手和四次挥手的方式。
(1)三次握手:
在TCP连接建立时,需要进行三次握手:
第一次握手:客户端向服务器发送一个SYN(同步)请求。
第二次握手:服务器收到客户端的SYN请求后,回复一个SYN+ACK(同步+确认)确认。
第三次握手:客户端收到服务器的SYN+ACK确认后,向服务器发送一个ACK(确认)确认连接建立成功。
(2)四次挥手:
在TCP连接断开时,需要进行四次挥手:
第一次挥手:客户端发送一个FIN(结束)请求。
第二次挥手:服务器收到客户端的FIN请求后,回复一个ACK确认。
第三次挥手:服务器发送一个FIN请求。
第四次挥手:客户端收到服务器的FIN请求后,回复一个ACK确认,表示连接断开。
频率 | 难度 | 重要级别 |
3次 | ✰✰ | ★✰ |
3.3.3 cookie和session
Cookie和Session都是Web开发中常用的技术,用于解决HTTP协议无状态的问题。
Cookie是浏览器本地存储数据的一种机制,它将数据存储在用户的浏览器中,当用户下一次访问同一网站时,浏览器会将该数据发送给服务器。Cookie的使用场景主要包括以下几种:
(1)记录用户的登录状态:当用户通过用户名和密码登录网站时,服务器可以向用户的浏览器发送一个包含登录信息的Cookie,当用户再次访问该网站时,服务器可以通过检查Cookie来判断用户是否已经登录。
(2)记录用户的偏好设置:当用户在网站上设置了一些偏好选项时,服务器可以通过Cookie将这些选项存储在用户的浏览器中,当用户再次访问该网站时,服务器可以根据Cookie中的信息来显示用户的偏好选项。
(3)跟踪用户的行为:当用户浏览网站时,服务器可以通过Cookie来跟踪用户的浏览行为,以便更好地了解用户的兴趣和需求。
Session是服务器端存储数据的一种机制,它将数据存储在服务器的内存或磁盘中,与Cookie不同的是,Session的数据并不存储在用户的浏览器中,而是存储在服务器上。Session的使用场景主要包括以下几种:
(1)记录用户的会话状态:当用户与网站进行交互时,服务器可以创建一个Session对象,用于存储用户的会话状态,包括用户的登录状态、购物车中的商品、浏览历史等。
(2)防止CSRF攻击:当用户进行敏感操作时,服务器可以通过Session来验证用户的身份,以避免CSRF攻击。
(3)实现多用户共享数据:当多个用户需要共享某些数据时,服务器可以通过Session来存储这些数据,以保证数据的一致性和可靠性。
频率 | 难度 | 重要级别 |
1次 | ✰ | ✰ |
4 MYSQL
4.1 索引
4.1.1 什么是聚簇索引和非聚簇索引?
索引分为:分为聚簇索引和非聚簇索引。
聚簇索引是将数据存储在索引上的一种方式,它会根据索引的键来对数据进行排序,并将数据保存在索引的叶子节点中。当需要访问数据时,查询会直接定位到相应的叶子节点上,从而提高了查询的速度。每个表只能有一个聚簇索引,并且它必须包含表的主键。
非聚簇索引则是将索引和数据分开存储的一种方式。它包含了索引键和指向存储数据的指针,当需要访问数据时,查询会先在索引中定位到相应的数据指针,再通过指针访问实际的数据。一个表可以有多个非聚簇索引,它们可以包含任意列或多列组合作为索引键。
相比之下,聚簇索引的查询速度更快,因为数据本身就存储在索引上;而非聚簇索引的写入速度更快,因为它不涉及对数据的排序和存储,但它的查询速度相对较慢,因为需要通过指针再次去访问实际的数据。选择使用哪种索引方式取决于应用程序的数据访问模式和需求。
频率 | 难度 | 重要级别 |
1次 | ✰✰ | ★✰ |
4.1.2 MYSQL索引类型?
主键索引:根据主键创建的。
唯一索引:索引里面的值没有重复。
复合索引:根据表中多个列创建的索引。
普通索引:在表中普通列建立的索引。
频率 | 难度 | 重要级别 |
2次 | ✰✰ | ★✰ |
4.1.3 索引失效的情况
在 MySQL 中,当查询语句无法使用索引时,可能会出现索引失效的情况。以下是一些可能导致索引失效的情况:
(1)不使用索引列:如果查询条件中不包含索引列,MySQL 将无法利用索引来加快查询速度。
(2)使用函数:如果在查询条件中使用函数,MySQL 将无法使用索引。
(3)字符串比较:如果在查询条件中使用字符串比较操作符(如 LIKE、IN、BETWEEN),MySQL 将无法使用索引。
(4)列类型不匹配:如果查询条件中的列类型与索引列类型不匹配,MySQL 将无法使用索引。
(5)NULL 值:如果索引列中包含 NULL 值,并且查询条件包含 IS NULL 或 IS NOT NULL,MySQL 将无法使用索引。
(6)复合索引顺序不正确:如果查询条件中的列顺序与复合索引中的顺序不一致,MySQL 将无法使用索引。
(7)数据量过大:如果索引中的数据量过大,MySQL 将无法使用索引。
(8)JOIN 操作中的非等值操作:如果在 JOIN 操作中使用了非等值操作,MySQL 将无法使用索引。
(9)查询结果超过阀值:如果查询结果超过 MySQL 进程中的排序缓存阀值,MySQL 将无法使用索引。
4.2 事务
4.2.1 MySQL事务隔离级别
MySQL支持四种标准的事务隔离级别:读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。
(1)读未提交(Read Uncommitted):最低的隔离级别,事务可以读取到未被提交事务的变更,也被称为“脏读”。但是,读取未提交数据可能导致不可重复读和幻影读。
(2)读已提交(Read Committed):在该隔离级别下,事务只能读取已经被提交的数据,避免了脏读。但是,读取的数据可能会被其他事务改变,可能导致不可重复读和幻影读。
(3)可重复读(Repeatable Read):在该隔离级别下,事务可以多次读取同一数据而不会受到其他事务的影响,避免了不可重复读。但是,其他事务仍然可以插入新的数据,可能导致幻影读。
(4)串行化(Serializable):最高的隔离级别,该隔离级别下,所有事务串行执行,确保每个事务看到的数据都是一致的。但是,由于所有事务都被串行化,可能会导致数据库性能下降。
频率 | 难度 | 重要级别 |
2次 | ✰✰ | ★✰ |
4.2.2 事务可能存在的问题
事务可能存在以下几个问题:
-
脏读(Dirty Read):事务读取了另一个事务还未提交的数据,如果这个事务最终回滚了,那么读取到的数据就是无效的。
-
不可重复读(Non-repeatable Read):事务在读取数据时,多次读取同一个数据,但在此期间另一个事务修改了数据,导致事务多次读取的数据不一致。
-
幻读(Phantom Read):事务按照某个条件对数据进行查询,但是在查询过程中,另一个事务插入了符合查询条件的新数据,导致事务的查询结果发生了变化。
-
提交失败(Commit Failure):事务在提交时发生错误,导致事务未能成功提交,需要手动处理。
-
死锁(Deadlock):事务之间互相等待对方释放资源,导致事务无法继续执行。
这些问题都是因为并发访问数据库时,多个事务之间对数据的操作发生了冲突,而事务的目的就是为了保证一组操作要么全部成功,要么全部失败,因此需要选择合适的隔离级别和并发控制策略来避免这些问题的发生。
频率 | 难度 | 重要级别 |
2次 | ✰✰ | ✰ |