后端开发笔试题目集合

数据结构

1.将关键字9, 17, 25, 33, 21, 77, 64, 53, 42, 31依次插入到初始为空的小根堆H中,得到的H是

小根堆是一个完全二叉树,每个节点都要小于等与他的左右节点

根节点是最小的值

最终:

[9, 17, 25, 33, 21, 77, 64, 53, 42, 31]

二叉树的形式:

1
2
3
4
5
6
7
8
9
        9
/ \
17 25
/ \ / \
33 21 77 64
/ \ /
53,42,31


2.在一个二维数组A中,假设每个数组元素的长度为3个存储单元,行下标i为0~9,列下标j为0~7,从首地址200开始连续按列优先存放,在这种情况下,元素A[9][2}的起始地址为( )

一列一列地存,每列里是从上到下。

所以每个元素的起始地址相对于前一个元素的起始地址增加3个存储单元

起始地址 = 首地址 + (j 行数 + i) 元素长度

所以最后是287

3.考虑以下递归函数:

1
2
3
4
5
6
7
int calculateI(int i) {
if (i <= 1) {
return i;
} else {
return calculateI(i - 1) + 1;
}
}

对于给定的初始i值(i>1),计算最终的i值是多少?

这个函数是每次调用的时候,i-1,然后调用的结果又+1

所以最后还是i

4.已知串S=’bccabcaac’,采用KMP算法进行模式匹配,则得到的next数组值为()

这道题采用手工求next数组的方法。

先求串S=’bccabcaac’的部分匹配值:

‘b’的前后缀都为空,最长相等前后缀长度为0。

‘bc’的前缀{b}交集后缀{c}为空

‘bcc’前缀{b,bc}交后缀{c,cc}为空

依次求出的部分匹配值如下表第三行所示,将其整体右移一位,低位用-1填充,如下表第四行所示。

PM是部分匹配值(Partial Match)

编号 1 2 3 4 5 6 7 8 9
S b c c a b c a a c
PM 0 0 0 0 1 2 0 0 0
next -1 0 0 0 0 1 2 0 0

next[1]=0所以,next数组整体+1

所以答案为011112311

计算机网络

基础

4.令牌总线访问控制方法是在物理总线上建立一个逻辑环,从逻辑上看是环状结构的局域网,从物理上看是总线状结构。

令牌总线MAC方法结合了令牌环和总线两种拓扑结构的优点

从物理结构来看:
- 采用总线型拓扑,所有站点都连接在同一条物理总线上
- 这种结构布线简单,易于扩展和维护
- 站点的物理连接就是一条直线型总线

从逻辑结构来看:
- 站点按照预先确定的顺序组成一个逻辑环
- 令牌在逻辑环中按固定顺序从一个站传递到下一个站
- 站点获得令牌后才能发送数据,发送完毕后将令牌传给下一站

8.属于DHCP客户端发送的消息是( )

discover

request

discover消息:当DHCP客户端启动时,会在本地网络上广播发送discover消息,用于发现DHCP服务器。这是客户端发起的第一步操作。

request消息:客户端收到服务器的offer消息后,会发送request消息,表明接受某台DHCP服务器提供的IP地址等配置信息。

offer:这是DHCP服务器对客户端discover消息的响应,用于向客户端提供可用的IP地址等配置信息,由服务器发送。

ack:这是DHCP服务器对客户端request消息的确认响应,表示同意将相关配置信息分配给该客户端,同样是由服务器发送。

发送的顺序分别是:

discover->offer->request->ack

c-s-c-s模式

10.在以太网中,帧长度是有明确限制的。根据IEEE 802.3标准规定,以太网帧的长度必须在64-1518字节之间:

最小帧长度为64字节:
- 这是为了确保冲突检测机制(CSMA/CD)能够正常工作
- 如果帧太短,可能无法及时检测到冲突
- 不包括前导码和帧起始定界符的7+1字节

最大帧长度为1518字节:
- 这个限制是由于物理层和链路层的技术约束
- 过长的帧会占用信道时间过长,影响网络性能
- 也会增加出错概率

11.Socket,即套接字,是一个对 TCP / IP协议进行封装 的编程调用接口。socket的使用类型主要有:

基于 TCP协议,采用 流的方式 提供可靠的字节流服务

基于 UDP协议,采用 数据报文 提供数据打包发送的服务

基于TCP协议的套接字提供面向连接的、可靠的字节流服务。TCP协议本身就是面向流的协议,能够保证数据的可靠传输。

基于UDP协议的套接字提供无连接的数据报服务。UDP是面向数据报的协议,每个UDP数据报都是一个独立的信息单位。

在Java Socket通信过程中:
- 服务器端需要先创建ServerSocket对象监听特定端口
- 客户端通过new Socket(ip,port)创建Socket对象连接服务器
- 服务器通过accept()接受连接并获得用于通信的Socket对象
- 双方通过各自的Socket对象进行数据传输

体系结构

13.在传输层可采用( )策略防止拥塞

  • 重传策略
  • 流控制策略

协议

BSC

1.在BSC(二进制同步通信)协议中,字符填充是为了避免数据中出现的控制字符序列与实际控制字符混淆。当数据中出现DLE(数据链路转义)字符时,需要在其后额外插入一个DLE字符作为填充。

若 BSC 帧的数据段中出现字符串“ A DLE STX ”,则字符填充后的输出为 ( )

所以字符串输出为A DLE DLE STX

OSPF

2.OSPF(开放最短路径优先)协议是一种链路状态路由协议,直接运行在IP层之上,使用IP协议号89。它不依赖于TCP或UDP等传输层协议。

tcp/IP

3.在 TCP 拥塞控制机制中,当拥塞窗口小于阈值时,拥塞窗口呈指数增长

TCP拥塞控制机制中,当拥塞窗口小于慢启动阈值(ssthresh)时,处于慢启动阶段,此时拥塞窗口是指数增长的。每收到一个ACK,拥塞窗口就加1,这意味着每经过一个RTT(往返时延),拥塞窗口就会翻倍,因此增长速度是指数级的。

4.在TCP/IP 协议簇中,直接为ICMP提供服务的协议是IP

在TCP/IP协议族中,IP协议确实是直接为ICMP提供服务的协议。因为ICMP(Internet Control Message Protocol,互联网控制消息协议)是IP层的重要补充协议,主要用于在IP主机和路由器之间传递控制消息。

IP协议为ICMP提供了网络层的寻址和路由功能,使ICMP消息能够在网络中传递。

6.TCP连接中的确认号反映了接收方期望收到的下一个序号,一个TCP报文段的序号和确认号与以下因素都有关系:

  1. 初始序号(ISN)
  2. 已传输的数据字节

7.RMI (Remote Method Invocation)默认采用TCP/IP作为通信协议。RMI是点对点的传输

\1. TCP/IP协议可以提供可靠的、面向连接的通信服务,能确保方法调用和返回值的准确传输。

\2. RMI需要在客户端和服务器之间建立持久的连接,进行双向通信,而TCP/IP的连接导向特性正好满足这一需求。

\3. TCP/IP具有错误检测和数据重传机制,保证了远程方法调用的数据完整性

http

12.http协议头字段中:

Expires:它通常的使用格式是Expires:Fri ,24 Dec 2027 04:24:07 GMT,后面跟的是日期和时间,超过这个时间后,缓存的内容将失效

Last-Modified / If-Modified:一般服务端在响应头中返回一个Last-Modified字段,告诉浏览器这个页面的最后修改时间

Content-Length:用于描述HTTP消息实体的传输长度

Etag/If-None-Match:用于验证缓存有效性

Content-Length与http缓存没有关系

mail

4.电子邮件系统的确主要由用户代理(User Agent, UA)和消息传输代理(Message Transfer Agent, MTA)两大部分组成

用户代理(UA):
- 是用户直接与之交互的客户端软件
- 提供编写、发送、接收、阅读邮件的界面
- 常见的如Outlook、Thunderbird等邮件客户端

消息传输代理(MTA):
- 负责邮件的存储和转发
- 实现邮件在网络中的传递
- 典型的如Sendmail、Postfix等服务器软件

dns

DNS在同时占用用TCP和UDP的53端口 在数据传送时使用可靠的TCP协议 在域名解析时使用UDP协议

可以进行从域名到ip的解析

属于应用层的协议

网络物理连接

5.两个厂商交换机之间双线互联,应该使用动态链路聚合

动态链路聚合的优势:

  1. 自动协商和检测 - 通过LACP协议,两端设备可以自动协商参数并检测链路状态
  2. 故障自动切换 - 当某条链路发生故障时,可以自动切换到备用链路
  3. 兼容性更好 - 动态协议可以更好地处理不同厂商设备之间的互通
  4. 维护便捷 - 无需手动配置大量参数,减少人为错误

物理设备

10.异步传递模式 ATM 采用称为信元的定长分组,并使用光纤信道传输。

12.物理层主要负责在物理介质上传输比特流。集线器(Hub)是最典型的物理层设备,它工作在OSI参考模型的第一层,主要功能是对接收到的信号进行放大和转发,实现物理层上的数据传输。

交换机工作在数据链路层(第二层),能够学习MAC地址并进行数据帧的转发

路由器工作在网络层(第三层),负责不同网络之间的数据包转发和路由选择

网卡虽然有物理层的功能,但它同时也工作在数据链路层,具有MAC地址

生活中其他常见的物理层设备还包括:
- 中继器:用于延长网络传输距离
- 光纤收发器:用于光电信号转换
- 网线和光纤:作为物理传输介质

13.下列哪项陈述描述了默认路由的作用 ( )

不存在通往目的主机的其它路由时,主机使用默认路由将数据传输到本地网络外的主机

默认路由主要用于处理目的地址不在路由表中的数据包转发,当路由表中没有特定的路由条目指向目的主机时,数据包会通过默认路由发送到本地网络之外。

14.以集线器组建的以太网上某个主机发送一个帧到此网络上的另一主机,则这个网络上的所有主机都会收到这个帧

集线器(Hub)是工作在物理层的网络设备,采用广播的形式转发数据,都会接受到

只有目的MAC地址与自己MAC地址匹配的主机才会将帧去掉首部和尾部,并上交给网络层处理。其他主机收到后会直接丢弃这个帧。

连接器

14.EIA-232(RS-232)接口标准规定使用DB-25连接器作为其标准连接器类型。DB-25连接器有25个引脚,能够满足RS-232标准定义的所有信号线的连接需求,包括数据传输、控制和地线等功能。

DB-15连接器:主要用于VGA视频接口,不是EIA-232标准规定的连接器类型。

RJ-45连接器:这是网络通信中使用的标准以太网接口连接器,用于双绞线网络连接,与EIA-232标准无关。

