【打卡day02】每天学习10道java面试题

目录

1.SQL查询语句的执行过程?

2.数据库的MVCC原理?

3.java的序列化与反序列化?

4.深拷贝和浅拷贝?

5.数据库的ACID是如何实现的?

6.AQS了解多少?

7.Spring事务的传播机制?

8.FutureTask的get()如果没有返回结果会怎样?如何打破阻塞?

9.线程和进程上下文区别,为什么进程切换需要资源多?

10.常见的HTTP状态码及解释。


1.SQL查询语句的执行过程?

【MySQL架构】

mysql的架构大致可以分为三层:客户端、service层、存储引擎层。

  1. 客户端层主要负责与MySQL server建立连接,发送查询请求以及接收响应的结果集。

  2. service层主要包括连接器、查询缓存、分析器、优化器、执行器等。这些组件包含了 MySQL 的大部分主要功能,例如平时使用最多的存储过程、触发器、视图都在这一层中。还有一个通用的日志模块 binlog

  3. 存储引擎层主要负责数据的存储和提取。MySQL 支持多个存储引擎,例如:InnoDBMyISAMMemory 等。

【service层组件功能介绍】

  1. 连接器:连接器主要负责身份认证和权限鉴别的工作。

  2. 查询缓存:将查询语句作为key,结果作为value,命中则直接返回,表发生了修改,缓存失效,不推荐使用,mysql8.0之后完全摒弃该模块功能。

  3. 分析器:词法分析和语法分析;词法分析会将一些相应的字符串识别为表名和列名;语法分析则分析sql语句是否符合sql语法。

  4. 优化器:一条查询 SQL 可以有 N 种执行方式,最后返回的结果都是相同的,优化器的作用是找到其中最好的执行计划。

  5. 执行器:MySQL 通过分析器知道了客户端要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。(执行前会判断相应表的执行权限)

【执行过程】

  1. 客户端发送请求,建立连接

  2. 服务端先查询缓存是否命中,命中就直接返回,否则继续往下执行。

  3. 接着来到分析器,进行词法分析、语法分析,一些系统关键字校验,校验语法是否合规等等。

  4. 然后优化器进行 SQL 优化,比如怎么选择索引之类,然后生成执行计划。

  5. 最后执行器调用存储引擎 API 查询数据,返回结果。

参考链接:面试官:说说一条查询SQL的执行过程_徐俊生的博客-CSDN博客_sql执行过程

2.数据库的MVCC原理?

MVCC,全称 Multi-Version Concurrency Control ,即多版本并发控制。MVCC 简单来说就是通过维护数据历史版本,从而解决并发访问情况下的读一致性问题。实现原理抓住:隐式字段、undo日志、版本链、快照读&当前读、Read View。

  • 隐式字段:对于InnoDB存储引擎,每行都会含有两个隐藏列,事务ID(DB_TRX_ID)和回滚指针(DB_ROLL_PTR)

  • undo日志:为了回滚而记录的日志。对于insert需要将记录的主键记录下来;对于delete,需要将整条记录的内容记录下来;对于update,需要将修改前的旧值记录下来。

  • 版本链:数据库中的记录发生变化后都会记录在undo日志中,然后用回滚指针指向undo日志地址,因此对该条记录的修改日志串联起来就形成了一个版本链,版本链的头结点就是该条记录最新的值。

  • 快照读:生成读时的一个快照,可能会读到旧的历史数据。

  • 当前读:读取的最新的数据。

  • Read View:这个快照包含:生成快照时的活跃读写事务id表m_ids、活跃读写事务id的最小值、生成该Read View时下一个分配的事务id和生成该Read View的事务id。

有了Read View,就可以判断一个数据能否访问了。

从图中可以看到,有两种不能访问的情况,一个是访问版本的事务ID大于生成ReadView时下一个分配的事务id时和访问版本的事务ID等于其中的一个活跃事务id时,(创建Read View时该事务还是活跃的,不能访问)。

Read View是用来解释Read Committed和Repeatable Read隔离级别的实现区别。因为Read Committed和Repeatable Read隔离级别都是读取已提交事务修改的记录,换言之,某个记录的修改的版本还未提交,该记录是不能被读取的。

READ COMMITTED隔离级别下,每次读取数据前都会创建一个Read View,这样保证每次都能读到其他事务已提交的数据。

REPEATABLE READ隔离级别下,在第一次读取数据时生成一个Read View,这样能保证后续读取的结果完全一致。

3.java的序列化与反序列化?

【序列化】

java序列化常见的方式有Java对象流序列化、JSON序列化、ProtoBuff序列化。

Java对象流序列化:通过Java对象的原生流(InputStream和OutPutStream之间的转化)方式转化,一般是对象输出流ObjectOutputStream和对象输入流ObjectInputStream。

Json序列化:将对象转换为Json格式的字符串。

ProtoBuff序列化:特点是跨语言、数据压缩、传输速度快、提高系统性能。

【反序列化】

序列化的逆过程。

4.深拷贝和浅拷贝?

