java面试题(持续更新)
简介
是笔者面试时所被问到的问题,面试碰到回答不了的技术问题将分享在这,都是鄙人自己收集的答案,可能答案不正确,欢迎指正,内容不定期更新
1.where 和 having的区别
答:
1where在结果返回前时执行,having是在得到结果后执行
2where不能使用聚合函数,having能使用聚合函数 ps:聚合函数=max sum count avg等
2.简述数据库第一范式第二范式第三范式
1.第一范式:存在非主属性对码的部分依赖关系 R(A,B,C) AB是码 C是非主属性 B–>C B决定C C部分依赖于B。如果关系R 中所有属性的值域都是单纯域,那么关系模式R是第一范式的。
那么符合第一模式的特点就有:有主关键字、主键不能为空、主键不能重复,、字段不可以再分。例如:
StudyNo | Name | Sex | Contact
20040901 john Male Email:kkkk@ee.net,phone:222456
20040901 mary famale email:kkk@fff.net phone:123455
以上的表就不符合,第一范式:主键重复(实际中数据库不允许重复的),而且Contact字段可以再分
所以变更为正确的是:
StudyNo | Name | Sex | Email | Phone
20040901 john Male kkkk@ee.net 222456
20040902 mary famale kkk@fff.net 123455
第二范式:存在非主属性对码的传递性依赖 R(A,B,C) A是码 A -->B ,B–>C。如果关系模式R是第一范式的,而且关系中每一个非主属性不部分依赖于主键,称R是第二范式的。所以第二范式的主要任务就是:满足第一范式的前提下,消除部分函数依赖。
StudyNo | Name | Sex | Email | Phone | ClassNo | ClassAddress
01 john Male kkkk@ee.net 222456 200401 A楼2
01 mary famale kkk@fff.net 123455 200402 A楼3
这个表完全满足于第一范式,主键由StudyNo和ClassNo组成,这样才能定位到指定行。但是,ClassAddress部分依赖于关键字(ClassNo-〉ClassAddress,所以要变为两个表:
表一
StudyNo | Name | Sex | Email | Phone | ClassNo
01 john Male kkkk@ee.net 222456 200401
01 mary famale kkk@fff.net 123455 200402
表二
ClassNo | ClassAddress
200401 A楼2
200402 A楼3
3.第三范式
不存在非主属性对码的传递性依赖以及部分性依赖 ,
StudyNo | Name | Sex | Email | bounsLevel | bouns
20040901 john Male kkkk@ee.net 优秀 $1000
20040902 mary famale kkk@fff.net 良 $600
这个完全满足了第二范式,但是bounsLevel和bouns存在传递依赖,更改为:
StudyNo | Name | Sex | Email | bouunsNo
20040901 john Male kkkk@ee.net 1
20040902 mary famale kkk@fff.net 2
bounsNo | bounsLevel | bouns
1 优秀 $1000
2 良 $600
这里可以用bounsNo作为主键,基于两个原因
(1)不要用字符作为主键。可能有人说:如果我的等级一开始就用数值就代替呢?
(2)但是如果等级名称更改了,不叫 1,2 ,3或优、良,这样就可以方便更改,所以一般优先使用与业务无关的字段作为关键字。
一般满足前三个范式就可以避免数据冗余。
3.@autowired和@resource的区别
@resource
来自JSR-250规范定义,默认通过byname去自动装配,其中有name和type两个值
(1)如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
(2)如果指定了name,则从Spring上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
(3)如果指定了type,则从Spring上下文中找到类型匹配的唯一bean进行装配,找不到或找到多个,都抛出异常
(4)如果既没指定name,也没指定type,则自动按照byName方式进行装配。如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行名称查找。如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
@autowired
来自spring的注解,通过bytype的形式去自动装配
默认按类型装配,默认情况下必须要求依赖对象存在,如果要允许null值,可以设置它的required属性为false。如果想使用名称装配可以结合@Qualifier注解进行使用
4.计算机网络的7层按顺序
应用层 应用程序的通信服务 例子:TELNET,HTTP,FTP,NFS,SMTP
表示层 定义数据格式及加密 例子:加密,ASCII等
会话层 如何开始、控制和结束一个会话 例子:RPC,SQL
传输层 选择差错恢复协议还是无差错恢复协议 示例:TCP,UDP,SPX。
网络层 端到端的包传输 IP,IPX
数据链路层 在单个链路上如何传输数据 ATM,FDDI
物理层 是有关传输介质的特性 连接头、帧、帧的使用、电流、编码及光调制等都属于各种物理层规范中的内容
5.怎么重写一个注解
1.添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.新建一个annotation类,annotation类添加如下注解
target注解用来解释注解能用的地方,如下是能用在参数和方法上,想知道更多请自行百度
retention是表明注解所有效的地方,有三种,一般用的是runtime
documented注解表明加入javadoc,我不太了解,应该有和没有都无所谓,加上总没错
public @interface ValidateToken {
}
@Documented
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
3.编写一个切面类
package com.dzq.springbootTest.aspect;
import com.dzq.springbootTest.service.TokenService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @ClassName: TokenAspect
* @Package: com.dzq.springbootTest.aspect
* @author: 越
* @date: 2021/3/31 20:58
* @Description: Token切面类
*/
@Aspect
@Component
@Slf4j
public class TokenAspect {
// public static final Logger logger = LoggerFactory.getLogger(TokenAspect.class);
@Autowired
public void setTokenService(TokenService tokenService) {
this.tokenService = tokenService;
}
private TokenService tokenService;
public static final String TOKEN_KEY = "token";
/**
* checkUrl,keyUrl,tokenScope是通过Spring的@Value注解来获取配置文件中的配置项
* @Value 等同于Spring原先的配置模式中的value
* <bean id="" class="">
* <property name="" value="">
* </bean>
*/
@Value(value = "${jwt.checkUrl}")
String checkUrl;
@Value(value = "${jwt.keyUrl}")
String keyUrl;
@Value(value = "${jwt.clientId}")
String tokenScope;
//切点表达式 aop横切的是 com.dzq.springbootTest.controller 包下的 任意类 任意方法 任意参数
@Pointcut("execution(* com.dzq.springbootTest.controller.*.*(..)) &&@annotation(com.dzq.springbootTest.annotation.ValidateToken)")
public void annotationPointcut() {
}
@Before("annotationPointcut()")
public void beforePointcut(JoinPoint joinPoint) {
System.out.println("Before");
// 此处进入到方法前 可以实现一些业务逻辑
}
@Around("annotationPointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Around");
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] params = methodSignature.getParameterNames();// 获取参数名称
Object[] args = joinPoint.getArgs();// 获取参数值
if (null == params || params.length == 0){
String mes = "Using Token annotation, the token parameter is not passed, and the parameter is not valid.";
log.info(mes);
throw new Exception(mes);
}
boolean hasToken = false;
int index = 0;
for (int i = 0; i < params.length; i++) {
if (TOKEN_KEY.equals(params[i])) {
hasToken = true;
index = i;
break;
}
}
if (!hasToken){
String mes = "The token parameter is not included in the requested parameter, the parameter is not valid.";
log.info(mes);
throw new Exception(mes);
}
this.checkToken(String.valueOf(args[index]));
return joinPoint.proceed();
}
/**
* 在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)
* @param joinPoint 切点
*/
@AfterReturning("annotationPointcut()")
public void doAfterReturning(JoinPoint joinPoint) {
System.out.println("AfterReturning");
}
private void checkToken(String token) {
// Decrypt decrypt = new Decrypt();// 这个类是自己的业务类,主要进行token验证(JWT)
try {
tokenService.checkToken(token, checkUrl, keyUrl, tokenScope);
} catch (Exception e) {
log.info(e.getMessage());
throw new RuntimeException(e.getMessage());
}
}
}
5.使用注解
/**
* @ApiIgnore 该注解表示,swagger2在生成文档的时候,忽略该方法或类。
*/
@ApiIgnore
@ValidateToken
@RequestMapping("getmessage")
public Response<User> getmessage(){
return Response.success("获取信息成功");
}
6.字节流和字符流的区别
1.字节流操作的基本单元为字节;字符流操作的基本单元为Unicode码元。
2.字节流默认不使用缓冲区;字符流使用缓冲区,并且使用flush()方法将缓冲区内容写到硬盘。
3.字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元。