屏蔽双绞线的缩写为stp

屏蔽双绞线是在非屏蔽双绞线(UTP)的基础上,在双绞线外层加装了金属屏蔽层,可以有效防止电磁干扰。

UTP(Unshielded Twisted Pair)是非屏蔽双绞线的缩写,与题目要求不符
CAT3是3类网线的简称,表示传输速率等级,不是屏蔽双绞线的缩写
CAT5E是5类增强型网线的简称,同样是表示传输速率等级,不是屏蔽双绞线的缩写

7.双绞线一般使用RJ-45接头和接口。RJ-45接头有8个引脚,完全满足双绞线传输的需求,广泛应用于以太网连接中

双绞线一般传输不超过100米

向量处理机

流水线执行时间计算:

\1. 首先看各条指令的执行特点:
- V3←存储器: 每个数需要6拍
- V4←V0+V1: 每个数需要6拍
- V5←V3*V4: 每个数需要7拍

\2. 执行时间分析:
- V3指令从开始到第一个数出来需要6拍,后续每拍出一个数
- V4指令必须等V0、V1准备好(假设已就绪),从开始到第一个结果需要6拍
- V5指令必须等V3、V4的第一个数都准备好才能开始,需要7拍产生第一个结果

\3. 关键路径分析:
- V3和V4可以并行执行
- V5必须等待V3、V4都有结果才能开始
- V3需要6拍出第一个数
- V5需要7拍处理,加上前面6拍等待,再加上处理完所有N个数的3拍
- 总时间 = 6 + 7 + 3 + N = 16 + N拍

NAT

NAT是英文“网络地址转换”的缩写

址转换又称地址翻译,用来实现私有地址和公用网络地址之间的转换

地址转换的提出为解决IP地址紧张的问题提供了一个有效途径

因为当内部网络的主机需要访问外部网络时,必须使用NAT(网络地址转换)来实现私有地址到公网地址的转换。否则内部使用私有IP地址的主机将无法与公网通信。

路由器

因为实际上是路由选择部分通过路由选择算法计算路由表,而分组转发部分根据路由表为分组选择输出端口。

路由器确实是一种具有多个输入/输出端口、专门用于转发分组的计算机系统。

路由器的两个主要功能部分就是路由选择(计算路由表)和分组转发(根据路由表转发分组)。

路由器的路由选择部分负责运行路由选择算法、计算和维护路由表,而分组转发部分则负责查询路由表并据此转发分组。这两部分的功能是不同但相互配合的。

虚拟网络

VPN

VPN(Virtual Private Network)指的是在互联网上建立的一个虚拟的安全通信隧道。它是通过软件技术在公共互联网上构建的安全通道,能够保证数据传输的安全性和私密性。

VPN不仅限于局域网内部,它的主要作用恰恰是跨越不同网络之间的安全连接,可以连接远程的局域网或单个用户。

VPN并非真实的物理线路,而是通过加密和隧道协议等软件技术在现有互联网基础设施上虚拟实现的安全通道。

VPN和防火墙的功能不同。防火墙主要用于控制进出网络的访问权限,而VPN主要用于在不安全的网络上建立安全的数据传输通道。VPN更侧重于数据传输的加密和安全性,而防火墙更侧重于访问控制。

VLAN

9.下面关于虚拟局域网 VLAN 的叙述错误的是 ()

AVLAN是由一些局域网网段构成的与物理位置无关的逻辑组。

B利用以太网交换机可以很方便地实现VLAN。

C每一个VLAN的工作站可处在不同的局域网中。

D虚拟局域网是一种新型局域网。

VLAN不是一种新型局域网,而是在现有局域网基础上的一种网络管理技术。它通过配置交换机等网络设备,将物理局域网划分成多个逻辑子网。

VLAN确实是由局域网网段构成的逻辑组,其划分与物理位置无关,可以根据功能、部门等需求进行灵活分组。

现代以太网交换机都支持VLAN功能,通过配置交换机端口的VLAN ID等参数就可以方便地实现VLAN。

VLAN的成员可以分布在不同的物理局域网中,只要这些局域网的交换机支持相同的VLAN即可实现通信。

VLAN技术的主要作用是:
\1. 提高网络安全性,限制广播域范围
\2. 减少网络负载,提升网络性能
\3. 简化网络管理,提供灵活的网络配置方案
\4. 降低网络设备成本,有效利用现有网络资源

操作系统

1.高响应比优先(HRRN)算法属于快速响应式调度算法

2.在操作系统中,PV操作用于管理资源的访问和同步。P用于申请资源,V用于释放资源

3.下面关于 Linux 进程地址空间中的代码段和数据段的说法错误的是

A 代码段用于存储程序的可执行指令

B 数据段用于存储初始化的全局和静态变量

C 代码段和数据段通常属于只读内存区域

D 在 Linux 中,代码段和数据段总是共享同一个物理页面

在Linux进程地址空间中,代码段和数据段是两个不同的内存区域,它们有着不同的特点和用途。

代码段(text segment)确实用于存储程序的可执行指令,这些指令是CPU直接执行的机器码。

数据段(data segment)用于存储已初始化的全局变量和静态变量,这些数据在程序启动时就被加载到内存中。

代码段通常是只读的,这样可以防止程序在运行时意外修改指令;而数据段中可能包含只读数据区(.rodata)。

代码段和数据段出于以下原因通常不会共享同一个物理页面:
1 内存保护需求不同:代码段需要执行权限,数据段需要读写权限
2 缓存效率考虑:分开存放有利于CPU缓存的使用效率
3 内存对齐要求:不同段可能有不同的对齐要求
4 安全性考虑:分开存放可以防止缓冲区溢出等攻击

4.某系统中有3个并发进程,都需要同类资源4 个,试问该系统不会发生死锁的最少资源数是()

系统不会发生死锁的安全条件是:

系统可用资源数 ≥ 所有进程最大需求数 - 1

这需要用到银行家公式

  • P:进程数

  • R:每个进程最多需要的资源数

所以此题的答案是10个

5.进程和线程是操作系统中最基本的概念,下列有关描述不正确的是()

A进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位

B线程之间的通信简单(共享内存即可,但须注意互斥访问的问题),而不同进程之间的通信通常需要调用内核实现

C由于线程没有独立的地址空间,因此同一个进程的一组线程可以访问该进程资源,这些线程之间的通信也很高效

D线程有独立的虚拟地址空间,但是拥有的资源相对进程来说,只有运行所必须的堆栈,寄存器等。

因为线程并不具有独立的虚拟地址空间,线程是共享所在进程的地址空间的。线程确实拥有独立的运行时资源如堆栈和寄存器等,但地址空间是与其所在进程的其他线程共享的

线程是轻量级的进程,它们共享所在进程的地址空间和其他资源,但拥有独立的运行时资源(如堆栈、程序计数器、寄存器等)。这种特性使得线程的创建、切换开销较小,且线程间通信效率较高。

进程是进行分配资源的最小单位,线程没有独立资源。线程是调度的最小单位

数据库

E-R图

1.在 E-R 图中,矩形用于表示实体

4.一般情况下,当对关系R和S进行自然连接时,要求R和S含有一个或者多个共有的属性,也就是表中的行

5.在数据库设计中,将E-R图转换成关系数据模型的过程属于逻辑设计阶段

6.在关系数据模型与面向对象模型的映射关系中,因为表之间的参考关系(外键关系)实际上对应的是类之间的关联关系(Association)或者继承关系(Inheritance)

表对应类是最基本的映射关系,数据库中的每个表都映射为面向对象模型中的一个类。表中的每条记录对应到面向对象模型中就是类的一个实例对象,这是对象-关系映射的核心概念。表的字段对应类的属性,这是数据库列与对象属性之间的直接映射关系,体现了数据的存储结构。

备份

2.增量备份能基于上次任意一种备份,将上次备份后发生变化的数据进行备份,并将备份后的数据进行标记

sql语句

3.在MySQL存储过程中,以下关于声明存储过程的参数类型的说法正确的是()

A使用DECLARE语句声明参数类型

B在参数名前加上“@”符号来表示参数类型

C在参数名后加上数据类型来声明参数类型

D在存储过程名后使用中括号来声明参数类型

在MySQL存储过程中,可以在存储过程名后使用括号来声明参数

在存储过程中,也可以使用DECLARE语句来声明变量的类型,但是不能用来声明存储过程的参数类型。在MySQL中,不需要在参数名前加上“@”符号来表示参数类型。

6.众所周知,MySQL通过使用绑定变量能够极大地提高执行效率,并且执行重复的语句。因为

  • 只需解析1次SQL语句
  • 仅发送参数和句柄
  • 参数之间缓存至内存中

8.通过CHARINDEX如果能够找到对应的字符串,则返回该字符串位置i(有效位置范围为1<= i <= length(input)),否则返回0。

注意位置是从1开始\

CHARINDEX ( expressionToFind , expressionToSearch [ , start_location ] )

expressionToFind :目标字符串,就是想要找到的字符串,最大长度为8000 。

expressionToSearch :用于被查找的字符串。

start_location:开始查找的位置,为空时默认从第一位开始查找。

多表查询

9.使用insert插入字段的时候,不能使用双引号,否则会执行报错。双引号代表的是字符串

10.MySQL 通过创建并填充临时表的方式来执行union查询。除非确实要消除重复的行,否则建议使用union all。原因在于如果没有all这个关键词,MySQL会给临时表加上distinct选项,这会导致对整个临时表的数据做唯一性校验,这样做的消耗相当高。

11.如果查询包括 GROUP BY 但你并不想对分组的值进行排序,你可以指定 ORDER BY NULL禁止排序

12.inner join时只会对非NULL的记录做join,并且2边都有的才会匹配上

right join意思是包含inner join的结果,匹配不上时,左表字段为 NULL

left join 正好相反,右表的字段为NuLL

视图

7.视图:

视图可以被嵌套,当SELECT语句的选择列表有TOP子句时,视图可以包含ORDER BY子句,其他情况下不行

视图不能对临时表或表变量进行引用。更新视图数据可用sp_refreshview。sp_helptext用于获取自定义视图创建的T_SQL文本

下列选项中的锁模式,可以用于数据修改操作,确保不会同时对同一资源进行多重更新的是()

排他锁,也就是我们经常说的写锁,x锁。

共享锁S:共享锁锁定的资源可以被其他用户读取,但是其他用户无法修改,在执行select时,sql server会对对象加共享锁。(其他人可读不可写)

排它锁X:(独占锁)其他人不能读也不能写(所以不会多重更新)。

