最近一段时间准备面试加入职,已经好长时间没写过东西了,今日周六,闲来无事,给大家分享一下自己的面试经验。目前大四,自己找的是实习工作(Java开发),技术知识浅薄或有不当之处还请轻喷,本人在学校的最后一个学期还在打篮球比赛,还有学生会上一摊子事,学习上的事就可想而知了。
然而本该在学校上课的最后一个学期,也因为疫情的原因不能返校上课,这期间在家,也是重拾学习这条贫苦人家走向光明的必经之路。在家自己也是复习了一遍之前学过的知识,然后学习了点新技术,网课结束后,看同学们都陆续找到工作,我也是慌了,赶紧进行复习,还好为时不晚,复习了一周就在招聘平台上广撒网。还是自己太年轻,大部份简历都石沉大海了,最后面试过了一家也就慌慌张张的来了,在确定来后到入职这段时间也过了两三家,只怪自己太匆忙,没有慎重的考虑。但从目前看来,其实还是挺不错的,具体什么公司也就不多说了。
下面就把我准备面试一周左右整理的面试题分享给大家,因为题量较多,我会分三次发出去,相信一次发好多好多题,你们就会是点进来一秒钟就放到收藏夹去吃灰了。还有里面可能有一些重复的题,自己也是注意到什么高频题就直接整理了,经验不足,但还是希望给目前找实习的小伙伴们一些帮助,还有想要简历的小伙伴也可以私聊我,我也是问过好多同学和学长学姐整理出了一份不咋地的个人简历。下面进入正题。
(这题很高频,让你自我介绍的时候也要说自己的专业技能)
这个问题大家一定要自己整理一个模板:
大概就是 像Java基础,数据库基础mysql,linux基础,html,css,js等基础知识都是必会的;
Java还要了解JVM和多线程这都是高频题,下面遇到具体题再细聊
还有就是对某一框架熟悉,例如ssm框架,springboot,并有过项目实战,
还有就是springcloud,redis缓存,前端框架Bootstrap,git,maven项目管理工具,docke操作等
一个网站应该再架构上考虑到分层,模块之间的解耦合等。如果节点比较多,可以建立分布式集群,考虑好容灾降级方案,做好数据缓存,提高资源利用效率,快速响应请求等。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。Java反射可以用来获取一个class对象或实例化一个class表示的类的对象,还可以获取构造方法,成员变量,成员方法。
反射的用途
1、各种开发框架中,如Spring。
2、创建数据库链接时,如Class class=Class.forName(“com.java.dbtest.TestConnection”)
3、在IDE中对象.属性,这时候会用到反射
java中泛型的引入主要是为了解决两个方面的问题:1.集合类型元素在运行期出现类型装换异常,增加编译时类型的检查,2.解决的时重复代码的编写,能够复用算法。下面通过例子来说明编译器的类型检查。
反射主要用它来获取运行期的对象数据,例如我的接口需要校验签名,不可能每个接口都要写一遍校验,最好的方式是使用aop来拦截调用,拿到一个上下文对象,要从这个对象里获取字段信息就必须通过反射方式;
泛型可以定义安全的数据结构,好比说我们事先约定好我传入什么类型,你接受什么类型,这样等到我获取的时候你给我的就一定是我要的类型,不会出现类型转换失败,至于为什么需要泛型?因为它可以帮我简化很多代码。比方说我要定义一个列表分页查询返回数据的类型,一般会给出 { (int) dataCount [总数量],(List) dataList [数据集合] },这个数据集合其实就是一个泛型,因为我要查的数据集可能会有多种类型(不同的表),因此这里就需要定义一个泛型,而不用我重复定义很多分页查询结果类型,大大简化了代码
开放题型,大家可以根据自己特质进行整理:(实习面试很少遇到此题)
1、好奇心:对世间万物好奇
2、学习力:快速掌握所需技能和知识
3、耐力:持续的好奇和学习不断不断突破自己
(集合的知识是高频题型,List,Set,Map这些自己整理一遍,这仨要扯能扯上半小时)
HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:
HashMap允许键和值是null,而Hashtable不允许键或者值是null。
Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。
HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)。 一般认为Hashtable是一个遗留的类。
HashMap :非同步,速度快,key/value允许为null
Hashtable:同步 , 速度慢,key/value不允许为null
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。 使用双亲委派模型的好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。因此,如果开发者尝试编写一个与rt.jar类库中重名的Java类,可以正常编译,但是永远无法被加载运行。
答:SQL注入是通过把SQL命令插入到web表单提交或通过页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL指令。
注入攻击的本质是把用户输入的数据当做代码执行。
举例如: 表单有两个用户需要填写的表单数据,用户名和密码,如果用户输入admin(用户名),111(密码),若数据库中存在此用户则登录成功。SQL大概是这样
SELECT * FROM XXX WHERE userName = admin and password = 111
但若是遭到了SQL注入,输入的数据变为 admin or 1 =1 # 密码随便输入,这时候就直接登录了,SQL大概是这样
SELECT * FROM XXX WHERE userName = admin or 1 = 1 # and password = 111 ,因为 # 在sql语句中是注释,将后面密码的验证去掉了,而前面的条件中1 = 1始终成立,所以不管密码正确与否,都能登录成功。
答: #{}在mybatis中的底层是运用了PreparedStatement 预编译,传入的参数会以 ? 形式显示,因为sql的输入只有在sql编译的时候起作用,当sql预编译完后,传入的参数就仅仅是参数,不会参与sql语句的生成,而${}则没有使用预编译,传入的参数直接和sql进行拼接,由此会产生sql注入的漏洞。
1.加载:根据路径找到类加载到内存中
2.验证:验证class类的准确性
3.准备:给类中的静态变量分配空间
4.解析:虚拟机将常量的符号引用变为直接引用,符号引用只是一个标示,而直接引用就是指向内存中的地址
5.初始化:对静态变量和静态代码块进行初始化工作
1.jvm角度:启动类加载器和其它类加载器
2.Java开发角度:启动类加载器,扩展类加载器,应用程序类加载器,自定义类加载器,四者为父子关系
类加载器是有层次的
一般是: 自定义类加载器 >> 应用程序类加载器 >> 扩展类加载器 >> 启动类加载器
如果一个类加载器收到加载类的请求,首先它自己不会加载,而是自己先委派给自己上一层的类加载器,直到委派给最顶层的类加载器先加载,当没有搜索到上层类加载器的时候,子类才会尝试加载。
客户端A:发送SYN连接报文,序列号为x,进入SYNC-SENT状态。
服务端B:发送SYN连接确认报文(SYN=1,ACK = 1),序列号为y(seq = y),
确认报文x(ack = x + 1),进入SYNC-RCVD状态。
客户端A:发送ACK确认报文(ACK = 1),序列号为x+1(seq = x + 1),确认报
文y+1(ack = y + 1),进入ESTABLISHED状态。
服务器B:收到后进入ESTABLISHED状态。
三次握手是为了防止,客户端的请求报文在网络滞留,客户端超时重传了请求报文,
服务端建立连接,传输数据,释放连接之后,服务器又收到了客户端滞留的请求报
文,建立连接一直等待客户端发送数据。
服务器对客户端的请求进行回应(第二次握手)后,就会理所当然的认为连接已建立,
而如果客户端并没有收到服务器的回应呢?此时,客户端仍认为连接未建立,服务
器会对已建立的连接保存必要的资源,如果大量的这种情况,服务器会崩溃。
服务端A:发送FIN报文(FIN = 1),序列号为u(seq = u),进入FIN-WAIT 1状态。
客户端B:发送ACK确认报文(ACK = 1),序列号为v(seq = v),确认报文u(ack = u + 1),
进入CLOSE-WAIT状态,继续传送数据。
服务端A:收到上述报文进入FIN-WAIT2状态,继续接受B传输的数据。
客户端B:数据传输完毕后,发送FIN报文(FIN = 1,ACK = 1),序列号为w(seq = w),确认
报文u(ack = u + 1),进入LAST-ACK状态。
服务端A:发送ACK确认报文(ACK = 1),序列号为u+1(seq = u + 1),确认报文w(ack = w + 1),
进入TIME-WAIT状态,等待2MSL(最长报文段寿命),进入CLOSED状态。
客户端B:收到后上述报文后进入CLOSED状态。
1、当客户端确认发送完数据且知道服务器已经接收完了,想要关闭发送数据口(当然确认信号还是可以发)
,就会发FIN给服务器。
2、服务器收到客户端发送的FIN,表示收到了,就会发送ACK回复。
3、但这时候服务器可能还在发送数据,没有想要关闭数据口的意思,所以服务器的FIN与ACK不是同时发送
的,而是等到服务器数据发送完了,才会发送FIN给客户端。
4、客户端收到服务器发来的FIN,知道服务器的数据也发送完了,回复ACK, 客户端等待2MSL以后,没有
收到服务器传来的任何消息,知道服务器已经收到自己的ACK了,客户端就关闭链接,服务器也关闭链接了。
第一问:List是接口,ArrayList是List的实现类。
第二问:ArrayList是List的实现类,HashSet是Set的实现类,List和Set都实现了Collection接口。
ArrayList底层是动态数组,HashSet底层是哈希表。
ArrayList存储的是对象的引用,HashSet存储的是之前检索对象用的hashcode,所以当存入对象时需要重写hashcode,如果只是比较对象,只需要重写equals方法。
ArrayList存储有序可重复的数据,HashSet存储无序不可重复的数据。
@Autowired(注解是属于spring的)按byType(类型装配)自动注入
依赖对象必须存在,如果要允许null值,可以设置它的required属性为false
@Autowired(required=false),
它也可以使用名称装配,配合@Qualifier注解
@Resource(注解是属于J2EE的)默认按byName(名称装配)自动注入,也可以按类型装配
@Resource的作用相当于@Autowired注解,
@Resource 名称可以通过name属性进行指定,如果没有指定的name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。
但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
共同点
@Autowired与@Resource都可以用来装配bean,
都可以写在字段上或setter方法上
(多线程是个重点,这部分有点不好理解,可以找点视频看比较清晰)
资源利用率提升,程序处理效率提高
代码会相对简单
软件运行速度提升
OOM,即OutOfMemory,内存溢出,原因是:分配的太少;用的太多;用完没释放。
内存泄漏:内存用完没有被释放。大量的内存泄漏就会导致OOM,也就是内存溢出。
常见的OOM情况有三种:
1)java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。
2)java.lang.OutOfMemoryError: PermGen space/ Metaspace------>java永久代(元数据)溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize/MetaspaceSize=64m -XX:MaxPermSize/MaxMetaspaceSize =256m的形式修改。另外,过多的常量也会导致方法区溢出。
3)java.lang.StackOverflowError ------>不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。
volatile是Java虚拟机提供的轻量级的同步机制。主要有三大特性:
本身是一种抽象的概念并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
1)JMM三大特性
可见性 原子性 有序性
2)JMM关于同步的规定
1、线程解锁前,必须把共享变量的值刷新回主内存;
2、线程加锁前,必须读取主内存的最新值到自己的工作内存;
3、加锁解锁是同一把锁;
1、加syns
2、使用的juc下AtomicInteger
是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){
}
public static Singleton getInstance() {
return instance;
}
}
这种方式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。 这种方式基于类加载机制避免了多线程的同步问题,但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到懒加载的效果。
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉模式申明了一个静态对象,在用户第一次调用时初始化,虽然节约了资源,但第一次加载时需要实例化,反映稍慢一些,而且在多线程不能正常工作。
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这种写法能够在多线程中很好的工作,但是每次调用getInstance方法时都需要进行同步,造成不必要的同步开销,而且大部分时候我们是用不到同步的,所以不建议用这种模式。
public class Singleton {
private volatile static Singleton instance;
private Singleton (){
}
public static Singleton getInstance() {
if (instance== null) {
synchronized (Singleton.class) {
if (instance== null) {
instance= new Singleton();
}
}
}
return singleton;
}
}
这种写法在getSingleton方法中对singleton进行了两次判空,第一次是为了不必要的同步,第二次是在singleton等于null的情况下才创建实例。在这里用到了volatile关键字,不了解volatile关键字的可以查看Java多线程(三)volatile域这篇文章,在这篇文章我也提到了双重检查模式是正确使用volatile关键字的场景之一。
在这里使用volatile会或多或少的影响性能,但考虑到程序的正确性,牺牲这点性能还是值得的。 DCL优点是资源利用率高,第一次执行getInstance时单例对象才被实例化,效率高。缺点是第一次加载时反应稍慢一些,在高并发环境下也有一定的缺陷,虽然发生的概率很小。DCL虽然在一定程度解决了资源的消耗和多余的同步,线程安全等问题,但是他还是在某些情况会出现失效的问题,也就是DCL失效,在《java并发编程实践》一书建议用静态内部类单例模式来替代DCL。
public class Singleton {
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
第一次加载Singleton类时并不会初始化sInstance,只有第一次调用getInstance方法时虚拟机加载SingletonHolder 并初始化sInstance ,这样不仅能确保线程安全也能保证Singleton类的唯一性,所以推荐使用静态内部类单例模式。
public enum Singleton {
INSTANCE;
public void doSomeThing() {
}
}
默认枚举实例的创建是线程安全的,并且在任何情况下都是单例,上述讲的几种单例模式实现中,有一种情况下他们会重新创建对象,那就是反序列化,将一个单例实例对象写到磁盘再读回来,从而获得了一个实例。反序列化操作提供了readResolve方法,这个方法可以让开发人员控制对象的反序列化。在上述的几个方法示例中如果要杜绝单例对象被反序列化是重新生成对象,就必须加入如下方法:
private Object readResolve() throws ObjectStreamException{
return singleton;
}
枚举单例的优点就是简单,但是大部分应用开发很少用枚举,可读性并不是很高,不建议用。
public class SingletonManager {
private static Map
private Singleton() {
}
public static void registerService(String key, Objectinstance) {
if (!objMap.containsKey(key) ) {
objMap.put(key, instance) ;
}
}
public static ObjectgetService(String key) {
return objMap.get(key) ;
}
}
用SingletonManager 将多种的单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。
compareAndSet方法的实现很简单,只有一行代码。这里涉及到两个重要的对象,一个是unsafe,一个是valueOffset。
什么是unsafe呢?Java语言不像C,C++那样可以直接访问底层操作系统,但是JVM为我们提供了一个后门,这个后门就是unsafe。unsafe为我们提供了硬件级别的原子操作。
至于valueOffset对象,是通过unsafe.objectFiledOffset方法得到,所代表的是AtomicInteger对象value成员变量在内存中的偏移量。我们可以简单的把valueOffset理解为value变量的内存地址。
我们上面说过,CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
而unsafe的compareAndSwapInt方法的参数包括了这三个基本元素:valueOffset参数代表了V,expect参数代表了A,update参数代表了B。
正是unsafe的compareAndSwapInt方法保证了Compare和Swap操作之间的原子性操作。
CAS的缺点:
1) CPU开销过大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力。
2) 不能保证代码块的原子性
CAS机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。
3) ABA问题 (银行取款问题)
这是CAS机制最大的问题所在。
利用unsafe提供的原子性操作方法。
2.什么事ABA问题?怎么解决?
当一个值从A变成B,又更新回A,普通CAS机制会误判通过检测。
利用版本号比较可以有效解决ABA问题。
常见的OOM情况有三种:
1)java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。
2)java.lang.OutOfMemoryError: PermGen space/** Metaspace------>java永久代(元数据)溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize/MetaspaceSize=64m -XX:MaxPermSize/MaxMetaspaceSize =256m的形式修改。另外,过多的常量也会导致方法区溢出。
3)java.lang.StackOverflowError ------>不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。
List , Set, Map都是接口,前两个继承至Collection接口,Map为独立接口
Set下有HashSet,LinkedHashSet,TreeSet
List下有ArrayList,Vector,LinkedList
Map下有Hashtable,LinkedHashMap,HashMap,TreeMap
Collection接口下还有个Queue接口,有PriorityQueue类
Connection接口:
— List 有序,可重复
ArrayList
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程不安全,效率高
Vector
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程安全,效率低
LinkedList
优点: 底层数据结构是链表,查询慢,增删快。
缺点: 线程不安全,效率高
—Set 无序,唯一
HashSet
底层数据结构是哈希表。(无序,唯一)
如何来保证元素唯一性?
1.依赖两个方法:hashCode()和equals()
LinkedHashSet
底层数据结构是链表和哈希表。(FIFO插入有序,唯一)
1.由链表保证元素有序
2.由哈希表保证元素唯一
TreeSet
底层数据结构是红黑树。(唯一,有序)
Map接口有三个比较重要的实现类,分别是HashMap、TreeMap和HashTable。
TreeMap是有序的,HashMap和HashTable是无序的。
Hashtable的方法是同步的,HashMap的方法不是同步的。这是两者最主要的区别。
这就意味着:
Hashtable是线程安全的,HashMap不是线程安全的。
HashMap效率较高,Hashtable效率较低。
如果对同步性或与遗留代码的兼容性没有任何要求,建议使用HashMap。 查看Hashtable的源代码就可以发现,除构造函数外,Hashtable的所有 public 方法声明中都有 synchronized关键字,而HashMap的源码中则没有。
Hashtable不允许null值,HashMap允许null值(key和value都允许)
父类不同:Hashtable的父类是Dictionary,HashMap的父类是AbstractMap
mysql索引为什么要用B+Tree数据结构
常见的数据结构
1.二叉树
2.红黑树
3.Hash
4.B Tree
5.B+Tree
a.二叉树
二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成。
不使用原因:会出现极端情况,一个节点只有一度,就是只有一个子节点,那读取树的一层就是一次I/O,那性能也不好
b.红黑树
红黑树即为平衡二叉树的一种
不使用原因:极端情况下,一个节点有2个子节点,那就出现一层只有2个节点的情况,这种性能也不好
c.Hash
不使用原因:Hash是把索引数据进行Hash算法对应一个地址,我们会发现这个好像性能很好啊,直接找到,但是我们想想,它能满足我们日常开发大部分情况吗?比如通过大于或者小于去筛选数据,所以说也不合适,当然mysql还是提供了Hash索引,毕竟有些场合还是用起来也不错
d.B Tree
1.度(Degree)-节点的数据存储个数 2.叶节点具有相同的深度 3.叶节点的指针为空 4.节点中的数据key从左到右递增排列
不使用原因:虽然解决了每一层的节点数的极端情况下,但是我们会发现每个节点存储了索引和数据,那一层能存储的数据太多也不好,毕竟内存能读取的数据大小就是10kb
e.B+Tree
1.B+Tree(B-Tree变种) 2.非叶子节点不存储data,只存储key,可以增大度 3.叶子节点不存储指针 4.顺序访问指针,提高区间访问的性能
使用原因:设计有几个方面
1.非叶子节点不存储data,只存储key,可以增大度
2.叶子节点不存储指针
3.顺序访问指针,提高区间访问的性能
B+Tree索引的性能分析
一般使用磁盘I/O次数评价索引结构的优劣
预读:磁盘一般会顺序向后读取一定长度的数据(页的整数倍)放入内存
局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用
B+Tree节点的大小设为等于一个页,每次新建节点直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,就实现了一个节点的载入只需一次I/O
B+Tree的度d一般会超过100,因此h非常小(一般为3到5之间)
不同的存储引擎有不同的索引实现
1.MyISAM索引实现(非聚集) 2.InnoDB索引实现(聚集)
a.MyISAM索引实现(非聚集)
—>MyISAM索引文件和数据文件是分离的
b.InnoDB索引实现(聚集)
1.数据文件本身就是索引文件 2.表数据文件本身就是按B+Tree组织的一个索引结构文件 3.聚集索引-叶节点包含了完整的数据记录 4.为什么InnoDB表必须有主键,并且推荐使用整型的自增主键? 5.为什么非主键索引结构叶子节点存储的是主键值?(一致性和节省存储空间)
(这个问题也很高频,还有和springmvc比不同)
Spring框架为开发Java应用程序提供了全面的基础架构支持。它包含一些很好的功能,如依赖注入和开箱即用的模块,如: Spring JDBC 、Spring MVC 、Spring Security、 Spring AOP 、Spring ORM 、Spring Test。
Spring Boot基本上是
Spring框架的扩展,它消除了设置
Spring应用程序所需的
XML配置`,为更快,更高效的开发生态系统铺平了道路。
以下是Spring Boot中的一些特点:
1:创建独立的spring应用。
2:嵌入Tomcat, Jetty Undertow 而且不需要部署他们。
3:提供的“starters” poms来简化Maven配置
4:尽可能自动配置spring应用。
5:提供生产指标,健壮检查和外部化配置
6:绝对没有代码生成和XML配置要求
部署环境中Spring Boot
对比Spring
的一些优点包括:
线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
2.1 在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程。
2.1 一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
多线程运行时间,系统不断的启动和关闭新线程,成本非常高,会过渡消耗系统资源,以及过渡切换线程的危险,从而可能导致系统资源的崩溃。这时,线程池就是最好的选择了。
二. 四种常见的线程池详解
ExecutorService是Java提供的用于管理线程池的类。该类的两个作用:控制线程数量和重用线程
2.1 Executors.newCacheThreadPool():可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务
线程池为无限大,当执行当前任务时上一个任务已经完成,会复用执行上一个任务的线程,而不用每次新建线程
2.2 Executors.newFixedThreadPool(int n):创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程。
因为线程池大小为3,每个任务输出打印结果后sleep 2秒,所以每两秒打印3个结果。
定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()
2.3 Executors.newScheduledThreadPool(int n):创建一个定长线程池,支持定时及周期性任务执行
2.4 Executors.newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
1、newCachedThreadPool:用来创建一个可以无限扩大的线程池,适用于负载较轻的场景,执行短期异步任务。(可以使得任务快速得到执行,因为任务时间执行短,可以很快结束,也不会造成cpu过度切换)
2、newFixedThreadPool:创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于负载较重的场景,对当前线程数量进行限制。(保证线程数可控,不会造成线程过多,导致系统负载更为严重)
3、newSingleThreadExecutor:创建一个单线程的线程池,适用于需要保证顺序执行各个任务。
4、newScheduledThreadPool:适用于执行延时或者周期性任务。
三. 缓冲队列BlockingQueue和自定义线程池ThreadPoolExecutor
BlockingQueue是双缓冲队列。BlockingQueue内部使用两条队列,允许两个线程同时向队列一个存储,一个取出操作。在保证并发安全的同时,提高了队列的存取效率。
自定义线程池(ThreadPoolExecutor和BlockingQueue连用):
自定义线程池,可以用ThreadPoolExecutor类创建,它有多个构造方法来创建线程池。
常见的构造函数:ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue)
由于synchronize是由JVM实现的,因此当加锁代码出现异常时,对象锁可以由JVM释放,包含以下三种情况:
1、 占有锁的线程执行完了代码块,然后释放对锁的占有;
2、 占有锁的线程发生了异常,此时JVM会让线程自动释放锁;
3、 占有锁的线程调用了wait()方法,从而进入了WAITING状态需要释放锁。
由于Lock是由JDK实现的,所以不像synchronize锁的获取和释放都是由JVM控制的,Lock的获取和释放都需要手动进行,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
区别如下:
方法一:基于XML的bean定义(需要提供setter方法)
方法二:基于注解的bean定义(不需要提供setter方法)
Spring为此提供了四个注解,这些注解的作用与上面的XML定义bean效果一致,在于将组件交给Spring容器管理。组件的名称默认是类名(首字母变小写),也可以自己修改:
@Component:当对组件的层次难以定位的时候使用这个注解
@Controller:表示控制层的组件
@Service:表示业务逻辑层的组件
@Repository:表示数据访问层的组件
使用这些注解的时候还有一个地方需要注意,就是需要在applicationContext.xml中声明contex:component-scan...一项,指明Spring容器扫描组件的包目录。
方法三:基于Java类的bean定义(需要提供setter方法)
Spring的自动注入
Spring提供了五种自动装配的类型
no:顾名思义, 显式指明不使用Spring的自动装配功能
byName:根据属性和组件的名称匹配关系来实现bean的自动装配
byType:根据属性和组件的类型匹配关系来实现bean的自动装配,有多个适合类型的对象时装配失败
constructor:与byType类似是根据类型进行自动装配,但是要求待装配的bean有相应的构造函数
autodetect:利用Spring的自省机制判断使用byType或是constructor装配
基于XML的自动装配
基于注解的自动装配
其实上面已经应用过了,这里再提一下@Resource和@Autowired的区别。@Resource默认是使用byName进行装配,@Autowired默认使用byType进行装配。