【浅拷贝】

只拷贝对象的成员变量的值,也就是基本变量的变量值和引用数据类型的地址值,而对于引用类型变量指向的堆中的对象不会拷贝。

【深拷贝】

完全拷贝一个对象,包括拷贝对象的成员变量值,堆中的对象也会拷贝一份。

【如何实现】

浅拷贝可以使用object的clone方法。

深拷贝通常有两种实现方式:重写克隆方法,引用类型变量单独克隆,可能涉及多层递归。序列化,现将被拷贝对象序列化,然后反序列化成拷贝对象。

5.数据库的ACID是如何实现的?

【ACID】分别对应原子性、一致性、隔离性和持久性。

【实现原理】

  • 原子性:由undo日志(回滚日志)实现的

  • 一致性:是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性,也就是其他三个特性最终目的就是为了达到一致性的效果。

  • 隔离性:通过mvcc实现的

  • 持久性:是有redo日志实现的,当做数据修改的时候,不仅在内存中操作,还会在redo log中记录这次操作。当事务提交的时候,会将redo log日志进行刷盘(redo log一部分在内存中,一部分在磁盘上)。当数据库宕机重启的时候,会将redo log中的内容恢复到数据库中,再根据undo log和binlog内容决定回滚数据还是提交数据。

6.AQS了解多少?

【是什么】

AQS(AbstractQueueSynchronizer)抽象队列同步器,java并发包的根基,并发包中的锁就是基于AQS实现的。

【细节】

AQS是基于一个FIFO的双向队列,其内部定义了一个节点类Node,volatile修饰的int类型的成员变量state来表示同步状态。

  • Node中的SHARED(静态Node类型的常量)是用来标记线程是在获取共享资源时被挂起放入AQS队列的,EXCLUSIVE(静态Node类型的常量)用来标记线程获取独占资源时被挂起放入AQS队列的。

  • state通过volatile保证了线程可见性,同时通过CAS机制保证了修改的原子性。

  • 获取state的方式有两种:独占方式和共享方式,一个线程通过独占方式获取了锁,其他线程就会在获取失败后进行阻塞;一个线程通过共享方式获取了锁,其他线程可以通过CAS方式进行获取。

  • 如果共享资源被占用,需要一定阻塞唤醒机制来保证锁的分配,AQS会将竞争共享资源失败的线程添加到一个变体的CLH队列中。

CLH队列是一个单链表实现的队列,申请线程只能在本地变量上自旋,不断轮询前驱节点的状态,如果发现前驱节点释放了锁就结束自旋。

AQS队列实际上是对CLH队列的变体,特性如下:

  • AQS中队列是个双向列表,也是先进先出。

  • 通过两个虚拟节点Head、Tail来组成队列结构,通过volatile关键字修饰保证可见性。

  • Head节点指向获取到锁的节点,虚拟节点本身不持有具体线程。

  • 获取不到同步状态,会将节点进行自旋,与CLH不同的是,AQS在节点自旋失败一定次数后会将线程进行阻塞,相对CLH队列性能要好些。

7.Spring事务的传播机制?

Spring 事务的传播机制说的是,当多个事务同时存在的时候——⼀般指的是多个事务⽅法相互调⽤时,Spring 如何处理这些事务的⾏为。

事务传播机制是使⽤简单的 ThreadLocal 实现的,所以,如果调⽤的⽅法是在新线程调⽤的,事务传播实际上是会失效的。

Spring默认的事务传播⾏为是PROPAFATION_REQUIRED,它适合绝⼤多数情况,如果多个ServiceX#methodX()都⼯作在事务环境下(均被Spring事务增强),且程序中存在调⽤链Service1#method1()-Service2#method2()->Service3#method3(),那么这3个服务类的三个⽅法通过Spring的事务传播机制都⼯作在同⼀个事务中。

图片以及参考来自三分恶大佬,大佬主页:三分恶的博客_CSDN博客-JavaSE,BUG笔记,Vue.js领域博主

8.FutureTask的get()如果没有返回结果会怎样?如何打破阻塞?

Future模式是多线程设计常用的一种设计模式。Future模式可以理解成:我有一个任务,提交给了Future,Future替我完成这个任务。期间我自己可以去做任何想做的事情。一段时间之后,我就便可以从Future那儿取出结果。 Future提供了三种功能:

  • 判断任务是否完成

  • 能够中断任务

  • 能够获取任务执行的结果

向线程池中提交任务的submit方法不是阻塞方法,而Future.get方法是一个阻塞方法,当submit提交多个任务时,只有所有任务都完成后,才能使用get按照任务的提交顺序得到返回结果,所以一般需要使用future.isDone先判断任务是否全部执行完成,完成后再使用future.get得到结果。(也可以用get (long timeout, TimeUnit unit)方法可以设置超时时间,防止无限时间的等待) 三段式的编程:1.启动多线程任务2.处理其他事3.收集多线程任务结果,Future虽然可以实现获取异步执行结果的需求,但是它没有提供通知的机制,要么使用阻塞,在future.get()的地方等待future返回的结果,这时又变成同步操作;要么使用isDone()轮询地判断Future是否完成,这样会耗费CPU的资源。 解决方法:CompletionService和CompletableFuture(按照任务完成的先后顺序获取任务的结果)