更新锁U:当SQL Server准备更新数据时,它首先对数据对象作更新锁锁定,这样数据将不能被修改,但可以读取。等到SQL Server确定要进行更新数据操作时,他会自动将更新锁换为独占锁,当对象上有其他锁存在时,无法对其加更新锁。

更新锁是s锁和x锁的结合

架构锁:在执行依赖于表架构的操作时使用。架构锁的类型为:架构修改 (Sch-M) 和架构稳定性 (Sch-S)。

函数

分组排名

RANK() OVER (PARTITION BY … ORDER BY …)

RANK():对指定分组内的记录进行排序,并返回排名(有并列名次,跳过下一个名次)。

OVER:定义一个窗口(作用范围)。

PARTITION BY:指定按照哪个字段进行分组(类似于 GROUP BY,但不影响原始行数)。

ORDER BY:在每个分组内指定排名顺序。

根据不同的衬衫种类shirt_type,按照销售单价shirt_price从低到高的顺序创建排序表()

1
2
3
4
5
6
7
SELECT
shirt_name,
shirt_type,
shirt_price,
RANK() OVER (PARTITION BY shirt_type ORDER BY shirt_price) AS ranking
FROM SHIRTABLE;

java

基础语法

1.下面 Java 代码能够编译通过的是()

A int arr[3] = {1, 2, 3};

B int arr[] = new int[3];

C int[] arr = new int[]{1, 2, 3};

D int[] arr = {1, 2, 3};

错误的是A,Java中声明数组时不能在[]中指定长度。

在面向对象设计中,主要存在三种基本的类关系:“USES-A”(使用关系)、”HAS-A”(组合关系)和”IS-A”(继承关系)。这三种关系构成了面向对象设计的基础。

继承抽象类,实现接口

运算法则

2.boolean b = true ? false : true==true ? false : true;b的值是?

首先要明确三元运算符的结合性是从右到左的,但此处有一个条件表达式true在最前面,所以先执行第一个三元运算

拆解步骤:
\1. 最外层的三元运算符: true ? false : (true==true ? false : true)
\2. 因为条件为true,所以直接返回false,后面的部分不再执行
\3. 所以 b = false

所以return false

3.double x=2.0; int y=4; x/=++y;

执行后x的值为0.4

复合赋值运算符/=的优先级低于++运算符
++y是前缀自增,会先进行自增运算再参与其他运算
double类型除以int类型,结果会自动转换为double类型

在进行除法运算的话,如果都是整形,那么会向下取整,舍去小数部分

4.Integer.valueOf()会优先使用缓存池中的对象
new Integer()每次都会创建新的对象
当涉及到基本类型时,包装类会自动拆箱进行值比较
使用equals()方法比较Integer对象时比较的是值而不是引用

其中=是复制,==是比较的是在内存中的地址,equals方法比较的才是值的大小

5.java中的基础数据类型:

byte、short、int、long、float、double、boolean和char。

String 属于引用类型

基本数据类型和引用类型的主要区别在于:
\1. 基本类型变量存储的是实际的数据值
\2. 引用类型变量存储的是对象的引用(内存地址)
\3. 基本类型在栈中分配内存,引用类型在堆中分配内存

byte是循环的,满了128就从-128开始

因为Integer类型的默认值是null而不是1。作为包装类型,Integer对象的默认值是null。int默认值是0

6.>> 是算术右移运算符,它使所有的位向右移动,但保持符号位不变。对于负数,左边会自动补1,正数则补0。

>>> 是逻辑右移运算符(也称无符号右移),它使所有的位向右移动,并且左边总是补0,不管原来的数是正数还是负数。

没有<<<=

7.当使用”+”运算符时,如果第一个操作数是String类型,后续的操作数会被自动转换为String类型并进行字符串连接运算从左到右进行

浮点数的默认类型是double,而不是float,然后合long都是占64位的存储空间

其中long用于存储整数值,范围为-2^63^到 2^63^-1,double用于存储浮点数,遵循IEEE 754标准。

6.因为在普通方法(非静态方法)内部不允许声明static变量

7.在Java中,final类是指被final关键字修饰的类这种类不能被继承且其设计就是为了不允许修改。String和StringBuffer是两个典型的final类。

- Java中使用final修饰类的主要目的是出于安全考虑,防止类被继承后改变其原有的行为。
- String类设计成final是因为它被广泛用于类加载机制和安全机制中,且其不可变性是很多设计的基础。
- StringBuffer设计成final主要是因为它的线程安全特性需要得到保证。

\1. Character.toString(myChar) - 这个静态方法将字符’g’转换为字符串”g”
\2. String.valueOf(myChar) - 这个方法同样将字符’g’转换为字符串”g”

8.访问权限修饰符的正确使用规则:
\1. 外部类:只能用public或默认
\2. 成员内部类:可以使用所有四种访问修饰符(public、protected、private和默认)
\3. 局部内部类:不能使用任何访问修饰符
\4. 匿名内部类:不能使用任何访问修饰符

9.因为Class类具有装载其他类的功能,它提供了许多方法来获取类的信息和进行类的动态加载,比如Class.forName()等方法可以用来加载类。

在Java中,Object类是所有类的根类,包括Class类在内的所有类都直接或间接继承自Object类。

接口(interface)并不继承自Object类。接口是一种特殊的抽象类型,它只定义行为规范但不提供实现。虽然接口可以声明Object类中的方法,但这并不意味着接口继承了Object类。

每个类都继承了Object类的toString()方法。如果一个类没有重写toString()方法,它仍然可以使用从Object类继承来的toString()方法,只是输出格式为”类名@散列码的十六进制表示”。

String

String类型只要是字符串一样,==与equals都一样,因为都在字符串常量池的一个位置里。会调用原先有的 String是需要初始化的

StringBuilder是非线程安全的,不需要维护线程同步,所以运行速度最快。

StringBuffer的所有公共方法都是synchronized修饰的,是线程安全的,适合在多线程环境下使用。

StringBuffer运行速度确实比String快。因为String的不可变性,每次操作都会产生新对象,而StringBuffer是可变的,在原对象上直接修改。但是他们都是final修饰的

补充说明:
- String适用于少量的字符串操作的情况
- StringBuilder适用于单线程下在字符缓冲区进行大量操作的情况
- StringBuffer适用于多线程下在字符缓冲区进行大量操作的情况
这三者性能从高到低排序为:StringBuilder > StringBuffer > String

super & this

在Java中使用super和this关键字有严格的语法规则

因为在子类构造方法中调用父类构造方法super()必须位于第一行,这是Java语言规范的要求。这样设计的原因是为了确保在初始化子类之前,父类已经完成初始化。

super()和this()都必须放在构造方法的第一行,这是Java编译器强制要求的。如果不遵循这个规则,代码将无法通过编译。

this()和super()不能同时出现在同一个构造函数中。因为它们都必须位于第一行,而一个方法的第一行只能有一条语句,所以它们是互斥的。

his()和super()只能在构造方法中使用,不能在static环境(包括static方法和static代码块)中使用。因为static成员属于类,而不是实例,而this和super都是和实例相关的概念。

super不仅可以在子类构造方法中使用,还可以在子类的实例方法中使用,用于调用父类被覆盖的方法或访问父类的属性。

final

final不能修饰接口和抽象类。final表示”最终的”含义,而接口和抽象类本身就是需要被实现或继承的,与final的语义相矛盾。final只能修饰具体的类、方法和变量。

final修饰的方法不能被重写(Override),但是可以被重载(Overload)。重写是子类对父类方法的覆盖,而重载是同一个类中方法名相同但参数不同。

final修饰的变量是常量,一旦被赋值就不能再次修改。对于基本类型,是值不能改变;对于引用类型,是引用不能改变(但对象的内容可以改变)。

对象

1.对象的四种创建方式

这四种方式各有特点和适用场景:
- new操作符适用于普通对象创建
- 反射方式适用于动态加载场景
- clone方式适用于对象复制场景
- 反序列化方式适用于数据传输场景

函数调用的两种主要参数传递方式:传值调用(call by value)和引用调用(call by reference)的特点。

传值调用保护了实际参数不被修改,而引用调用则允许通过引用修改实际参数的内容,但不能改变引用本身指向的地址

2.substring indexOf方法

substring方法的特点是包含起始位置,但不包含结束位置的字符。因此要获取两个#之间的内容

3.实例化对象的顺序:

父类B静态代码块->子类A静态代码块->父类B非静态代码块->父类B构造函数->子类A非静态代码块->子类A构造函数

匿名对象类

匿名内部类可以继承一个类或实现一个接口,并且可以重写父类的方法
方法重写时,子类方法返回值类型、方法名和参数必须与父类相同
通过对象调用方法时,优先调用对象实际类型中的方法(动态绑定)

反射

反射机制允许在运行时加载类、访问属性、调用方法、构造对象

  • 运行时动态操作对象
  • 解耦合,提高灵活性

使用场景:

  • 框架开发(如 Spring)
  • 注解处理
  • 序列化、对象复制
  • 动态代理

2.下面关于 Java 中反射机制的说法正确的是()

A反射机制可以在程序运行时获取类的信息

B反射机制可以动态地创建对象、调用方法和访问属性

C反射机制能够提高程序的性能和安全性

D反射机制只能用于访问 public 访问控制修饰符修饰的成员

反射机制实际上会降低程序的性能,因为它需要在运行时进行类型检查和解析。同时,反射也可能破坏封装性,带来安全风险,因为它可以访问私有成员。

反射机制不仅可以访问public成员,通过setAccessible(true)方法,它还可以访问private、protected等其他访问控制级别的成员。这也是反射强大但需要谨慎使用的原因之一。

所以选择A和B这是反射的关键特性和优势

JAVA反射机制主要提供以下功能:

在运行时判断一个对象所属的类

在运行时构造一个类的对象

在运行时判断一个类所具有的成员变量和方法

在运行时调用一个对象的方法

Map

Hashmap

当在遍历HashMap的同时对其进行结构性修改(如删除元素)时,会抛出ConcurrentModificationException异常。代码会运行错误

比如:

1
2
3
4
5
6
7
8
9
10
11
12
public` `static` `void` `main(String[] args) {
``Map<Integer, String> map = ``new` `HashMap<>();
``map.put(``1``, ``"A"``);
``map.put(``2``, ``"B"``);
``map.put(``3``, ``"C"``);
``map.forEach((key, value) -> {
``if` `(key == ``2``) {
``map.remove(key);
``}
``});
``System.out.println(map.size());
}

会抛出异常,运行错误

正确做法是:

正确做法之一是使用 Iterator 遍历并使用其 remove() 方法:

1
2
3
4
5
6
7
8
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
if (entry.getKey() == 2) {
iterator.remove(); // 安全删除
}
}

