文章目录
一、问题
- 不希望字段序列化,怎么办
- 数据库连接池的作用
- 两个表,查询时候可能有连接操作,有哪些可能的连接操作
- HTTP 常见请求中哪些请求是幂等的
- 项目后台接收到页面的请求之后,是怎样解析和处理的
【未解决】 - 如果想取得请求的 URL ,从哪里取
- 怎么控制线程的执行,比如主线程中启动了两个子线程,要等待两个子线程执行完毕,应该怎么样做,可以调用线程的 stop() 方法,有什么危害吗
- 访问修饰符
- static 关键字有什么用,在方法上加、变量前加、静态内部类和普通类的区别、
普通内部类总是和一个外部类的对象绑定,这个绑定关系是什么时候产生的? - 内部类可以在方法中直接初始化某一个类的匿名的子类,这种内部类有什么特点(我答:它可以访问这个类的变量)它是在方法中 new
出来的嘛,它可以访问方法的局部变量吗,可以读、写局部变量吗 - lamda 表达式怎么写的,-> 后面一定要有大括号吗,可以没有吗
- lamda 表达式可以访问所在方法的局部变量吗,可以读、可以写吗?比如说有个 int类型的变量,在 lamda 表达式中可以改这个变量的值吗?有什么方法可以改吗?
- 所有的对象都可以做 HashMap 的 key 吗?有特殊要求吗?
✨(应当覆写 equals 和 hashCode)✨ - 一行一行读取文本文件 text,一行一行
- 已经有垃圾回收了,可以打开文件,就不管了吗?会出现什么问题?怎么解决?
- finally 有什么用
- 有新的语法可以自动关闭资源
- 注册一个 bean 需要用到哪些注解
- @Service 和 @Comment??? 的区别,可以用 Compont 吗
- Bean 如果需要用到 另一个 Bean ,怎么做依赖注入?注解怎么办
✨(使用 @Resource 或 @AutoWired)✨ - Spring AOP 使用场景,比如说原本 Controller 是正常接收请求的,如果想要所有的 Controller 都在执行请求之前先执行某个检查,怎么做?前置通知怎么写?怎么写一个前置通知。
- NAT 是怎么工作的
【未解决】 - ORM
✨(对象关系映射~Mybatis 框架)✨ - 泛型是怎么处理的泛型在虚拟机中可以看到吗 ?是怎么实现的
(在编译阶段进行类型擦除,强制转化为限定的类型,未限定的话转化成 Object ) - Windows 下的服务是怎么做的
【未解决】 - TTL
- 异步 IO 是怎么做的
【未解决】 - Java 怎么排查 死锁
- 操作系统中的内核态和用户态
- 虚拟内存
- drop、delete 和 truncate 的区别
- 自己怎么写注解
二、复盘
1. transient 关键字
在属性前添加 transient 关键字,该属性就不会被序列化。
transient 的作用就是把 这个字段的生命周期 仅存于 调用者的内存中 而不会写到磁盘里 持久化。
Java 序列化提供两种方式,一种是实现 Serializable 接口,另一种是 实现 Extranlizable 接口,需要重写 writeExternal 和 readExternal 方法。它的效率比 Serializable 高一些,并且可以决定哪些属性需要序列化(即使 transient 修饰的),但是对 大量对象,或者 重复对象,效率低。
静态变量是不会被序列化的,即使没有 transient 关键字修饰。因为 静态变量是在全局区,而序列化是写到磁盘上的,所以 JVM 查找静态变量的值,是从全局区查找的,而不是磁盘上。
2. 数据库连接池的作用
- 避免了频繁创建、释放资源带来的开销
- 响应迅速
- 统一配置和管理
3. 联结表
等值连接(也叫内联结):用 where 子句,内连接都是自然联结
外联结:左外联结/右外联结 left/right outter join… … on
全联结 outer join
4. 幂等性
GET、HEAD、PUT、DELETE 具有幂等性。
5.取得请求的 URL
HttpServletRequest.getRequestURL()
6.停止线程
stop( ) 方法是过时方法了,应当被废弃,因为它会立即停止,将导致代码逻辑不完整。
注释中说这个方法本质是不安全的,调用stop( )终止一个线程会释放 它已经锁定的所有监视器 (这将导致沿堆栈向上传播为检查的ThreadDeath异常被抛出),若此前,受这些 被释放的监视器保护 的对象存在不一致性,并且这些对象对其他线程可见,这就会导致一些意想不到的后果。源码:
@Deprecated
public final void stop() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
checkAccess();
if (this != Thread.currentThread()) {
security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
}
}
// A zero status value corresponds to "NEW", it can't change to
// not-NEW because we hold the lock.
if (threadStatus != 0) {
resume(); // Wake up thread if it was suspended; no-op otherwise
}
// The VM can handle all thread states
stop0(new ThreadDeath());
}
停止线程的方法:
- 方法一
通过设置标识位,例如:
public class Exercise implements Runnable
{
private boolean flag=true;
@Override
public void run() {
int count=1;
while (flag)
{
System.out.println("count:"+count);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println("逻辑已执行完");
}
}
public static void main(String[] args) throws InterruptedException {
Exercise exercise=new Exercise();
Thread thread=new Thread(exercise);
thread.start();
Thread.sleep(500);
exercise.flag=false;
}
}
主线程 sleep,子线程就会执行,sleep 期限到时,修改标志位,然后主线程结束,子线程继续执行,一个循环后就会退出。这样可以保证 run() 方法中的业务逻辑全部跑完。
- 方法二
通过设置标识位+异常处理,例如:
public class Exercise implements Runnable
{
private static boolean flag=true;
@Override
public void run() {
int count=1;
while (flag)
{
System.out.println("count="+count);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
this.flag=false;
e.printStackTrace();
}
finally {
count++;
System.out.println("逻辑已执行完");
}
}
}
public static void main(String[] args) throws InterruptedException {
Exercise exercise=new Exercise();
Thread thread=new Thread(exercise);
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
主线程 sleep,子线程就会执行,sleep 期限到时,修改标志位,然后主线程结束,子线程继续执行,循环中捕获到 InterruptedException ,下一个循环执行完就会退出。
- 方法3
创建线程池,使用 shutDown() 或者 shotDownNow() 方法。
7.访问修饰符
8. Lambda 表达式
一个可传递的代码块,可以在以后执行一次或多次。
要使用 函数式编程 有一个前提——接口必须只有 一个 方法。需要这种接口的对象时,就可以提供一个 Lambda 表达式,这种接口称为 函数式接口。
语法:参数,箭头-> 以及一个表达式,如:
(Stirng first,String second) ->
{
if(first.length()<second.length()) return -1;
else if(first.length()>second.length()) return 1;
else return 0;
}
即使 Lambda 表达式没有参数,仍然要提供空括号,就像无参方法一样。
( )->System.out.println("1");
如果可以推导出一个 Lambda 表达式的参数类型,则可以忽略其类型。如:
( first,second ) ->
{
first.length()-second.length();
}
如果方法只有一个参数,而且这个参数的类型可以推导出来,可以省略小括号。
如果只有一行 可以不用大括号 ,在参数 和 函数 之间加一个箭头 -> 即可。
由于 Lambda 表达式可以直接赋值给一个变量,就可以直接把 Lambad 作为参数传递给函数。
在 Lambda表达式中 可以访问外层作用域【也就是所在方法】已初始化的值,但是不能修改。(相当于 Lambda 表达式将外层局部变量当做 final 常量来使用。)
由 “final” 可以想到,未初始化的外层局部变量不能被 Lambda 表达式访问,更不能在 Lambda块内对其初始化。
Lambda表达式 可以获取或者更改其外层类的属性(基本数据类型 或者 引用数据类型 均可)。
9. AutoCloseable 自动关闭支持
JDK 1.7 追加了 AutoCloseable 接口,实现 close() 方法,自动进行关闭处理。一般要结合 try… catch 。
10.@Component
Component注解 也就是 “Controller注解”、“Service注解” 和“Repository注解” 的通用注解,可以和它们起到相同的作用
(在不清楚使用那个注解的时候,可以统统使用Component,为了代码逻辑清晰,还是建议使用具体的注解)。
这四个注解都是类级别的, 可以不带任何参数,也可以带一个参数,代表bean名字,在进行注入的时候就可以通过名字进行注入了。
11.AOP 前置通知
<!--配置AOP-->
<aop:config>
<!--顺序要求在此,不能再 aop:config 外部-->
<aop:pointcut id="pt1" expression="execution(* Service.impl.*.*(..))"></aop:pointcut>
<aop:aspect id="logAdvice" ref="logger">
<!--配置前置通知>
<aop:before method="BeforePrintLog" pointcut-ref="pt1"></aop:before-->
<!--配置后置通知>
<aop:after-returning method="AfterReturningPintLog" pointcut-ref="pt1"></aop:after-returning-->
<!--配置异常通知>
<aop:after-throwing method="AfterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing-->
<!--配置最终通知>
<aop:after method="finalPrintLog" pointcut-ref="pt1"></aop:after-->
<!--配置切入点表达式
id 属性用于指定表达式的唯一标准
expression 属性用于指定表达式内容
此标签写在 aop:aspect 标签之中,只能在当前切面使用
它还可以写在 aop:aspect 标签之外,就是所有标签可用
-->
<!--配置环绕通知-->
<aop:around method="arroundPrintLog" pointcut-ref="pt1"></aop:around>
12.TTL
TTL是 Time To Live 的缩写,该字段指定 IP 包被路由器丢弃之前允许通过的最大网段数量。TTL是 IPv4 报头的一个8 bit 字段。
注意:TTL与DNS TTL有区别。二者都是生存时间,但一个指 ICMP 包的转发次数(跳数),一个指域名解析信息在DNS中的存在时间。
TTL 由 IP数据包的发送者设置,在 IP数据包从源到目的的整个转发路径上,每经过一个路由器,则把该 TTL 的值减1,然后再将IP包转发出去。如果在IP包到达目的IP之前,TTL减少为0,路由器将会丢弃收到的TTL=0的 IP 包,并向 IP 包的发送者发送 ICMP time exceeded 消息,以防止数据包不断在 IP 互联网络上永不终止地循环。
13. Java 排查死锁
使用 IDEA的控制台的 Dump Threads 图标。
15.用户态、内核态-虚拟内存
Linux有虚拟内存机制,每一个进程都有 4GB 的虚拟内存。
每个进程创建加载的时候,会被分配一个大小为 4G 的连续的虚拟地址空间,虚拟的意思就是,其实这个地址空间时不存在的,仅仅是每个进程“认为”自己拥有4G的内存,而实际上,它用了多少空间,操作系统就在磁盘上划出多少空间给它,等到进程真正运行的时候,需要某些数据并且数据不在物理内存中,才会触发缺页异常,进行数据拷贝
更准确一点的说,系统将虚拟内存分割为称为 虚拟页(Virtual Page,VP) 的大小固定的块,每个虚拟页的大小为P = 2^n 字节,类似地,物理内存被分割为物理页(Physical Page,PP),大小也为P字节(物理页也称为页帧(page frame))。
高1G内存,也就是3~4G内存是内核空间,放置内核代码和其他维护的数据。
低3G,也就是0~3G内存是用户空间。
❓为什么要有用户态和内核态❓
在计算机系统中,通常运行着两类程序:系统程序和应用程序,为了保证系统程序不被应用程序有意或无意地破坏,为计算机设置了两种状态:
-
系统态(也称为管态或核心态),操作系统在系统态运行——运行操作系统程序
-
用户态(也称为目态),应用程序只能在用户态运行——运行用户程序
在实际运行过程中,处理机会在系统态和用户态间切换。相应地,现代多数操作系统将 CPU 的指令集分为特权指令和非特权指令两类。 -
特权指令——在系统态时运行的指令
对内存空间的访问范围基本不受限制,不仅能访问用户存储空间,也能访问系统存储空间,特权指令只允许操作系统使用,不允许应用程序使用,否则会引起系统混乱。 -
非特权指令——在用户态时运行的指令
一般应用程序所使用的都是非特权指令,它只能完成一般性的操作和任务,不能对系统中的硬件和软件直接进行访问,其对内存的访问范围也局限于用户空间。
❓用户态切换到内核态的3种方式❓ -
系统调用 【陷入指令】
这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过 系统调用 申请使用操作系统提供的服务程序完成工作,比如 fork( ) 实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。 -
中断
当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
如:如鼠标点击,点击完成后会向CPU发出中断信号,假设进程在用户态,cpu会暂停处理下一条指令,而去内核空间对应的中断处理程序执行后续的操作.
这样就是一次用户态到内核态的转变。 -
异常
当 CPU 在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
❓内核态切换到用户态的方式❓
设置程序状态字
16.drop、delete、 truncate 的区别
-
drop:删除内容和定义,释放空间。简单来说就是把整个表去掉.以后要新增数据是不可能的,除非新增一个表。
-
truncate:删除内容、释放空间但不删除定义。与 drop 不同的是,它只是清空表数据而已。
-
delete:删除内容不删除定义,不释放空间。
区别:
TRUNCATE TABLE 在功能上 与 不带 Where 子句的 Delete 语句相同:二者均删除表中的全部行。但 TRUNCATE TABLE 比 Delete 速度快,且使用的系统和事务日志资源少。
Delete 语句每次删除一行,并在事务日志中为所删除的每行记录一项。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。
TRUNCATE TABLE 删除表中的所有行,但表结构及其列、约束、索引等保持不变。新行标识所用的计数值重置为该列的种子。如果想保留标识计数值,请改用 Delete。
对于由 FOREIGN KEY 约束引用的表,不能使用 TRUNCATE TABLE,而应使用不带 Where 子句的 Delete 语句。由于 TRUNCATE TABLE 不记录在日志中,所以它不能激活触发器。
TRUNCATE TABLE 不能用于参与了索引视图的表。 -
速度:
一般来说: drop> truncate > delete -
安全性:
:小心使用drop 和truncate,尤其没有备份的时候.。 -
使用上:
删除部分数据行用 delete ,注意带上 where 子句. 回滚段要足够大。
删除表,当然用 drop。
想保留表而将所有数据删除. 如果和事务无关,用 truncate 即可. 如果和事务有关,或者想触发 trigger,还是用 delete.
如果是整理表内部的碎片,可以用 truncate 跟上 reuse stroage,再重新导入/插入数据。
17.自定义注解
比如:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Name {
//名字自定义注解
public String value() default "";
}
(1)
@Retention:翻译为持久力、保持力。即用来修饰自定义注解的生命力。
注解的生命周期有三个阶段:
- Java源文件阶段
- 编译到class文件阶段
- 运行期阶段
使用了 RetentionPolicy 枚举类型定义了三个阶段:
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
* (注解将被编译器忽略掉)
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
* (注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为)
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
* (注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到)
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
如果一个注解被定义为 RetentionPolicy.SOURCE,则它将被限定在 Java源文件 中,那么这个注解 既不会参与编译 也不会在运行期起任何作用,这个注解就和一个注释是一样的效果,只能被阅读Java文件的人看到;
如果一个注解被定义为 🌹RetentionPolicy.CLASS🌹【默认】,则它将被编译到Class 文件中,那么编译器可以在编译时根据注解做一些处理动作,但是运行时JVM(Java虚拟机)会忽略它,我们在运行期也不能读取到;
如果一个注解被定义为 🌼RetentionPolicy.RUNTIME🌼【常用】,那么这个注解可以在运行期的加载阶段被加载到 Class对象中。那么在程序运行阶段,我们可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。我们实际开发中的自定义注解几乎都是使用的RetentionPolicy.RUNTIME;
在默认的情况下,自定义注解是使用的RetentionPolicy.CLASS。
(2)
@Target 表示注解作用在哪些地方,可用于修饰 类、接口、属性、方法、构造方法、局部变量、注解、包:
public enum ElementType {
/** 类,接口(包括注解类型)或枚举的声明 */
TYPE,
/** 属性的声明 */
FIELD,
/** 方法的声明 */
METHOD,
/** 方法形式参数声明 */
PARAMETER,
/** 构造方法的声明 */
CONSTRUCTOR,
/** 局部变量声明 */
LOCAL_VARIABLE,
/** 注解类型声明 */
ANNOTATION_TYPE,
/** 包的声明 */
PACKAGE
}
其他:
(3)@Documented:是被用来指定 自定义注解是否能随着被定义的 Java文件生成到 JavaDoc 文档当中。
(4) @Inherited:是指定 某个自定义注解 如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。@Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。