参考链接:future.get方法阻塞问题的解决,实现按照任务完成的先后顺序获取任务的结果_傅里叶、的博客-CSDN博客_future.get

9.线程和进程上下文区别,为什么进程切换需要资源多?

上下文切换就是从当前执行任务切换到另一个任务执行的过程。但是,为了确保下次能从正确的位置继续执行,在切换之前,会保存上一个任务的状态。

【为什么进程切换需要资源多】

1).线程是进程的一部分。 进程是表示资源分配的基本单位,又是调度运行的基本单位,是程序执行的一个实例; 线程是进程中执行运算的最小单位,即执行处理机调度的基本单位,是进程中的一个执行流。 2).内存空间不同。 每一个进程拥有自己独立的内存空间,而线程共享进程的内存空间。 进程上下文切换与线程上下文切换最主要的区别就是线程的切换虚拟空间内存是相同的(因为都是属于自己的进程),但是,进程切换的虚拟空间内存则是不同的。

同时,这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。

参考链接:进程上下文切换与和线程上下文切换有什么区别?_bootdubbo的博客-CSDN博客

10.常见的HTTP状态码及解释。

  • 1xx:信息提示

  • 2xx:成功

  • 3xx:重定向

  • 4xx:客户端错误

  • 5xx:服务器错误

HTTP状态码说明
101切换协议。
200确定。客户端请求已成功。
301已永久移动。此请求和之后所有的请求都应该转到指定的 URI。
302对象已移动。对于基于表单的身份验证,此消息通常表示为“对象已移动”。请求的资源临时驻留在不同的 URI。由于重定向有时可能会改变,客户端将来在请求时应该继续使用 RequestURI。只有在 CacheControl 或 Expires 标题字段中指示,此响应才能够缓存。
304未修改。客户端请求的文档已在其缓存中,文档自缓存以来尚未被修改过。客户端使用文档的缓存副本,而不从服务器下载文档。
307临时重定向。
400错误的请求。
401访问被拒绝。
403服务器拒绝请求。
404服务器找不到请求的网页。
500内部服务器错误。
504网关超时。
505HTTP 版本不受支持。

参考链接:HTTP状态码(完整版)_超级字节码的博客-CSDN博客

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现每天打卡人数的统计,可以考虑以下步骤: 1. 定义一个打卡记录类,包含打卡人的姓名和打卡时间等信息。 2. 在程序中创建一个日期变量,用于记录当前日期。 3. 创建一个打卡记录列表,用于存储所有打卡记录。 4. 在程序运行时,读取打卡记录文件,将其中的打卡记录加入打卡记录列表中。 5. 遍历打卡记录列表,对每个记录进行处理:如果记录的日期等于当前日期,则将该记录的姓名计入当天打卡人数。 6. 将当天打卡人数输出或保存到文件中。 下面是一个示例程序,可以作为参考: ```java import java.io.*; import java.text.*; import java.util.*; public class Attendance { // 打卡记录类 static class Record { String name; // 打卡人姓名 Date time; // 打卡时间 public Record(String name, Date time) { this.name = name; this.time = time; } } public static void main(String[] args) { // 创建日期变量 Date today = new Date(); // 创建打卡记录列表 List<Record> records = new ArrayList<>(); // 读取打卡记录文件 try (BufferedReader reader = new BufferedReader(new FileReader("attendance.txt"))) { String line; SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); while ((line = reader.readLine()) != null) { String[] fields = line.split(","); String name = fields[0]; Date time = dateFormat.parse(fields[1]); records.add(new Record(name, time)); } } catch (IOException | ParseException e) { e.printStackTrace(); } // 统计当天打卡人数 int count = 0; SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); for (Record record : records) { if (dateFormat.format(record.time).equals(dateFormat.format(today))) { count++; } } // 输出当天打卡人数 System.out.println("今天打卡人数:" + count); } } ``` 在示例程序中,我们先定义了一个打卡记录类 Record,包含打卡人姓名和打卡时间两个属性。然后在程序中创建了一个日期变量 today,用于记录当前日期;创建了一个打卡记录列表 records,用于存储所有打卡记录。 在读取打卡记录文件时,我们使用了 BufferedReader 和 FileReader 两个类,逐行读取记录文件并解析每行数据,将解析出的打卡记录加入到打卡记录列表中。 接下来,我们使用 SimpleDateFormat 类将日期格式化为 yyyy-MM-dd 的形式,遍历打卡记录列表,对每个记录进行处理,如果记录的日期等于当前日期,则将该记录的姓名计入当天打卡人数。 最后,输出当天打卡人数。注意,这里我们使用了 System.out.println() 方法将结果输出到控制台,你可以将其改为将结果保存到文件中,具体实现方式可以参考 Java 的文件操作相关内容。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值