java.util.HashMap类是Java集合框架中实现键值对存储的主要类,它实现了Map接口,允许我们使用键值对(key-value pair)的形式来存储数据。HashMap使用哈希表的数据结构,每个元素都包含一个键和与之对应的值。

HashMap的主要特点:
\1. 允许null键和null值
\2. 不保证元素的顺序
\3. 非线程安全
\4. 查找效率高,时间复杂度接近O(1)
\5. 键必须是唯一的,而值可以重复

HashMap中解决哈希冲突采用的是链地址法(拉链法),而不是开放地址法

在HashMap的实现中,当多个key的哈希值映射到数组的同一个位置时,HashMap会在该位置构建一个链表(JDK1.8之后在链表长度超过8时会转换为红黑树)来存储所有映射到该位置的Entry。这种方式就是链地址法。

HashMap的底层确实使用Entry数组(在JDK1.8中改名为Node但本质相同)存储键值对。每个Entry包含key、value、next指针等信息。

HashMap 的添加元素流程

  1. 计算 key 的 hash 值,定位数组索引
  2. 若该索引为空,直接插入节点
  3. 若存在冲突(哈希碰撞):
    • 使用链表或红黑树进行存储
  4. 添加元素后,判断是否超过阈值(容量 × 负载因子):

    • 若超过,触发扩容

    HashMap 扩容加载因子为什么是 0.75?

0.75 是经验值,在时间效率(查找速度)和空间利用率之间取得平衡

太低会浪费内存,太高会增加哈希碰撞。

HashMap 扩容为什么扩容为数组长度的 2 倍?

旧长度为 n,新长度为 2n

  • 原 hash 值与新容量 & 计算时,元素位置要么保持不变,要么移动到 index + n

这样可以避免重新计算 hash,提高扩容效率

hashmap并不是线程安全的,

多线程环境下使用:

  • ConcurrentHashMap
  • Collections.synchronizedMap
  • 自行加锁

    ConcurrentHashMap 的实现原理

DK 1.7:

  • Segment 分段锁机制(ReentrantLock)

JDK 1.8:

  • CAS + synchronized 实现并发控制
  • 使用 链表 + 红黑树 解决冲突
  • 核心结构:
    • Node[] 数组 + 每个桶内链表或红黑树
    • 高并发下比 Hashtable 更优

HashSet

HashSet是基于HashMap实现的无序集合,不保证元素的顺序

不允许重复元素(根据 equals()hashCode() 判断)。

不保证元素顺序

允许 null 元素,最多一个。

线程不安全

HashSet作为Java集合框架中的一个重要实现类,通过hashCode()和equals()这两个方法的组合来确保元素的唯一性。这是因为HashSet内部实际使用HashMap来存储数据,其中元素的hashCode值用于确定存储位置,而equals方法则用于处理hash冲突时的比较。

具体工作流程是:
\1. 当添加元素时,先调用hashCode()方法计算元素的哈希值
\2. 根据哈希值确定元素在HashSet中的存储位置
\3. 如果发生hash冲突,则调用equals()方法判断元素是否真正相等

linkedHashSet

LinkedHashSet在HashSet的基础上增加了一个双向链表来维护元素的插入顺序,因此是有序的。

有序集合,迭代顺序为插入顺序。

插入、删除、查找操作时间复杂度仍为 O(1)

TreeMap

TreeMap基于红黑树实现,可以保证键的自然顺序或指定顺序

保证键的有序性

  • 默认按键的 自然顺序(Comparable) 排序。
  • 或使用构造函数传入的 Comparator 自定义排序。

键必须实现 Comparable 接口或提供 Comparator

查询、插入、删除操作时间复杂度为 O(log n)

不允许 null 键(会抛 NullPointerException),但允许 null 值。

线程不安全

Hashtable

老版本的 Map 实现,线程安全,所有方法都被 synchronized 修饰。

不允许 null 键或 null 值

不保证顺序

已被 ConcurrentHashMap 替代,在现代项目中已很少使用。

Collection

collection 的子接口包括List,set,queue。而Map包括三个实现类Hash

Vector

基于 数组实现的动态数组

线程安全,所有方法都用 synchronized 修饰。

线程安全导致性能较低,不推荐在新项目中使用,推荐使用 ArrayList + 显式同步

允许 null 和重复元素

是stack的父类

ArrayList

ArrayList确实维护了元素的插入顺序。ArrayList内部使用动态数组实现,按照元素添加的顺序存储,我们可以通过索引顺序访问元素。

ArrayList不是不可变的(immutable)。我们可以添加、删除、修改ArrayList中的元素。当元素数量超过当前容量时会自动扩容

ArrayList允许重复元素,不保证元素唯一性。

ArrayList不是线程安全的,也就是说不保证同步(synchronized)。如果需要线程安全的ArrayList,可以使用Collections.synchronizedList()方法将其包装成同步集合。或者使用CopyOnWriteArrayList

或者通过显式加锁来同步访问

CopyOnWriteArrayList

写时复制机制(Copy-On-Write)

  • 每次写操作(如 add、remove)会:
    • 复制当前数组
    • 在新数组上修改
    • 替换原数组引用

优点:

  • 读操作无需加锁,读写分离,读性能高

缺点:

  • 写操作开销大,不适合写多读少场景

为什么 new ArrayList<>() 时建议指定初始化容量值?

默认容量是 10,若元素较多,频繁扩容会影响性能。

每次扩容都会:

  • 创建新数组
  • 复制旧数据到新数组

提前预估容量能避免不必要的扩容和数据迁移开销,提高性能

为什么 ArrayList 默认扩容机制是扩容为原数组的 1.5 倍?

  • ArrayList 的扩容机制是:

    1
    newCapacity = oldCapacity + (oldCapacity >> 1); // 即1.5倍
  • 兼顾性能与内存浪费的平衡

    • 小扩容频繁迁移,效率低
    • 大扩容浪费内存
    • 1.5 倍是经验权衡结果,比 Hashtable 的 2 倍更节省空间

LinkedList

基于 双向链表 实现。在中间插入或删除元素只需要改变相邻节点的引用,操作开销是固定的。

插入和删除操作效率高,适用于频繁插入/删除的场景。要访问任意位置的元素必须从头节点或尾节点遍历,不能像数组那样直接通过索引访问,因此不支持高效的随机访问。

支持 null 和重复元素

插入顺序即遍历顺序。

访问元素性能不如 ArrayList(需要从头/尾遍历)。

实现了 Deque 接口,可作为队列或栈使用。

不是线程安全的

concurrent

两者内部都使用 ReentrantLockCondition 控制线程安全和阻塞操作。

它们都属于 阻塞队列(Blocking Queue)的一种实现,适用于多线程生产者-消费者模型。

两者构造方法中可以设置容量上限(有界)。

  • new LinkedBlockingQueue<>(1000) 限定最大容量为 1000。

如果使用无参构造,默认容量是:Integer.MAX_VALUE理论上无界

所以 从默认行为看是无界的,但实际上 可以设置为有界队列

LinkedBlockingQueue

  • 阻塞队列,线程安全
  • 基于链表结构
  • 支持 FIFO(先进先出)操作
  • 插入满了会阻塞,移除空了也会阻塞
  • 常用于生产者-消费者模型
  • 支持一个方向的插入和移除(头出尾进)。

使用 ReentrantLock 实现线程安全

使用两个锁:takeLock、putLock,避免入队和出队相互阻塞

LinkedBlockingDeque

线程安全

基于链表结构

双端阻塞队列(支持两端操作)

既可以作为 队列(FIFO),也可以作为 栈(LIFO) 使用

能实现 队列模型(tail add,head remove)

也能实现 栈模型(head add,head remove)

3.下面关于 Java 中集合相关的说法正确的是()

A List 是一个有序的集合,可以包含重复的元素

B Java 中的集合框架只包括 List、Set 两种类型的集合

C Set 是一个无序的集合,不允许包含重复的元素

D Map 是一种键值对的集合,其中键和值都可以重复

Java集合框架不仅包括List和Set,还包括Map、Queue等多种集合类型。这是对Java集合框架范围的错误理解。

Map中的键(Key)必须是唯一的,不能重复,而值(Value)可以重复。这是Map的基本特性,确保了每个键都能唯一标识一个值。比如HashMap、TreeMap等Map实现类都必须遵守这个规则。

Set是一个不允许重复元素的集合接口。Set的实现类(如HashSet)不保证元素的存储顺序,因此是无序的。虽然LinkedHashSet保持了插入顺序,TreeSet按照自然顺序排序,但Set接口本身的特性是无序的。

异常

Java中所有异常和错误的基类是java.lang.Throwable。其中:

- Error和Exception都继承自Throwable
- RuntimeException是Exception的子类

非RuntimeException通常称为受检异常(checked exception),代表程序外部的错误状况,比如文件读写、网络连接等。这类异常必须显式处理,要么使用try-catch捕获,要么在方法签名中用throws声明。

FileNotFoundException是IOException的子类,属于受检查异常。当程序中可能出现此类异常时,必须使用try-catch进行处理或者通过throws声明抛出,否则会导致编译错误。这种机制能够帮助开发者在编译阶段就发现并处理可能的异常情况。

Error表示系统级的错误和资源耗尽的情况,如StackOverflowError、OutOfMemoryError等。这类错误一般是不可恢复的,因此不需要也不应该捕获。

RuntimeException(运行时异常)属于非受检异常(unchecked exception),虽然可以捕获,但不强制要求必须捕获。这类异常通常由程序错误导致,如数组越界(ArrayIndexOutOfBoundsException)、空指针(NullPointerException)等,应该通过程序逻辑来预防,而不是依赖异常处理机制。

NullPointerException是运行时异常(RuntimeException的子类),属于非受检查异常,不需要强制处理或声明。

ClassCastException也是运行时异常,在类型转换失败时抛出,同样不需要在编译时处理。

IndexOutOfBoundsException同样是运行时异常,在访问数组或集合的越界位置时抛出,不需要显式声明或处理。

ArithmeticException也是运行时异常,它同样继承自RuntimeException类,典型场景是除数为零的情况。

在Java异常处理的多重catch语句块中,Exception类应该放在最后捕获。这是因为Exception是所有异常类的父类,如果将它放在前面会导致其他更具体的异常类型永远无法被捕获到。

NoSuchMethodException也是编译时异常,通常在反射操作中使用某个不存在的方法时抛出,它需要在编译期就处理。

IOException是编译时异常,它直接继承自Exception类而不是RuntimeException。编译器会强制要求程序员必须进行异常处理(使用try-catch或throws声明)。

