后端面试面经每日随机day1
面经问题来自多个网站网友分享,每一章是同一场面试,所以问题数量不等,大部分回答由大模型生成加以个人删改,若有问题,欢迎指正,参考非标准答案只供参考,具有个人主观向。客户端发送FIN包,服务器回应ACK包,服务器发送FIN包,客户端回应ACK包确认断开。6、浏览器解析渲染:客户端接收响应后,解析HTML文档,构建DOM树和CSSOM树,合并为渲染树,进行布局和绘制,最终呈现页面内容。3、发送HTTP
1、Java的String类型可以被继承吗?
Java中的String类被声明为final,因此不能被继承。以下是具体原因和影响:
final关键字的作用final修饰的类禁止被其他类继承。String类的定义如下:
public final class String implements Serializable, Comparable<String>, CharSequence { ... }
设计目的
-
不可变性
String的不可变性(如substring、concat等操作生成新对象)依赖final类保证。若允许继承,子类可能破坏不可变性的设计原则。 -
安全性
String广泛用于JVM底层(如类加载机制)、哈希存储(如HashMap的键)。允许继承可能导致恶意子类篡改行为,引发安全漏洞。 -
性能优化
JVM对String有特殊优化(如字符串常量池),假设String不可变是优化前提。继承可能干扰这些优化。
替代方案
若需扩展字符串功能,可通过以下方式实现:
-
工具类:封装静态方法处理字符串(如
StringUtils)。 -
组合模式:持有
String成员并提供扩展方法。
class CustomString {
private String str;
// 添加自定义方法
public String reverse() { ... }
}
2、String、StringBuilder、StringBuffer的区别?
区别概述
String、StringBuilder和StringBuffer均为Java中用于处理字符串的类,主要区别在于可变性、线程安全性和性能。
String(不可变字符串)
-
不可变性:String对象一旦创建,其内容不可修改。任何修改操作(如拼接、替换)均会生成新对象。
-
线程安全:因不可变性,天然线程安全。
-
性能:频繁修改会导致大量对象创建,内存和性能开销较大。
-
适用场景:字符串常量或无需修改的场景。
StringBuilder(可变字符串,非线程安全)
-
可变性:直接修改内部字符数组,无需创建新对象。
-
线程安全:非线程安全,适用于单线程环境。
-
性能:比StringBuffer更高(无同步开销)。
-
适用场景:单线程下频繁修改字符串。
StringBuffer(可变字符串,线程安全)
-
可变性:与StringBuilder类似,支持原地修改。
-
线程安全:通过
synchronized方法保证线程安全。 -
性能:因同步锁,性能略低于StringBuilder。
-
适用场景:多线程下频繁修改字符串。
性能对比
-
执行速度:StringBuilder > StringBuffer > String(频繁修改时)。
-
内存占用:String因不可变性可能产生更多临时对象。
代码示例
// String(生成新对象)
String str = "Hello";
str += " World"; // 新对象
// StringBuilder(原地修改)
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 直接修改
// StringBuffer(线程安全修改)
StringBuffer sbf = new StringBuffer("Hello");
sbf.append(" World"); // 同步修改
选择建议
- 无需修改且需线程安全:String。
- 单线程频繁修改:StringBuilder。
- 多线程频繁修改:StringBuffer。
3、ArrayList、LinkedList的区别?
ArrayList基于动态数组实现,适合查询,查询复杂度为o(1);
LinkedList基于链表实现,适合插入、删除,查询复杂度为o(n);
4、讲一下HashMap?
HashMap 是 Java 集合框架中的一种数据结构,基于哈希表实现,用于存储键值对(Key-Value)。它允许键(Key)和值(Value)为 null,并且是非线程安全的。HashMap 的底层通过数组和链表(或红黑树)的组合实现高效的查找、插入和删除操作。
工作原理
当向 HashMap 中插入一个键值对时,HashMap 会通过键的 hashCode() 方法计算哈希值,再通过哈希函数(如取模运算)确定其在数组中的索引位置。如果多个键的哈希值冲突(即计算到相同的索引),HashMap 会使用链表或红黑树存储这些键值对。
-
链表(JDK 1.7 及之前):冲突的键值对以链表形式存储,查找时需遍历链表。
-
红黑树(JDK 1.8 及之后):当链表长度超过阈值(默认为 8)时,链表转换为红黑树,提高查找效率。
-
时间复杂度:
-
理想情况下(无哈希冲突):
O(1)。 -
最坏情况下(哈希冲突严重):
O(log n)(JDK 1.8 后红黑树优化)。
-
-
负载因子(Load Factor):默认为 0.75,表示当 HashMap 容量达到 75% 时触发扩容(数组大小翻倍)。
HashMap 是非线程安全的,多线程环境下可能导致数据不一致。若需线程安全,可使用:
-
ConcurrentHashMap:分段锁或 CAS 机制实现高效并发。 -
Collections.synchronizedMap(Map):通过同步包装器实现线程安全,但性能较低。
5、HashMap中链表为什么要转为红黑树?
性能优化
链表在查找时需要遍历,时间复杂度为O(n),而红黑树的查找时间复杂度为O(log n)。当链表长度较长时,转换为红黑树能显著提升查询效率。
防止哈希碰撞攻击
恶意构造大量哈希冲突的数据可能导致链表过长,使得HashMap退化为链表结构。红黑树的平衡性可以避免这种极端情况下的性能恶化。
阈值控制
Java 8中设定当链表长度超过8且数组长度大于等于64时,链表会自动转为红黑树。这个阈值经过测试能平衡空间和时间开销。
6、sleep和wait的区别
sleep属于thread类,不会释放锁,可以在任意位置调用,超时自动恢复或通过 interrupt() 强制唤醒
wait属于object类,会释放锁(必须配合 synchronized 使用),只能在同步块内调用,需要显式notify/notifyall唤醒
7、Synchonize和ReentrantLock的区别
实现机制
-
synchronized是 Java 语言内置的关键字,基于 JVM 的监视器锁(Monitor)实现,无需手动释放锁。 -
ReentrantLock是 Java 并发包(java.util.concurrent.locks)提供的类,基于 API 实现,需要显式调用lock()和unlock()方法管理锁。
锁的获取与释放
-
synchronized在进入同步代码块时自动获取锁,退出时自动释放,包括异常退出。 -
ReentrantLock必须显式调用unlock()释放锁,通常结合try-finally块确保锁释放,避免死锁。
功能特性
-
ReentrantLock提供更灵活的锁控制:-
可以公平锁:通过构造函数指定公平性(
new ReentrantLock(true)),减少线程饥饿。 -
可中断:
lockInterruptibly()允许在等待锁时响应中断。 -
超时机制:
tryLock(long timeout, TimeUnit unit)可设置获取锁的超时时间。
-
-
synchronized不支持上述功能,但代码更简洁。
性能差异
-
在 Java 6 及之后版本,
synchronized经过优化(如偏向锁、轻量级锁),性能与ReentrantLock接近。 -
高竞争场景下,
ReentrantLock的吞吐量可能更高,但需权衡代码复杂性。
使用场景
-
synchronized适合简单的同步需求,代码更易维护。 -
ReentrantLock适合需要高级功能(如公平性、超时)的复杂场景。
示例代码对比
synchronized 示例
public synchronized void method() {
// 同步代码块
}
ReentrantLock 示例
private final ReentrantLock lock = new ReentrantLock();
public void method() {
lock.lock();
try {
// 同步代码块
} finally {
lock.unlock();
}
}
具体可参看下面博客
[java 锁 02 - synchronized vs ReentrantLock ] - 十三山入秋 - 博客园
8、进程之间如何通信
管道(Pipe)
管道是半双工的,数据只能单向流动。通常用于父子进程通信。创建管道使用pipe()系统调用,一个进程写入数据,另一个进程读取。
命名管道(FIFO)
命名管道允许无亲缘关系的进程通信。通过mkfifo()创建,像普通文件一样操作,但数据遵循先进先出原则。
消息队列
消息队列是消息的链表,存储在内核中。进程通过msgget()、msgsnd()和msgrcv()发送和接收消息。克服了管道只能承载无格式字节流的缺点。
共享内存
共享内存是最快的IPC方式,多个进程映射同一块内存区域。通过shmget()创建,shmat()附加到进程地址空间。需配合信号量等机制解决同步问题。
信号量
信号量用于进程间同步,控制对共享资源的访问。通过semget()和semop()操作,支持PV原语(等待和信号操作)。
套接字(Socket)
套接字支持不同主机或同一主机的进程通信。分为流式套接字(TCP)和数据报套接字(UDP),提供全双工通信能力。
信号(Signal)
信号用于通知进程异步事件。通过kill()发送信号,signal()或sigaction()处理信号。适用于少量数据的简单通知。
9、get请求和post请求的区别
HTTP请求方法的本质差异
GET请求用于从服务器获取数据,参数直接附加在URL后,可见且长度受限(约2048字符)。POST请求用于向服务器提交数据,参数包含在请求体中,适合传输敏感或大量数据(如文件上传)。
安全性对比
GET请求参数暴露在URL中,可能被浏览器历史记录或服务器日志保存,不适合传递密码等敏感信息。POST请求数据在请求体内,相对更安全,但仍需配合HTTPS加密确保传输安全。
数据长度与编码限制
GET请求受URL长度限制,不适合传输大量数据。POST请求无严格长度限制,支持多种编码类型(如multipart/form-data),可上传二进制文件。
幂等性与缓存特性
GET请求是幂等的(多次执行结果相同),可被缓存或收藏为书签。POST请求非幂等(如重复提交订单可能产生多次交易),通常不被缓存。
10、url请求的全过程
1、DNS解析:浏览器检查本地缓存是否存在该域名对应的IP地址。若不存在,向DNS服务器发起递归查询,最终获取目标服务器的IP地址。
2、建立TCP连接:通过三次握手与服务器建立TCP连接。客户端发送SYN包,服务器回应SYN-ACK包,客户端再发送ACK包确认连接。
3、发送HTTP请求:建立连接后,浏览器构建HTTP请求报文,包含请求方法、请求头、请求体等信息,通过TCP连接发送给服务器。
4、服务器处理请求:服务器接收请求后,根据路径和参数处理请求,可能涉及数据库查询、业务逻辑处理等操作,生成响应数据。
5、返回HTTP响应:服务器将响应数据封装为HTTP响应报文,包含状态码、响应头、响应体等信息,通过TCP连接返回给客户端。
6、浏览器解析渲染:客户端接收响应后,解析HTML文档,构建DOM树和CSSOM树,合并为渲染树,进行布局和绘制,最终呈现页面内容。
7、关闭TCP连接:完成数据传输后,通过四次挥手断开TCP连接。客户端发送FIN包,服务器回应ACK包,服务器发送FIN包,客户端回应ACK包确认断开。
更多推荐


所有评论(0)