计算机网络:
Tcp/ip四层模型是什么:
应用层 :主要提供了两个终端设备上的应用程序之间的信息交流服务,决定了信息交换的格式,消息会交给传输层来传输。
传输层 :主要是提供了两个终端设备上的进程之间的通信提供通用的数据传输服务。其中传输层有两个协议:一个是TCP,TCP 提供面向连接的,可靠的数据传输服务。另一个是UDP,UDP提供无连接的,尽最大努力的数据传输服务
网络层 : 主要负责为分组网络上的不同主机之间提供通行服务,并选择合适的路由找到目的主机。
网络接口层 :它可以看做是数据链路层和物理层的合体。具体来说,数据链路层负责把网络层交下来的IP数据报组装成帧,在两个相邻节点间的链路上传输帧。每一帧包含数据和必要的控制信息(如同步信息,地址信息,差错控制)。
物理层实现了相邻节点之间的比特流的透明传输,尽可能屏蔽掉具体传输介质和物理设备之间的差异
Http协议的三次握手和四次挥手:
在发送HTTP请求之前,需要建立TCP连接,
第一次握手是:客户端发送带有SYN标志的报文到服务器
第二次握手是:服务端发送带有SYN/ACK标志的报文到客户端
第三次握手是:发送带有ACK标志的报文到服务端
三次握手的目的是建立一个可靠的通信信道,简单来说,就是使双方确认自己的发送与接收是正常的。
四次挥手:
第一次挥手:客户端发送一个带有FIN标志的报文,并停止发送数据
第二次挥手:服务端收到连接释放报文后立即发出确认报文,服务端进入关闭等待状态,此时客户端到服务端的TCP连接已经释放
第三次挥手:服务端向客户端发送连接释放报文,进入最后确认状态,等待客户端的确认
第四次挥手:客户端收到服务端的连接释放报文后,立即发出确认报文,并进入时间等待状态,等待两倍的最长报文段寿命后才释放TCP连接进入关闭状态,而服务端收到客户端的确认报文后立即进入关闭状态。至此TCP完全关闭。
Java基础
基本数据类型有哪些:
java中有8中基本数据类型:它们又可以分为4类:第一类是4种整数型:byte、short、int、long;第二类是浮点型:float、double;第三类是字符型:char;第四类是布尔型:boolean;
引用数据类型有哪些:
有三类:数组引用类型、类引用类型、接口引用类型
Stringbuilder和stringbuffer的区别
StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
总的来说,单线程适合用StringBuilder,多线程适合使用StringBuffer.
Object类有哪些方法:
返回对象的hash值,对象的拷贝,唤醒线程,线程等待,获取当前对象的类,判断指定对象与该对象是否相等。
序列化如何实现的:
如果对象需要实例化,那么它的所属的类必须实现 Serializable
接口才能序列化。
序列化就是将数据结构或对象转换成二进制字节流的过程
反序列化就是将序列化产生的二进制字节流转换成数据结构或者对象的过程;序列化的主要目的是通过网络传输对象或者是将对象存储到数据库、文件系统、内存中。不想序列化的变量可以使用 transient 关键字修饰,不能修饰方法和类。
Java的优势是什么:
简单易学、面向对象(封装、继承、多态)
平台无关性:
支持多线程;
可靠新、安全性、支持网络编程
什么是面向对象:
面向对象是一种更优秀的程序设计方法,它的基本思想是使用类、对象、继承、封装、消息等基本概念进行程序设计。面向对象开发的程序更易维护,易复用,易扩展
三大特征:
封装:指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。
继承:继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或者新的功能,也可以使用父类的功能,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间,提高开发效率。子类拥有父类的所有方法和属性,但不能访问父类的私有方法和私有属性,子类也可以拥有自己的方法和属性,子类可以使用自己的方式实现父类的方法。
多态:指一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
多态不能调用“只在子类存在但在父类不存在”的方法;
如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。
反射的运用场景:
反射在框架中被经常,通过反射可以获取到任意一个类的所有属性和方法,并且调用他们。使用像Spring/Springboot,MyBatis等框架都大量使用类反射机制,这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。还有Java中的注解的实现是用到了反射。
Io流有哪些:
按照流的流向分,可以分为输入流和输出流
按照操作单元分,可以分为字节流和字符流
按照流的角色分,可以分为节点流和处理流
InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer:所有输入流的基类,前者是字节输出流,后者是字符输出流。
Hashcode() 和 equals() 的关系:
hashCode()用于获取哈希值,
equals() 用于比较两个对象是否相同,这里一般指重写后的equals(),它们搭配一起使用时需要遵循两个规定:
如果两个对象相等,则它们必须有相同的哈希码
如果两个对象有相同的哈希码,则它们不一定相等
== 与 equals 的区别:
==
:
作用于基本数据类型的时候,是比较两个数值是否相等
作用于引用数据类型的时候,是比较两个对象的内存地址是否相等,即判断他们是否是同一个对象
equals
:
没有重写时,Object 默认是使用 == 号来实现的,也就是比较两个对象的地址值是否相等
重写后,一般是按照对象的内容进行比较,如果两个对象的内容相等,则认为对象相等,否则认为对象不等。
Hashmap与hashtable的区别:
从线程HashMap 是非线程安全的, Hashtable 是线程安全的,因为 Hashtable内部的方法基本都使用了Synchronized 修饰。但不推荐使用Hashtable,如果要求线程安全的话,可以使用 concurrentHashMap
从效率上,因为线程安全的因素,Hashmap要比Hashtable效率要高一些。
从对Null key 和 null value 的角度来说,hashmap支持null kay 和 null value,但是只能有一个null key,value没有限制。而hashtable不支持nullkey和null value,否则会抛出空指针异常。
从初始容量以及扩容量的大小来说:创建时如果不设置初始大小的话,hashtable默认初始大小是11,之后每次扩容,容量变为原来的2n+1,而hashmap的默认初始容量为16,之后每次扩容,容量变为原来的两倍,也就是说,hashmap都是以2的幂次方进行扩容的。
从底层的数据结构来说:hashmap底层使用了数组+链表/红黑树的数据结构,当数组容量超过默认的负载因子之后会自动扩容,当链表的长度大于阈值的时候(一般默认阈值为8),同时数组长度大于64的时候,链表会转换成红黑树,在数组小于64的时候优先扩容数组而不是把链表转为红黑树。
而负载因子之所以是0.75是基于时间与空间的权衡,是0.75的时候,空间利用率比较高的同时,有避免了较多的哈希冲突,使得底层的链表或是红黑树的高度比较低,提高了查询效率和空间利用率
Arraylist与linkedlist的实现/扩容:
两者都是线程不安全的,
ArrayList底层使用的是Object数组,而LinkedList底层使用的是双向链表数据结构。因此,Arraylist如果在头和尾插入或删除数据的话是O(1)但是如果是数组内其他的位置,时间复查度会大大提高,LinkedList由于是双向链表,头尾插入也都是O(1) 在链表其他位置插入的由于需要找到插入的位置,所以需要O(n)的时间复查度。Arraylist理所当然的支持随机访问,LinkedList则不支持。
ArrayList每次扩容都是变为原来的1.5倍,底层其实是创建一个更大的数组,把当前的数组拷贝过去。
重载与重写的区别:
重载就是同一个类中多个同名的方法,根据不同的传参,做出不同的处理;相同名字不同参数,构造方法可以重载。
重写就是当子类继承父类的相同方法,输入的数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法,也就是对父类方法的重新改造,外部样子不变,内部逻辑可以改变,构造方法不能重写,构造方法相当于一个构造器,是不能重写的。
Final的作用:
修饰的类不能被继承
修饰的方法不能被重写
修饰的变量会变成常量,基本数据类型不可修改,引用数据类型不可指向其他对象。
Volatile的作用和原理:
修饰的变量是共享的不稳定的,要求每次都要从主内存中读取,以及防止JVM的指令重排,还有一个重要的作用就是保证变量的可见性。
并发编程的三个重要特性:
原子性:一次操作或多次操作,要么都成功执行,要么都不执行,synchronized可以保证原子性
可见性,当一个线程修改了共享变量的值,那么其他共享该变量的线程都能立即看到最新的共享变量,volatile关键字可以保证共享变量的可见性
有序性:代码在执行过程中的先后顺序,Java在编译器以及运行期间的指令优化,代码的执行顺序不一定是按照编写的顺序执行,volatile关键字可以禁止指令进行重排优化。
并发与多线程:(重点)
线程池的参数以及创建:
线程池的好处:
降低资源消耗:通过重复利用已经创建好的线程可以降低创建和 消耗线程的资源消耗
提高响应速度:当任务到达时,不需要等待线程的创建就能立即 执行
提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不 仅会消耗系统资源,还会降低系统的稳定性,使 用线程池可以统一对线程进行管理、调优和监控。
第一种是:通过使用ThreadPoolExecutor的方式创建线程池,
第二种是:通过Executor框架的工具类 Executors来实现
其中主要有三个方法:
FixedThreadPool:返回一个固定线程数量的线程池
SingleThreadExecutor:返回只有一个线程的线程池
CachedThreadPool:返回一个可根据实际情况调整线程数量的线程池
线程池主要有6个参数:
corePoolSize :核心工作线程数
maximumPoolSize:最大线程数
WorkQueue:用于传输和保存等待执行的任务的阻塞队列
keepAliveTime:多余线程存活时间
unit:keepAliveTime的时间单位
ThreadFactory:线程创建工厂,用于创建新的线程
handler:饱和策略,当线程池和队列都满了,再加入的线程会执行此策 略
线程之间的同步问题:
操作系统中一般有下面三种线程同步的方式:
1.互斥量:采用互斥对象机制,只有拥有互斥对象的线程才可以访问共享资源,例如synchronized和lock
2.信号量:它允许同一时刻多个线程访问同一个资源,但是要需要控制同一时刻访问共享资源的线程最大数量。
3.事件:例如Wait/Notify:通过通知操作的方式实现多线程之间的同步,还可以实现多线程优先级的比较操作。
/synchronized 与 lockde 区别:
有5点区别 层次 范围 释放 时间 特点
-
synchronized是java的关键字,在JVM层面实现加锁解锁;Lock是一个接口,在代码层面实现加锁和解锁
-
synchronized可以使用在代码块上和方法上,而lock只能写在代码里。
-
synchronized在代码执行完或者出现异常时会自动释放锁,但lock不会自动释放锁,需要在finally中显示的释放锁
-
synchronized会导致线程拿不到锁而一直等待,lock可以设置获取锁失败的超时时间。
-
synchronized锁可重入、不可中断、非公平,Lock锁可重入、可中断、可公平也可不公平,并可以细分读写锁以提高效率。
Threadlocal是干嘛的:
主要用来解决让每个线程绑定自己的值,可以将ThreadLocal类比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。如果创建了一个ThreadLocal类的变量,那么访问这个变量的每个线程都会有这个变量的本地副本。
乐观锁与悲观锁:
悲观锁:总是假设最坏的情况,每次拿数据的时候都认为别人后修改数据,所以每次拿数据都要上锁,java中悲观锁主要通过synchronized和lock接口来实现。
乐观锁:每次拿数据的时候都认为别人不会修改数据,所以拿数据的时候不上锁,但是在更新的时候会判断一下在此期间别人有没有更新这个数据。乐观锁使用于多读的应用类型,可以提高吞吐量。
Reentrantlock:
ReentrantLock:它是可重入、互斥、实现了Lock接口的锁,它使用synchronized方法和块具有相同的基本行为和语义,并且扩展了其功能。另外,它还有一个可以创建公平锁的构造方法,但由于会大幅降低程序的运行效率,不太推荐使用。
Run和start方法的区别
run()方法被称为线程执行体,它的方法体代表了线程需要完成的任务,而start() 方法用来启动线程,调用 start() 方法启动线程时,系统会把该run()方法当做线程执行体来处理。如果直接调用run(),会把run()当做一个普通方法去执行,而不是一个线程执行体。
Thread创建线程和使用runnable创建线程有什么区别
线程类只是实现了Runnable接口还可以继承其他类,而Thread创建的线程已经继承了Thread类,不能在继承其他的父类了
Runnable创建的线程可以多个线程共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想。
Thread创建线程编写简单,如果需要访问当前线程,则无须使用Thread.currentThread() 方法,直接使用this 即可获得当前线程;而Runnable编程稍微复杂一些,如果需要访问当前线程,必须使用currentThread()方法
线程的生命周期包含5个阶段,
包括:新建、就绪、运行、阻塞、销毁。
框架相关:
Spring与springboot的区别
Spring 是一款开源的轻量级的 Java 开发框架,旨在提高开发人员的开发效率和系统的可维护性。我们一般说的 Spring 一般是指 Spring Framework,它是很多模块的集合,使用这些模块可以很方便的协助我们进行开发。Spring Boot旨在简化Spring 开发,减少配置文件,开箱即用。
/aop的实现机制?:
AOP意思是:面向切面编程,是一种编程思想,是对OOP的补充,是通过预编译方式和运行期动态代理的方式实现不修改源代码的情况下给程序动态统一的添加功能的技术。面向对象编程式将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面,所谓的切面,相当于对象的间横切点,可以把对象看做是一个个横切面组成,我们可以将其单独抽象成单独的模块,AOP技术利用一种成为“横切”的技术,剖解对象内部,把影响多个类的公共行为抽取出来封装成一个可重用的模块,并将其命名为切面,AOP支持两种实现方式,一个是利用JDK动态代理技术,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,在该代理对象中织入切面模块代码。例如可以通过SpringAOP可以实现在程序中的日志功能。
动态代理的实现机制:
Ioc的原理:
IOC 就是控制反转的意思,控制就是指我们创建对象的权利,而反转就是说吧这个创建对象的权利交给系统,让系统去创建对象。将对象之间的依赖关系交给IOC进行管理,在我们需要使用某个对象时,只需要向IOC容器要就可以了,而不需要我们去处理对象的依赖关系。
Springmvc常用的一些注解和作用:
Spring哪里运用了反射:ioc控制反转
Springboot自动装配:
Bean原理:
算法数据结构:
二叉树的遍历有多少种:
Morris遍历:
栈与队列的区别:
最短路径算法有哪些:
红黑树的特点:
Hash冲突解决:
Mysql:
引擎:
默认引擎是InnoDB
索引:
关键字与数据的映射关系称为索引,关键字是从数据中提出的用于标识、检索数据特定内容。在MySql中索引底层采用的是B+树的数据结构。
事务的隔离级别:
Read-uncommitted:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读、或不可重复读。
read-committed:允许读取并发事务已经提交的数据,可以防止脏读,但是幻读或不可重复读仍然可能发生
repeatable-read:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以防止脏读、不可重复读,但幻读仍然可能发生
serializeble:最高的隔离级别,完全服从ACID的隔离级别,所有事务依次执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读、幻读。默认的隔离级别是repeatable-read级别
有什么类型的索引:
在MySql中四种索引;分别是普通索引、唯一索引、主键索引和全文索引
数据库调优:
设计数据库时:字段的设计、数据库表、存储引擎
利用好Mysql自身提供的功能,例如索引等
横向扩展:Mysql集群、负载均衡、读写分离
SQL语句的优化
例如字段的设计、比如存储一个IP地址时,使用字符串要存储16个字节,而使用unsigned int 只需要4个字节。
能用定长就不用非定长数据类型,因为非定长会随着数据的增大而增大,非定长较大的数据会丢失精度。
能用notNUL就不要用null设置字段,因为在Mysql中null需要另外存储,运算也需要特殊的运算符,判断耗时。
操作系统:
进程与线程的区别:
死锁:
怎么解决死锁:
手写多线程生产者与消费者(阻塞队列实现、信号量实现)
中间件:
Redis:
过期时间怎么设置:
redis中,除了字符串类型有自己独特的设置过期时间的命令setex外,其他数据类型都要依靠expire 命令来设置过期时间
数据类型有哪些:
String、Hash、List、set、zset
String是一组字节,在redis中String 是二进制安全的,这意味着它们具有已知长度,而且不受任何特定终止字符的影响。
List是被定义为字符串列表,按插入顺序排序,可以将元素插入头部或尾部。
hash:是键值对的集合,在redis中,哈希是字符串字段与字符串值之间的映射。因此它们时候用来存储对象。
set:是无序字符串的集合,
zset:有序集合类似于无序集合,也是一组非重复的字符串集合,但是排序集中的每个成员都与一个分数相关联,该分数用于获取从最小到最高分数的有序集合。
另外redis还提供了Bitmap,HyperLogLog, GEO等数据类型,但这些类型都是基于上面说的核心数据类型实现的,在5.0版本中,redis新增了Streams数据类型,它是一个功能强大的、支持多播的、可持久化的消息队列。
Set 与 zset 的区别:
zset有序,set无序,zset 底层使用的是压缩列表以及跳表,当元素数量少于128个,以及menber的大小都小于64字节时使用压缩列表,否则将使用跳跃表;set底层使用的是hashtable实现的。
如何实现的分布式锁:
略
Redis的单线程架构:
redis的网络IO和键值对的读取的单线程的,但是对于其他的功能,例如持久化、异步删除等是依赖其他线程完成的,所以说redis底层应该是多线程的,说redis是单线程的只是习惯的说法。
Redis为什么这么快?:
首先,redis的大部分操作都是在内存中完成的,读写速度很快,其次,对于服务端程序来说,线程的切换和竞争锁通常是性能杀手,而redis的单线程避免了线程切换和竞争锁所产生的消耗;最后,由于redis采用了IO多路复用机制,使其在网络IO中可以并发的处理大量的客户端请求,实现高吞吐率。
Redis两种持久化的方式和特点:
redis中有RDB和AOF两种持久化的方式:
RDB:在指定的时间间隔内,将内存中的数据集的快照写入磁盘中,文件名为dump.rdb,适合大规模的数据恢复,对数据库的完整性和一致性要求不高,由于是一定时间间隔内备份一次数据,如果数据库意外关闭,会丢失最后一次快照的所有修改。
AOF,以日志形式记录redis的每一次操作,只允许追加文件,不允许改写文件,redis启动时会从头到尾执行一遍该文件,以较慢的速度恢复数据,文件名为appendonly.aof,在最恶劣的环境下,最多丢失不超过2秒的数据,数据完整性高,但是对磁盘持续的进行IO操作代价太高。在redis中默认使用的是RDB持久化策略。
如何实现redis高可用:
redis的高可用主要有哨兵模式和集群模式;哨兵模式是一个分布式结构,它包含若干个哨兵节点和数据节点,每个哨兵节点后监控着其的数据节点和哨兵节点,当哨兵节点发现数据节点不可达时,会对节点做出下线标识。如果被标识的是主节点,为防止误判,哨兵节点会与其他哨兵节点协商,当大多数节点都认为主节点不可达时,它们便会选出一个哨兵节点来做故障转移工作,可以将从节点晋升为主节点,同时会实时的通知应用方,整个过程是自动化的,实现高可用。集群模式:redis集群采用虚拟槽分区来实现数据分片,并把所有的键根据哈希函数映射到0~16383整数槽内,每一个节点负责维护一部分槽以及槽所映射的键值数据。
说说缓存穿透、击穿、雪崩的区别:
缓存穿透:大量的请求访问不存在的数据时,使得请求全部到达存储层,导致负载过大,直至宕机。原因可能是业务层误删了缓存和库中的数据,或者是有人恶意访问不存在的数据。解决方法:1、存储层未命中时,返回空值对象存入缓存中,当再次请求该空值数据时,缓存直接返回空值。2、将数据存入布隆过滤器中,访问缓存之前,先由布隆过滤器过滤,若访问的数据不存在则直接返回空值。缓存击穿:某些热点数据失效的瞬间,大量的请求直达到存储层,导致服务崩溃。解决方法,1,热点数据不设置过期时间,2加互斥锁,当一个线程访问数据时,其他线程只能等待,当先线程访问后,缓存的数据将被重建,其他的线程也就可以从缓存中取数据了。缓存雪崩:缓存中的大量数据同时过期、或是redis节点出现故障导致服务不可用,缓存层无法继续提供服务时,大量请求直达存储层,导致数据库宕机,解决方法:1、避免数据同时过期,可以设置随机过期时间,2、设置热点数据永不过期,也就是不设置过期时间,3、采用redis集群,一个宕机还有其他可以用。4、启用降级和熔断措施。
Kafka:
项目:
为什么做这个项目:
因为这个项目能比较好的锻炼到我的能力,
项目的难点:
项目整体架构:
技术对比:
选择原因:
Redis具体用来解决了什么问题:
高频数据访问的问题。
Kafka具体解决了什么问题:
如何引导面试官?
从他问到的问题,看看可不可以扩展,多扩展一些,自己熟悉的知识点
反问:
我觉得我这次的面试表现不太好,您有什么建议或者评价给我吗?
假如我有幸加入贵公司,入职前我会有一段空挡期,有什么值得注意或者建议学习的吗?
实习多久才能转正?
5月20号面试:
介绍一下项目:
由于我的母校并没有属于自己的校园论坛,所以我就想开发一个校园论坛,这个项目主要使用了 Spring boot、SSM 框架,使用了 Redis 和 kafka 等中间件、使用 MySQL 和 Elasticsearch 等工具以及使用 Spring Security 框架进行权限的管理,我主要实现了登录注册、发帖子、评论、点赞、关注、私信、站内通知、帖子搜索、权限管理等功能模块。
Spring boot的好处:
- 可以快速构建项目
- 可以对主流开发框架的无配置集成
- 项目可以独立运行无需外部依赖Servlet容器
- 提供运行时的应用监控
- 可以极大地提高开发、部署效率
- 可以与云计算天然集成
线程的创建有几种方式:
-
一个是实现Runnable接口,
-
一种是创建Thread的子类,然后重写父类的Run方法。
容器的层次结构:
Java中的容器主要由两大接口派生而来,一个是Collection接口,主要用于存放单一元素;另一个是Map接口,主要用于存放键值对,对于Collection接口,下面又有三个主要的值接口:List,Set,Queue。
项目中常用那个容器
ArrayList:用于保存一些帖子的信息
HashMap:用于封装一些页面属性以及一些对象,然后传给页面模板,由模板引擎把数据动态的添加到页面中,然后在返回给客户端。
Security权限控制的流程
先由客户端发出请求,该请求会被拦截器拦截,通过拦截器获取用户的信息,在拦截器中构建用户认证的结果,存入Security中,以便Security进行授权,Security根据用户的身份,授权该身份可以访问的资源,如果强行访问未授权的资源会返回403错误代码,并提示权限不足。
配置Security的配置类SecurityConfig,在里面设置资源的访问权限,例如对一些静态资源的访问,我们可以设置为不设限,任何用户都可以访问
而对于一些需要登录、以及要验证身份的页面,可以按照用户的身份设置该身份可以访问哪些资源。如果权限不足则返回错误页面。
Io流的层次结构:
InputStream 和 OutputStream是字节输入输出流的基类,其下有几个常用的子类,FilterInputStream、FiltInputStream、ByteArrayInputStream、BufferedInputStream、DateInputStream, FileOutputStream、FilterOutputStream、PrintStream、DateOutputStream、BufferedOutputStream.
Reader 和 Writer 是字符输入输出流的基类,其下有几个常用的子类:
BufferedReader、InputStreamReader、FilterReader、FileReader、BufferedWriter、OutputStreamWriter、FilterWriter、FileWriter
Spring的核心组件:
有6个
- Spring Core
- Spring Aspects
- Spring AOP
- Spring Data Access/Integration
- Spring Web
- Spring Test
Spring boot 有哪些核心组件
Spring boot的核心组件有4个
- Starter: Starter 帮我们封装好了所有需要的依赖,避免我们自己添加导致的一些Jar包冲突或者缺少包的情况;
- Autoconfigure: autoconfigure内容是配置Bean实例到Spring容器的实际代码实现包,然后提供给starter依赖。
- CLI: 命令行工具
- actuator: actuator是Spring Boot的监控插件,本身提供了很多接口可以获取当前项目的各项运行状态指标。
Spring boot 的启动流程
9千字长文带你了解SpringBoot启动过程–史上最详细 SpringBoot启动流程-图文并茂_java叶新东老师的博客-CSDN博客_springboot框架流程图
启动时的那个注解application具体做了什么
Mybatic的动态生成标签
MyBatis的9种动态标签_chenzm666666的博客-CSDN博客_mybatis动态sql标签有哪些
熟悉哪些排序算法?
快速排序:
void quickSort(int[] q, int l, int r) {
if (l >= r) return;
int i = l - 1, j = r + 1;
int x = q[(l + r) >> 1];
while (i < j) {
do i++; while (q[i] < x);
do j--; while (q[j] > x);
if (i < j) swap(q, i, j);
}
quickSort(q, l, j);
quickSort(q, j + 1, r);
}
void swap(int[] q, int i, int j) {
int temp = q[i];
q[i] = q[j];
q[j] = temp;
}
冒泡排序:
void sort(int[] q) {
for (int i = 0; i < q.length - 1; i++) {
boolean flag = true;
for (int j = q.length - 1; j > i; j--) {
if (q[j] < q[j - 1]) {
int temp = q[j];
q[j] = q[j - 1];
q[j - 1] = temp;
flag = false;
}
}
if (flag) break;
}
}
插入排序:
选择排序:
冒泡的两层for分别的作用
外层for的作用是每次提取出未排序部分的最大值下沉到数组的右边,第二次循环则放在次最右边
内层for的作用是依次进行相邻两个数值的比较,小的放在前,大的在后,循环的次数随着外层确定的数的增加而减少
快速排序的应用场景
适合于待排序的元素是随机分布的,这时快速排序的平均时间复杂度最好,如果待排序的元素原本就有序的话,快速排序的时间复杂度最差为O(n ^ 2)
重载与重写
项目的难点
Kafka消息队列实现站内通知
用户的点赞、评论和关注等操作都会产生一个消息,把用户的三个操作分别设置成三个消息队列,分别为点赞消息队列、评论消息队列、关注消息队列,具体的流程为:用户的每一次点击事件都被分装成一个消息事件,由生产者程序把根据消息的类型将消息发送到对应的消息队列中,而消费者程序将持续监听消息队列,有消息时就会自动消费消息事件,把消息事件携带的信息封装为消息对象再转为JSON格式的数据存入到数据库中,当用户查看消息页面时读取数据库数据即可。数据库中有一个标志位用于标识用户是否已经读过该消息,新增的消息默认是0,读过的消息设置为1,删除的消息设置为2。