Java的异常捕获遵循“先具体后笼统”的原则,即先捕获子类异常,再捕获父类异常。这样设计是为了确保每种具体的异常都能得到恰当的处理。

特性

volatile

4.下列关于 Java 中 volatile 关键字的说法正确的是()

A volatile 关键字修饰的变量被修改之前会从主存中读取最新的值覆盖掉cpu缓存

B volatie 底层实现遵循 happens-before 原则

C volatile 关键字修饰的共享变量是线程安全的

D volatile 关键字可以保证被修饰变量在运算时不会进行指令重排

volatile关键字是Java中用于保证变量可见性和有序性的重要机制。

volatile变量在每次被线程访问时,都强制从主内存中重新读取最新值,而不是使用线程工作内存中的值。这确保了变量的可见性。

volatile的实现确实遵循happens-before原则。happens-before原则是Java内存模型中的重要概念,它保证了volatile写操作一定happens-before于后续对这个volatile变量的读操作。

volatile关键字通过内存屏障(Memory Barrier)来阻止指令重排序。它能确保volatile变量读写操作的顺序性,防止编译器和处理器对这些操作进行重排序优化。

volatile不能保证线程安全。它只能保证变量的可见性和禁止指令重排序,但不能保证原子性。

volatile只能保证可见性和有序性,无法保证互斥性和原子性。例如count++这样的操作,volatile无法保证其原子性,因为这个操作实际包含读取、递增、写入三个步骤。所以不能包装线程安全

volatile只能用于修饰变量,不能修饰方法和类。作用是告诉编译器和虚拟机,该变量可能会被多个线程同时访问,因此不应该进行编译器优化或缓存

volatile不能完全替代锁机制。虽然volatile能保证可见性和有序性,但无法保证原子性,因此在需要互斥访问或原子操作的场景下,仍然需要使用synchronized等锁机制来实现线程安全。

volatile 关键字能够确保被它修饰的变量在被修改后,立即被刷新到主内存中,同时能够防止指令重排序的优化

constructor

constructor是类的构造方法,它会在使用new关键字创建类的实例时自动执行,用于初始化对象的属性。

ass中的constructor是可以省略的。如果一个类没有显式定义constructor,JavaScript会自动添加一个空的constructor方法。

constructor是类的特殊方法,不需要与类同名。而且类中的其他方法也可以与类同名,这不受限制。

jvm

垃圾回收机制

5.在Java中,当对象的所有引用都消失后,对象使用的内存将自动回收是Garbage Collection 只关心堆空间的对象

Java 堆内存被划分为:

大多数情况下,对象在新生代 Eden 区分配,当 Eden 区空间不够时,发起 Minor GC。

  • 新生代(Young Generation)包括 Eden 区、Survivor From 区、Survivor To 区
  • 老年代(Old Generation)

  • 重点: 对象一般在新生代的 Eden 区分配,但也不是绝对!

    对象分配规则(重点)

    • 多数对象会在 Eden 区分配。
    • 某些大对象会直接进入老年代(如超过 PretenureSizeThreshold 阈值)。
    • 经历多次 Minor GC 后仍然存活的对象,会晋升到老年代。
    • JVM 参数如 MaxTenuringThreshold 控制晋升阈值。
    • 某些情况下,如果 Survivor 空间不够,会触发 直接进入老年代(担保分配)

YGC / Minor GC

  • 只清理新生代内存(尤其是 Eden)。
  • 存活的对象会尝试复制到一个 Survivor 区。
  • 如果 Survivor 不足,触发担保分配(可能进入老年代)。

Full GC 触发条件

  • 老年代空间不足。
  • 方法区(元空间)空间不足。
  • System.gc() 被调用。
  • 新生代担保失败等。

Garbage Collection(垃圾回收)是Java中的一种自动内存管理机制,当程序中的对象不再被引用时,JVM会自动回收这些对象占用的内存空间。C选项正确地描述了这一机制:当对象的所有引用都消失后,对象使用的内存将自动回收。

6.局部变量在Java中必须要先初始化后才能使用,直接运行的话直接会编译失败

7.以下哪些jvm的垃圾回收方式采用的是复制算法回收

A新生代串行收集器

B老年代串行收集器

C并行收集器

D新生代并行回收收集器

E老年代并行回收收集器

Fcms收集器

复制算法主要用于垃圾回收中存活对象较少的场景,通常应用在新生代的垃圾回收中,复制算法的特点是把内存分为两块,每次只使用其中一块。当这一块内存用完,就将还存活的对象复制到另一块上面,然后把已使用过的内存空间一次清理掉。

老年代收集器使用的是标记-整理算法

CMS(Concurrent Mark Sweep)收集器采用的是标记-清除算法

JVM垃圾回收算法:

A标记清除算法:是最基础的垃圾回收算法,分为“标记”和”清除“两个阶段。首先标记出所有需要回收的对象,然后统一回收。优点是实现简单,缺点是会产生大量内存碎片。

B分代回收算法::基于对象存活周期的不同,将内存划分为新生代和老年代,对不同区域采用不同的垃圾回收算法。新生代对象存活率低,采用复制算法;老年代对象存活率高,采用标记整理或标记清除算法。

C标记整理算法:标记过程与标记清除算法一样,但后续步骤不是直接清理,而是让所有存活的对象都向内存空间一端移动,然后清理掉边界以外的内存。解决了内存碎片的问题。

D复制算法:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

还有分代收集算法:根据对象存活时间将内存分为新生代、老年代等,采用不同的回收策略

JMM

JMM是通过控制主内存与线程的本地内存(工作内存)之间的交互来实现内存可见性保证。每个线程都有自己的工作内存,线程对变量的所有操作都必须在工作内存中进行。

synchronized的语义保证了在同步块开始时会从主内存读取最新值到工作内存,在同步块结束时会将修改后的变量值刷新回主内存,这样保证了变量在多线程间的可见性。

volatile关键字的语义保证了对volatile变量的每次读操作都会从主内存中读取最新值,每次写操作也都会立即刷新到主内存,从而保证了变量在多线程之间的可见性。

因为仅仅构造了一个包含final字段的不可变对象,并不能自动保证对象对其他线程可见。要确保对象对其他线程可见,还需要通过happens-before关系来建立正确的内存可见性保证,比如使用volatile变量或者同步块。

java类加载器

java类加载的两个重要特点:

\1. 父类优先:保证父类的静态初始化先于子类执行
\2. 静态先行:所有静态初始化都在实例创建之前完成

java类加载器是JVM的重要组成部分,

引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的

扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。

系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类

通过组合关系。每个ClassLoader内部都持有一个父类加载器的引用。这种设计使得类加载器之间的关系更灵活,也更符合面向对象的设计原则。

tomcat为每个App创建一个Loader,里面保存着此WebApp的ClassLoader。需要加载WebApp下的类时,就取出ClassLoader来使用

这四种类加载器共同组成了Java的类加载体系,遵循双亲委派模型:
\1. 先将类加载请求委托给父类加载器
\2. 父加载器无法加载时,子加载器才会尝试加载
\3. 确保Java核心类库的安全性和一致性

这种机制保证了Java运行环境的稳定性和安全性。

Bootstrap ClassLoader是JVM的一部分,它使用C++实现,负责加载Java核心类库(如rt.jar)。它是所有类加载器的最顶层,比较特殊的是它不是一个普通的Java类。

除了Bootstrap ClassLoader,其他所有的ClassLoader都有父类加载器。比如Extension ClassLoader的父加载器是Bootstrap ClassLoader,Application ClassLoader的父加载器是Extension ClassLoader。因为他是最顶层的

因为JVM在判定两个class是否相同时,不仅要判断类名是否相同,还要判断加载这个类的类加载器是否相同。这就是”类的唯一性”原则,同一个类文件被不同的类加载器加载,在JVM中会被认为是不同的类。

线程

java运行时内存分为“线程共享”和“线程私有”两部分

\1. 方法区(B):用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。它是各个线程共享的内存区域。

\2. Java堆(D):是虚拟机管理的最大的一块内存区域,几乎所有的对象实例和数组都在堆上分配。Java堆是垃圾收集器管理的主要区域,也是线程共享的。

程序计数器:是线程私有的,用于记录线程执行的字节码指令地址。每条线程都需要有一个独立的程序计数器,各个线程之间计数器互不影响。

Java虚拟机栈:也是线程私有的,它描述的是Java方法执行的线程内存模型。每个线程在创建时都会创建一个虚拟机栈,栈中的栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

线程共享的内存区域包括方法区和Java堆而程序计数器和虚拟机栈则是线程私有的。这种内存结构的设计有助于保证多线程运行时的数据安全和隔离。

迭代器

ava 的迭代器实现基于 内部类,通常实现 Iterator 接口

每次调用 next() 会返回当前指向的元素,并将指针向后移动。

使用 modCount(结构修改次数)防止在遍历时结构被修改(快速失败机制 fail-fast)。

抽象类

抽象类是一种特殊的类,其最显著的特征就是不能被实例化。抽象类主要用于被其他类继承,为子类提供通用的属性和方法实现,同时也可以声明抽象方法要求子类必须实现。只有当子类是非抽象类时才必须实现所有抽象方法,如果子类也是抽象类则可以不实现父类的抽象方法。

抽象类中可以包含普通方法,也可以包含抽象方法,并不要求所有方法都是抽象方法。抽象类中的普通方法可以有具体的实现代码。

Java只支持单继承,一个类只能继承一个父类,包括抽象类。虽然可以实现多个接口,但不能继承多个抽象类。一个类可以被声明为抽象类,即使它不包含任何抽象方法。这种设计可以用来阻止类的实例化

抽象类可以有构造方法。尽管抽象类不能被实例化,但其构造方法可以被子类通过super()调用,用于初始化从抽象类继承的属性。

抽象类的主要作用是作为基类使用,通过继承和多态机制实现代码的复用和扩展。它既可以包含抽象方法强制子类实现,又可以提供通用方法的具体实现,是面向对象编程中重要的设计工具。

abstract 方法必须在abstract类或接口中。

接口是一种特殊的抽象类型,其中定义的方法默认都是public abstract的。在Java接口中,方法的修饰符具有严格的限制。

默认访问权限是default(包访问权限)

泛型

泛型是一种参数化类型机制,允许在类、接口、方法中使用类型参数。

好处:

编译期类型检查

避免强制类型转换

提高代码复用性

5.下列关于 Java 中泛型(Generics)的说法正确的是()

A 泛型可以在编译时检查类型安全性,避免运行时出现类型转换异常

B 泛型可以应用于类、接口和方法,但不能用于数组

C 泛型中的类型参数可以是任何类或接口类型,甚至包括基本数据类型

D 泛型中的类型参数只能是对象类型,不能是基本数据类型

泛型的一个主要优势就是在编译时进行类型检查。这可以帮助开发者在编码阶段就发现类型不匹配的问题,而不是等到运行时才出现ClassCastException。这提高了代码的类型安全性和可靠性。泛型类型信息在编译时被擦除(类型擦除),因此在运行时无法直接查询泛型类型。这意味着泛型仅提供编译时的类型检查,而在运行时,泛型变量和泛型方法的类型被当作它们的原始类型(如Object)处理。

在Java中,类型变量(即泛型类型参数)是抽象的,它们不是具体的类型,因此不能直接对它们进行初始化。相反,类型变量用于定义泛型类、接口或方法的类型参数,这些参数在实例化泛型类型或调用泛型方法时会被具体的类型参数所替代。

Java泛型支持任何引用类型(类或接口)作为类型参数。这包括自定义类、集合类、包装类等所有对象类型。但是不能使用基本类型

Java泛型不仅可以应用于类、接口和方法,还可以用于数组。虽然不能直接创建泛型数组(如new T[]),但可以声明泛型数组类型。例如List[] array是合法的。

不能创建参数化类型的数组,因为会进行类型擦除

Java 泛型在 编译后类型被擦除,变成原始类型(Object 或限定边界类型)

6.封装、继承、多态是面向对象的三大特征

封装就是将属性私有化,提供公有的方法访问私有属性,修改属性的可见性来限制对属性的访问,并为每个属性创建一对取值( getter )方法和赋值( setter )方法,用于对这些属性的访问。
如:

1
2
3
4
5
6
7
private String name;
public String getName(){
return;
}
public void setName(String name){
this.name=name;
}

通过封装,可以实现对属性的数据访问限制,同时增加了程序的可维护性。
由于取值方法和赋值方法隐藏了实现的变更,因此并不会影响读取或修改该属性的类,避免了大规模的修改,程序的可维护性增强

7.instanceof是可以判断一个对象是否是类或者接口的对象

8.同一个类的不同对象会在堆内存中占用不同的内存空间,而静态成员则是该类所有对象共享的,存储在方法区中的静态区

9.因为Integer类型的默认值是null而不是1。作为包装类型,Integer对象的默认值是null。

int和Integer的主要区别还包括:

int是基本数据类型,而Integer是引用类型

int变量存储在中,而Integer对象存储在堆中

int不可以为null,而Integer可以为null

Integer提供了更多的方法来操作数据

10.在使用 interface 声明一个外部接口时,只可以使用public修饰符修饰该接口

11.java8中,下面ThreadLocal类用到了解决哈希冲突的开放定址法

12构造方法调用顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Base
{
private String baseName = "base";
public Base()
{
callName();
}

public void callName()
{
System. out. println(baseName);
}

static class Sub extends Base
{
private String baseName = "sub";
public void callName()
{
System. out. println (baseName) ;
}
}
public static void main(String[] args)
{
Base b = new Sub();
}
}

首先调用父类Base的构造方法
\2. 在Base构造方法中调用了callName()方法
\3. 由于此时是多态调用,会调用子类Sub重写的callName()方法
\4. 此时子类Sub的实例变量baseName还未初始化(还未执行子类的构造方法)
\5. 因此子类中访问baseName时得到null

序列化

12.以下关于对象序列化描述正确的是

A使用FileOutputStream可以将对象进行传输

B使用PrintWriter可以将对象进行传输

C使用transient修饰的变量不会被序列化

D对象序列化的所属类需要实现Serializable接口

在Java对象序列化中,C和D是正确的答案。

C选项正确:transient关键字用于声明不需要序列化的成员变量。当一个对象被序列化时,被transient修饰的变量的值不会被保存,在反序列化后,这些变量会被设置为默认值。这通常用于那些不需要或不应该被序列化的敏感数据或临时数据。

D选项正确:要实现对象序列化,该对象的类必须实现Serializable接口。这是Java序列化机制的基本要求,Serializable是一个标记接口,表明该类的对象可以被序列化。

A选项错误:FileOutputStream是字节流,它只能处理原始字节数据的写入,不能直接序列化对象。要序列化对象,需要使用ObjectOutputStream包装FileOutputStream。

B选项错误:PrintWriter是处理字符数据的输出流,主要用于写入文本数据,不能直接进行对象序列化。要序列化对象必须使用ObjectOutputStream

需要注意的是,正确的对象序列化过程通常需要:
\1. 实现Serializable接口
\2. 使用ObjectOutputStream进行序列化
\3. 可以通过transient关键字控制某些字段不被序列化,

json

JSON格式有严格的语法规则要求

A选项 {company:4399} 错误原因:
- JSON中的键必须用双引号括起来
- 使用了中文冒号而不是英文冒号
正确写法应该是 {“company”:4399}

C选项 {[4399,4399,4399]} 错误原因:
- JSON对象必须是键值对的形式
- 数组不能直接作为对象的值,必须有键名
正确写法应该是 {“array”:[4399,4399,4399]}

接口

在Java中接口是一种完全抽象的类型,主要用于定义对象的行为规范

接口中的方法默认就是public和abstract的。这是Java接口的特性,即使不显式声明这些修饰符,编译器也会自动添加。这样可以确保接口方法的可访问性和抽象性。

Java确实使用interface关键字定义接口,使用implements关键字实现接口。这是Java的基本语法规则,体现了面向对象编程中接口的语法实现方式。

Java支持多接口实现,一个类可以同时实现多个接口,这体现了Java的多继承特性。同时,接口之间也可以通过extends关键字实现继承,且支持多继承。

方法

1.抽象方法被子类重写实现时,不能声明为虚方法。子类在实现抽象方法时,只能将其实现为具体方法。

虚方法可以被子类继承和重写,这是虚方法的基本特性,通过virtual关键字声明。

抽象方法是一种特殊的方法,只有方法的声明而没有具体的实现代码,所以不能带有方法体。

非抽象子类继承抽象类时,必须实现所有抽象方法。这是抽象方法的强制要求,确保子类提供具体的实现。

虚方法和抽象方法都支持多态,但有明显区别:
- 虚方法有具体实现,子类可选择是否重写
- 抽象方法没有实现,子类必须实现
- 子类实现抽象方法时只能实现为具体方法,不能声明为虚方法

7.在java中重写方法应遵循规则的包括

在Java中重写(Override)方法确实需要遵循一些规则

可以有不同的访问修饰符

参数列表必须完全与被重写的方法相同

访问修饰符的限制不一定要大于被重写方法。实际上是可以相等,也可以更宽松,但不能更严格。例如,如果父类方法是protected,子类重写的方法可以是protected或public,但不能是private。

参数列表必须相同而不是不同。如果参数列表不同,那就变成了方法重载(Overload)而不是方法重写(Override)

覆盖(重写)只有出现在父类与子类之间,而重载可以出现在同一个类中

覆盖(重写)的特点:
- 必须发生在继承关系中的父类和子类之间
- 方法名、参数列表必须完全相同
- 返回值类型可以是父类方法返回值的子类型
- 访问修饰符不能比父类更严格

重载的特点:
- 可以在同一个类中定义
- 方法名必须相同
- 参数列表必须不同(参数类型、个数或顺序)
- 返回值类型可以不同

8.总结来说,hashCode和equals方法之间存在如下约束:
- equals返回true的两个对象必须具有相同的hashCode,值相等才能hash值相等
- hashCode相同的两个对象不一定equals返回true,因为可能hash碰撞了
- hashCode不同的两个对象一定equals返回false

hashcode是靠值来比较的,equals也是通过值来比较的

9.静态方法和和非静态成员

实例变量可以通过对象实例访问

实例方法可以通过对象实例调用

实例方法method1()不能通过类名直接调用,必须通过对象实例调用。因为实例方法需要依赖对象状态。

静态方法可以通过类名直接调用,这是正确的访问方式。

基本原则:
\1. 静态成员(静态变量和静态方法)可以通过类名直接访问
\2. 非静态成员(实例变量和实例方法)必须通过对象实例访问
\3. 不能通过类名直接访问非静态成员

Collection

Collection是java.util下的接口,它是各种集合结构的父接口

Collections是java.util下的类,它包含有各种有关集合操作的静态方法

线程

1.Java多线程实现有两种主要方式:继承Thread类和实现Runnable接口,

继承Thread类(选项A):
- 直接继承Thread类
- 重写run()方法
- 创建线程对象后调用start()方法启动线程
- 优点是编码简单直观
- 缺点是Java不支持多继承,如果类已经继承了其他类就不能再继承Thread

实现Runnable接口(选项B):
- 实现Runnable接口
- 实现run()方法
- 将实现类实例传入Thread构造函数创建线程对象
- 调用start()方法启动线程
- 优点是可以避免单继承限制,更适合多个线程共享同一个资源的情况
- 这是更常用的方式

还可以使用使用Callable接口,Callable接口的call()方法确实可以返回值,并且能够抛出异常。这是它区别于Runnable接口run()方法的重要特征。run()方法既不能返回值,也不能抛出受检异常。

2.线程的六种状态:

new、runnable、blocked、waiting、timed waiting、terminated

yield和sleep是Java中常用的线程控制方法:

sleep方法会导致当前线程暂停指定时间,在这段时间内线程会释放CPU资源,不会消耗CPU时间片

yield方法调用后,只是让当前线程让出CPU执行权,但不一定会发生线程切换。如果没有其他相同优先级的线程在等待CPU资源,该线程可能会继续执行。

yield方法执行后,线程从running状态转为ready(就绪)状态,而不是waiting状态。这是一个重要的状态转换概念。

TLS

10.TLS(线程局部存储)是一种特殊的存储机制,它为每个线程提供独立的变量副本

TLS确实是解决多线程访问冲突的一种技术。通过为每个线程提供独立的变量副本,避免了线程间的数据竞争,从而解决了并发访问冲突问题。

它会为每个线程创建并维护一个独立的变量副本,这些副本与特定线程绑定,其他线程无法访问。

虽然TLS为每个线程提供了独立的变量副本,但这并不意味着完全不需要同步。如果变量的操作涉及多个步骤,或者存在其他共享资源的访问,仍然可能需要同步机制

Java中的ThreadLocal类就是TLS技术的一个具体实现。它提供了创建线程局部变量的功能,使每个线程都拥有自己的变量副本。

11.ThreadLocal是Java中实现线程本地存储的重要机制。

ThreadLocal确实采用哈希表的实现方式,在Thread类中有一个ThreadLocalMap成员变量,用于存储本线程的ThreadLocal变量。每个线程访问ThreadLocal变量时,实际是在操作自己的ThreadLocalMap中的副本。

ThreadLocal的设计目的就是为了保证线程安全,它为每个线程提供了独立的变量副本,使得每个线程都可以独立地改变自己的副本,而不会影响其他线程的数据。

ThreadLocal不是继承自Thread类,它是一个独立的类,主要用于实现线程本地存储。

ThreadLocal并没有实现Runnable接口,它与线程的执行方式无关,只负责提供线程本地变量的存储机制。

ThreadLocal的重要作用恰恰相反,它是为了避免多线程间共享数据,而是让每个线程都拥有自己的数据副本,实现线程间的数据隔离。共享数据会导致线程安全问题,而ThreadLocal正是解决这个问题的一种方案。

用了开放定址法来解决了hash冲突问题

线程分类

线程实现主要分为三类:用户级线程(ULT)、内核级线程(KLT)和混合型线程实现。

线程实现主要分为三类:用户级线程(ULT)、内核级线程(KLT)和混合型线程实现。轻量级进程(LWP)不是线程的实现方式,而是操作系统内核用来支持线程运行的一种机制。

分析三种线程实现方式:

\1. 用户级线程(ULT):
- 线程的创建、调度和管理都由用户程序完成
- 操作系统对线程一无所知
- 优点是切换开销小,缺点是无法利用多处理器

\2. 内核级线程(KLT):
- 线程的创建、调度和管理都由内核完成
- 操作系统直接对线程进行调度
- 优点是可以利用多处理器,缺点是系统调用开销大

\3. 混合线程:
- 结合了ULT和KLT的优点
- 用户级线程与内核级线程进行多对多映射
- 既保证了系统调用的效率,又可以充分利用多处理器

线程共享和线程私有

线程共享:

方法区:用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。它是各个线程共享的内存区域。

Java堆:是虚拟机管理的最大的一块内存区域,几乎所有的对象实例和数组都在堆上分配。Java堆是垃圾收集器管理的主要区域,也是线程共享的。

线程私有:

程序计数器:是线程私有的,用于记录线程执行的字节码指令地址。每条线程都需要有一个独立的程序计数器,各个线程之间计数器互不影响。

Java虚拟机栈:也是线程私有的,它描述的是Java方法执行的线程内存模型。每个线程在创建时都会创建一个虚拟机栈,栈中的栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

线程安全

HashMap是线程不安全的,在多线程环境下同时操作HashMap可能会导致数据不一致。如果需要线程安全的Map实现,应该使用ConcurrentHashMap或Collections.synchronizedMap()。说白了就是加锁

单线程是安全的

在单例模式中,double-check(双重检查锁定)写法并不能完全保证线程安全。由于Java内存模型的原因,指令重排序可能导致对象初始化失败。要实现完全的线程安全,需要使用volatile关键字修饰单例实例。

因为SimpleDateFormat是非线程安全的。当多个线程同时使用同一个SimpleDateFormat对象时,可能会导致解析和格式化错误。这是因为SimpleDateFormat的设计中包含了可变的成员变量,在多线程环境下会相互影响。在实际开发中,建议为每个线程创建独立的SimpleDateFormat实例,或使用ThreadLocal来保证线程安全。

TreeMap同样是非线程安全的集合类。虽然它能够保证键值对按照键的自然顺序或自定义顺序存储,但在多线程环境下使用仍然会有安全问题。

线程池

ExecutorService关闭机制是线程池使用中的重要知识点

shutdown()方法会让线程池进入”关闭”状态,此时不再接受新的任务提交,但会继续执行队列中的任务直到完成。这是一种平缓的关闭方式。

hutdownNow()方法会尝试终止所有正在执行的任务,并返回等待执行的任务列表(List)。这些任务是尚未开始执行的任务。

awaitTermination(long timeout, TimeUnit unit)是阻塞方法,它会等待直到以下三种情况之一发生:
- 所有任务执行完成
- 到达指定的超时时间
- 当前线程被中断
这个方法常用于确保线程池完全关闭

多线程

1.CyclicBarrier和CountDownLatch确实都可以让一组线程等待其他线程。CyclicBarrier用于让一组线程互相等待,直到所有线程都到达某个公共屏障点。CountDownLatch则允许一个或多个线程等待其他线程完成一组操作。

2.新建线程调用start()方法后,线程并不会立即进入运行状态。线程的状态变化是:新建→就绪→运行。调用start()方法后,线程会进入就绪状态,等待CPU调度才能进入运行状态。这取决于线程调度器的调度策略。start()方法会创建新的线程并执行run()方法,而直接调用run()方法只会在当前线程中执行,不会启动新线程。

1。下列哪些操作会使线程释放锁资源

根据线程获取锁的抢占机制,锁可以分为公平锁和非公平锁。根据锁只能被单个线程持有还是能被多个线程共同持有,锁可以分为独占锁和共享锁。

ReadWriteLock允许一个资源可以被多线程同时进行读操作,ReentrantLock是以独占方式实现的

IO流

1.Java IO确实包含了字符流和字节流两种输入输出方式。字节流以字节为单位进行操作(如InputStream、OutputStream),字符流以字符为单位进行操作(如Reader、Writer)。这是Java IO的基本架构设计。

InputStream和OutputStream都是抽象类,它们分别是所有字节输入流和输出流的抽象基类。作为抽象类,它们不能直接实例化使用,必须使用它们的具体子类,如FileInputStream、ByteArrayOutputStream等。

Reader和Writer确实是字符流的抽象基类,它们提供了字符流操作的基本接口。所有的字符流类都继承自这两个抽象类。

Scanner类不仅可以从键盘读取数据,还可以从文件、字符串等多种数据源读取数据。它是一个通用的数据读取类,可以解析各种格式的输入。例如可以使用Scanner(File file)构造方法来读取文件,使用Scanner(String source)来读取字符串等。

File类中的mkdir()和mkdirs()方法都可以用来创建文件夹,其中:
- mkdir()方法用于创建单个目录
- mkdirs()方法用于创建多级目录,如果父目录不存在会自动创建父目录

2.在Java IO中,按照功能可以将流分为节点流和处理流两大类。

DataInputStream和BufferedInputStream都属于处理流(处理流也叫包装流)。处理流是包装在节点流之上,为程序提供更强大的读写功能。其中:
- DataInputStream 是用于读取基本数据类型的处理流
- BufferedInputStream 是缓冲输入流,可以提高读取效率

FileInputStream是典型的节点流,它直接从数据源(文件)读取数据。
InputStream是所有输入流的抽象基类,它本身既不是节点流也不是处理流。

知识点:
\1. 节点流是直接与数据源相连,负责读写数据的流。如FileInputStream、FileOutputStream等。
\2. 处理流是在节点流基础上对数据进行加工处理的流。如BufferedInputStream、DataInputStream等。
\3. 处理流的优点:
- 性能的提高
- 操作的便捷
- 可以提供特定数据类型的读写支持

System.out实际上是PrintStream类的对象实例,

print()和println()方法是由PrintStream类定义的。

动态代理

两种方式:

  • JDK 动态代理:基于接口(Proxy + InvocationHandler
  • CGLIB 动态代理:基于继承(生成子类)

原理:

  • JDK 动态生成实现类字节码,代理接口方法调用
  • CGLIB 使用 ASM 字节码框架生成子类字节码

JDK 动态代理和 CGLIB 动态代理的区别?

项目 JDK 动态代理 CGLIB 动态代理
基于 接口 类(继承)
是否必须接口
原理 Proxy + 反射 生成子类字节码
性能 JDK 性能略低 CGLIB 性能高但内存占用大
限制 final 方法不可代理 final 类无法继承代理

技术栈

Servle

在实际开发中主要使用javax.servlet和javax.servlet.http这两个包。

Servlet是基于Java的Web组件,具有“一次编写,到处运行”的特性,可以运行在任何支持Java的服务器上。

Servlet确实是在服务器进程中通过多线程方式运行service方法。每个请求由一个线程处理,这种机制比CGI更高效。

Servlet提供了丰富的API和工具类,能够方便地处理HTTP请求、响应、会话管理等常见Web开发任务。相比之下CGI需要自己处理这些细节。

Servlet的生命周期可以分为初始化阶段,运行阶段和销毁阶段三个阶段,以下过程属于初始化阶段是:

加载Servlet类及.class对应的数据

创建ServletConfig对象

创建Servlet对象

13.ServletConfig可以获得Servlet的初始化参数

每个Servlet都有自己的ServletConfig对象,可以通过init()方法获得。开发者可以在web.xml中通过标签为Servlet配置初始化参数,然后在代码中通过ServletConfig的getInitParameter()方法获取这些参数值。

ServletContext用于获取整个Web应用程序的配置信息和共享数据

HttpServletResponse接口是Servlet处理HTTP响应的核心接口。

因为读取路径信息是HttpServletRequest接口的职责。HttpServletResponse主要负责响应相关的操作,而不负责请求信息的读取。

HttpServletResponse可以通过addCookie()方法向客户端写入Cookie。

HttpServletResponse的基本功能之一,通过setHeader()等方法可以设置响应头信息。

读取路径信息应该使用HttpServletRequest接口的方法,如getRequestURI()、getContextPath()、getServletPath()等。这体现了Servlet API中请求和响应职责的明确分工 - HttpServletRequest负责获取请求信息,HttpServletResponse负责生成响应。

CGI

而CGI程序虽然也可以用多种语言编写,但往往需要针对不同的操作系统和服务器环境进行修改和重新编译。

CGI采用多进程方式处理请求,每个请求都会创建新的进程,处理完成后进程就会被销毁。这种方式资源消耗较大。

Spring

Spring框架是一个非常流行的Java开发框架,Spring本身并不提供AOP方式的日志系统。Spring支持使用AOP进行日志操作,但是它需要集成第三方的日志框架如Log4j、SLF4J等。Spring只是提供了AOP的基础设施,让开发者能够使用AOP的方式来实现日志功能。

Spring确实是一个支持快速开发Java EE应用的轻量级框架,它提供了很多便捷功能来简化企业级Java开发。

依赖注入(DI)是Spring框架的核心特性之一,它通过IoC容器来管理对象的依赖关系,降低了代码耦合度。

Spring提供了声明式事务管理功能,开发者可以通过注解或XML配置的方式来管理事务,不需要编写大量的事务管理代码。

SpringBoot自带的Tomcat默认使用的是8080端口,默认端口一般在本地运行时使用

SpringFactoriesLoader是Spring Boot的组件.

spring bean的作用域:singleton、prototype、request、session、globalSession。

spring的Ioc的注入方式:基于属性注入、基于构造方法注入、基于setter方法注入。

Spring 创建bean的方式分别是用构造器来实例化,使用静态工厂方法实例化和使用实例工厂方法实例化

Bean的作用域:

Bean的作用域可以通过@Scope注解来修改,该注解有五个不同的取值。

应是定义为request的Bean;作用域为Session的Bean在同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean。

每次通过Spring容器获取prototype定义的Bean时,容器都将创建一个新的Bean实例。作用域为globalSession的Bean来讲,在一个全局的HTTP Session中,容器会返回该Bean的同一个实例

spring事务

事务传播方式:

传播行为 当前有事务 当前无事务 是否新建事务 是否挂起原事务
REQUIRED 加入 新建 可能
SUPPORTS 加入 非事务执行
MANDATORY 加入 抛出异常
REQUIRES_NEW 新建 新建
NOT_SUPPORTED 非事务执行 非事务执行
NEVER 抛出异常 非事务执行
NESTED(嵌套事务) 嵌套执行(回滚独立) 新建事务

PROPAGATION_REQUIRED: required

如果当前存在事务,则加入该事务;否则创建一个新的事务。

最常用的一种传播行为

PROPAGATION_SUPPORTS supports

如果当前存在事务,则加入事务;否则以非事务方式执行。

适合那些既可以有事务也可以没有事务的操作(例如只读查询)。

PROPAGATION_MANDATORY mandatory

总是开启一个新的事务,如果当前存在事务,则挂起当前事务

常用于日志记录、补偿操作等需要独立提交或回滚的场景。

PROPAGATION_NOT_SUPPORTED not supported

以非事务方式执行操作,如果当前存在事务,则挂起事务

适合做一些与事务无关的操作,如发送邮件、记录日志。

PROPAGATION_NEVER never

不能在事务中执行,如果当前存在事务则抛出异常。

用于必须保证非事务性执行的场景。

PROPAGATION_NESTED nested

如果当前存在事务,则在嵌套事务中执行;否则创建新事务。

依赖底层数据库是否支持保存点(savepoint)机制

✅ 支持内部事务失败只回滚内层,不影响外层事务。

Spring提供了声明式事务、编程式事务两种事务管理方案。

声明式事务,只需通过XML或注解进行配置,即可实现对事务的管理

编程式事务,需要通过TransactionTemplate组件执行SQL,达到管理事务的目的。

在有些场景下,我们需要获取事务的状态,是执行成功了还是失败回滚了,那么使用声明式事务就不够用了,需要编程式事务。

spring注解

@EnableAutoConfiguration:

@EnableAutoConfiguration由@SpringBootApplication引入,它的主要功能是启动Spring应用程序上下文时进行自动配置,它会尝试猜测并配置项目可能需要的Bean。从源代码得知@Import是@EnableAutoConfiguration注解的组成部分,也是自动配置功能的核心实现者

该注解会扫描各个jar包下的spring.factories文件,并加载文件中注册的AutoConfiguration类等

@EnableAutoConfiguration的关键功能是通过@Import注解导入的ImportSelector来完成的。

@ComponentScan:

@ComponentScan注解用于定义Bean的扫描策略。

默认规则是对当前包及其子包中的Bean进行扫描。

@ComponentScan注解的basePackages属性用于自定义要扫描哪些包。

@ComponentScan注解只是定义了扫描范围,在此范围内带有特定注解的Bean才会被载入容器。

自动扫描只会扫描启动类同级或者启动类下面的包中的spring注解

@Transactional注解

@Transactional可以作用在类上,代表这个类的所有公共非静态方法都将启用事务。

可以通过@Transactional的propagation属性,指定事务的传播行为。

可以通过@Transactional的isolation属性,指定事务的隔离级别。

可以通过@Transactional的rollbackFor属性,指定发生哪些异常时回滚。

spring mvc

Spring MVC的组件有:DispatcherServlet HandlerMapping ModelAndView,Spring MVC的核心组件是DispatcherServlet,它负责分发所有的请求。

mvc设计模式下Model代表的是数据,View代表的是用户界面,Controller代表的是数据的处理逻辑,它是Model和View这两层的桥梁。

MVC的处理过程,首先控制器接受用户的请求,并决定应该调用哪个模型来进行处理,然后模型用业务逻辑来处理用户的请求并返回数据,最后控制器用相应的视图格式化模型返回的数据,并通过表示层呈现给用户。

具体的切换是这样

request->model->controller->view->response

请求URL是localhost:8080/test/?id=6

使用@RequestParam

1
2
3
@PostMapping("/test")
public CommonResult publishCourse(@RequestParam String id) {
}

请求URL是localhost:8080/test/6

使用@PathVariable

1
2
3
4
  @PostMapping("/test/{id}")
public CommonResult publishCourse(@PathVariable String id) {
}
来源:牛客网

@patjvaroanle用于绑定动态参数

将请求URL中的模板变量映射到接口方法的参数上

view

View是视图的顶层接口

AbstractJackson2View不是逻辑视图,它不依赖ViewResolver的定位,直接将模型渲染为json

AbstractUrlBasedView是逻辑视图,它依赖ViewResolver定位模板,然后将模型传入模板并渲染。

model

ModelAndView对象,既可以存储模型数据,又可以存储模板路径。

Model对象只能存放模型数据,Model 对象可以被自动实例化。

拦截器

Spring MVC拦截器包含三个方法:preHandle()、postHandle()、afterCompletion()。

注解

@RequestMapping可以声明类或方法的访问路径,还可以声明请求的方式。

@PathVariable可以将请求路径中的参数,绑定到控制器中方法的参数。

@RequestParam可以将请求对象中的参数,绑定到控制器中方法的参数。

@ResponseBody一般在异步获取数据时使用,但不代表它只能应用于异步请求之中。

上传功能

在Spring MVC中实现上传功能,主要依赖MultipartHttpServletRequest从读取请求中的文件,然后对读取到的MultipartFile类型进行处理

用 Spring Boot 上传文件时,只要前端传 multipart/form-data,后端用 @RequestParam MultipartFile file,根本不需要手动解析 Request。

AOP

AOP(面向切面编程)是一种编程范式,它并不是要替代面向对象编程(OOP),而是作为面向对象的一种有益补充。AOP和OOP各有其适用场景,两者是相辅相成的关系。

AOP的核心思想就是将横切关注点(cross-cutting concerns)从业务逻辑中分离出来,统一管理。这些分散在系统各处的相同功能(如日志、事务、安全等)被称为”方面”,AOP确实能够将这些代码集中实现。

通过AOP可以避免代码重复使系统更加模块化,降低了各个功能模块之间的耦合度,确实有助于提高系统的可维护性。

在Spring Aop中JDK动态代理,是Java提供的动态代理技术,可以在运行时创建接口的代理实例。CGLib动态代理,采用底层的字节码技术,在运行时创建子类代理的实例

Spring Aop中:

连接点(join point),对应的是具体被拦截的对象,因为Spring只支持方法,所以被拦截的对象往往就是指特定的方法,AOP将通过动态代理技术把它织入对应的流程中。

切点(point cut),有时候,我们的切面不单单应用于单个方法,也可能是多个类的不同方法,这时,可以通过正则式和指示器的规则去定义,从而适配连接点。切点就是提供这样一个功能的概念。

通知(advice),就是按照约定的流程下的方法,分为前置通知、后置通知、环绕通知、事后返回通知和异常通知,它会根据约定织入流程中。

切面(aspect),是一个可以定义切点、各类通知和引入的内容,SpringAOP将通过它的信息来增强Bean的功能或者将对应的方法织入流程。

织入

织入,就是将方面组件中定义的横切逻辑,织入到目标对象的连接点的过程。

可以在编译时织入,需要使用特殊的编译器。

可以在装载类时织入,需要使用特殊的类装载器。

可以在运行时织入,需要为目标生成代理对象。

JDBC

JDBC 提供了三种方式:

  • Statement:用于静态 SQL 执行;
  • PreparedStatement:用于预编译 SQL,有参数占位符;
  • CallableStatement:用于执行数据库中的存储过程。

PreparedStatement 会在第一次执行时将 SQL 编译为执行计划,并缓存起来,之后再次执行相同 SQL 模板(不同参数)时可重用编译结果,因此执行效率高于每次都需要重新解析和编译 SQL 的 Statement

PreparedStatement 中的 “?” 是 占位符,每个“?” 只能绑定 一个具体的值(如一个字符串、整数等)不能是多个值

由于 PreparedStatement 会将 SQL 和参数分开处理,不会将参数当作 SQL 语句的一部分拼接,因此可以有效避免SQL 注入攻击

PreparedStatement是CallableStatement的父接口

ResultSet中记录行的第一列索引为1,这是JDBC规范明确规定的。JDBC采用从1开始的列索引计数方式,这与数据库中的列计数方式保持一致。

AWT

AWT中TextField是专门用于文本输入的组件类,它允许用户输入和编辑单行文本

Menu是菜单组件类,用于创建下拉菜单,不是文本框组件

Label是标签组件类,用于显示不可编辑的文本标签

List是列表组件类,用于显示可选择的列表项目

TextField作为文本框组件的主要特点包括:
\1. 支持文本输入和编辑
\2. 可以设置文本框的大小和位置
\3. 可以响应文本变化事件
\4. 可以设置是否支持编辑、是否可见等属性
\5. 可以通过getText()和setText()方法获取和设置文本内容

偏向锁是乐观锁,重量级锁适用于大量线程同时竞争锁,追求吞吐量,轻量级锁使用自旋来获取 偏向锁的撤销,需要等待全局安全点

synchronized锁和ReentrantLock锁都可以锁重入

synchronized锁是非公平锁,而ReentrantLock可以通过修改参数来实现公平锁。

根据线程获取锁的抢占机制,锁可以分为公平锁和非公平锁。根据锁只能被单个线程持有还是能被多个线程共同持有,锁可以分为独占锁和共享锁。

ReadWriteLock允许一个资源可以被多线程同时进行读操作,ReentrantLock是以独占方式实现的

synchronized不能被主动打断,而ReentrantLock锁可以。

synchronized不支持多个条件变量,而ReentrantLocK可以调用newCondition方法实现多个条件变量。

JDK特性

虚拟线程

虚拟线程(Virtual Thread)是轻量级线程,JDK 21 稳定版引入

基于 Project Loom,由 JDK 提供调度,而不是依赖操作系统内核线程。

原理:

  • 虚拟线程绑定在平台线程上执行
  • 阻塞时自动挂起,让出平台线程,无需占用系统资源

优点:

  • 更少内存开销
  • 更高并发数(百万级别)
  • 简化异步编程(无需使用回调或线程池)

分布式

1.Mapreduce是用于分布式数据分析的通用计算模型和运行时系统