【2022面试大总结】历时一个多月的Java面试大总结_本人亲历纯干货(附答案)
因为某些原因从上家公司离职,于是有机会跟着面试官疯狂学习;在面试中发现自己的技术也有很大的问题,就算是一个查漏补缺的机会吧!就这次的面试经历我总结整理了一份我面试被问到的问题以及我认为重要的知识点,分享出来做个记录!
文章目录
- 【2022面试大总结】历时一个多月的Java面试大总结_本人亲历纯干货(附答案)
- 1、面向对象的三个特征
- 2、谈谈你对多态的理解
- 3、jdk和jre的区别
- 4、什么是Java程序的主类?应用程序和小程序的主类有何不同?
- 5、Java有哪些数据类型
- 6、为什么Java里有基本数据类型和引用数据类型?
- 7、Java的包装类型有那些
- 8、自动装箱与拆箱
- 9、String中常用的方法
- 10、String和StringBuffer、StringBuilder的区别是什么?
- 11、== 和equals的区别?
- 12、如何反转字符串
- 13、StringBuffer常用方法
- 14、StringBuilder常用方法
- 15、String与StringBuilder和StringBuffer的相互转化
- 16、方法重写和重载有啥区别
- 17、抽象类和接⼝及普通类的区别
- 18、四种访问控制符是啥?他们的区别?
- 19、是否可以继承String类?
- 20、final和finally以及finalize区别?
- 21、Int和Integer的区别?
- 22、Array 和 ArrayList 有何区别?
- 23、Mybatis中${}与#{}号的区别
- 24、SpringBoot配置加载顺序
- 25、SpringBoot自动装配原理是什么?
- 26、SpringBoot运行项目的几种方式?
- 26、开启SpringBoot特性的几种方式?
- 27、Spring中常用的注解
- 28、@Autowired和@Resource有啥区别
- 29、final 在 java 中有什么作用?
- 30、xml与html的区别联系
- 31、SpringBoot中常用的注解
- 32、什么是对象的序列化,什么是对象的反序列化?
- 33、break ,continue ,return的区别及作用
- 34、什么是反射机制?
- 35、反射机制优缺点
- 36、Java获取反射的三种方法
- 37、Redis中的数据类型有哪些?
- 38、SQL语句的执行顺序
- 39、SQL 常用的聚合函数
- 40、MySQL中咋么写分页(limit的用法)
- 41、查询过慢你会咋样优化SQL语句
- 42、Mysql中的存储引擎有哪些?
- 43、MySQL中InnoDB 支持的事务隔离级别以及区别?
- 44、char和varchar区别?
- 45、事务的四大特征是什么?
- 46、你对Mysql的索引有了解么?
- 47、主键、外键和索引的区别?
- 48、唯一索引比普通索引快吗为什么?
- 49、解释MySQL外连接、内连接的区别
- 50、主键递增问题
- 51、UNION和UNION ALL的区别
- 52、MySQL一张表最多能存多少数据?
- 53、MySQL中有哪几种锁?
- 54、SQL锁的优化策略
- 55、如何通俗地理解三个范式?
- 56、MySQL根据生日计算年龄
- 57、Mybatis是如何进行分页的?分页插件的原理是什么?
- 58、Mybatis 动态sql有什么用?执行原理?有哪些动态sql
- 59、什么是MyBatis?
- 60、MyBatis优缺点
- 61、MyBatis编程步骤是什么样的?
- 62、请说说MyBatis的工作原理
- 63、谈谈你对Mybatis 缓存的理解
- 64、MyBatis咋么省略路径(不要全路径)
- 65、咋么保证数据库的数据跟redis同步
- 66、char型变量中能不能存贮一个中文汉字?为什么?
- 67、String s = new String(“kak”);创建了几个字符串对象?
- 68、接口是否可继承接口?抽象类是否可实现接口?抽象类是否可继承具体类?
- 69、什么时候用断言(assert)?
- 70、集合和数组的区别
- 71、谈谈你对集合框架的理解?
- 72、ArrayList 和 LinkedList 的区别是什么?
- 73、ArrayList和Vector 的区别是什么?
- 74、说一下ArrayList的优缺点
- 75、List 和 Map、Set 的区别
- 76、HashMap和HashTable有什么区别?
- 77、哪些集合类是线程安全的
- 78、Iterator 怎么使用?有什么特点?
- 79、队列和栈是什么?有什么区别?
- 80、Map集合的遍历(四种)
- 81、TCP和UDP的区别
- 82、TCP的三次握手与四次挥手
- 83、SpringBoot与SSM框架相比优点在哪里
- 84、SpringBoot的配置文件有哪几种格式?它们有什么区别?
- 85、Redis是单线程还是多线程
- 86、Redis有哪些优缺点?
- 87、Redis中一个字符串类型的值能存储最大容量是多少?
- 88、Redis的持久化机制是什么?各自的优缺点?
- 89、Redis的过期键的删除策略
- 90、如何选择合适的持久化方式
- 91、Redis主要消耗什么物理资源?
- 92、Redis的内存用完了会发生什么?
- 93、Redis如何做内存优化?
- 94、什么是Redis穿透?
- 95、什么是Redis雪崩?
- 96、Redis最适合的场景?
- 97、Redis内存淘汰策略
- 98、SpringBoot和SpringCloud区别
- 99、你在开发中咋么限制由连续连点击造成重复请求?
- 100、什么情况下会分库分表,目的是啥?
- 101、说一下MySQL的分库分表
- 102、SpringBoot中咋么配置Mybatis
- 103、&和&&有啥区别
- 104、创建线程的方式
- 105、创建线程的三种方式的对比?
- 106、并行和并发有什么区别?
- 107、什么是多线程?多线程有优缺点?
- 108、Java中线程的基本状态
- 109、阻塞状态有哪几种
- 110、可以直接调用Thread类的run ()方法么?
- 111、启动线程方法start()和 run()有什么区别?
- 112、线程中的wait()和 sleep()方法有什么区别?
- 113、线程中常用的方法
- 114、notify()和notifyAll()有什么区别?
- 115、为什么我们调用start() 方法时会执行run() 方法,为什么我们不能直接调用run() 方法?
- 116、interrupt和stop区别
- 117、为什么要用 join()方法?
- 118、简述一下你了解的设计模式
- 119、谈谈你对Spring中的IOC和DI的理解
- 120、什么是面向切面编程?
- 121、AOP有哪些实现方式
- 122、AOP的通知类型
- 123、Spring MVC原理
- 124、SpringMVC常用的注解
- 125、RestController和Controller有啥区别
- 126、http请求分为哪几个部分
- 127、面向对象编程中的六大原则
- 128、雪花算法是咋么实现的?
- 129、简单说一下冒泡排序
- 130、mysql里面的having的用法
- 131、什么是线程死锁
- 132、死锁产生的必要条件是啥?
- 133、如何避免线程死锁
- 134、进程和线程之间的区别
- 135、为啥线程不安全的效率高
- 136、谈谈你对线程池的理解
- 137、线程池四种创建方式?
- 138、线程池都有哪些状态?
- 139、线程池的执行原理?
- 140、谈谈你对synchronized 的理解,你在项目中用到过么?
- 141、Linux的基础命令
- 142、Windows和Linux的区别
- 142、开发中常见的异常类型
- 143、咋么理解Java中的实例化
- 144、SpringBoot咋么集成Mybatis-Plus?
- 145、MyBatis返回的结果咋么跟实体对应起来?
- 146、Mybatis如何实现模糊搜索?
- 147、MySQL去除重复的数据?
- 148、你有哪些良好的编码习惯
- 149、构造器可以被重写吗?
- 150、Jdk1.8新特性有哪些
- 151、Integer可以用equals比较吗?
- 152、说一下面向对象跟面向过程
- 153、接口可以继承接口么?
- 154、class里面有哪些方法?
- 155、私有方法的可以反射么?
- 156、Mybatis-Plus咋么做分页?
- 166、LambdaQueryWrapper和QueryWrapper的区别
- 167、简述一下Redis的事务
- 168、nginx的配置文件有哪些元素
- 169、去除一个String里面的逗号
- 170、MyBatis与Hibernate 有什么不同?
- 171、二分查找法
- 172、说一下你对单例模式的理解
- 173、JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?
- 174、MyBatis中配置mapper的方法
- 175、Maven怎么用,是什么?
- 176、你在开发中常用到的Maven的命令
- 177、说一下socket编程模型
- 178、tcp/ip网络模型(OSI七层)
- 179、struts2与springmvc的区别?
- 180、如何解决session共享?
- 181、GC原理
- 182、你知道的不可变类型有哪些?String是?
- 183、终止线程的三种方式?
- 184、jsp中的九个对象及其对应类型
- 185、get与post区别?
- 186、sql查询中in和exist的区别
- 187、数组和链表的区别 ?
- 188、IO流的分类
- 189、同步与异步,阻塞与非阻塞的区别
- 190、说一下IO、BIO、NIO和AIO?
- 191、Spring中用到的设计模式
- 192、feign是咋么进行服务间调用
- 193、什么是Nginx?为什么要用Nginx?
- 194、Nginx的优缺点?
- 195、Git中常用的命令
- 196、什么是RabbitMQ?为什么要用它?
- 197、Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点?
- 198、elasticsearch的倒排索引是什么?
- 199、Ikanalyzer的分词模式
- 200、VUE的生命周期
- 201、VUE咋么传值?
- 202、v-if和v-show有啥区别?
- 203、接口请求一般放在哪个生命周期中?
- 204、nacos心跳机制?
- 205、Java 容器都有哪些?
- 206、说一下Nginx的正向代理和反向代理
- 207、什么是 Swagger?你用 Spring Boot 实现了它吗?
- 208、遍历 D 盘下面所有的文件
- 209、异常的体系结构
- 210、BS 与 CS 的联系与区别
- 211、项目的生命周期
- 212、什么是 UML?UML 中有哪些常用的图?
- 写到最后
1、面向对象的三个特征
继承:继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。
- 子类拥有父类非private的属性和方法
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展
- 子类可以用自己的方式实现父类的方法
封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
多态:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。
Java有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法);也就是方法重写(子类继承父类并重写父类中已有的或抽象的方法)和对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
- 如果说是4大特性,那么我们就把抽象加上去;
2、谈谈你对多态的理解
同一个对象,在不同时刻体现出来的不同状态
多态的关键是每个子类都要重写方法,实现了继承了同样的方法名称但是又有每个的特点,就像龙生九子,每个不一样,有两个好处,一个是类关系清晰,另一个是方法调用方便,只要有传入实参就行。
多态是Java面向对象三个特征中的一个也是做主要的一个,所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。
多态的实现
Java实现多态有三个必要条件:继承、重写、向上转型。
- 继承:在多态中必须存在有继承关系的子类和父类(实现关系接口)。
- 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
- 向上转型:在多态中需要将父类或者父接口引用指向子类Fu f= new Zi(),只有这样该引用才能够具备技能调用父类的方法和子类的方法。
只有满足了这三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。
向上转型
父类对象通过子类对象去实例化,实际上就是对象的向上转型。向上转型是不需要进行强制类型转换的,但是向上转型会丢失精度。
向下转型
所谓向下转型,也就是说父类的对象可以转换为子类对象,但是需要注意的是,这时则必须要进行强制的类型转换。
多态的好处
- 可替换性:多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。
- 可扩充性:多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
- 接口性:多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。
- 灵活性:它在应用中体现了灵活多样的操作,提高了使用效率。
- 简化性:多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。
3、jdk和jre的区别
由下图可知JDK包含JRE在其中也有JVM,我认为在回答这个问题也可以说说JVM;
JDK(Java Development Kit)Java开发工具包也是核心,只有安装了JDK配好环境变量才可以运行成功;主要包含三部分:
- JVM虚拟机就是Java运行时环境
- Java的基础类库,这个类库的数量非常大
- Java的开发工具
JRE(Java Runtime Environment)Java运行时环境,他会运行在JVM上;在JDK中有一个名为jre的目录,里面包含两个文件夹bin和lib,lib就是JVM工作所需要的类库的.class文件已经是Jar包了;
JVM(Java Virtual Machine)Java虚拟机转换环境,正是因为有JVM所以Java的跨平台性比较好(只要在Java虚拟机上运行的目标代码也就是字节码那么只需编译一次就可以可以在多种平台上不加修改地运行);我们编译的.Class文件就是在JVM上运行的文件,它在解释class的时候需要调用解释所需要的类库lib(jre包含lib类库);
4、什么是Java程序的主类?应用程序和小程序的主类有何不同?
一个程序中可以有多个类,但只能有一个类是主类。在Java应用程序中,这个主类是指包含main()方法的类。而在Java小程序中,这个主类是一个继承自系统类JApplet或Applet的子类。应用程序
的主类不一定要求是public类,但小程序的主类要求必须是public类。主类是Java程序执行的入口点。
5、Java有哪些数据类型
Java语言是强类型语言,对于每一种数据都定义了明确的具体的数据类型,在内存中分配了不同
大小的内存空间。
Java中的数据类型分为两种,基本数据类型和引用数据类型;
基本数据类型
引用数据类型
- 类(class): 类是一个模板,它描述一类对象的行为和状态
- 接口(interface)
- 数组
- 枚举
- 注解
- 注:String也是引用类型,他只是代表一个类是不可变的(如果需要对字符串做改变可以选择StringBuffer类或StringBuilder类),对String类的任何改变都是返回一个新的String类对象。因为有常量池所以不用new关键字来创建对象;
6、为什么Java里有基本数据类型和引用数据类型?
存储方式:引用类型在堆里而基本类型在栈里。栈空间小且连续,存取速度比较快;在堆中则需要new,对基本数据类型来说空间浪费率太高;
传值方式:基本类型是在方法中定义的非全局基本数据类型变量,调用方法时作为参数是按数值传递的;引用数据类型变量,调用方法时作为参数是按引用传递的;
7、Java的包装类型有那些
Java提供了8种包装类;基本类型对应的包装类型:
int ----> Integer(多)
char ----> Character(多)
byte ----> Byte
short----> Short
long ----> Long (多)
float----> Float
double—> Double
boolean —>Boolean
1)每个基本类型都存在一个默认的包装类类型(引用类型);
2)Object可统一所有数据,包装类的默认值是null;
3)将基本数据类型封装成对象的好处在于可以在对象中定义更多的功能方法操作该数据;
4)常用于基本数据类型与字符串之间的转换;
5)包装类实际上就是持有了一个基本类型的属性,作为数据的存储空间(Byte中有一个byte属性),还提供了常用的转型方法,以及常量,即可以存储值,又具备了一系列的转型方法和常用常量,比直接使用基本类型的功能更强大;
8、自动装箱与拆箱
自动装箱:将基本类型用它们对应的引用类型包装起来,调用valueOf(int i)(int---->Integer)
自动拆箱:将包装类型转换为基本数据类型,调用intValue()(Integer—>int)
9、String中常用的方法
我这里根据功能来列举如果问到肯定多多益善么!
判断功能
// (常用)比较两个字符串内容是否相同,区分大小写;
public boolean equals (Object anObject);
//比较两个字符串的内容是否相同,不区分大小写;
public boolean equalsIgnoreCase (String anotherString);
//判断对象是否为空;
public boolean isEmpty();
//判断字符串是否包含在指定字符串中;
public boolean contains(String s);
//判断字符串是否以指定的字符串开头
public boolean startsWith(String prefix);
//判断字符串是否以指定的字符串结束
public boolean endsWith(String suffix);
获取功能
//返回此字符串的长度(实际长度);
public int length ();
//将指定的字符串连接到该字符串的末尾(拼接的功能);
public String concat (String str);
//返回指定索引处的char值;
public char charAt (int index);
//返回指定子字符串第一次出现在该字符串内的索引;
public int indexOf (String str);
//返回一个子字符串,从beginIndex开始截取字符串到字符串结尾;
public String substring (int beginIndex);
//返回一个子字符串,从beginIndex到endIndex截取字符串。含beginIndex,不含endIndex;
public String substring (int beginIndex, int endIndex);
转换功能
//转换小写
String toLowerCase();
//转换大写
String toUpperCase();
//将字符串转换成字符数组
public char[] toCharArray();
//将int类型转换成字符串
public static String valueOf(int i);
//使用平台默认编码集(GBK)将字符串转换成字节数组
public byte[] getBytes();
分割功能
//将字符串按照regex拆分为字符串数组
public String[] split(String regex);
/**
* 案例
*/
String str = "hji-hi-sd-bih";
String[] strArray = str.split("-");//遇到“——”进行分割
for(int k = 0;k < strArray.length;k++){
System.out.println(strArray[k]);
}
替换功能
//将新的字符串替换掉以前的旧的子串;
public String replace(String oldStr,String newStr):
//去除两端空格
public String trim()
10、String和StringBuffer、StringBuilder的区别是什么?
String:字符串是常量,作为方法形参传递,不会改变实际参数,一旦被赋值不能被更改;每次对String的操作都会生成新的String对象,这样效率低下并且会浪费有限的内存空间;
- 适用于少量的字符串操作的情况
StringBuffer:线程安全的可变字符序列,能够被多次的修改并且不产生新的未使用对象,执行效率低(字符串缓冲区);
- 适用多线程下在字符缓冲区进行大量操作的情况
StringBuilder:线程不安全的类,能够被多次的修改并且不产生新的未使用对象,单线程程序中使用,不同步,执行效率高;
- 适用于单线程下在字符缓冲区进行大量操作的情况
11、== 和equals的区别?
- 对于基本类型,==比较的是值;
- 对于引用类型,==比较的是两个对象地址值是否相同;
- equals不能用于基本类型的比较;
- 如果没有重写equals,equals默认比较是地址值就相当于==;
- 如果重写了equals方法,equals比较的是字符串的内容是否相同;
12、如何反转字符串
将对象封装到StringBuilder中,调用reverse方法反转。
public static void main(String[] args) {
//键盘录入字符串,将字符串进行反转------>结果String
Scanner sc = new Scanner(System.in) ;
//提示并录入数据
System.out.println("输入一个字符串:");
String line = sc.next() ;
StringBuilder sb = new StringBuilder(line) ;
String result = sb.reverse().toString() ;
System.out.println("result:"+result);
}
13、StringBuffer常用方法
可变、线程安全、效率低
添加功能
//将这些数据添加到指定的缓冲区末尾,返回值是StringBuffer本身
StringBuffer append(int/boolan/long…String/StringBuffer…);
//在指定的位置处插入指定str字符串
public StringBuffer insert(int offset,String str);
public static void main(String[] args) {
//创建一个缓冲区对象
StringBuffer sb = new StringBuffer() ; //底层char [] char = new char[16] ;
System.out.println("sb:"+sb); //空的
sb.append("hello");
sb.append("A");
sb.append(true);
sb.append(12);
System.out.println("sb:"+sb);//helloAtrue12
StringBuffer sb1 = new StringBuffer();
sb1.append("hello").append("Java").append(100);
System.out.println("sb1:"+sb1);//helloJava100
//insert指定字符插入
sb.insert(3, "kaka") ;
System.out.println("sb:"+sb);//helkakaloAtrue12
}
删除功能
//删除指定位置处的字符,返回字符串缓冲区本身(重点)
StringBuffer deleteCharAt(int index)
//删除从指定位置到指定位置结束(end-1),返回字符串缓冲区本身
public StringBuffer delete(int start,int end)
public static void main(String[] args) {
//创建缓冲区对象
StringBuffer sb = new StringBuffer() ;
sb.append("hello").append("Java").append("100") ;
System.out.println(sb);//helloJava100
System.out.println("sb:"+sb.deleteCharAt(5));// helloava100
//第二个索引开始删除5-1个字符
System.out.println("sb:"+sb.delete(2, 5));//heava100
}
反转功能
//将缓存区中的字符序列—反转
public StringBuffer reverse()
//将字符串缓冲区对象—转成String
public String toString()
public static void main(String[] args) {
//键盘录入字符串,将字符串进行反转------>结果String
Scanner sc = new Scanner(System.in) ;
//提示并录入数据
System.out.println("输入一个字符串:");
String line = sc.next() ;
StringBuffer sb = new StringBuffer(line) ;
String result = sb.reverse().toString() ;
System.out.println("result:"+result);
}
替换功能
//使用指定的字符串替换,从指定位置开始,到指定位置结束(end-1);
public StringBuffer replace(int start,int end,String str)
public static void main(String[] args) {
//创建一个缓冲区对象
StringBuffer sb = new StringBuffer() ;
//追加内容
sb.append("hello").append("java").append("EE") ;
System.out.println("sb:"+sb);//hellojavaEE
//从第5个位置开始到7个位置结束进行替换
System.out.println("replace:" + sb.replace(5, 7, "kaka"));//hellokakavaEE
}
截取功能
//start开始直到结束
public String substring(int start)
//start开始,end-1处结束进行截取
public String substring(int start,int end)
14、StringBuilder常用方法
可变、线程不安全、效率高
//创建Stringbuilder对象
StringBuilder strB = new StringBuilder();
//append(String str)/append(Char c):字符串连接
System.out.println(strB.append("ch").append("111").append('c'));//ch111c
//toString():返回String类的对象
System.out.println(strB.toString());//ch111c
//setCharAt(int i, char c):将第 i 个代码单元设置为 c(可以理解为替换)
strB.setCharAt(2, 'd');
System.out.println(strB);//chd11c
//insert(int offset, String str)/insert(int offset, Char c):在指定位置之前插入字符(串)
System.out.println(strB.insert(2, "LS"));//chLSd11c
System.out.println(strB.insert(2, 'L'));//chLLSd11c
//delete(int startIndex,int endIndex):删除起始位置(含)到结尾位置(不含)之间的字符串
System.out.println(strB.delete(2, 4));//chSd11c
15、String与StringBuilder和StringBuffer的相互转化
String---->StringBuffer
public static void main(String[] args) {
String s = "hello" ;
//方式1:通过StringBuffer的构造方法
StringBuffer sb = new StringBuffer(s) ;
System.out.println(sb);//hello,类型还是StringBuffer
//方式2:空参构造+append(...)
StringBuffer sb2 = new StringBuffer() ;
sb2.append(s) ;
System.out.println(sb2);//hello
}
StringBuffer----->String
public static void main(String[] args) {
StringBuffer buffer = new StringBuffer("world") ;
//public String toString() 利用toString
String str2 = buffer.toString() ;
System.out.println(str2);
}
String----->StringBuilder
public static void main(String[] args){
String s = "hello";
StringBuilder sb = new StringBuilder(s);
System.out.println(sb);
}
StringBuilder----->String
public static void main(String[] args){
StringBuilder sb = new StringBuilder();
sb.append("abc").append("efg");
String s = sb.toString();
System.out.println(s);
}
16、方法重写和重载有啥区别
方法的重载(Overload)和重写(Override)都是实现多态的方式,前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分;
重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写;父类的静态方法能够被子类继承,但是不能被子类重写,即使子类的静态方法与父类中的静态方法完全一样,也是两个不同的方法;
17、抽象类和接⼝及普通类的区别
抽象类和普通类的区别
包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,和普通类一样,同样可以拥有成员变量和普通的成员方法,抽象类和普通类的区别如下:
- 抽象方法的访问修饰符必须为public和protected。抽象方法是用来被继承的,所以不能用private修饰;
- 抽象类不能被实例化。
- 如果一个类继承于抽象类,则子类必须实现父类的抽象方法,如果子类没有实现父类的抽象方法,则子类必须也是一个抽象类。
抽象类和接口区别
- 一个类只能继承一个抽象类(单继承),而一个类可以实现多个接口。
- 抽象类可以有构造方法,接口中不能有构造方法。
- 抽象类中可以有成员变量,接口中没有成员变量。(被final修饰变成了常量)
- 抽象类中可以有普通方法,接口中所有方法都必须是抽象的。(1.8后允许接口定义非抽象方法)
- 抽象类中抽象方法的访问类型可以是public,protected,但接口中抽象方法的访问类型只能是public,并且默认为public abstract(省略则自动默认补全);
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
- 接口使用interface修饰,抽象类使用abstract修饰;
- 都不能不能被实例化;
18、四种访问控制符是啥?他们的区别?
四种访问修饰符分别是:public /protected/default(默认)/private
19、是否可以继承String类?
String类是final类,不可以被继承。
20、final和finally以及finalize区别?
final修饰符:可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、
修饰变量表示该变量是一个常量不能被重新赋值;
finally代码块中:一般作用在try-catch代码块中,在处理异常时通常将一定要执行的代码方法放在finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
注:有些情况不会执行finally
- 只有与finally对应的try语句块得到执行的情况下,finally语句块才会执行。如果在执行try语句块之前已经返回或抛出异常,那么try对应的finally语句并没有执行;
- 我们在try语句块中执行了System.exit (0) 语句,终止了Java虚拟机的运行;
- 如果在try-catch-finally语句中执行return语句,finally语句在该代码中一定会执行,因为finally用法特殊会撤销之前的return语句,继续执行最后的finally块中的代码;
finalize一个方法:属于所有类的父类Object类的一个方法,也就是说每一个对象都有这么个方法;Java中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作;调用super.finalize();
- 这个方法在GC启动该对象被回收的时候被调用。GC可以回收大部分的对象(凡是new出来的对象GC都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。
- 特殊情况下,需要程序员实现finalize,当对象被回收的时候释放一些资源,比如:一个socket链接,在对象初始化时创建,整个生命周期内有效,那么就需要实现finalize,关闭这个链接。
21、Int和Integer的区别?
- Integer是int的包装类,int则是java的⼀种基本数据类型
- Integer变量必须实例化后才能使⽤,⽽int变量不需要
- Integer实际是对象的引⽤,当new⼀个Integer时,实际上是⽣成⼀个指针指向此对象;⽽int则是直接
存储数据值 - Integer的默认值是null,int的默认值是0
22、Array 和 ArrayList 有何区别?
Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。
Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。
Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。
23、Mybatis中${}与#{}号的区别
- ${}是字符串替换,#{}是预编译处理;
- Mybatis在处理#{}时,会将sql中的#{}替换为问号,调用PreparedStatement的set方法来赋值;
- Mybatis在处理
时
,
把
{}时,把
- #{}方式能够很大程度防止sql注入,提高系统的安全性;
- ${}方式一般用于传入数据库对象,例如传入表名,需要拼接的时候使用;
24、SpringBoot配置加载顺序
- properties文件
- YAML文件
- 系统环境变量
- 命令行参数
25、SpringBoot自动装配原理是什么?
主要是SpringBoot的启动类上的核心注解SpringBootApplication注解主配置类,在这个注解里面有好几个注解:
- @SpringBootConfiguration继承自@Configuration,二者功能也一致,标注当前类是配置类。
- @ComponentScan用于类或接口上主要是指定扫描路径,如果不写这个扫描路径的话,默认就是启动类的路径;跟Xml里面的<context:component-scan base-package=“” />配置一样;
- 然后就是@EnableAutoConfiguration注解打开自动配置功能。
有了这个EnableAutoConfiguration的话就会:
- 找到配置文件META_INF/Spring.factories中的所有自动配置类,并加载可能用到的自动配置类,这些自动配置类都是以AutoConfiguration结尾来命名的,它实际上就是一个JavaConfig形式的Spring容器配置类;
- 会将exclude和excludeName属性携带的类排除
- 过滤,将满足条件(@Conditional)的自动配置类返回
- 它通过@Bean导入到Spring容器中,以Properties结尾命名的类是和配置文件进行绑定的。它能通过这些以Properties结尾命名的类中取得在全局配置文件中配置的属性,我们可以通过修改配置文件对应的属性来修改自动配置的默认值,来完成自定义配置
26、SpringBoot运行项目的几种方式?
-
打包用命令或者放到容器中运行
打成jar包,使用java -jar xxx.jar运行
打成war包,放到tomcat里面运行
-
直接用maven插件运行 maven spring-boot:run
-
直接执行main方法运行
26、开启SpringBoot特性的几种方式?
- 继承spring-boot-starter-parent项目
- 导入spring-boot-dependencies项目依赖
27、Spring中常用的注解
- @Controller:用于标注控制层组件,标记到类上就是一个SpringMVC的controller对象;分发处理器将会扫描使用了该注解的类的方法。被Controller标记的类就是一个控制器,这个类中的方法,就是相应的动作;
- @ResponseBody:作⽤于⽅法上,可以将整个返回结果以某种格式返回,如json或xml格式;
- @RestController:相当于@Controller+@ResponseBody;
- @Component:在类定义之前添加@Component注解,他会被spring容器识别,并转为bean(不好归类时,使用这个注解进行标注);
- @Repository:对Dao实现类进⾏注解 (特殊的@Component);
- @Service:⽤于对业务逻辑层进⾏注解 (特殊的@Component);
- @RequestMapping:用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径;
- @Autowired:对类成员变量、方法及构造函数进行标注,完成自动装配的工作,可以消除set、get方法;
- @Qualifier:该注解和@Autowired 搭配使用,用于消除特定 bean 自动装配的歧义
- @RequestParam:⽤于获取传⼊参数的值,类似于request.getParameter(“name”)
- @PathViriable:⽤于定义路径参数值,取出url模板中的变量作为参数;
- @RequestHeader:可以把Request请求header部分的值绑定到方法参数上;
- @CookieValue:⽤于获取请求的Cookie值;
- @SessionAttributes:将值放到session作用域中,卸载class上面;
28、@Autowired和@Resource有啥区别
@Resource和@Autowired都可以作为注入属性的修饰,在接口仅有单一实现类时,两个注解的修饰效果相同,可以互相替换,不影响使用。但是他们也有不同的地方:
- @Resource是Java自己的注解,@Resource有两个属性是比较重要的,分是name和type;Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
- @Autowired是spring的注解,是spring2.5版本引入的,Autowired只根据type进行注入,不会去匹配name。如果涉及到type无法辨别注入对象时,那需要依赖@Qualifier或@Primary注解一起来修饰。
- @Autowired默认按byType自动装配,而@Resource默认byName自动装配。
- @Autowired只包含一个参数:required,表示是否开启自动准入,默认是true。而@Resource包含七个参数,其中最重要的两个参数是:name 和 type。
- @Autowired如果要使用byName,需要使用@Qualifier一起配合。而@Resource如果指定了name,则用byName自动装配,如果指定了type,则用byType自动装配。
- @Autowired能够用在:构造器、方法、参数、成员变量和注解上,而@Resource能用在:类、成员变量和方法上。
- @Autowired是spring定义的注解,而@Resource是JSR-250定义的注解。
@Autowired的装配顺序
@Resource的装配顺序
29、final 在 java 中有什么作用?
- final修饰的成员变量,必须在声明的同时赋值,一旦创建不可修改(常量);
- final修饰的方法,不能被子类重写;
- final类中的方法默认是final的,该类不能被继承;
- private类型的方法默认是final的;
30、xml与html的区别联系
联系:二者均可以使用浏览器解析,都在内存中形成dom模型;
区别:
场景: xml描述数据;html展示数据;
标签来源: xml自定义标记;html预定义标记;
书写规范:
- xml:只能有一个root(根)节点
- xml:区分大小写,html不区分
- xml:中标记的属性值必须使用引号,而html可以不用
- xml:标记必须关闭;而html可以不关闭
- xml:不可以交叉嵌套
31、SpringBoot中常用的注解
- @SpringBootApplication:启动注解
- @SpringBootConfiguration:继承@Configuration注解,主要用于加载配置文件
- @EnableAutoConfiguration :开启自动配置功能
- @ComponentScan :主要用于组件扫描和自动装配
- @Transactional:事务注解
- @ControllerAdvice:定义全局异常处理类
- @ExceptionHandler:声明异常处理方法
32、什么是对象的序列化,什么是对象的反序列化?
Serialization(序列化)是一种将对象以一连串的字节描述的过程;反序列化deserialization是一种将这些字节重建成一个对象的过程;
序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,就使得数据能够被轻松地存储和传输。
Java序列化是指把Java对象转换为字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程;
1、序列化是干什么的?
简单说就是为了保存在内存中的各种对象的状态,并且可以把保存的对象状态再读出来。虽然你可以用自己的各种方法来保存Object states, 但是Java给你提供一种应该比你自己好的保存对象状态的机制、那就是序列化。
2、什么情况下需要序列化?
- 当你想把的内存中的对象保存到一个文件或者数据库中时候;
- 当你想用套接字在网络上传送对象的时候;
- 当你想通过RMI传输对象的时候(RMI->Remote Method Invocation 远程方法调用)
33、break ,continue ,return的区别及作用
break:跳出总上一层循环,不再执行循环(结束当前的循环体);
continue:跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件);
return:程序返回,不再执行下面的代码(结束当前的方法 直接返回);
34、什么是反射机制?
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
静态编译:在编译时确定类型,绑定对象
动态编译:运行时确定类型,绑定对象
35、反射机制优缺点
优点: 运行期类型的判断,动态加载类,提高代码灵活度;
缺点: 性能瓶颈:反射相当于一系列解释操作,通知JVM要做的事情,性能比直接的Java代码要慢很多;
36、Java获取反射的三种方法
- 通过new对象实现反射机制;
- 通过路径实现反射机制;
- 通过类名实现反射机制;
public class Student {
private int id;
String name;
protected boolean sex;
public float score;
}
public class Get {
//获取反射机制三种方式
public static void main(String[] args) throws ClassNotFoundException {
//方式一(通过建立对象)
Student stu = new Student();
Class classobj1 = stu.getClass();
System.out.println(classobj1.getName());
//方式二(所在通过路径-相对路径)
Class classobj2 = Class.forName("fanshe.Student");
System.out.println(classobj2.getName());
//方式三(通过类名)
Class classobj3 = Student.class;
System.out.println(classobj3.getName());
}
}
37、Redis中的数据类型有哪些?
Redis支持五种数据类型:String(字符串),hash(哈希),list(集合),set(无序集合)及 zset(sorted set有序集合;
- String (Key-Value):String是最常用的一种数据类型,普通的key/value存储都可以归为此类;
- Hash(Key-Value):Hash是一个string类型的field和value的映射表,这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段;
- List:是一个链表结构,主要功能是push, pop, 获取一个范围的所有的值等。操作中key理解为链表名字;
- Set:它是string类型的无序集合。set是通过hash table实现的,可以进行添加、删除和查找。对集合我们可以取集,交集,差集,因为set堆放的是一堆不重复值的集合,所以可以做全局去重的功能;
- ZSet:Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。
38、SQL语句的执行顺序
查询中用到的关键词主要包含六个,其中select和from是必须的,其他关键词是可选的;
-- MySQL的编写顺序
SELECT 列名 FROM 表名 WHERE 条件 GROUP BY 分组 HAVING 过滤条件 ORDER BY 排序列 LIMIT 起始行,总条数
这六个关键词的执行顺序与sql语句的书写顺序并不是一样的,而是按照下面的顺序来执行 :
from–where–group by–having–select–order by
from:需要从哪个数据表检索数据
where:过滤表中数据的条件
group by:如何将上面过滤出的数据分组
使用聚合函数进行计算
having:对上面已经分组的数据进行过滤的条件
计算所有表达式
select:查看结果集中的哪个列,或列的计算结果
order by:按照什么样的顺序来查看返回的数据
39、SQL 常用的聚合函数
聚合函数是对一组值进行计算并返回单一的值的函数,它经常与 select 语句中的 group by 子句一同使用。
- a. avg():返回的是指定组中的平均值,空值被忽略;
- b. count():返回的是指定组中的项目个数;
- c. max():返回指定数据中的最大值;
- d. min():返回指定数据中的最小值;
- e. sum():返回指定数据的和,只能用于数字列,空值忽略;
- f. group by():对数据进行分组,对执行完 group by 之后的组进行聚合函数的运算,计算每一组的值;
- 用having去掉不符合条件的组,having子句中的每一个元素必须出现在select列表中(只针对于mysql);
40、MySQL中咋么写分页(limit的用法)
因为我们公司用的分页是pagehelper组件,我也没有去研究它,后来才知道MySQL中的limit并不单单是限制还可以用来实现分页,那么我就分享一下limit的一些用法吧;
limit子句用于限制查询结果返回的数量。
用法:【select * from tableName limit i,n 】
参数:
- tableName : 数据表;
- i : 查询结果的索引值(默认从0开始);
- n : 查询结果返回的数量;
一个参数
如果只给定一个参数n,表示记录数返回最大的记录行数目;等价与等价于LIMIT 0,n;
-- 检索前5个记录行
SELECT * FROM tableName LIMIT 5;
SELECT * FROM tableName LIMIT 0,5;
两个参数
两个参数,第一个参数表示offset, 第二个参数为记录数。
-- 检索记录行6-15
SELECT * FROM tableName LIMIT 5,10;
基本的分页方式
SELECT … FROM … WHERE … ORDER BY … LIMIT …
-- 当t_id等于123时按照id排序去查询51~60的数据
SELECT * FROM tableName WHERE t_id = 123 ORDER BY id LIMIT 50, 10
41、查询过慢你会咋样优化SQL语句
- Where 子句中:where表之间的连接必须写在其他 Where 条件之前,那些可以过滤掉最大数量记录的条件必须写在 Where子句的末尾.HAVING最后;
- 尽量避免写子查询;
- 用 EXISTS 替代 IN、用 NOT EXISTS 替代 NOT IN;
- 避免在索引列上使用计算;
- 避免在索引列上使用 IS NULL 和 IS NOT NULL;
- 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引;
- 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描;
- 应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描;
- 当只要一行数据时使用 limit 1:查询时如果已知会得到一条数据,这种情况下加上limit 1会增加性能。因为 mysql 数据库引擎会在找到一条结果停止搜索,而不是继续查询下一条是否符合标准直到所有记录查询完毕;
42、Mysql中的存储引擎有哪些?
其实数据的存储引擎有很多,但是我认为只要答出来两个就可以了,那就是MyISAM(默认)和InnoDB;
数据库存储引擎是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建、查询、更新和删除数据。不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能,使用不同的存储引擎,还可以获得特定的功能。现在许多不同的数据库管理系统都支持多种不同的数据引擎。
存储引擎主要有: 1. MyISAM、2. InnoDB、3. Memory、4. Archive、5. Federated ;
MyISAM存储引擎
MyISAM是MySQL官方提供默认的存储引擎,其特点是不支持事务、表锁和全文索引,对于一些 OLAP(联机分析处理)系统,操作速度快。适用于一些大量查询的应用,但对于有大量写功能的应用不是很好。甚至你只需要update 一个字段整个表都会被锁起来。而别的进程就算是读操作也不行要等到当前 update 操作完成之后才能继续进行。另外,MyISAM 对于 select count(*)这类操作是超级快的。
主要特点:
- 因为没有提供对数据库事务的支持,也不支持行级锁和外键,所以当 INSERT(插入)或 UPDATE(更新)数据时即写操作需要锁定整个表,效率便会低一些。
- 不支持事务,但是每次查询都是原子的;
- 支持表级锁,即每次操作是对整个表加锁;
- 存储表的总行数;
- 一个 MYISAM 表有三个文件:索引文件、表结构文件、数据文件;
- 采用菲聚集索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致,但是辅索引不用保证唯一性;
InnoDB存储引擎
InnoDB存储引擎支持事务,对于一些小的应用会比MyISAM还慢,但是支持行锁及外键约束,所以在写操作比较多的时候会比较优秀。并且,它支持很多的高级应用,例如:事务。InnoDB底层存储结构为B+树, B树的每个节点对应InnoDB的一个page,page大小是固定的,一般设为16k。其中非叶子节点只有键值,叶子节点包含完成数据。
一个InnoDB引擎存储在一个文件空间(共享表空间,表大小不受操作系统控制,一个表可能分布在多个文件里),也有可能为多个(设置为独立表空,表大小受操作系统文件大小限制,一般为 2G),受操作系统文件大小的限制;主键索引采用聚集索引(索引的数据域存储数据文件本身),辅索引的数据域存储主键的值;因此从辅索引查找数据,需要先通过辅索引找到主键值,再访问辅索引;最好使用自增主键,防止插入数据时,为维持 B+树结构,文件的大调整;
适用场景:
- 经常更新的表,适合处理多重并发的更新请求;
- 支持事务;
- 可以从灾难中恢复(通过 bin-log 日志等);
- 外键约束。只有他支持外键;
- 支持自动增加列属性auto_increment;
43、MySQL中InnoDB 支持的事务隔离级别以及区别?
SQL 标准定义的四个隔离级别为
- 读未提交(READ UNCOMMITTED):未提交读隔离级别也叫读脏,就是事务可以读取其它事务未提交的数据;
- 读已提交(READ COMMITTED):在其它数据库系统比如 SQL Server 默认的隔离级别就是提交读,已提交读隔离级别就是在事务未提交之前所做的修改其它事务是不可见的。
- 可重复读(REPEATABLE READ):保证同一个事务中的多次相同的查询的结果是一致的,比如一个事务一开始查询了一条记录然后过了几秒钟又执行了相同的查询,保证两次查询的结果是相同的,可重复读也是 mysql 的默认隔离级别。
- 可串行化(serializable ):可串行化就是保证读取的范围内没有新的数据插入,比如事务第一次查询得到某个范围的数据,第二次查询也同样得到了相同范围的数据,中间没有新的数据插入到该范围中。
44、char和varchar区别?
char是一种固定长度的类型,无论储存的数据有多少都会固定长度,如果插入的长度小于定义长度,则可以用空格进行填充。而varchar是一种可变长度的类型,当插入的长度小于定义长度时,插入多长就存多长。
- 最大长度:char最大长度是255字符,varchar最大长度是65535个字节;
- 定长:char是定长的,不足的部分用隐藏空格填充,varchar是不定长的;
- 空间使用:char会浪费空间,varchar会更加节省空间;
- 查找效率:char查找效率会很高,varchar查找效率会更低;
- 尾部空格:char插入时可省略,vaechar插入时不会省略,查找时省略;
45、事务的四大特征是什么?
数据库事务transanction正确执行的四个基本要素,ACID:
- 原子性(Atomicity):整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样;
- 一致性(Correspondence):在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏;
- 隔离性(Isolation):事务查看数据操作时数据所处的状态,要么是另一个并发事务修改数据之前的状态,要么是另一个并发事务修改它之后的状态。事务不会查看中间状态的数据;
- 持久性(Durability):在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚;
#开启事务
START TRANSACTION;#SET autoCommit = 0;#方式2 设置自动提交为0 关闭自动提交 | 1 开启自动提交
#1账户扣钱
UPDATE account SET money = money - 1000 WHERE id = 1;
#2账户加钱
UPDATE account SET money = money + 1000 WHERE id = 2;
#执行提交 ---成功
COMMIT;
#执行回滚 ---失败
ROLLBACK;
- 开启事务后,在当前事务内执行的语句均属于当前事务,成功再执行COMMIT,失败要进行ROLLBACK;
46、你对Mysql的索引有了解么?
个人认为这个问题比较广,我第一次的时候就说了:一般数据量大的情况下会给字段加索引来添加一个标识提高查询的效率,感觉不是面试官想要的于是就有了以下的总结:
索引(Index)是帮助 MySQL 高效获取数据的数据结构。常见的查询算法,顺序查找,二分查找,二叉排序树查找,哈希散列法,分块查找,平衡多路搜索树B+树(B-tree)
索引的目的是什么?
- 快速访问数据表中的特定信息,提高检索速度;
- 创建唯一性索引,保证数据库表中每一行数据的唯一性;
- 加速表和表之间的连接;
- 使用分组和排序子句进行数据检索时,可以显著减少查询中分组和排序的时间;
索引对数据库系统的负面影响是什么
- 创建索引和维护索引需要耗费时间,这个时间随着数据量的增加而增加;
- 索引需要占用物理空间,不光是表需要占用数据空间,每个索引也需要占用物理空间;当对表进行增、删、改的时候索引也要动态维护,这样就降低了数据的维护速度;
为数据表建立索引的原则有哪些
- 在最频繁使用的、用以缩小查询范围的字段上建立索引;
- 在频繁使用的、需要排序的字段上建立索引;
什么情况下不宜建立索引
- 对于查询中很少涉及的列或者重复值比较多的列,不宜建立索引。
- 对于一些特殊的数据类型,不宜建立索引,比如文本字段(text)等;
索引建立常见原则
- 选择唯一性索引:唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录;
- 为经常需要排序、分组和联合操作的字段建立索引;
- 为常作为查询条件的字段建立索引;
- 限制索引的数目:越多的索引,会使更新表变得很浪费时间;
- 尽量使用数据量少的索引;
- 如果索引的值很长,那么查询的速度会受到影响,尽量使用前缀来索引;
- 如果索引字段的值很长,最好使用值的前缀来索引;
- 删除不再使用或者很少使用的索引;
- 最左前缀匹配原则,非常重要的原则;mysql 会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,范围查询会导致组合索引半生效。比如 a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,c 可以用到索引,d 是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d 的顺序可以任意调整;
- 尽量选择区分度高的列作为索引:区分度的公式是表示字段不重复的比例;某字段的区分度的公式是 count(distinctcol)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,查找匹配的时候可以过滤更多的行, 唯一索引的区分度是 1,而一些状态、性别字段可能在大数据面前区分度就是 0;
- 索引列不能参与计算,保持列“干净”:带函数的查询不参与索引;
- 尽量的扩展索引,不要新建索引;如表中已经有 a 的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可;
- and之间的部分可以乱序,比如 a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式。where 字句有 or 出现还是会遍历全表;
- Where子句中经常使用的字段应该创建索引,分组字段或者排序字段应该创建索引,两个表的连接字段应该创建索引;
- like模糊查询中,右模糊查询(321%)会使用索引,而%321 和%321%会放弃索引而使用全局扫描;
逻辑角度索引的分类
索引分为四类:单列索引(普通索引,唯一索引,主键索引)、组合索引、全文索引、空间索引;
1、单列索引:一个索引只包含单个列,但一个表中可以有多个单列索引。
-
普通索引(index):MySQL中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值,纯粹为了查询数据更快一点;
-- 直接创建: create index index_name on table_name (column(length)); -- 修改表结构添加索引: alter table table_name add index index_name on(column(length)); -- 创建表的时候创建索引: create table table_name( ..... .... .... index index_name(column(length)) ) -- 删除索引: drop index index_name on table;
-
唯一索引(unique):索引列中的值必须是唯一的,但是允许为空值;
-- 直接创建: create unique index index_name on table_name (column(length)); -- 修改表结构添加索引: alter table table_name add unique index index_name on(column(length)); -- 创建表的时候创建索引: create table table_name( ..... .... .... unique index index_name(column(length)) )
-
主键索引(primary key):主键索引是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值,一般是在创建表的时候指定主键,主键默认就是主键索引;
create table table_name( ........ ...... ..... primary key(column) )
2、组合索引:在表中的多个字段组合上创建的索引,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用,使用组合索引时遵循最左前缀集合;
alter table table_name add index index_name(column,column,column..);
3、全文索引:只有在MyISAM引擎上才能使用,只能在CHAR,VARCHAR,TEXT类型字段上使用全文索引;全文索引就是在一堆文字中通过其中的某个关键字找到该字段所属的记录行;允许有重复值和空值,主要用来查找文本中的关键字,而不是直接与索引中的值比较,它更像是一个搜索引擎,全文索引需要配合match against操作使用,而不是一般的where语句加like;
4、空间索引:空间索引是对空间数据类型的字段建立的索引,MySQL中的空间数据类型有四种,GEOMETRY、POINT、LINESTRING、POLYGON;在创建空间索引时,使用SPATIAL关键字。要求引擎为MyISAM,创建空间索引的列,必须将其声明为NOT NULL;
物理存储角度索引的分类
聚集索引和非聚集索引
1、聚集索引:聚集索引确定表中数据的物理顺序,一个表中只能包含一个聚集索引,但该索引可以包含多个列,聚集索引对于经常要搜索范围值的列特别有效,使用聚集索引找到包含第一个值的行后,便可以确保包含后续索引值的行在物理相邻;
2、聚集索引中索引的叶节点就是数据节点,而非聚集索引的叶节点仍然是索引节点,只不过有一个指针指向相应的数据块;
数据结构角度索引的分类
hash索引和B+Tree索引
1、hash索引:hash索引基于hash表实现,只有查询条件精确匹配hash索引中的所有列才会用到hash索引,存储引擎会为hash索引中的每一列都计算hash码,hash索引中存储的就是hash码,所以每次读取都会进行两次查询;
- 仅仅能满足”=“,”<=>“查询,不能使用范围查询;
- hash索引无法用于数据的排序操作;
- hash索引不能利用部分索引键查询;
- hash索引在任何时候都不能避免表扫描;
- hash索引遇到大量hash值相等的情况后性能并不一定比BTree索引高;
2、B+Tree索引:B+Tree索引在MyISAM和InnoDB存储引擎中的实现不同
- MyISAM中data域保存数据记录的地址,MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录,MyISAM的索引方式也叫做”非聚集的“。MyISAM索引文件和数据是分离的,索引文件仅仅保存数据记录的地址;
- 在InnoDB中表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录,这个索引的key是数据表的关键,这种索引也叫做聚集索引;
47、主键、外键和索引的区别?
定义
主键:唯一标识一条记录,不能有重复的,不允许为空;
外键:表的外键是另一表的主键, 外键可以有重复的, 可以是空值;
索引:该字段没有重复值,但可以有一个空值;
作用
主键:用来保证数据完整性;
外键:用来和其他表建立联系用的;
索引:是提高查询排序的速度;
个数
主键:主键只能有一个;
外键:一个表可以有多个外键;
索引:一个表可以有多个唯一索引;
48、唯一索引比普通索引快吗为什么?
唯一索引不一定比普通索引快, 还可能慢;
- 查询时, 在未使用 limit 1 的情况下, 在匹配到一条数据后, 唯一索引即返回, 普通索引会继续匹配下一条数据, 发现不匹配后返回; 如此看来唯一索引少了一次匹配, 但实际上这个消耗微乎其微;
- 更新时, 这个情况就比较复杂了。 普通索引将记录放到 change buffer 中语句就执行完毕了。而对唯一索引而言, 它必须要校验唯一性, 因此, 必须将数据页读入内存确定没有冲突, 然后才能继续操作。对于写多读少的情况, 普通索引利用 change buffer 有效减少了对磁盘的访问次数, 因此普通索引性能要高于唯一索引;
49、解释MySQL外连接、内连接的区别
外连接
左连接(左外连接):以左表作为基准进行查询,左表数据会全部显示出来,右表如果和左表匹配的数据则显示相应字段的数据,如果不匹配则显示为 null;
右连接(右外连接):以右表作为基准进行查询,右表数据会全部显示出来,左表如果和右表匹配的数据则显示相应字段的数据,如果不匹配则显示为 null;
全连接:先以左表进行左外连接,再以右表进行右外连接;
全连接
先以左表进行左外连接,再以右表进行右外连接
内连接
根据某个条件筛选出符合条件的记录,不符合条件的记录不会出现在结果集中,显示表之间有连接匹配的所有行;
交叉连接
交叉连接又叫笛卡尔积,它是指不使用任何条件,直接将一个表的所有记录和另一个表中的所有记录一一匹配;
50、主键递增问题
一张表里面有 ID 自增主键,当 insert 了 17 条记录之后,删除了第 15,16,17 条记录,再把 Mysql 重启,再 insert 一条记录,这条记录的ID是18还是15?
如果表的类型是MyISAM,那么是18;因为MyISAM表会把自增主键的最大ID记录到数据文件里,重启MySQL自增主键的最大ID也不会丢失;
如果表的类型是InnoDB,那么是15;因为InnoDB表只是把最大ID记录到内存中,所以重启数据库或者对表进行optimize(优化)操作,都会导致最大的ID丢失;
51、UNION和UNION ALL的区别
语法:
SELECT 列名 FROM 表名 1 UNION SELECT 列名 FROM 表名2
SELECT 列名 FROM 表名 1 UNION ALL SELECT 列名 FROM 表名2
#合并 t1 和t2两张表的结果。纵向合并,去除重复的记录
SELECT * FROM t1
UNION
SELECT * FROM t2
#合并结果集,不去除重复记录
SELECT * FROM t1
UNION ALL
SELECT * FROM t2
- 合并的两个结果集,列数必须相同,列类型、列名可以不同
52、MySQL一张表最多能存多少数据?
MySQL本身并没有对单表最大记录数进行限制,这个数值取决于你的操作系统对单个文件的限制本身。业界流传是500万行。超过500万行就要考虑分表分库了。阿里规范中提出单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表;
53、MySQL中有哪几种锁?
对于这个问题我们可以先回答锁的概念,然后再说有几种锁;
数据库是一个多用户使用的共享资源。当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。
加锁是实现数据库并发控制的一个非常重要的技术。当事务在对某个数据对象进行操作前,先向系统发出请求,对其加锁。加锁后事务就对该数据对象有了一定的控制,在该事务释放锁之前,其他的事务不能对此数据对象进行更新操作。基本锁类型:锁包括行级锁和表级锁;
锁包含:
- 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分 MySQL 引擎支持。最常使用的 MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁);
- 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;行级锁是一种排他锁,防止其他事务修改此行;
- 页面锁:锁定粒度介于行级锁和表级锁中间的一种锁。开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般;表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录;
54、SQL锁的优化策略
- 读写分离;
- 分段加锁;
- 减少锁持有的时间;
- 多个线程尽量以相同的顺序去获取资源不能将锁的粒度过于细化,不然可能会出现线程的加锁和释放次数过多,反而效率不如一次加一把大锁;
55、如何通俗地理解三个范式?
范式是具有最小冗余的表结构
第一范式(列都是不可再分):1NF 是对属性的原子性约束,要求属性具有原子性,不可再分解;如果每列都是不可再分的最小数据单元(也称为最小的原子单元),则满足第一范式(1NF);
第二范式(每个表只描述一件事情):2NF 是对记录的惟一性约束,要求记录有惟一标识,即实体的惟一性;首先满足第一范式,并且表中非主键列不存在对主键的部分依赖。 第二范式要求每个表只描述一件事情;
第三范式(不存在对非主键列的传递依赖):3NF 是对字段冗余性的约束,即任何字段不能由其他字段派生出来,它要求字段没有冗余;第三范式定义是,满足第二范式,并且表中的列不存在对非主键列的传递依赖。除了主键订单编
号外,顾客姓名依赖于非主键顾客编号;
范式化设计优缺点
优点:可以尽量得减少数据冗余,使得更新快,体积小;
缺点:对于查询需要多个表进行关联,减少写得效率增加读得效率,更难进行索引优化;
反范式化
优点:可以减少表得关联,可以更好得进行索引优化;
缺点:数据冗余以及数据异常,数据得修改需要更多的成本;
56、MySQL根据生日计算年龄
SELECT TIMESTAMPDIFF(YEAR, birthday, CURDATE())
- 只需把第二个参数“birthday”更换成你要计算的生日日期
57、Mybatis是如何进行分页的?分页插件的原理是什么?
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页, 而非物理分页,可以在SQL内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。 分页插件的基本原理是使用 Mybatis 提供的插件接口,实现自定义插件,在插件的拦截 方法内拦截待执行的SQL,然后重写SQL,根据dialect方言,添加对应的物理分页语句和物理分页参数;
分页插件原理:
实现MyBatis提供的接口,实现自定义插件,在插件的拦截方法内拦截待执行的SQL,然后重写SQL;
例如:
select * from student
拦截sql后重写为:
select t.* from (select * from student) t limit 0 , 10
58、Mybatis 动态sql有什么用?执行原理?有哪些动态sql
Mybatis 动态sql可以在Xml映射文件内,以标签的形式编写动态 sql;
执行原理是根据表达式的值完成逻辑判断并动态拼接 sql 的功能;
Mybatis提供了 9 种动态 sql 标签:
-
trim:可实现where/set标签的功能;
有4个属性:
prefix:表示在trim标签包裹的SQL前添加指定内容
suffix:表示在trim标签包裹的SQL末尾添加指定内容
prefixOverrides:表示去掉(覆盖)trim标签包裹的SQL指定首部内容,去掉多个内容写法为and |or(中间空格不能省略)(一般用于if判断时去掉多余的AND |OR)
suffixOverrides:表示去掉(覆盖)trim标签包裹的SQL指定尾部内容(一般用于update语句if判断时去掉多余的逗号) -
where:类似于where,只有一个以上的if条件满足的情况下才去插入WHERE关键字;
-
set:用于解决动态更新语句存在的符号问题;
-
foreach:对集合进行遍历;
-
if:当参数满足条件才会执行某个条件;
-
choose:按顺序判断其内部when标签中的test条件是否成立,如果有一个成立,则choose结束;
-
when:条件用于判断是否成立;
-
otherwise:如果所有的when条件都不满足时,则执行otherwise中的SQL;
-
bind:可以从OGNL(对象图导航语言)表达式中创建一个变量并将其绑定到上下文;
59、什么是MyBatis?
Mybatis是⼀个优秀的基于Java的持久层框架,它内部封装了JDBC,使开发者只需要关注sql语句本身,⽽不需要花费精⼒去处理加载驱动、创建连接、创建statement等繁杂的过程。
Mybatis通过xml或注解的⽅式将要执⾏的各种statement配置起来,并通过java对象和statement中sql的动态参数进⾏映射⽣成最终执⾏的sql语句,最后由mybatis框架执⾏sql并将结果映射为java对象并返回;
MyBatis ⽀持定制化 SQL、存储过程以及⾼级映射。MyBatis 避免了⼏乎所有的JDBC 代码和⼿动设置参数以及获取结果集。MyBatis 可以使⽤简单的 XML 或注解来配置和映射原⽣信息,将接⼝和 Java 的 POJO映射成数据库中的记录;
60、MyBatis优缺点
MyBatis的优点
- 简单易学,容易上⼿(相⽐于Hibernate) —- 基于SQL编程;
- JDBC相⽐,减少了50%以上的代码量,消除了JDBC⼤量冗余的代码,不需要⼿动开关连接;
- 很好的与各种数据库兼容(因为MyBatis使⽤JDBC来连接数据库,所以只要JDBC⽀持的数据库MyBatis都⽀持,⽽JDBC提供了可扩展性,所以只要这个数据库有针对Java的jar包就可以就可以与MyBatis兼容),开发⼈员不需要考虑数据库的差异性;
- 提供了很多第三⽅插件(分⻚插件 / 逆向⼯程);
- 能够与Spring很好的集成;
- MyBatis相当灵活,不会对应⽤程序或者数据库的现有设计强加任何影响,SQL写在XML⾥,从程序代码中彻底分离,解除sql与程序代码的耦合,便于统⼀管理和优化,并可重⽤;
- 提供XML标签,⽀持编写动态SQL语句;
- 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护;
MyBatis的缺点
- SQL语句的编写⼯作量较⼤,尤其是字段多、关联表多时,更是如此,对开发⼈员编写SQL语句的功底有⼀定要求;
- SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库;
61、MyBatis编程步骤是什么样的?
1、 创建SqlSessionFactory
2、 通过SqlSessionFactory创建SqlSession
3、 通过sqlsession执行数据库操作
4、 调用session.commit()提交事务
5、 调用session.close()关闭会话
- 可看62条;
62、请说说MyBatis的工作原理
MyBatis 的工作原理如下图:
- 读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息;
- 加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表;
- 构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory;
- 创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法;
- Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护;
- MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息;
- 输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程;
- 输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程;
63、谈谈你对Mybatis 缓存的理解
Mybatis中有一级缓存和二级缓存,默认情况下一级缓存是开启的,而且是不能关闭的。一级缓存是指SqlSession级别的缓存,当在同一个SqlSession中进行相同的SQL语句查询时,第二次以后的查询不会从数据库查询,而是直接从缓存中获取,一级缓存最多缓存1024条SQL。二级缓存是指可以跨SqlSession的缓存。是mapper级别的缓存,对于mapper级别的缓存不同的sqlsession是可以共享的;
一级缓存原理(sqlsession级别,默认打开)
基于PerpetualCache的HashMap本地缓存,其存储作用域为Session,当 Session flush 或close之后,该Session中的所有Cache就将清空。
第一次发出一个查询sql,sql 查询结果写入sqlsession的一级缓存中,缓存使用的数据结构是一个map(key:MapperID+offset+limit+Sql+所有的入参;value:用户信息)。同一个 sqlsession再次发出相同的sql,就从缓存中取出数据。如果两次中间出现 commit 操作(修改、添加、删除),本 sqlsession中的一级缓存区域全部清空,下次再去缓存中查询不到,所以要从数据库查询,从数据库查询到再写入缓存;
二级缓存原理(mapper级别)
二级缓存的范围是Mapper(Namespace)级别(mapper同一个命名空间),mapper 以命名空间为单位创建缓存数据结构,结构是 map。mybatis的二级缓存是通过CacheExecutor实现的。CacheExecutor其实是 Executor的代理对象。所有的查询操作,在 CacheExecutor 中都会先匹配缓存中是否存在,不存在则查询数据库。
具体使用需要配置:
- Mybatis全局配置中启用二级缓存配置
- 在对应的 Mapper.xml 中配置cache节点
- 在对应的select查询节点中添加 useCache=true
缓存数据更新机制
当某一个作用域(一级缓存 Session/二级缓存 Namespaces)的进行了C/U/D操作后,默认该作用域下所有 select 中的缓存将被clear;
64、MyBatis咋么省略路径(不要全路径)
mybatis.mapper-locations在SpringBoot配置文件中使用,作用是扫描Mapper接口对应的XML文件,扫描的是resources下的mapper文件夹中所有的xml结尾的文件。
- 如果全程使用@Mapper注解,可以不使用该配置,SpringBoot提倡“约定优于配置”
mybatis.mapper-locations =classpath:/mappers/*.xml
65、咋么保证数据库的数据跟redis同步
大多业务操作可以根据下图来做缓存,包括面试官问我我就是这么回答的,但是可能没答到点子上;
如果下次还问到我,我会这么回答:
首先不一致主要分为三种情况:
- 数据库有数据,缓存没有数据;(在读数据的时候会自动把数据库的数据写到缓存,因此不一致自动消除)
- 数据库有数据,缓存也有数据,数据不相等;(数据库更新了,但是删除缓存失败了)
- 数据库没有数据,缓存有数据;(数据库的数据删了,但是删除缓存的时候失败了)
解决方案:
- 对删除缓存进行重试,数据的一致性要求越高,我越是重试得快;
- 定期全量更新,简单地说,就是我定期把缓存全部清掉,然后再全量加载;
- 给所有的缓存一个失效期;
双写或者数据库和缓存更新(比较官方的回答)
一旦设计到双写或者数据库和缓存更新等操作,就很容易出现数据一致性的问题。无论是先写数据库,在删除缓存,还是先删除缓存,在写入数据库,都会出现数据一致性的问题;
- 先删除了redis缓存,但是因为其他什么原因还没来得及写入数据库,另外一个线程就来读取,发现缓存为空,则去数据库读取到之前的数据并写入缓存,此时缓存中为脏数据;
- 如果先写入了数据库,但是在缓存被删除前,写入数据库的线程因为其他原因被中断了,没有删除掉缓存,就也会出现数据不一致的情况;
写和读在多数情况下都是并发的,不能绝对保证先后顺序,就会很容易出现缓存和数据库数据不一致的情况,我们有以下两种方式:
方案一:采用延时双删策略
基本思路:
在写库前后都进行删除缓存操作,并且设置合理的超时时间;
基本步骤:
- 先删除缓存
- 再写数据库
- 休眠一段时间
- 再次删除缓存
public void write(String key,Object data){
redisUtils.del(key);
db.update(data);
Thread.Sleep(100);
redisUtils.del(key);
}
- 注:休眠的时间是根据自己的项目的读数据业务逻辑的耗时来确定的。这样做主要是为了保证在写请求之前确保读请求结束,写请求可以删除读请求造成的缓存脏数据;
该方案的弊端:集合双删策略+缓存超时策略设置,这样最差的结果就是在超时时间内数据存在不一致,又增加了写请求的耗时;
方案二:一步更新缓存(基于订阅Binlog的同步机制)
基本思路:
mysql Binlog增强订阅消费+消息队列+增量数据更新到redis;
读redis:热数据基本上都在redis;
写mysql:增删改都是操作mysql;
更新redis数据:mysql的数据操作Binlog,来更新redis;
- 数据操作主要分为两大块: 一个是全量,将全部数据写去redis;另一个就是增量(update、insert、delete),实时更新;
- 读取binlog后分析,利用消息队列推送更新各台的redis缓存数据。这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新;
66、char型变量中能不能存贮一个中文汉字?为什么?
char型变量是用来存储Unicode编码的字符的,Unicode编码字符集中包含了汉字,所以char型变量中当然可以存储汉字啦。如果某个特殊的汉字没有被包含在Unicode编码字符集中,那么这个char型变量中就不能存储这个特殊汉字;
- Unicode编码占用两个字节,所以char类型的变量也是占用两个字节;
67、String s = new String(“kak”);创建了几个字符串对象?
两个对象:一个是静态区的”kak”,一个是用new创建在堆上的对象;
68、接口是否可继承接口?抽象类是否可实现接口?抽象类是否可继承具体类?
接口可以继承(extends)接口,而且支持多重继承。抽象类可以实现(implements)接口,抽象类可继承具体类也可以继承抽象类;
69、什么时候用断言(assert)?
断言在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制,个人理解跟If一样;一般来说,断言用于保证程序最基本、关键的正确性。断言检查通常在开发和测试时开启。为了保证程序的执行效率,在软件发布后断言检查通常是关闭的。断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为true;如果表达式的值为 false,那么系统会报告一个AssertionError。断言的使用如下面的代码所示:
assert(a > 0); // throws an AssertionError if a <= 0
- 断言不应该以任何方式改变程序的状态。如果希望在不满足某些条件时阻止代码的执行,就可以考虑用断言来阻止它;
70、集合和数组的区别
长度的区别
数组:长度是固定的,length属性判断长度(String中也存在length()方法);
集合:长度是可变的,size()方法;
内容的区别
数组:只能存储同一种数据类型 int[] arr = new int[5];
集合:只能存储一种数据类型;
存储数据类型问题
数组:既可以存储基本数据类型,又可以存储引用数据类型;
集合:只能存储引用数据类型;
71、谈谈你对集合框架的理解?
这个问题是我面试这段时间认为最难回答的一个问题,原因就他问的范围太大了不知道该咋么回答;就我而言遇到这种问题首先给他说个总的,从最大的开始说起,在说一下里面有哪些内容,包括里面的区别;如果你理解到位了也可以说一下底层的实现那么这时候面试官的眼睛就亮了;下面是我的思路可以参考一下:
首先上一张图,在自己脑子里有一个大概的框架:
集合框架他分为单列集合和双列集合,单列集合都继承自Collection而双列集合就是常说的Map;那么我们先来说说单列集合,单列集合分为List和Set都是继承自Collection 接口(这时候我们可以说List跟Set的区别):
List和Set的区别
List
- 一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引;
- 常用的实现类有 ArrayList、LinkedList 和 Vector;
- 和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变;
- List 支持for循环,也就是通过下标来遍历,也可以用迭代器;
- 存储和取出是一致的;
Set
- 一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性;
- Set 接口常用实现类是 HashSet、LinkedHashSet 以及TreeSet;
- 检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变;
- 因为他无序,无法用下标来取得想要的值,所以只能用迭代器;
- 存储和取出不一致(不能保证该顺序恒久不变);
接着我们可以说一下List的三个子集的区别:
ArrayList、LinkedList 与voctor的区别
ArrayList
底层结构是动态数组,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,查找和遍历的效率较高,增删慢;
LinkedList
底层结构是双向链表,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用;
Voctor
底层结构是数组,支持线程的同步,即某一时刻只有一个线程能够写 Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问 ArrayList 慢;属于线程安全的集合,增删慢,查询慢;
说完之后呢,我们可以往下继续说Set的子集,Set里面最常用的就是HashSet和TreeSet,先说一下HashSet的实现原理以及它如何保证数据的不重复性:
HashSet实现原理
HashSet内部是基于HashMap实现的,底层使用Hash表实现,存取速度快;HashSet的值存放于HashMap的key上,HashMap的value统一为present,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层HashMap 的相关方法来完成,HashSet排列无序,不允许重复的值;
HashSet如何保证数据不重复
HashSet 存储元素的顺序并不是按照存入时的顺序(和 List 显然不同)而是按照哈希值来存的所以取数据也是按照哈希值取得。元素的哈希值是通过元素的hashcode方法来获取的,HashSet首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals 方法 如果equls结果为true,HashSet 就视为同一个元素。如果equals为false就不是同一个元素;
接着就可以说一下TreeSet,他的内部是TreeMap而TreeMap的底层是红黑树一种自平衡的二叉树,所以我们也可以说一下红黑树的相关东西;
TreeSet红黑树
TreeSet底层使用二叉树的原理实现的,排列无序,不可重复;排序存储;内部是TreeMap的SortedSet;对新add()的对象按照指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入的二叉树指定的位置;
Integer 和 String 对象都可以进行默认的 TreeSet 排序,而自定义类的对象是不可以的,自己定义的类必须实现 Comparable 接口,并且覆写相应的 compareTo()函数,才可以正常使用;在覆写 compare()函数时,要返回相应的值才能使 TreeSet 按照一定的规则来排序;
Red - Black - Tree 红黑树结构简述
红黑树一种自平衡的二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black;
public static void main(String[] args) {
//创建TreeSet集合对象
TreeSet<Integer> ts = new TreeSet<Integer>();
//add()添加
ts.add(20) ;
ts.add(18) ;
ts.add(23) ;
ts.add(22) ;
ts.add(17) ;
ts.add(24) ;
ts.add(19) ;
ts.add(18) ;
ts.add(24) ;
//遍历集合
for(Integer i : ts) {
System.out.print(i+" ");//17 18 19 20 22 23 24
}
}
遍历原理:拿上面的数字20,18,23,22,17,24,19,18,24来说:
存储元素:
- 添加元素时,第一个元素20作为根节点---->root
- 后面添加的元素都需要和根节点进行比较
- 如果比根节点小,作为根节点的左孩子(根节点左边的子节点)
- 如果比根节点打,作为根节点的右孩子(根节点右边的子节点)
- 如果元素相等,不会进入到红黑树的结构中
遍历元素:
使用中序遍历(左根右)
- 首先访问根节点的左孩子,如果这个左孩子仍然有左孩子继续访问,一直到没有左孩子的节点,输出该节点17;
- 依据中序遍历次序,输出17的父节点18;
- 在输出18的右孩子19;
- 以节点18为大的左孩子输出完毕,在输出整个二叉树的根节点20;
- 依据中序遍历次序左根右,找到根节点20的右孩子23,因为23有左孩子所以输出23的左孩子22;
- 输出22的根节点23;
- 在输出23的右孩子24;
- 最终得到序列17,18,19,20,22,23,24
这样单列集合就差不多了;如果你能说到这里并且面试官没有打断你,那么恭喜你就应该稳了;但是我们不要断继续说双列集合:
双列集合Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。Map的常用实现类:HashMap、TreeMap、HashTable等等;接着我们就可以说说他们之间的关系以及各自的特点:
HashTable(线程安全)
Hashtable键不可重复,值可以重复;底层是Hash表;Key和Value都不能为Null;很多映射的常用功能与HashMap类似,不同的是它承自 Dictionary 类,并且是线程安全的,任一时间只有一个线程能写 Hashtable,并发性不如 ConcurrentHashMap,因为 ConcurrentHashMap 引入了分段锁。Hashtable 不建议在新代码中使用,不需要线程安全的场合可以用 HashMap 替换,需要线程安全的场合可以用 ConcurrentHashMap 替换;
TreeMap(可排序)
TreeMap实现SortedMap接口,**键不可重复,值可以重复;底层是基于红黑树(Red-Black tree)实现;是一个有序的key-value集合;线程非同步的;**能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。如果使用排序的映射,建议使用 TreeMap。在使用 TreeMap 时,key 必须实现 Comparable 接口或者在构造 TreeMap 传入自定义的Comparator,否则会在运行时抛出 java.lang.ClassCastException 类型的异常;
HashMap(数组+链表+红黑树,线程不安全)
HashMap**键不可重复,值可以重复;底层是Hash表;key和value都可以为null;**根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。HashMap 非线程安全,即任一时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致;
说完三个基本特点后,我们可以着重说一下HashMap的实现原理,因为我认为他是整个集合框架里比较重要的一个模块:
HashMap的实现原理
HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变;
HashMap的数据结构: 在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体;
HashMap基于Hash算法实现的
- 当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标;
- 存储时,如果出现hash值相同的key,此时有两种情况:
如果key相同,则覆盖原始值;
如果key不同(出现冲突),则将当前的key-value放入链表中;- 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值;
- HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。需要注意JDK1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(N)到O(logN);
HashMap在JDK1.8之前和JDK1.8之后中有哪些不同?
在Java中保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做拉链法的方式可以解决哈希冲突;
HashMap JDK1.8之前
JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个单向链表。若遇到哈希冲突,则将冲突的值加到链表中即可;
HashMap JDK1.8之后
我们知道之前查找的时候,根据hash值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度为O(N)。相比于之前的版本,为了降低这部分的开销JDK1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,会将链表转换为红黑树,在这些位置进行查找时可以降低时间复杂度,以减少搜索时间由O(N)变为O(logN);
JDK1.8之前与JDK1.8之后比较
说到这里我个人觉得对这个问题已经有一个比较全面的解答了,这时候就可以等面试官问其他问题了;
72、ArrayList 和 LinkedList 的区别是什么?
- 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现;
- 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找;
- 增加和删除效率:在非首尾的增加和删除操作,LinkedList要比ArrayList效率要高,因为ArrayList增删操作要影响数组内的其他数据的下标;
- 内存空间占用:LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素;
- 线程安全:ArrayList和LinkedList都是不同步的,也就是不保证线程安全;
总之,在需要频繁读取集合中的元素时推荐使用ArrayList,而在插入和删除操作较多时推荐使用LinkedList。LinkedList的双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点;
73、ArrayList和Vector 的区别是什么?
这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合;
- 线程安全:Vector使用了Synchronized来实现线程同步,是线程安全的,而ArrayList是非线程安全的;
- 性能:ArrayList在性能方面要优于Vector;
- 扩容:ArrayList和Vector 都会根据实际的需要动态的调整容量,只不过在Vector扩容每次会增加1倍,而ArrayList只会增加50%;
- Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间;
- Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist。
74、说一下ArrayList的优缺点
ArrayList的优点如下:
- ArrayList底层以数组实现,是一种随机访问模式。ArrayList实现了RandomAccess接口,因此查找的时候非常快;
- ArrayList在顺序添加一个元素的时候非常方便;
- ArrayList 比较适合顺序添加、随机访问的场景;
ArrayList的缺点如下:
- 删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能;
- 插入元素的时候,也需要做一次元素复制操作,缺点同上;
75、List 和 Map、Set 的区别
结构特点
List和Set是存储单列数据的集合,Map是存储键和值这样的双列数据的集合;List中存储的数据是有顺序,并且允许重复;Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的;Set中存储的数据是无序的,且不允许有重复,但元素在集合中的位置由元素的hashcode决定,位置是固定的(Set集合根据hashcode来进行数据的存储,所以位置是固定的,但是位置不是用户可以控制的,所以对于用户来说set中的元素还是无序的);
实现类
List 接口有三个实现类:
- LinkedList:基于链表实现,链表内存是散乱的,每一个元素存储本身内存地址的同时还存储下一个元素的地址。链表增删快,查找慢;
- ArrayList:基于数组实现,非线程安全的,效率高,便于索引,但不便于插入删除;
- Vector:基于数组实现,线程安全的,效率低;
Map接口有三个实现类:
- HashMap:基于hash表的Map接口实现,非线程安全,高效,支持null值和null键;
- HashTable:线程安全,低效,不支持 null 值和 null 键;
- LinkedHashMap:是 HashMap 的一个子类,保存了记录的插入顺序;
- SortMap 接口:TreeMap能够把它保存的记录根据键排序,默认是键值的升序排序;
Set 接口有两个实现类:
- HashSet:底层是由HashMap实现,不允许集合中有重复的值,使用该方式时需要重写equals()和hashCode()方法;
- LinkedHashSet:继承与HashSet,同时又基于LinkedHashMap来进行实现,底层使用的是LinkedHashMap;
区别
List集合中对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象,例如通过list.get(i)方法来获取集合中的元素;Map中的每一个元素包含一个键和一个值,成对出现,键对象不可以重复,值对象可以重复;Set集合中的对象不按照特定的方式排序,并且没有重复对象,但它的实现类能对集合中的对象按照特定的方式排序,例如TreeSet 类,可以按照默认顺序,也可以通过实现 Java.util.Comparator接口来自定义排序方式;
76、HashMap和HashTable有什么区别?
- 线程安全: HashMap是非线程安全的,HashTable是线程安全的;HashTable内部的方法基本都经过synchronized修饰;
- 效率: 因为线程安全的问题,HashMap要比HashTable效率高一点。另外,HashTable基本被淘汰,不要在代码中使用它;
- 对Null key 和Null value的支持: HashMap中null可以作为键但是这样的键只有一个,可以有一个或多个键所对应的值为null。但是在HashTable中put进的键值只要有一个null,直接抛NullPointerException;
- 初始容量大小和每次扩充容量大小的不同;
- 创建时如果不指定容量初始值,Hashtable默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的2倍;
- 创建时如果给定了容量初始值,那么Hashtable会直接使用你给定的大小而HashMap会将其扩充为2的幂次方大小。也就是说HashMap总是使用2的幂作为哈希表的大小;
- 底层数据结构: JDK1.8以后的HashMap在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制;
- 推荐使用:在Hashtable的类注释可以看到,Hashtable是保留类不建议使用,推荐在单线程环境下使用HashMap替代,如果需要多线程使用则用 ConcurrentHashMap 替代;
77、哪些集合类是线程安全的
- Vector:比ArrayList多了个同步化机制(线程安全);
- Stack:栈,也是线程安全的,继承于Vector;
- Hashtable:就比HashMap多了个线程安全;
- ConcurrentHashMap:是一种高效但是线程安全的集合;
78、Iterator 怎么使用?有什么特点?
首先我们要知道迭代器Iterator是什么:Iterator是为了方便的处理集合中的元素,Java中出现了一个对象提供了一些方法专门处理集合中的元素;例如删除和获取集合中的元素.该对象就叫做迭代器(Iterator);
Iterator接口源码中的方法:
- java.lang.Iterable接口被java.util.Collection接口继承,java.util.Collection接口的iterator()方法返回一个Iterator 对象;
- Object next():获得集合中的下一个元素;
- boolean hasNext() :检查集合中是否还有元素;
- remove():方法将迭代器新返回的元素删除;
79、队列和栈是什么?有什么区别?
队列先进先出,栈先进后出;二者遍历数据速度不同;栈只能从头部取数据也就最先放入的需要遍历整个栈最后才能取出来,而且在遍历数据的时候还得为数据开辟临时空间,保持数据在遍历前的一致性;队列则不同,他基于地址指针进行遍历,而且可以从头或尾部开始遍历,但不能同时遍历,无需开辟临时空间,因为在遍历的过程中不影像数据结构,速度要快的多;
80、Map集合的遍历(四种)
通过键找值
set< K > keySet() {}
- 获取所有键的集合
- 遍历键的集合,获取到每一个键
- 根据键找值
public static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>();
//添加元素
map.put("卡卡", "小卡") ;
map.put("牛牛", "小牛") ;
map.put("堂堂", "小堂") ;
map.put("涛涛", "小涛") ;
//获取所有的键的集合
Set<String> set = map.keySet() ;
for(String key :set) {
//通过键获取值 V get(Object key)
String value = map.get(key) ;
System.out.println(key+"="+value);
}
}
根据键值对对象找键和值
public Set<Map.Entry<K,V>> entrySet()
Map.Entry<K,V>接口中:K getKey():获取键、V getValue():获取值;
- 获取所有键值对对象的集合
- 遍历键值对对象的集合,获取到每一个键值对对象
- 根据键值对对象找键和值
public static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>() ; //HashMap:哈希表(元素唯一,无序!)
//添加元素
map.put("卡卡", "小卡") ;
map.put("牛牛", "小牛") ;
map.put("堂堂", "小堂") ;
map.put("涛涛", "小涛") ;
//通过map集合获取所有的键值对对象
Set<Map.Entry<String, String>> entrySet = map.entrySet() ;
for(Map.Entry<String, String> entry:entrySet ) {
//通过entry所有的键值对对象
String key = entry.getKey() ;
String value = entry.getValue() ;
System.out.println(key+"="+value);
}
}
通过迭代器遍历
public static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>() ; //HashMap:哈希表(元素唯一,无序!)
//添加元素
map.put("卡卡", "小卡") ;
map.put("牛牛", "小牛") ;
map.put("堂堂", "小堂") ;
map.put("涛涛", "小涛") ;
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<String, String> entry = iterator.next();
System.out.println("key="+entry.getKey()+" ; value="+entry.getValue());
}
}
使用lambda表达式
public static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>() ; //HashMap:哈希表(元素唯一,无序!)
//添加元素
map.put("卡卡", "小卡") ;
map.put("牛牛", "小牛") ;
map.put("堂堂", "小堂") ;
map.put("涛涛", "小涛") ;
map.forEach((k,v)-> System.out.println("key="+k+" ; value="+v));
}
81、TCP和UDP的区别
UDP协议:User Datagram Protocol用户数据报协议,将数据源和目的封装成数据包中,发送数据包到接收端(等于发广播);
- 一种无需连接的传输层协议(不需要建立连接通道);
- 不可靠连接协议,只管发送不管对方是否接收;
- 无连接是不可靠协议,不需要建立连接速度快,所以发送和接收数据的执行效率高;
- 发送数据大小有限制的不超过64kb;
TCP协议:Transmission Control Protocol传输控制协议,建立连接形成传输数据的通道(等于和陌生人打电话处理事情);
- 一种面向连接的、可靠的、基于字节流的传输层通讯协议(需要建立连接通道);
- 可靠连接协议(安全的)
- 必须建立连接所以执行效率会稍低;
- 发送数据的大小无限制,越大越慢;
- 发送数据: 通过通过内的字节输出流OutputStream 写数据到服务器端
- 建立连接时需三次握手(success),断开连接时需四次挥手;
82、TCP的三次握手与四次挥手
因为对网络编程不是很熟悉,本人参考TCP的三次握手与四次挥手理解及面试题(很全面)大佬的博文;
三次握手
- 建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers);
- 服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
- 客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手;
四次挥手
- 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号;
- 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间;
- 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据);
- 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认;
- 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过最长报文段寿命的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态;
- 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些;
83、SpringBoot与SSM框架相比优点在哪里
这种比较类型的问题个人感觉只需要回答一方面就行了,如果你对SSM不是很擅长的话,就抓住SpringBoot的优势说,那么SSM就自然而然的否定了;
Spring Boot 主要有如下优点:
- 容易上手,提升开发效率,为 Spring 开发提供一个更快、更简单的开发框架;
- 开箱即用,远离繁琐的配置,删除了繁琐的xml配置文件;
- 提供了一系列大型项目通用的非业务性功能,例如:安全管理、运行数据监控、运行状况检查和外部化配置等;
- 内嵌Tomcat容器,项目可独立运行;
- SpringBoot总结就是使编码变简单、配置变简单、部署变简单、监控变简单等等;
- 提供starter,简化maven配置;
84、SpringBoot的配置文件有哪几种格式?它们有什么区别?
.properties和.yml,它们的区别主要是书写格式不同;
.properties
键值对的形式,文件没有层次结构;
app.user.name = javastack
.yml
文件是树状结构的,可以清晰显示配置的层级;
app:
user:
name: javastack
- .yml 格式不支持
@PropertySource
注解导入配置,除此之外可以yml可以完全代替properties;
85、Redis是单线程还是多线程
Redis是单线程的;那么为啥是单线程的呢?
官方解答
Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络宽带。既然单线程容易实现,而且CPU不会成为瓶颈,那么顺理成章的采用单线程的方案;
线上详解
(1)不需要各种锁的性能消耗
Redis的数据结构并不全是key-value形式的,还有list,hash等复杂的结构,这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在hash中添加或删除一个对象,这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加;总之,在单线程的情况下,就不用去考虑各种锁的问题,不存在加锁和释放锁的操作,没有因为可能出现的死锁而导致的性能消耗;
(2)单线程多进程集群方案
单线程的威力实际上非常强大,每核心效率也非常高,多线程自然是可以比单线程有更高的性能上限,但是在今天的计算环境中,即使是单机多线程的上限也往往不能满足需要了,需要进一步摸索的是多服务器集群化的方案,这些方案中多线程的技术照样是用不上的。所以单线程、多进程的集群不失为一个时髦的解决方案;
(3)CPU消耗
采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU。但是如果CPU称为Redis的瓶颈,或者不想让服务器其它CPU核闲置,那怎么办?可以考虑多起几个Redis进程,Redis是key-value数据库,不是关系型数据库,数据之间没有约束。只要客户端分清哪些key放在哪个Redis进程中就可以了;
小结
- 代码更清晰,处理逻辑更简单;
- 不用考虑各种锁的问题,不存在加锁和释放锁的操作,没有因为可能出现死锁而导致的性能问题;
- 不存在多线程切换而消耗CPU;
- 无法发挥多核CPU的优势,但可以采用多开几个Redis实例来完善;
86、Redis有哪些优缺点?
优点
- 读写性能优异, Redis能读的速度是110000次/s,写的速度是81000次/s;
- 支持数据持久化,支持AOF和RDB两种持久化方式;
- 支持事务,Redis的所有操作都是原子性的,谓的原子性就是对数据的更改要么全部执行,要么全部不执行;同时Redis还支持对几个操作合并后的原子性执行;
- 数据结构丰富,除了支持string类型的value外还支持hash、set、zset、list等数据结构;
- 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离;
- 速度快,因为数据存在内存中;
- 支持丰富数据类型,支持string,list,set,sorted set,hash;
- 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除;
缺点
- 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上;
- Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复;
- 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性;
- Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费;
87、Redis中一个字符串类型的值能存储最大容量是多少?
一个字符串类型的值能存储最大能存储512M;
88、Redis的持久化机制是什么?各自的优缺点?
什么是Redis持久化? 持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失;
Redis提供两种持久化机制RDB(默认)和AOF机制;
RDB:是Redis DataBase缩写快照
RDB是Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期;记录Redis数据库的所有键值对,在某个时间点将数据写入一个临时文件持久化结束后,用这个临时文件替换上次持久化的文件达到数据恢复;
优点:
- 只有一个文件 dump.rdb,方便持久化;
- 容灾性好,一个文件可以保存到安全的磁盘;
- 性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了Redis的高性能;
- 相对于数据集大时,比 AOF 的启动效率更高;
缺点:
数据安全性低;RDB 是间隔一段时间进行持久化,如果持久化之间Redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候;
AOF:持久化
AOF持久化(即Append Only File持久化),是将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据。当两种方式同时开启时数据恢复Redis会优先选择AOF恢复;
优点:
- 数据安全,AOF持久化可以配置 appendfsync 属性,有always属性,每进行一次命令操作就记录到AOF文件中一次;
- 通过append模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题;
- AOF机制的rewrite模式。AOF文件没被 rewrite 之前(文件过大时会对命令 进行合并重写),可以删除其中的某些命令(比如误操作的 flushall);
缺点:
- AOF文件比RDB文件大,且恢复速度慢;
- 数据集大的时候,比RDB启动效率低;
俩种持久化对比
- AOF文件比RDB更新频率高,优先使用AOF还原数据;
- AOF比RDB更安全也更大;
- RDB性能比AOF好;
- 如果两个都配了优先加载AOF;
89、Redis的过期键的删除策略
我们都知道,Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是
指当Redis中缓存的key过期了,Redis如何处理;
- 定时删除:在设置键的过期时间的同时,创建一个定时器timer,让定时器在键的过期时间来临时,立即执行对键的删除操作;该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响
缓存的响应时间和吞吐量; - 惰性删除:放任键过期不管,只有当访问一个key时,才会判断该key是否已过期,过期则清除;如果没有过期, 就返回该键;该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存;
- 定期删除:每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键, 以及要检查多少个数据库, 则由算法决定。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果;
- expires字典会保存所有设置了过期时间的key的过期时间数据,其中key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键;
90、如何选择合适的持久化方式
一般来说, 如果想达到足以媲美PostgreSQL的数据安全性,你应该同时使用两种持久化功能。在这种情况下,当 Redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整;
- 如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失,那么你可以只使用RDB持久化;
- 有很多用户都只使用AOF持久化,但并不推荐这种方式,因为定时生成RDB快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比AOF恢复的速度要快,除此之外,使用RDB还可以避免AOF程序的BUG;
- 如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式;
91、Redis主要消耗什么物理资源?
Redis主要消耗的是内存;
92、Redis的内存用完了会发生什么?
如果达到设置的上限,Redis的写命令会返回错误信息(但是读命令还可以正常返回);
你可以配置内存淘汰机制,当Redis达到内存上限时会冲刷掉旧的内容;
93、Redis如何做内存优化?
1、缩短键值的长度
- 缩短值的长度才是关键,如果值是一个大的业务对象,可以将对象序列化成二进制数组;
- 首先应该在业务上进行精简,去掉不必要的属性,避免存储一些没用的数据;
- 其次是序列化的工具选择上,应该选择更高效的序列化工具来降低字节数组大小;
- 以JAVA为例,内置的序列化方式无论从速度还是压缩比都不尽如人意,这时可以选择更高效的序列化工具,如: protostuff,kryo等;
2、共享对象池
对象共享池指Redis内部维护[0-9999]的整数对象池。创建大量的整数类型redisObject存在内存开销,每个redisObject内部结构至少占16字节,甚至超过了整数自身空间消耗。所以Redis内存维护一个[0-9999]的整数对象池,用于节约内存。 除了整数值对象,其他类型如list,hash,set,zset内部元素也可以使用整数对象池。因此开发中在满足需求的前提下,尽量使用整数对象以节省内存;
3、字符串优化
4、编码优化
5、控制key的数量
94、什么是Redis穿透?
Redis穿透就是用户请求透过Redis去请求mysql服务器,导致mysql压力过载。但一个web服务里,极容易出现瓶颈的就是mysql,所以才让Redis去分担mysql 的压力,所以这种问题是万万要避免的;
一般的缓存系统,都是按照key去缓存查询,如果不存在对用的value,就应该去后端系统查找(比如DB数据库)。一些恶意的请求会故意查询不存在的key,请求量很大就会对后端系统造成很大的压力。这就叫做缓存穿透;
解决方法:
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击;
- 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
- 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力;
95、什么是Redis雪崩?
Redis雪崩就是Redis服务由于负载过大而宕机,导致mysql的负载过大也宕机,最终整个系统瘫痪;也就是某key对应的数据存在,但在Redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期,一般都会从数据库中加载数据并设置到缓存中,这个时候大并发的请求可能会瞬间把数据库压垮;
解决方法:
- Redis集群,将原来一个人干的工作,分发给多个人干;
- 缓存预热(关闭外网访问,先开启mysql,通过预热脚本将热点数据写入缓存中,启动缓存。开启外网服务)
- 不同的key,设置不同的过期时间,让缓存失效的时间尽量均匀,不然过期时,Redis压力会大;
96、Redis最适合的场景?
缓存
将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率;
会话缓存(Session Cache)
最常用的一种使用 Redis 的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如 Memcached)的优势在于:Redis 提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗? 幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用 Redis 来缓存会话的文档。甚至广为人知的商业平台Magento 也提供 Redis 的插件;
可以使用 Redis 来统一存储多台应用服务器的会话信息。当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性;
全页缓存(FPC)
除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进。 再次以Magento为例,Magento提供一个插件来使用 Redis作为全页缓存后端。 此外,对 WordPress 的用户来说,Pantheon 有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面;
队列
Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如 Python)对 list 的 push/pop操作。 如果你快速的在 Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery 有一个后台就是使用 Redis 作为 broker,你可以从这里去查看;
排行榜
Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的 10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可: 当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:ZRANGE user_scores 0 10 WITHSCORES Agora Games 就是一个很好的例子,用Ruby实现的,它的排行榜就是使用 Redis 来存储数据的,你可以在这里看到;
发布/订阅(了解,没用过网上看的)
Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用 Redis 的发布/订阅功能来建立聊天系统;
分布式锁实现
在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现;
计数器
可以对 String 进行自增自减运算,从而实现计数器功能。Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量;
97、Redis内存淘汰策略
- noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键;
- allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键;
- volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键;
- allkeys-random:加入键的时候如果过限,从所有key随机删除;
- volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐;
- volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键;
- volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键;
- allkeys-lfu:从所有键中驱逐使用频率最少的键;
98、SpringBoot和SpringCloud区别
- SpringBoot可以离开SpringCloud独立开发项目,但是SpringCloud离不开SpringBoot,属于依赖关系;
- SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架;
99、你在开发中咋么限制由连续连点击造成重复请求?
对于一些用户请求,如果是查询类操作并无大碍,但其中有些是涉及写入操作的一旦重复了,可能会导致很严重的后果,例如交易的接口如果重复请求可能会重复下单;防止连续点击,主要分为前端限制和接口限制;
前端方面
防抖是控制次数,节流是控制频率;
防抖(debounce)
防抖就是指触发事件后在n秒内函数只能执行一次,如果在n秒内又触发了事件,则会重新计算函数执行时间;当一个动作连续触发,则只执行最后一次;
函数防抖的简单实现:
const _debounce = (func, wait) => { let timer; return () => { clearTimeout(timer); timer = setTimeout(func, wait); }; };
函数防抖在执行目标方法时,会等待一段时间。当又执行相同方法时,若前一个定时任务未执行完,则
clear
掉定时任务,重新定时;
节流(throttle)
节流就是指连续触发事件但是在n秒中只执行一次函数;限制一个函数在一定时间内只能执行一次;
1、函数节流的setTimeout`版简单实现
const _.throttle = (func, wait) => { let timer; return () => { if (timer) { return; } timer = setTimeout(() => { func(); timer = null; }, wait); }; };
- 函数节流的目的,是为了限制函数一段时间内只能执行一次。因此,通过使用定时任务,延时方法执行。在延时的时间内,方法若被触发,则直接退出方法。从而,实现函数一段时间内只执行一次。
2、函数节流的时间戳版简单实现
const throttle = (func, wait) => { let last = 0; return () => { const current_time = +new Date(); if (current_time - last > wait) { func.apply(this, arguments); last = +new Date(); } }; };
- 其实现原理,通过比对上一次执行时间与本次执行时间的时间差与间隔时间的大小关系,来判断是否执行函数。若时间差大于间隔时间,则立刻执行一次函数。并更新上一次执行时间;
加loading
给按钮上添加loading,直到请求完成后才结束loading,可以在一定程度防止重复请求;
接口方面
在数据库添加唯一字段
在数据库建表的时候在ID字段添加主键约束,账号,名称的信息添加唯一性约束。确保数据库只可以添加一条数据;可以有效的防止了数据重复提交;
加一张关系表
每次请求在进行业务处理前都往该表插入一条记录,请求完成后删除该记录。通过使用数据库中的唯一键保证在同一时刻只有一个请求可以正常插入request表,插入失败的请求将不做业务处理。但是如果请求量比较大的话会损耗DB的性能且不容易扩展;
设置请求次数
通过用户具有唯一性的ID验证用户的身份,每次用户调用接口,将此用户的ID进行记录,在有效的时间之内,用户每调用一次接口,给调用的次数+1,当超过指定的次数时(次数根据自己的时间情况进行定义),将调用这个接口的用户ID加入黑名单中,当锁定之后移除在黑名单中的用户ID进行解锁(锁定时间根据实际情况进行定义),但是每次用户在短时间内调用接口,实时记录的ID,次数,时间可以存到数据库或者Redis;
100、什么情况下会分库分表,目的是啥?
一般当存储占用100G+时、数据增量每天 200w+时、单表条数 1 亿条+时会考虑分库分表;分库分表的目的就是为了解决由于数据量过大而导致数据库性能降低的问题,将原来独立的数据库拆分成若干数据库组成,将数据大表拆分成若干数据表组成,使得单一数据库、单一数据表的数据量变小,从而达到提升数据库性能的目的;
101、说一下MySQL的分库分表
写在前面:因为我接触过的项目数据量都不是很大,所以没有涉及到分库的概念,但是面试是会问到的,之后看到了IT邦德大佬的MySQL分库分表,何时分?怎么分?一文,个人认为写的很到位,所以借鉴这篇文章结合我的理解,整理一下;
回答思路可以是:首先回答什么是分库分表,然后分别说一说他们会在什么场景下使用,然后说一下他们的优缺点就OK了;
什么是分库分表
MySQL是我们用的比较多的数据库,如果在使用过程中出现性能问题,会采用 mysql 的横向扩展,使用主从复制来提高读性能;要是解决写入问题,需要进行分库分表。分库分表是业务发展到一定阶段,数据积累到一定量级而衍生出来的解决方案。当数据量级到达一个阶段, 写入和读取的速度会出现瓶颈,即使是有索引,索引也会变的很大,而且数据库的物理文件大的会使备份和恢复等操作变的很困难。这个时候已经严重危害到了业务,最有效的解决方案就是分库分表了;数据库表的拆分解决的问题主要是存储和性能问题,MySQL在单表数据量达到一定量级后,性能会急剧下降,相比较于收费数据库来说,还是处于弱势,但是表的拆分这个策略却适用于几乎所有的关系型数据库;
分库
分库是指把一个数据库拆分为多个数据库,一般分为垂直分库和水平分库;
垂直分库:
垂直分库以表为依据,按照业务归属不同,将不同的表拆分到不同的业务库中。每个库可以放在不同的服务器上,核心理念是专库专用;
- 结果:垂直分库的结果是每个库的表结构都不一样;每个库的数据也不一样,没有交集;所有库的并集是全量数据;
- 场景:系统绝对并发量上来了,并且可以抽象出单独的业务模块;
- 分析:到这一步,基本上就可以服务化了。例如随着业务的发展一些公用的配置表、字典表等越来越多,这时可以将这些表拆到单独的库中。再有随着业务的发展孵化出了一套业务模式,这时可以将相关的表拆到单独的库中,甚至可以服务化。
水平分库:
水平分库是以字段为依据,按照一定策略(hash、range 等),将一个库中的数据拆分到多个库中;
- 结果:水平分库的结果是每个库的结构都一样,但是每个库的数据都不一样,没有交集;所有库的并集是全量数据;
- 场景:系统绝对并发量上来了,分表难以根本上解决问题,并且还没有明显的业务归属来垂直分库;
- 分析:库多了,IO 和 CPU 的压力自然可以成倍缓解;
分表
分表指的是通过一定规则,将一张表分解成多张不同的表,一般分为垂直分表和水平分表;
垂直分表:
垂直分表即“宽表拆窄表”,以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。垂直分表一般是表中的字段较多,将冗余字段,不常用字段,数据较大,长度较长(例如 text 类型字段)的拆分到“扩展表“。一般是针对那种几百列的宽表,也可以避免在查询时,数据量太大造成的“跨页”问题。
- 结果:垂直分表的结果是每个表的结构都不一样;每个表的数据也不一样,一般来说,每个表的字段至少有一列交集,一般是主键,用于关联数据;所有表的并集是全量数据;
- 场景:系统绝对并发量并没有上来表的记录并不多,但是字段多并且热点数据和非热点数据在一起,单行数据所需的存储空间较大。以至于数据库缓存的数据行减少,查询时会去读磁盘数据产生大量的随机读IO,产生IO瓶颈;
- 分析:垂直分表比较适用于那种字段比较多的表,原则是将热点数据(可能会冗余经常一起查询的数据)放在一起作为主表,非热点数据放在一起作为扩展表。这样更多的热点数据就能被缓存下来,进而减少了随机读 IO。拆了之后,要想获得全部数据就需要关联两个表来取数据;
通常我们按以下原则进行垂直拆分
- 把不常用的字段单独放在一张表;
- 把 text,context等大字段拆分出来放在附表中;
- 经常组合查询的列放在一张表中;
水平分表:
水平分表是以字段为依据,按照一定策略(hash、range 等),将一个表中的数据拆分到多个表中,也称为库内分表;
- 结果:水平分表的结果是每个表的结构都一样;每个表的数据都不一样,没有交集;所有表的并集是全量数据;
- 场景:系统绝对并发量并没有上来,只是单表的数据量太多,影响了 SQL 效率,加重了 CPU 负担,以至于成为瓶颈;
- 分析:表的数据量少了,单次 SQL 执行效率高,自然减轻了 CPU 的负担;
优缺点
垂直拆分优点
- 跟随业务进行分割,和最近流行的微服务概念相似,方便解耦之后的管理及扩展;
- 高并发的场景下,垂直拆分使用多台服务器的 CPU、I/O、内存能提升性能,同时对单机数据库连接数、一些资源限制也得到了提升;
- 能实现冷热数据的分离;
水平拆分的优点
- 水平扩展能无限扩展,不存在某个库某个表过大的情况;
- 能够较好的应对高并发,同时可以将热点数据打散;
- 应用侧的改动较小,不需要根据业务来拆分;
- 分库分表的顺序应该是先垂直分,后水平分,先垂直分表,再垂直分库,再水平分库,最后水平分表。因为垂直分更简单,更符合人们处理现实世界问题的方式;
102、SpringBoot中咋么配置Mybatis
可以在Maven中导入Mybatis依赖进而导入Mybatis
引入依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
<scope>runtime</scope>
</dependency>
配置yml文件
引入数据源、Mybatis配置
#配置数据源
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root
#MyBatis配置
mybatis:
mapper-locations: classpath:mapper/*.xml #对应mapper映射xml文件所在路径
编写映射文件
我们在配置的相应的路径下创建Mapper接口文件以及其映射文件即可;
103、&和&&有啥区别
&按位与:不管前面的条件是否正确,后面都执行;
&&逻辑与:前面条件正确时才执行后面,不正确时就不执行;&&效率比较高;当且仅当两个操作数均为 true时,其结果才为true,只要有一个为false就为false;
- &&是逻辑运算中的“短路”,若"&&“前的条件为false时判断结果为false,不再执行”&&“后的语句块。”&&"前为true则继续进行进行后半部分的判断 ;
104、创建线程的方式
一般创建线程常用继承Thread类和实现Runnable接口两种方式,当然也可以使用ExecutorService、Callable、Future实现有返回结果的多线程;这里主要介绍前两种;
继承Thread类实现多线程
一个类只要继承了Thread就是多线程的实现类,必须重写run方法;
步骤:
自定义一个类,这个类继承自Thread类
在这个类重写Thread类中的run方法(一般存储耗时的代码)
在主线程中开启子线程 main线程
- 创建自定义线程类对象
- 使用线程类对象调用start()方法
public class TestExtendsThreads {
public static void main(String[] args) {
//创建线程对象
MyThread1 t1 = new MyThread1();
t1.start();//线程启动(JVM调用run方法)
for(int i = 1; i<50; i++){
System.out.println("Main:"+i);
}
}
}
//线程类 自定义线程
class MyThread1 extends Thread{
public void run(){
//线程的执行具有随机性,存储"耗时代码"
for(int i = 1; i<50; i++){
System.out.println("MyThread1:"+i);
}
}
}
实现Runnable接口方式实现多线程
只需要实现一个抽象方法:public void run();
步骤:
自定义一个类 , 实现 Runnable接口
实现接口中提供的功能:public abstract void run() ; 比较耗时的操作
用户线程main,创建资源类对象
- 创建Thread类对象,将上面对象作为参数传递
- 分别启动线程
Thread类的构造方法
public Thread(Runnable target):分配一个新的Thread对象
public Thread(Runnable target, String name):分配一个新的Thread对象
Thread.currentThread():获取正在运行的线程
public class TestImplementsRunnable {
public static void main(String[] args) {
//创建资源类对象象
MyRunnable mr1 = new MyRunnable();
//创建线程类对象
Thread t1 = new Thread(mr1);
Thread t2 = new Thread(mr1);
//分别启动线程
t1.start();
t2.start();
for(int i = 1; i<50; i++){
System.out.println("Main:"+i);
}
}
}
class MyRunnable implements Runnable{
public void run(){
for(int i = 1; i<50; i++){
//Thread.currentThread()正在运行的线程
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
- Runnable接口必须要定义一个run的无参数方法
- 实现接口,只是将当前类编成任务类,本身不是个线程
- 更灵活、提供了能力、不影响继承
使用实现Callable接口的方式
步骤:
创建线程池对象: ExecutorService pool = Executors.newFiexdThreadPool(int nThreads){}
提交异步任务 submit(Callable call)
- Callable:接口 (Functional Interface:函数式接口:接口中只有一个抽象方法)
- V call():计算结果
关闭资源:shutdown():关闭线程池资源对象
public class MyCallable implements Callable {
@Override
public Object call() throws Exception{
for(int i = 0 ;i < 200; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return null;
}
}
public static void main(String[] args) {
//创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
//提交异步任务
pool.submit(new MyCallable());
pool.submit(new MyCallable());
//关闭资源
pool.shutdown();
}
105、创建线程的三种方式的对比?
采用实现Runnable、Callable 接口的方式创建多线程
优点:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个 target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模
型,较好地体现了面向对象的思想;
缺点:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法;
使用继承Thread类的方式创建多线程
优点:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用 this 即可获得当前线程;
缺点:
线程类已经继承了Thread类,所以不能再继承其他父类;
Runnable和Callable的区别
- Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run();
- Callable的任务执行后可返回值,而Runnable的任务是不能返回值的;
- Call方法可以抛出异常,run方法不可以;
- 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果;
106、并行和并发有什么区别?
并发:多个任务在同一个CPU核上,按细分的时间片轮流(交替)执行,从逻辑上来看那些任务是同时执行;
并行:单位时间内,多个处理器或多核处理器同时处理多个任务,是真正意义上的“同时进行”;
串行:有n个任务,由一个线程按顺序执行。由于任务、方法都在一个线程执行所以不存在线程不安全情况,也就不存在临界区的问题;
比如:
并发 = 俩个人用一台电脑;
并行 = 俩个人分配了俩台电脑;
串行 = 俩个人排队使用一台电脑;
107、什么是多线程?多线程有优缺点?
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务;
多线程的好处
可以提高 CPU 的利用率。在多线程程序中,一个线程必须等待的时候,CPU 可以运行其它的线程而不是等待,这样就大大提高了程序的效率。也就是说允许单个程序创建多个并行执行的线程来完成各自的任务;
多线程的劣势
- 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
- 多线程需要协调和管理,所以需要 CPU 时间跟踪线程;
- 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;
108、Java中线程的基本状态
实现多线程就需要在主线程创建线程对象,但是线程因为需求的不同往往不是理性状态;当线程被创建并启动以后它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5 种状态。尤其是当线程启动以后,它不可能一直"霸占"着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换;
线程的状态_基础(理想化状态)
-
New(初始状态):当程序使用new关键字创建了一个线程之后(Thread t = new MyThread()),该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值;
-
Ready(就绪状态):初始状态调用**start()**方法就可以启动线程进入就绪状态等待OS选中并分配时间片;处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了start()此线程立即就会执行;
注:就绪状态是进入到运行状态的唯一入口,也就是说线程要想进入运行状态执行,首先必须处于就绪状态中;
-
Running(运行状态):当被OS选中并且获得了时间片之后就会进入该状态,如果时间片到期就会回到就绪状态,要想继续执行只能等待下一次被OS选中;
-
Terminated(终止状态):当所有的代码都执行完毕后由主线程或独立线程调用**run()**方法结束后,进入该状态,并释放时间片,处于终止状态的线程具有继续运行的能力;
线程的状态_等待
但是在真正执行过程中往往不会是理想的状态,我们出门坐公交都会有等车的时候,更何况是线程!因此线程也会有等待的状态;
- New(初始状态)
- Ready(就绪状态)
- Running(运行状态):上面说了当被OS选中并且获得了时间片之后就会进入该状态;
- Timed Waiting(限期等待):当调用了**sleep(i)**方法后就会进入Timed Waiting(限期等待),当所给的i(2000毫秒)到期后就会冲回到Ready,参与时间片的竞争;
- Waiting(无限等待):当调用了**join()**方法后就会进入Waiting(无限等待),类似于插队谁掉用join方法就会优先执行直到该线程执行完毕,其他线程才可以竞争时间片;
- Terminated(终止状态)
线程状态_阻塞
我们在坐车的时候如果运气不好你不光要等,在路上有可能会有堵车的情况,因此我们的线程也会有阻塞的状态!
- New(初始状态)
- Ready(就绪状态)
- Blocked(阻塞状态):处于运行状态中的线程由于某种原因(加入同步锁synchronized),暂时放弃对CPU的使用权停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态;
- Running(运行状态)
- Timed Waiting(限期等待)
- Waiting(无限等待)
- Terminated(终止状态)
109、阻塞状态有哪几种
根据阻塞产生的原因不同,阻塞状态可以分为三种;
- 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
- 同步阻塞:在运行时期加入同步锁synchronized多个线程抢到时间片后在抢锁,谁先抢到锁谁执行,没有拿到锁的就会暂时放弃对CPU的使用权,进入阻塞状态;他只能等待抢到锁的线程释放锁,然后在去竞争锁,竞争到锁从阻塞状态回到就绪状态那时间片执行自己的代码;
- 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O 处理完毕时,线程重新转入就绪状态;
110、可以直接调用Thread类的run ()方法么?
可以;但是如果我们调用了Thread 的run()方法,它的行为就会和普通的方法一样,会在当前线程中执行。为了在新的线程中执行我们的代码,必须使用Thread.start()方法;
111、启动线程方法start()和 run()有什么区别?
- start()方法用于启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;通过调用Thread类的start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行;
- 方法run()称为线程体用于执行线程的运行时代码,它包含了要执行的这个线程的内容,这时线程就进入运行状态,开始运行run函数当中的代码。 Run方法运行结束, 此线程终止,然后CPU再调度其它线程;
- run() 可以重复调用,而start() 只能调用一次;
112、线程中的wait()和 sleep()方法有什么区别?
sleep方法和wait方法都可以用来放弃CPU一定的时间也就是暂停线程的执行,不同点在于如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器,详细区别:
- 类的区别:sleep()方法属于Thread 类,wait()方法属于Object 类;
- 是否释放锁:在调用sleep()方法的过程中,线程不会释放对象锁,而wait()会释放锁;
- 用途:sleep()通常被用于暂停执行,wait()通常被用于线程间交互/通信;
- 用法:sleep()方法导致了程序暂停执行指定的时间,让出CPU该其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态,或者可以使用wait(longtimeout)超时后线程会自动苏醒;wait()方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()或者notifyAll()方法;
113、线程中常用的方法
线程相关的基本方法有wait(强迫一个线程等待),notify(通知一个线程继续执行),notifyAll(所有线程继续执行),sleep(强迫一个线程睡眠N毫秒),join(等待线程终止),yield(线程让步)等等;
获取和设置线程名称
线程的名字一般在启动前设置,两个名字可以重复但是一般不这样做;
- public final void setName(String name):给线程可以设置线程名称
- public final String getName():获取该线程名称
- public static Thread currentThread():得到正在运行的线程
设置守护线程
守护线程是运行在后台的一种特殊进程,在程序中只要有一个线程在运行,整个程序不会消失,设置一个守护线程即使程序结束,后台仍然会继续执行;在Java中垃圾回收线程就是特殊的守护线程;(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)
- public final void setDaemon(boolean on):设一个线程是否是一个守护线程 ,参数如果为true守护线程/后台线程
- public
join()方法
在线程的操作中加入join()方法(类似于插队)让一个线程强制执行,此线程运行期间其他线程必须等待此线程执行完毕后才可以继续执行(进入就绪再次竞争时间片);
- public final void join():让其他的线程加入到当前线程
sleep()方法
程序中调用sleep方法,可以使得线程暂时休眠;(类似于睡觉,醒来继续竞争)
- public static void sleep(long millis):让该线程休眠millis毫秒
yied()方法
在线程操作中,调用yied()方法可以使当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片;(类似与礼让)
- public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
线程的优先级
Java程序中所有线程在运行之前都会在就绪状态,因此就存在优先级的问题!默认的优先级是5,
- public final void setPriority(int newPriority):更改线程的优先级
- public final int getPriority():获取优先级
优先级都是自定义的常量:
public static final int MAX_PRIORITY 10 :最大优先级
public static final int NORM_PRIORITY 5 :默认优先级
public static final int MIN_PRIORITY 1 :最小优先级
线程停止
当一个线程运行时,另外一个线程可以直接调用interrupt()方法中断其运行状态,也可以是stop()方法;
- public void interrupt():中断线程一种状态(睡眠,其他状态…)
- public final void stop():强迫线程停止执行(虽然过时方法,可以用)
线程唤醒(notify)
Object类中的notify() 方法,唤醒在此对象监视器上等待的单个线程让其继续运行,如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意的,并在对实现做出决定时发生,线程通过调用其中一个wait() 方法,在对象的监视器上等待,直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。类似的方法还有notifyAll() ,唤醒再次监视器上等待的所有线程;
其他方法
- sleep():强迫一个线程睡眠N毫秒;
- isAlive(): 判断一个线程是否存活;
- join(): 等待线程终止;
- activeCount(): 程序中活跃的线程数;
- enumerate(): 枚举程序中的线程;
- currentThread(): 得到当前线程;
- isDaemon(): 一个线程是否为守护线程;
- wait(): 强迫一个线程等待;
114、notify()和notifyAll()有什么区别?
notifyAll()会唤醒所有的线程,notify()只会唤醒一个线程;
如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁;就必须等其他线程调用notify()或者notifyAll():使用notifyall(),可以唤醒所有处于wait状态的线程,使其重新进入锁的争夺队列中,而notify()只能唤醒一个,具体唤醒哪一个线程由虚拟机控制;
- 如果没把握,建议使用notifyAll(),防止notify()因为信号丢失而造成程序异常;
115、为什么我们调用start() 方法时会执行run() 方法,为什么我们不能直接调用run() 方法?
当new一个Thread,线程进入了新建状态。调用start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start()会执行线程的相应准备工作,然后自动执行run()方法的内容,这是真正的多线程工作。
而直接执行run() 方法,会把run 方法当成一个main线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
- 当你调用start()方法时你将创建新的线程可启动线程并使线程进入就绪状态,也会执行在run()方法里的代码。但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码,只会把run方法当作普通方法去执行;
116、interrupt和stop区别
interrupt:中断线程的状态(睡眠状态),依然可以执行线程;
stop:线程被强迫终止,在启动线程之前,如果调用线程的stop方法,不会执行线程;
117、为什么要用 join()方法?
很多情况下,主线程生成并启动了子线程,需要用到子线程返回的结果,也就是主线程需要在子线程结束后再结束,这时候就要用到join()方法;
System.out.println(Thread.currentThread().getName() + "线程运行开始!");
Thread1 son = new Thread1();
son.setName("线程B");
son.join();
System.out.println("这时thread1执行完毕之后才能执行主线程");
118、简述一下你了解的设计模式
设计模式的水太深就说自己最熟悉的、用得最多的回答,不要自己给自己挖坑;因为我Spring方面用的比较多,spring里面的控制反转有用到工厂模式,有时也会写写单例,所以我一般就说这两个,下面这纯属复制混个眼熟;
所谓设计模式,就是一套被反复使用的代码设计经验的总结(情境中一个问题经过证实的一个解决方案)。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式使人们可以更加简单方便的复用成功的设计和体系结构;
设计模式主要分为三类:
- 创建型:对类的实例化过程的抽象化;
- 结构型:描述如何将类或对象结合在一起形成更大的结构;
- 行为型:对在不同的对象之间划分责任和算法的抽象化;
共23种设计模式,包括:
Abstract Factory(抽象工厂模式),Builder(建造者模式),Factory Method(工厂方法模式),Prototype(原始模型模式),Singleton(单例模式);Facade(门面模式),Adapter(适配器模式),Bridge(桥梁模式),Composite(合成模式),Decorator(装饰模式),Flyweight(享元模式),Proxy(代理模式);Command(命令模式),Interpreter(解释器模式),Visitor(访问者模式),Iterator(迭代子模式),Mediator(调停者模式),Memento(备忘录模式),Observer(观察者模式),State(状态 模式 ),Strategy(策略 模式 ),Template Method(模板方法模式),Chain Of Responsibility(责任链模式);
面试被问到关于设计模式时,可以拣常用的回答:
工厂模式(IOC里面用到了):
工厂类可以根据条件生成不同的子类实例,这些子类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作(多态方法)。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例;
代理模式:
给一个对象提供一个代理对象,并由代理对象控制原对象的引用。实际开发中,按照使用目的的不同,代理可以分为远程代理、虚拟代理、保护代理、Cache 代理、防火墙代理、同步化代理、智能引用代理;
适配器模式:
把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起使用的类能够一起工作;
模板方法模式:
提供一个抽象类,将部分逻辑以具体方法或构造器的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法(多态实现),从而实现不同的业务逻辑。除此之外,还可以讲讲上面提到的门面模式、桥梁模式、单例模式、装潢模式(Collections 工具类和 I/O 系统中都使用装潢模式)等;
119、谈谈你对Spring中的IOC和DI的理解
用过Spring框架就一定都会听过Spring的IOC(控制反转) 、DI(依赖注入)这两个概念,属于Spring的两大核心;
IOC(inverse of control)是面向对象编程中的一种设计原则,用来降低系统之间的耦合度。在程序代码编译之先就已经确立相互间的依赖关系 (由代码控制的依赖关系),将对象的创建由容器完成,事先不知道所对应的对象,只有执行到对应的位置才可以知道对应的对象,也就是说传统的Java开发模式中,当需要一个对象时,我们会自己使用new或者间接调用构造方法创建一个对象。而在spring开发模式中,spring容器使用了工厂模式为我们创建了所需要的对象,不需要我们自己创建了,直接调用spring提供的对象就可以了,这是控制反转的思想;
DI(dependency injection)依赖注入,如果说控制反转是一种设计思想,而依赖注入是控制反转的一种实现方式;编译的时候不知道,只有在运行时由容器动态注入,就是在spring创建时为对应的属性赋值;spring使用javaBean对象的set 方法或者带参数的构造方法为我们在创建所需对象时将其属性自动设置所需要的值的过程,就是依赖注入的思想;
120、什么是面向切面编程?
AOP(Aspect Oriented Programming)面向切面编程,与OOP面向对象编程相辅相成;在OOP思想中我们将事物纵向抽成一个个的对象,基本单元是类。而在AOP面向切面编程中,我们将分散在各处的相同业务集中管理的编程方式横向抽成一个切面,对这个切面进行一些如权限控制、事物管理,记录日志等公用操作处理的过程就是面向切面编程的思想,基本单元是Aspect(切面);
比如我们把功能分为核心业务功能和周边功能,面向切面的编程思想是在添加周边功能的时候,不影响核心业务功能;假设把用户的请求、新增、删除是核心业务,日志是周边功能,在增加新的日志时不影响核心的业务功能;
AOP底层是动态代理,如果是接口采用JDK动态代理,如果是类采用CGLIB方式实现动态代理;AOP的优势是代码高可用、高复用、后期维护方便;
AOP核心概念
- 切入点(Pointcut):对连接点进行拦截的定义(那些方法上和类上切入);
- 连接点(Joinpoint):因为Spring只支持方法类型的连接点,所以连接点是程序类中的客观存在的方法(还可以是字段或者构造器),可被Spring拦截并切入内容;
- 增强/通知(Advice):所谓通知指的就是拦截到连接点之后要执行的代码,也就是为切入点添加额外功能,AOP提供了5种Advice类型:前置、后置、异常、返回、环绕通知;
- 织入(Weaving):将增强/通知添加到目标类的具体连接点上,进而创建新的代理类的过程;
- 切面(aspect):由切点和增强/通知组成,将横切逻辑织入切面所指定的连接点;也就是说:他定义了在什么地方、什么时候、做什么增强;
- 目标对象(Target):代理的目标对象;
- 引介(Introduction):一种特殊的增强,可在运行期间为类动态的添加Filed和Method;
- 代理(Proxy):被AOP织入通知后,产生的结果类;
121、AOP有哪些实现方式
实现AOP的技术,主要分为两大类:静态代理和动态代理
静态代理
指使用AOP框架提供的命令进行编译,从而在编译阶段就可生成AOP代理类,因此也称为编译时增强;优点是可以在不修改目标对象的前提下扩展目标对象的功能;
例子:
1、创建IMath接口
/**
* 接口调用Calculator
* 父类接口指向子类实现
* Created by Kak
*/
public interface IMath {
public int plus(int a ,int b);
public int minus(int a ,int b);
public int multi(int a ,int b);
public int div(int a ,int b);
}
2、创建Calculator实现IMath接口
/**
* Created by Kak
*/
public class Calculator implements IMath{
public int plus(int a, int b){
return a + b;
}
public int minus(int a, int b){
return a - b;
}
public int multi(int a, int b){
return a * b;
}
public int div(int a, int b){
return a / b;
}
}
3、创建StaticProxy.java实现IMath
静态代理类由原始类的接口加辅助功能加原始类的业务方法组成
import java.util.Random;
/**
* Created by Kak
*/
public class StaticProxy implements IMath{
private IMath math;
public StaticProxy(IMath math){
this.math = math;
}
@Override
public int plus(int a, int b) {
long begin = System.currentTimeMillis();
int result = math.plus(a,b);
this.sleeping();
long end = System.currentTimeMillis();
System.out.println("方法耗时:"+(end - begin));
return result;
}
@Override
public int minus(int a, int b) {
long begin = System.currentTimeMillis();
int result = math.minus(a,b);
this.sleeping();
long end = System.currentTimeMillis();
System.out.println("方法耗时:"+(end - begin));
return result;
}
@Override
public int multi(int a, int b) {
long begin = System.currentTimeMillis();
int result = math.multi(a,b);
this.sleeping();
long end = System.currentTimeMillis();
System.out.println("方法耗时:"+(end - begin));
return result;
}
@Override
public int div(int a, int b) {
long begin = System.currentTimeMillis();
int result = math.div(a,b);
this.sleeping();
long end = System.currentTimeMillis();
System.out.println("方法耗时:"+(end - begin));
return result;
}
public void sleeping(){
Random random = new Random(1000);
try {
Thread.sleep(random.nextInt());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
优点:
- 解决了开闭原则,没有修改Calculator类,扩展出了StaticProxy类;
- 解决了单一职责的问题,Calculator类不再需要去计算耗时与延时操作;
- 引入接口解决了依赖倒转问题;
缺点:
如果项目中有多个类,则需要编写多个代理类,工作量大,不好修改,不能应对变化,维护性差;
动态代理
动态代理分为: JDKProxy和CGLIB,默认的策略是如果目标类是接口,则使用 JDK 动态代理技术,否则使用CGLIB来生成代理;
JDK动态接口代理
JDK动态代理就是动态创建代理类,为原始类的对象添加辅助功能,只能代理有接口的实现; 主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。InvocationHandler是一个接口通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象;
例子
1、创建IMath接口
2、创建Calculator实现IMath接口
3、创建DynamicProxy加入动态代理实现InvocationHandler接口
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Random;
/**
* JDK动态代理
* Created by Kak
*/
public class DynamicProxy implements InvocationHandler{
Object targetObject;
public Object getObject(Object SrcObject){
this.targetObject = SrcObject;
//获取代理对象
/**
* 作用:使用动态代理自动给正在执行的对象的接口生成一个新的派生对象,此对象还有invoke方法中附带的逻辑
* 第一个参数:需要执行方法的对象的类加载器
* 第二个参数:需要执行方法的对象实现的接口
* 第三个参数:InvocationHandler的实现对象
*/
Object o = Proxy.newProxyInstance(SrcObject.getClass().getClassLoader(),SrcObject.getClass().getInterfaces(),this);
return o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long begin = System.currentTimeMillis();
sleeping();
//执行原有的方法
/**
* 第一个参数:方法所在的对象
* 第二个参数:方法执行的实参
*/
Object result = method.invoke(targetObject,args);
long end = System.currentTimeMillis();
System.out.println("方法耗时:" + (end - begin));
return result;
}
public void sleeping(){
try {
Thread.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4、创建测试类
/**
* Created by Kak
*/
public class TestCalculator {
public static void main(String[] args) {
//源对象
Calculator cal = new Calculator();
//自定义的附带有计算耗时的动态代理
DynamicProxy proxy = new DynamicProxy();
IMath newObj = (IMath)proxy.getObject(cal);
int plus = newObj.plus(1, 10);
System.out.println(plus);
}
}
JDK动态代理的优缺点
优势:
- 解决了静态代理存在的问题;
- JDK内置的Proxy动态代理可以在运行时动态生成字节码,而没必要针对每个类编写代理类;使用到了一个接口InvocationHandler与Proxy.newProxyInstance静态方法;
劣势:
- 被代理的类必须实现接口,未实现接口则没办法完成动态代理;
CGLib 动态代理
CGLIB(Code Generation Library)是一个开源项目,高性能,高质量的代码生成类库,可以在运行期扩展Java类与实现Java接口,动态生成字节码CGLib基于ASM的字节码生成库,可以再运行期动态生成新的class。和JDK动态代理相比较:JDK创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过CGLib使用继承的方式实现动态代理;
例子
1、编写Calculator.java需要被代理的类
2、 编写DynamicProxy.java实现cglib代理
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.Random;
/**
* Created by Kak.
*/
public class DynamicProxy implements MethodInterceptor{
Object targetObj;
public Object getObject(Object srcObj){
this.targetObj = srcObj;
//创建代码增强器
Enhancer enhancer = new Enhancer();
//设定增强器的父类
enhancer.setSuperclass(srcObj.getClass());
//设定增强器回调对象
enhancer.setCallback(this);
//获取附加逻辑的新对象
Object o = enhancer.create();
return o;
}
/**
* @param o 需要代理的对象
* @param method 需要执行的方法
* @param objects 执行方法所需要的实参
* @param methodProxy 代理的对象
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long begin = System.currentTimeMillis();
sleeping();
Object invoke = method.invoke(targetObj, objects);
long end = System.currentTimeMillis();
System.out.println("耗时:"+ (end - begin));
return invoke;
}
public void sleeping(){
try {
Thread.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3、编写测试类
/**
* Created by Kak .
*/
public class TestCglib {
public static void main(String[] args) {
Calculator newObj = (Calculator)new DynamicProxy().getObject(new Calculator());
int minus = newObj.minus(20, 30);
System.out.println(minus);
}
}
注意:
- CGLIB继承被代理的类,重写方法,织入通知,动态生成字节码并运行(动态的生成一个子类去覆盖所要代理的类);
- 因为是继承所以final类是没有办法动态代理的;
- 必须引入CGLIB的jar包;
- 不需要接口可以在运行时,动态生成新的派生匿名对象从而附加新的功能;
122、AOP的通知类型
定义通知类可以达到通知的效果;
前置通知(Method Advice):在某些连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常);
后置通知(After Returning Advice):在某连接点正常完成后执行的通知:例如一个方法没有抛出任何异常正常返回; 有异常不执行,无返回值,方法会因为异常而结束;
异常通知(Throws Advice):在方法抛出异常退出时执行的通知;
环绕通知(Around Advice):包围一个链接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。他也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行;
123、Spring MVC原理
Spring的模型-视图-控制器(MVC)框架是围绕一个DispatcherServlet来设计的,这个Servlet会把请求分发给各个处理器,并支持可配置的处理器映射、视图渲染、本地化、时区与主题渲染等,甚至还能支持文件上传;
DispatcherServlet流程
DispatcherServlet是SpringMVC工作流程的中心,负责调用其他组件,这个类会在系统启动的时候加载;
Http请求到DispatcherServlet
(1) 客户端向服务器发送请求,请求被SpringMVC前端控制器DispatchServlet捕获;
HandlerMapping寻找处理器
(2) DispatcherServle对请求URL进行解析,得到请求资源标识符(URL),然后根据该URL调用HandlerMapping将请求映射到处理器HandlerExcutionChain;
调用处理器Controller
(3) DispatcherServlet将请求提交到 Controller;
Controller调用业务逻辑处理后,返回ModelAndView
(4)(5)调用业务处理和返回结果:Controller调用业务逻辑处理后,返回ModelAndView;
DispatcherServlet查询ModelAndView
(6)(7)处理视图映射并返回模型: DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图;
ModelAndView反馈浏览器HTTP
(8) Http响应:DispatcherServle通过model解析出ModelAndView()中的参数进行解析最终展现出完整的view并返回给客户端;
124、SpringMVC常用的注解
其实SpringMVC中的注解在SpringBoot中也必不可少,大部分都不算是独有的!
- @Controller:用于标注控制层组件,标记到类上就是一个SpringMVC的controller对象;分发处理器将会扫描使用了该注解的类的方法。被Controller标记的类就是一个控制器,这个类中的方法,就是相应的动作;
- @ResponseBody:作⽤于⽅法上,可以将整个返回结果以某种格式返回,如json或xml格式;
- @RestController:相当于@Controller+@ResponseBody;
- @Component:在类定义之前添加@Component注解,他会被spring容器识别,并转为bean(不好归类时,使用这个注解进行标注);
- @Repository:对Dao实现类进⾏注解 (特殊的@Component);
- @Service:⽤于对业务逻辑层进⾏注解 (特殊的@Component);
- @RequestMapping:用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径;
- @Autowired:对类成员变量、方法及构造函数进行标注,完成自动装配的工作,可以消除set、get方法;
- @RequestParam:⽤于获取传⼊参数的值,类似于request.getParameter(“name”)
- @PathViriable:⽤于定义路径参数值,取出url模板中的变量作为参数;
- @RequestHeader:可以把Request请求header部分的值绑定到方法参数上;
125、RestController和Controller有啥区别
@RestController注解相当于@ResponseBody + @Controller合在一起的作用;
-
使用@Controller注解,在对应的方法上,视图解析器可以解析return 的jsp,html页面,并且跳转到相应页面;若返回json等内容到页面,则需要加@ResponseBody注解;
-
@RestController注解,相当于@Controller+@ResponseBody两个注解的结合,返回json数据不需要在方法前面加@ResponseBody注解了,但使用@RestController这个注解,就不能返回jsp,html页面,视图解析器无法解析jsp,html页面;
126、http请求分为哪几个部分
HTTP请求的组成:状态行、请求头、消息主体三部分组成;
HTTP响应的组成:状态行、响应头、响应正文;
127、面向对象编程中的六大原则
- 单一职责原则(Single Responsibility Principle):即一个类只负责一项职责;
- 里氏替换原则(Liskov Substitution Principle):子类可以扩展父类的功能,但不能改变父类原有的功能,不要破坏继承体系;
- 依赖倒置原则(Dependence Inversion Principle):核心思想是面向接口编程;
- 接口隔离原则(Interface Segregation Principle):建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少,也就是设计接口的时候要精简单一;
- 迪米特法则(Law Of Demeter):对于被依赖的类来说,无论逻辑多么复杂都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息,也就是降低耦合;
- 开闭原则(Open Close Principle):一个软件实体如类、模块和函数应该对扩展开放,对修改关闭;
128、雪花算法是咋么实现的?
雪花算法(SnowFlake)算法,是Twitter开源的分布式id生成算法。其核心思想就是:使用64位long类型的数字作为全局唯一ID,且ID引入了时间戳,基本上保持自增的;
组成部分
这64个bit中,其中1个bit是不用的,然后用其中的41bit作为毫秒数,用10bit作为工作机器id,12bit作为序列号;
可以分为四部分
- 1个bit:最高位一位存储0或者1,0代表整数,1代表负数,一般都是0,所以最高位不变;
- 41个bit:存储毫秒级时间戳;
- 10个bit:存储机器码(包括5位机房id和5位机器id)
- 12个bit:表示的序号,就是某个机房某台机器上这一毫秒内同时生成的id的序号;
雪花算法的优点
- 高性能高可用:生成时不依赖于数据库,完全在内存中生成;
- 容量大:每秒中能生成数百万的自增ID;
- ID自增:存入数据库中,索引效率高;
雪花算法的缺点
- 依赖与系统时间的一致性,如果系统时间被回调,或者改变,可能会造成id冲突或者重复;
- 当然几乎没有公司会修改服务器时间,修改以后会导致各种问题,公司宁愿新加一台服务器也不愿意修改服务器时间,但是不排除特殊情况;
129、简单说一下冒泡排序
冒泡排序比较的是相邻的元素,如果第一个数比第二个数大(小),就交换两个元素,没个相邻元素都这么比,直到比较最后一对,此时最后的数应该是最大(小)的;对所有元素重复此操作,就可以得出一个递增(递减)的数组;
需求:键盘录入一个数组(5个不同的整数)通过冒泡排序将数组进行排序并打印
- 0索引和1索引比较,如果0索引大于1索引的值,交换位置否则位置不变;1索引和2索引比较,方法类似
public class BubbleTest {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int[] arr = new int[5];
for(int i = 0; i < 5; i++){
System.out.println("输入第"+(i+1)+"个数字:");
arr[i] = sc.nextInt();
}
System.out.println("排序前:");
printArray(arr);
BubbleShot(arr);
System.out.println("排序后:");
printArray(arr);
}
//冒泡排序
private static void BubbleShot(int[] arr) {
for(int i = 0; i < arr.length-1; i++){
for(int j = 0; j < arr.length-1-i; j++){
if(arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
//遍历数组
private static void printArray(int[] arr) {
System.out.print("[");
for(int i = 0; i < arr.length; i++){
if(i == arr.length-1){
System.out.println(arr[i] + "]");
}else{
System.out.print(arr[i] + "\t");
}
}
}
}
130、mysql里面的having的用法
在SQL中增加HAVING子句原因是,WHERE关键字无法与聚合函数一起使用,HAVING子句可以让我们筛选分组后的各组数据;用having去掉不符合条件的组,having子句中的每一个元素必须出现在select列表中(只针对于mysql);
131、什么是线程死锁
死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程),也就是两个线程相互等待对方释放对象锁;多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止;
132、死锁产生的必要条件是啥?
- 互斥条件:在一段时间内某资源只由一个进程占用,如果此时还有其它进程请求资源,就只能等待,直至占有资源的进程用毕释放;
- 占有且等待条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放;
- 不可抢占条件:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来;
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。(比如一个进程集合,A在等B,B在等C,C在等A);
133、如何避免线程死锁
- 避免一个线程同时获得多个锁;
- 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源;
- 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制;
134、进程和线程之间的区别
要想知道二者的区别就先得整明白什么是线程什么是进程?
- 进程:一个在内存中运行的应用程序。 每个正在系统上运行的程序都是一个进程;
- 线程:进程中的一个执行任务(控制单元), 它负责在程序里独立执行;
注:一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据;
进程与线程的区别
- 根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位;
- 资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小;
- 包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程;
- 内存分配:同一进程的线程共享本进程的地址空间和资源,而进程与进程之间的地址空间和资源是相互独立的;
- 影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃有可能导致整个进程都死掉,所以多进程要比多线程健壮;
- 执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行;
135、为啥线程不安全的效率高
线程的安全是以牺牲效率为代价的,所谓线程安全就是多了个加锁、解锁的操作,比如100亿个操作中都要加锁和解锁,线程是安全了,但效率就下降了。而有些软件是以效率为主的,为了提高效率,就少了加锁,解锁的操作,虽然容易出现并发访问问题,但效率却提高了;
136、谈谈你对线程池的理解
这个问题也是一个范围比较广的问题,我们可以从什么实现城池出发,然后说一下他的作用最后说一下线程池的优点;
什么是线程池
Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。在开发过程中,合理地使用线程池能够带来许多好处;
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗;
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行;
- 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用;
线程池作用
线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率;如果一个线程所需要执行的时间非常长的话,就没必要用线程池了(不是不能作长时间操作,而是不宜。本来降低线程创建和销毁,结果你那么久我还不好控制还不如直接创建线程),况且我们还不能控制线程池中线程的开始、挂起、和中止;
线程池有什么优点
- 降低资源消耗:重用存在的线程,减少对象创建销毁的开销;
- 提高响应速度:可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。当任务到达时,任务可以不需要的等到线程创建就能立即执行;
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控;
- 附加功能:提供定时执行、定期执行、单线程、并发数控制等功能;
137、线程池四种创建方式?
- newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程;
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待;
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行;
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行;
138、线程池都有哪些状态?
- RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务;
- SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务;
- STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程;
- TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated();
- TERMINATED:terminated()方法结束后,线程池的状态就会变成这个;
139、线程池的执行原理?
提交一个任务到线程池中,线程池的处理流程如下:
- 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程;
- 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程;
- 判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务;
140、谈谈你对synchronized 的理解,你在项目中用到过么?
在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行,synchronized可以修饰类、方法、变量;
synchronized底层实现原理
Synchronized的底层是通过一个monitor(监视器锁)的对象来完成,每个对象有一个监视器锁(monitor)。每个Synchronized修饰过的代码当它的monitor被占用时就会处于锁定状态并且尝试获取monitor的所有权 :
- 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;
- 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;
- 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权;
synchronized关键字最主要的三种使用方式
- 修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁;
- 修饰静态方法: 也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态synchronized方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁;
- 修饰代码块: 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁;
总结: synchronized关键字加到static静态方法和synchronized(class)代码块上都是是给Class类上锁。synchronized关键字加到实例方法上是给对象实例上锁;
synchronized可重入的原理
重入锁是指一个线程获取到该锁之后,该线程可以继续获得该锁。底层原理维护一个计数器,当线程获取该锁时,计数器加一,再次获得该锁时继续加一,释放锁时,计数器减一,当计数器值为0时,表明该锁未被任何线程所持有,其它线程可以竞争获取锁;
141、Linux的基础命令
cd :切换目录
cd ../ ;跳到上级目录
cd /opt ;不管现在到那直接跳到指定的opt文件夹中
cd ~ ;切换当前用户的家目录。root用户的家目录就是root目录
pwd :显示当前工作目录的绝对路径
ls :list的缩写,查看当前目录下的所有文件夹
- ls 只列出文件名或目录名
ls -a ;显示所有文件夹,隐藏文件也显示出来
ls -R ;连同子目录一起列出来
ll:list的缩写,查看当前目录下的所有详细信息和文件夹
- ll 结果是详细,有时间,是否可读写等信息
ll -a ;显示所有文件,隐藏文件也显示出来
ll -R ;连同子目录内容一起列出来
ll -h ;友好展示详情信息,可以看大小
ll -al ;即能显示隐藏文件又能显示详细列表
touch:创建文件
touch test.txt ;创建test.txt文件
touch /opt/java/test.java ;在指定目录创建test.java文件
mkdir:创建目录
mkdir 文件夹名称 ;在此目录创建文件夹
mkdir /opt/java/jdk ;在指定目录创建文件夹
cat :查看文件命令(concatenate:显示或把多个文本文件连接起来)
- 可以快捷查看当前文件的内容,不能快速定位到最后一页
cat lj.log ;快捷查看文件命令
Ctrl + c ;暂停显示文件
Ctrl + d ;退出查看文件命令
more:分页查看文件命令 (more:更多的意思)
- 不能快速定位到最后一页
回车:向下n行,需要定义,默认为1行。
空格键:向下滚动一屏或Ctrl+F
B:返回上一层或Ctrl+B
q:退出more
less:分页查看文件命令 (lese:较少的意思)
- 可以快速定位到最后一页
less -m 显示类似于more命令的百分比
less -N 显示每行的行号。(大写的N)
两参数一起使用如:less -mN 文件名,如此可分页并显示行号
空格键:前下一页或page down
回车:向下一行
b:后退一页 或 page up
q:退出。
d:前进半页
u:后退半页
tail:查看文件命令(看最后多少行)
tail -10 ;文件名 看最后10行
cp:copy单词缩写,复制功能
cp /opt/java/java.log /opt/logs/ ;把java.log 复制到/opt/logs/下
cp /opt/java/java.log /opt/logs/aaa.log ;把java.log 复制到/opt/logs/下并且改名为
aaa.log
cp -r /opt/java /opt/logs ;把文件夹及内容复制到logs文件中
mv:move单词缩写,移动功能,该文件名称功能
mv /opt/java/java.log /opt/mysql/ ;移动文件到mysql目录下
mv java.log mysql.log ;把java.log改名为mysql.log
rm:删除文件或文件夹(remove:移除的意思)
-f或--force 强制删除文件或目录。删除文件不包括文件夹的文件
-r或-R或--recursive 递归处理,将指定目录下的所有文件及子目录一并删除。
-rf 强制删除文件夹及内容
rm 文件名 ;安全删除命令 (yes删除 no取消)
rm -rf 强制删除文件夹及内容
rm -rf * 删除当前目录下的所有内容。
rm -rf /* 删除Linux系统根目录下所有的内容。系统将完蛋。
find:查找指定文件或目录(find:找到的意思)
* 表示0~多个任意字符。
find -name 文件名;按照指定名称查找在当前目录下查找文件
find / -name 文件名按照指定名称全局查找文件
find -name '*文件名' ;任意前缀加上文件名在当前目录下查找文件
find / -name '*文件名*' ;全局进行模糊查询带文件名的文件
vim:改进版文本编辑器
- 不管是文件查看还是文件编辑 按 Shift + 上或者下可以上下移动查看视角
输入”vim 文件名” 打开文件,刚刚时是”一般模式”。
一般模式:可以浏览文件内容,可以进行文本快捷操作。如单行复制,多行复制,单行删除,多行删除,(退出)等。
插入模式:可以编辑文件内容。
底行模式:可以进行强制退出操作,不保存 :q!
可以进行保存并退出操作 :wq
按下”i”或”a”或”o”键,从”一般模式”,进入”插入模式(编辑模式)”。
在编辑模式下按”Esc” 即可到一般模式
在一般模式下按”:”,冒号进入底行模式。
在一般模式下的快捷键
dd ;删除一整行
X ;向前删除 等同于windowns系统中的删除键
x ;向后删除和大写x相反方向
Ctrl + f ;向后看一页
Ctrl + b ;向前看一页
u ;撤销上一步操作
/word ;向下查找word关键字 输入:n查找下一个,N查找上一个(不管是哪个查找都是全局查找 只不过
n的方向相反)
?log ;向上查找log关键字 输入:n查找上一个,N查找下一个
:1,90s/redis/Redis/g ;把1-90行的redis替换为Redis。语法n1,n2s/原关键字/新关键字/g,n1
代表其实行,n2代表结尾行,g是必须要的
:0 ;光标移动到第一行
:$ ;光标移动到最后一行
:300 ;光标移动到300行,输入多少数字移动到多少行
:w ;保存
:w! ;强制保存
:q ;退出
:q! ;强制退出
5dd ;删除后面5行,打一个参数为自己填写
5x ;删除此光标后面5个字符
d1G ;删除此光标之前的所有
d0 ;从光标当前位置删除到此行的第一个位置
yy ;复制
p ;在光标的下面进行粘贴
P ;在光标的上门进行粘贴
yum install -y lrzsz 命令(实现win到Linux文件互相简单上传文件)
#(实际上就是在Linux系统中下载了一个插件)下了了此安装包后就可以实现win系统到linux之间拉文件拉文件
#等待下载完了就可以输入:
rz 从win系统中选择文件上传到Linux系统中
sz 文件名 选择Linux系统的文件复制到win系统中
tar :解压、压缩命令
常用的组合命令:
-z 是否需要用gzip压缩。
-c 建立一个压缩文件的参数指令(create) –压缩
-x 解开一个压缩文件的参数指令(extract) –解压
-v 压缩的过程中显示文件(verbose)
-f 使用档名,在f之后要立即接档中(file)
常用解压参数组合:zxvf
常用压缩参数组合:zcvf
解压命令:
tar -zxvf redis-3.2.8.tar.gz ;解压到当前文件夹
tar -zxvf redis-3.2.8.tar.gz -C /opt/java/ ;解压到指定目录
压缩命令:
tar -zcvf redis-3.2.8.tar.gz redis-3.2.8/ ;
语法 tar -zcvf 压缩后的名称 要压缩的文件
tar -zcvf 压缩后的文件(可指定目录) 要压缩的文件(可指定目录)
ps :process status:进程状态,类似于windows的任务管理器
常用组合:
ps -ef 标准的格式查看系统进程
ps -aux BSD格式查看系统进程
ps -aux|grep redis BSD格式查看进程名称带有redis的系统进程(常用技巧)
//显示进程的一些属性,需要了解(ps aux)
USER //用户名
PID //进程ID号,用来杀死进程的
%CPU //进程占用的CPU的百分比
%MEM //占用内存的的百分比
VSZ //该进程使用的虚拟內存量(KB)
RSS //该进程占用的固定內存量(KB)
STAT //进程的状态
START //该进程被触发启动时间
TIME //该进程实际使用CPU运行的时间
clear:清屏命令
kill 命令用来中止一个进程------------(ps类似于打开任务管理器,kill类似于关闭进程)
kill -5 进程的PID ;推荐,和平关闭进程
kill -9 PID ;不推荐,强制杀死进程
ifconfig:用于查看和更改网络接口的地址和参数,包括IP地址、网络掩码、广播地址,使用权限是超级用户;
如果此命令输入无效,先输入yum -y install net-tools
ifconfig
ping :用于检测与目标的连通性
- 语法:ping ip地址
1、在Windows操作系统中cmdipconfig,查看本机IP地址:
2、再到LInux系统中输入 ping ip地址
按Ctrl + C 可以停止测试
free:显示系统内存
#显示系统内存使用情况,包括物理内存、交互区内存(swap)和内核缓冲区内存。
-b 以Byte显示内存使用情况
-k 以kb为单位显示内存使用情况
-m 以mb为单位显示内存使用情况
-g 以gb为单位显示内存使用情况
-s<间隔秒数> 持续显示内存
-t 显示内存使用总
top:显示当前系统正在执行的进程的相关信息,包括进程 ID、内存占用率、CPU 占用率
-c 显示完整的进程命令
-s 保密模式
-p <进程号> 指定进程显示
-n <次数>循环显示
file:可查看文件类型
file 文件名
重启linux
Linux centos 重启命令:reboot
关机linux
Linux centos 关机命令:halt
同步时间命令
ntpdate ntp1.aliyun.com
查看时间命令
date
142、Windows和Linux的区别
Windows适合普通用户进行娱乐办公使用,Linux适合软件开发部署;
Windows是微软开发的操作系统,民用操作系统,可用于娱乐、影音、上网。 Windows操作系统具有强大的日志记录系统和强大的桌面应用。好处是它可以帮我们实现非常多绚丽多彩的效果,可以非常方便去进行娱乐、影音、上网;
Linux的应用相对单纯很多,没有什么绚丽多彩的效果,因此Linux的性能是非常出色的,可以完全针对机器的配置有针对性的优化;
142、开发中常见的异常类型
NullPointerException:空指针异常
ClassCastException 类型强制转换异常
IllegalArgumentException 传递非法参数异常
ArithmeticException 算数运算异常
IndexOutOfBoundsException 下标越界异常
NumberFormatException 数字格式异常
ClassNotFindException 加载请求异常
143、咋么理解Java中的实例化
实例化这个概念理解起来比较枯燥,正巧我在网上看到一个例子,分享一下;
你要买一个苹果,售货员给你一个苹果;
你要买一苹果, 相当于 --------- Apple apple = null;
这个时候你并没有拿到苹果---------java没有给你申请内存,这个时候apple还什么内容都没有,只是告诉别人apple是个苹果
售货员给你个苹果 ----------apple = new Apple();
这个时候你拿到了苹果 ---------- java给你开辟了空间,并且apple可以使用苹果的特性,比如:apple.getPrice(),apple.getTaste()
这就是apple的实例化
144、SpringBoot咋么集成Mybatis-Plus?
MyBatis-Plus(简称 MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生;
1、在SpringBoot项目中导入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
2、编写Mapper接口并继承BaseMapper即可;
3、这样就可以使用Mybatis-Plus的语法了;
145、MyBatis返回的结果咋么跟实体对应起来?
如果说实体类的属性和表的列名一一对应,名字一样,那就自动解决了这个问题。但是如果实体类的属性和表的列名不一致,这就需要我们手动的把它们关联起来,我一般在开发中会用一下两种方法:
1、写SELECT语句的时候,顺便写上查出的数据的列名称对应的实体属性;
- 可以在查询时给字段起一个别名;
2、将查出的数据的列名称对应的实体属性单独定义,形式为…
<resultMap id="Result01" type="model.student">
<id column="student_id" property="id" jdbcType="VARCHAR" />
<result column="studnet_name" property="name" jdbcType="VARCHAR" />
<result column="student_sex" property="sex" jdbcType="VARCHAR" />
</resultMap>
<!--然后在写SELECT语句时,就可以引用了-->
<select id="studnetInfo" resultMap="Result01">
select * from student
where
student_id=#{id,jdbcType=VARCHAR}
</select>
146、Mybatis如何实现模糊搜索?
在工作中一般有三种方法
1、使用concat函数进行拼接
- concat属于数据库函数,用于字符串连接,而且可以使用
#
作为占位符,防止 SQL 注入;
<!--模糊查询姓名-->
<select id="findList" resultType="com.ninong.admin.Student">
select t1.* from student t1
<where>
<if test="name !=null and name != ''">
and t1.name like concat ('%',#{name},'%')
</if>
</where>
</select>
2、使用bind元素
- bind元素可以创建一个变量并将其绑定到上下文。使用bind标签就可以对某个字段进行’封装’,比如给上面的name字段两端各加一个百分号;
<!--模糊查询姓名 -->
<select id="findList" resultType="com.ninong.admin.Student">
select t1.* from student t1
<bind name="name" value="'%' + name + '%'" />
<where>
<if test="name !=null">
name LIKE #{name}
</if>
</where>
</select>
3、Java中拼接
//String searchText = "%" + text + "%";
String searchText = new StringBuilder("%").append(text).append("%").toString();
parameterMap.put("name", searchText);
SELECT * FROM tableName WHERE name LIKE #{name};
147、MySQL去除重复的数据?
-
使用DISTINCT关键字
SELECT DISTINCT <字段名> FROM <表名>
-
使用Group by关键进行分组;
148、你有哪些良好的编码习惯
我之前的一个经理告诉我,评定一个程序员是否优秀首先看的不是他会多少东西,知识面有多广而是看他的写的代码是否规范,是否简洁可读,换句话就是说有没有一个很好的编码习惯。我们可以参考阿里的编码规范《阿里巴巴 Java 开发手册 》来要求自己;
- 版权有限制,大家可以去网上下载也可以私聊我发给大家!
149、构造器可以被重写吗?
由于构造器不能继承,所以就不能被重写。但是,在同一个类中,构造器是可以被重载的;
150、Jdk1.8新特性有哪些
jdk1.8Stream流
151、Integer可以用equals比较吗?
可以,所有整型包装类对象之间值的比较,全部使用equals方法比较;
152、说一下面向对象跟面向过程
面向过程
面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现;
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素;
缺点:没有面向对象易维护、易复用、易扩展;
面向对象
面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装方便我们使用就是面向对象了;
面向对象是模型化的,你只需抽象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法。需要什么功能直接使用就可以了,不必去一步一步的实现,至于这个功能是如何实现的,管我们什么事?我们会用就可以了;
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护;
缺点:性能比面向过程低;
153、接口可以继承接口么?
可以,接口继承是为了在不修改接口的情况下,扩展接口的功能Java中的类是单继承,但接口可以多继承。比如List接口继承Collection接口;
154、class里面有哪些方法?
可以查阅API我举几个常用的例子;
- 类名.getMethods() :获取此类的所有public方法(父类的,实现接口的,自己的);
- 类名.getDeclaredMethods():获取本类中的所有方法,包括私有的(private、protected、默认以及public)的方法;
- 类名.getConstructors():获取所有的构造方法;
- 类名.getSuperclass:获取父类;
155、私有方法的可以反射么?
私有方法是可以反射的,那么下一个问题就是:私有(private)属性及方法可以通过反射访问,那么private的意义是什么?
首先我们就得知道什么是反射
在运行过程中,对于任何一个类。都能够知道这个类的所有属性和方法,对于任何一个对象,都能够调用它的任何一个方法和属性这种动态获取的信息以及动态调用对象的方法的功能成为Java的反射机制;
然后我们得知道私有的目的
在一个类中,为了不让外界访问到某些属性和方法,通常将其设置为private,用正常的方式(对象名.属性名,对象名.方法名)将无法访问此属性与方法,但是通过反射机制可以知到这个类中的所有属性和方法。
那么反射机制的存在跟private访问修饰矛盾吗?
然后回答一下这个问题
首先private的存在并不是为了实现Java程序的完全安全,Java的安全机制是层层相扣、十分复杂的。private只是为了Java的正常开发过程中的一种约束,也是符合OOP(面向对象编程)的封装,实现内部的部分不可见。就好比超市仓库会挂一个牌子“闲人免进”,但是如果非要进去的话也不是不行。而且在java开发过程中有时候确实会需要用的反射机制的功能,比如测试/性能等场景下;
156、Mybatis-Plus咋么做分页?
我们可以使用分页插件PaginationInnerInterceptor;官方介绍:https://baomidou.com/pages/97710a/#paginationinnerinterceptor
1、config.java中添加以下代码
@Configuration
@MapperScan("com.baomidou.cloud.service.*.mapper*")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
2、分页函数
/**
* 根据 entity 条件,查询全部记录(并翻页)
*
* @param page 分页查询条件(可以为 RowBounds.DEFAULT)
* @param queryWrapper 实体对象封装操作类(可以为null),用于作为查询条件,为null时默认查询所有记录。
*/
<E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
3、测试
@Test
public void testPage(){
//设置分页的条件,从第2的开始查询5条记录
Page<Account> page = new Page<>(2,5);
//分页,默认查询所有的记录
Page<Account> accountPage = accountDao.selectPage(page, null);
accountPage.getRecords().forEach(System.out::println);
}
166、LambdaQueryWrapper和QueryWrapper的区别
这两个都属于Mybatis-Plus中的写法;
写法区别
LambdaQueryWrapper
List<CoalPriceExecute> coalPriceExecutes = baseMapper.selectList(new LambdaQueryWrapper<CoalPriceExecute>()
.eq(CoalPriceExecute::getCoalEnterId, dto.getCoalEnterId())
.eq(CoalPriceExecute::getExecuteTime, dto.getExecuteTimeDate())
.ne(CoalPriceExecute::getExecuteStatus, DicConstantEnum.COAL_PRICE_EXECUTE_YZF.getCode()));
QueryWrapper
List<CoalPriceExecute> coalPriceExecutes = baseMapper.selectList(new QueryWrapper<CoalPriceExecute>()
.lambda()
.eq(CoalPriceExecute::getCoalEnterId, dto.getCoalEnterId())
.eq(CoalPriceExecute::getExecuteTime, dto.getExecuteTimeDate())
.ne(CoalPriceExecute::getId, dto.getId())
.ne(CoalPriceExecute::getExecuteStatus, DicConstantEnum.COAL_PRICE_EXECUTE_YZF.getCode()));
使用区别
- QueryWrapper 的列名匹配使用的是 “数据库中的字段名(一般是下划线规则)”;
- LambdaQueryWrapper的列名匹配使用的是“Lambda的语法,偏向于对象”;
优势
LambdaQueryWrapper的写法如果有错,则在编译期就会报错,而QueryWrapper需要运行的时候调用该方法才会报错;
167、简述一下Redis的事务
这个问题又是一个比较广的一个问题,我们可以先说说什么是事务?然后再说说Redis事务的概念,再说一下Redis事务的三个阶段,最后说一下Redis事务相关命令那么我认为就算完整了;
什么是事务?
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断;事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行;
Redis事务的概念
Redis事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中;
- redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令;
Redis事务的三个阶段
- 事务开始 MULTI
- 命令入队
- 事务执行 EXEC
事务执行过程中,如果服务端收到有EXEC、DISCARD、WATCH、MULTI之外的请求,将会把请求放入队列中排队;
Redis事务相关命令
Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH四个原语实现的;Redis会将一个事务中的所有命令序列化,然后按顺序执行;
- redis不支持回滚,“Redis在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
- 如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
- 如果在一个事务中出现运行错误,那么正确的命令会被执行;
WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令;
MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行;
EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值null;
通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出;
UNWATCH命令可以取消watch对所有key的监控;
其它问题
1、Redis事务支持隔离性吗?
Redis是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完
所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的;
2、Redis事务保证原子性吗,支持回滚吗?
Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失
败,其余的命令仍会被执行;
168、nginx的配置文件有哪些元素
Nginx配置文件是nginx.conf
worker_processes 1; # worker进程的数量
events { #事件区块开始
worker_connections 1024; # 每个worker进程支持的最大连接数
} #区块链的结束
http { #http区块开始
include mime.types; # Nginx支持的媒体类型库文件
default_type application/octet-stream; # 默认的媒体类型
server_tokens off; #隐藏版本号
sendfile off; #关闭高效传输模式
keepalive_timeout 65; #连接超时
server { # 第一个Server区块开始,表示一个独立的虚拟
#主机站点
listen 80 ; # 提供服务的端口,默认80
server_name 192.168.31.155 127.0.0.1 ; # 提供服务的域名主机名
charset utf-8; #编码格式
location / { # 第一个location区块开始
root html; # 站点的根目录,相当于Nginx的安装目录
index index.html index.htm; # 默认的首页文件,多个用空格分开
} # 第一个location区块结束
error_page 500502503504 /50x.html; # 出现对应的http状态码时,使用50x.html回应客户
location = /50x.html { # location区块开始,访问50x.html
root html; # 指定对应的站点目录为html
}
}
}
169、去除一个String里面的逗号
170、MyBatis与Hibernate 有什么不同?
相同点:
- Hibernate 与 MyBatis 都可以是通过 SessionFactoryBuider 由 XML 配置文件生成 SessionFactory,然后由 SessionFactory 生成 Session,最后由 Session 来开启执行事务和 SQL 语句。其中 SessionFactoryBuider, SessionFactory,Session 的生命周期都是差不多的。
- Hibernate 和 MyBatis 都支持 JDBC 和 JTA 事务处理。
Mybatis 的好处:
- MyBatis 可以进行更为细致的 SQL 优化,可以减少查询字段。
- 将 sql 语句与 java 代码进行分离;
- 提供了将结果集自动封装称为实体对象和对象的集合的功能,queryForList 返回对象集合,用 queryForObject 返回单个对象;
- 提供了自动将实体对象的属性传递给 sql 语句的参数。
Hibernate:
- Hibernate 的 DAO 层开发比 MyBatis 简单,Mybatis 需要维护 SQL 和结果映射
- 因为 hibernate 自动生成 sql 语句,我们无法控制该语句,我们就无法去写特定的高效率的 sql。
- 对于一些不太复杂的 sql 查询,hibernate 可以很好帮我们完成,但是,对于特别复杂的查询,hibernate 就很难适应了,这时候用 Mybatis 就是不错的选择, 因为 ibatis 还是由我们自己写 sql 语句;
171、二分查找法
二分查找又称折半查找,它是一种效率较高的查找方法;
二分查找要求(前提):
- 必须采用顺序存储结构
- 必须按关键字大小有序排列
二分查找法原理就是将数组分为三部分,依次是中值(所谓的中值就是数组中间位置的值)前,中值,中值后;将要查找的值和数组的中值进行比较,若小于中值 则在中值前面找,若大于中值则在中值后面找,等于中值时直接返回。然后依次 是一个递归过程,将前半部分或者后半部分继续分解为三部分;
172、说一下你对单例模式的理解
单例就是该类只能返回一个实例;
单例所具备的特点
- 私有化的构造函数
- 私有的静态的全局变量
- 公有的静态的方法
单例实现的主要步骤
- 将该类的构造方法定义为私有方法,(私有化构造器)这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;
- 在该类内提供一个公共静态返回该类实例对象的⽅法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;
饿汉式
先初始化对象,Single类⼀进内存,就已经创建好了对象;
public class Single{
//直接创建对象
private static Single s=new Single();
//私有化构造函数
private Single(){}
//返回对象实例
public static Single getInstance()
{
return s;
}
}
懒汉式
对象是⽅法被调⽤时才初始化,也叫做对象的延时加载;
- Single类进内存,对象还没存在,只有调⽤了getInstance⽅法时,才建⽴对象
public class Single{
//声明变量
private static Single s=null;
//私有构造函数
private Single(){}
//提供对外方法
public static synchronize Single getInstance()
{
if(s==null){
s=new single();
}
return s;
}
}
173、JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?
-
数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
解决:在 SqlMapConfig.xml 中配置数据链接池,使用连接池管理数据库链接。
-
Sql 语句写在代码中造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java 代码。
解决:将 Sql 语句配置在 XXXXmapper.xml 文件中与 java 代码分离。
-
向 sql 语句传参数麻烦,因为 sql 语句的 where 条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决: Mybatis 自动将 java 对象映射至 sql 语句。
-
对结果集解析麻烦,sql 变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成 pojo 对象解 析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象;
174、MyBatis中配置mapper的方法
- Mapper 接口方法名和 mapper.xml 中定义的每个 sql 的 id 相同
- Mapper 接口方法的输入参数类型和mapper.xml 中定义的每个 sql 的 parameterType的类型相同
- Mapper 接口方法的返回值类型和 mapper.xml 中定义的每个 sql 的resultType的类型相同
- Mapper.xml 文件中的 namespace 即是 mapper接口的全限定类路径;
- 实体类与数据库的字段不对应时,应该使用resultMap;
- column对应数据库字段、property对应实体类字段;
175、Maven怎么用,是什么?
在项目中jar包的资源越多,管理就越麻烦:
- 繁琐:为每个项目手动导入所需要的jar,前提是需要手机jar包;
- 复杂:如果jar升级,就需要重新搜集jar;
- 冗余:相同的jar包会在不同的项目中保存多份;
Maven是基于项目对象模型(POM),可以通过一小段描述信息来管理项目的构建、报告和文档的软件项目管理工具,同时也是一个项目管理和构建自动化工具,maven能处理从创建、编译、测试、运行、清理、打包、部署 各个环节的项目管理,它还提供了一个仓库的概念,统一的帮助管理项目中的jar包避免冲突问题;最大可能的避免环境配置不同所产生的问题(在你的电脑上能运行,在我的电脑上就不能运行)
- 首先需要下载Maven的压缩包
- 减压后需要放到一个没有特殊符号的目录
- 配置环境变量MAVEN_HOME
- 创建工作目录,值为本地库
- 修改setting文件设置本地仓库、设置jdk环境、设置镜像(阿里云的镜像)
- 分别设置安装目录和工作目录
- 在pom中添加依赖
Maven中找依赖的顺序:
- 本地仓库
- 私服(没有配置忽略)
- 公共仓库(没有配置忽略)
- 中央仓库
176、你在开发中常用到的Maven的命令
-
mvn -v:查看版本
-
mvn clean:对项目进行清理,清理的过程中会删除删除target目录下编译的内容
-
mvn compile:编译项目源代码
-
mvn test:对项目的运行测试。
-
mvn packet:可以打包后的文件存放到项目的target 目录下,打包好的文件通常都是编译后生成的class文件。
-
mvn install:在本地仓库生成仓库的安装包可以供其他项目引用,同时打包后的文件存放到项目的 target 目录下,生成jar或war
对项目打包有三种打包方式,pom打包,jar包和war包。打包方式在pom.xml文件中进行指定。pom工程一般是聚合工程,代表父工程,负责管理jar包的版本、maven插件的版本等,主要做统一的依赖管理。
jar包就是普通的打包方式,可以是pom工程的子工程。
war包的都是web工程,是可以直接放到tomcat下运行的工程。
打成pom包和jar包的工程在新建的时候可以不需要制定maven项目的原型,达成war包的项目需要制定maven项目原型,指定的原型通常为maven-archetype-webapp,代表web项目。
-
mvn deploy :发布命令,将打包的文件发布到远程参考,提供其他人员进行下载依赖
177、说一下socket编程模型
Socket(套接字:可以用来实现不同虚拟机或计算机之间的通信)是网络中的一个通讯节点;任意一个Socket都由IP地址+端口号,
Socket分为两种类型:
- 面向连接的Socket通讯协议(TCP)
- 面向无连接的Socket通讯协议(UDP)
Socket原理
- 通信的两端都有Socket
- 网络通信其实就是Socket间的通信
- 数据在两个Socket间通过IO流传输
- 底层通过流的方式进行数据传输
开发步骤
建立通信连接(会话):
- 创建ServerSocket,监听某个端口,指定端口号;
- 调用accept等待客户端接入;
客户端请求服务器:
- 创建Socket,指定服务器IP+端口号;
- 使用输出流发送请求数据给服务器;
- 使用输入流接收响应数据到客户端(等待)
服务器响应客户端:
- 服务端接收到客户端的请求后,创建新线程并启动;
- 使用输入流接收请求数据到服务器(等待)(关闭)
- 使用输出流发送响应数据给客户端(关闭)
声命周期共三个阶段:
- 打开socket
- 使用Socket收发数据
- 关闭Socket
public class Server {
public static void main(String[] args)throws Exception {
//1.创建服务套接字
ServerSocket server = new ServerSocket(6666);
System.out.println("服务器已启动");
//2.调用accept等待客户端
Socket client = server.accept();
//3.通过客户端获取输入输出流
InputStream is = client.getInputStream();
InputStreamReader isr = new InputStreamReader(is,"UTF-8");
BufferedReader br = new BufferedReader(isr);
OutputStream os = client.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os,"UTF-8");
PrintWriter pw = new PrintWriter(osw);
//4.读取数据
String message = br.readLine();
System.out.println("客户端说:"+message);
//5.响应数据
pw.println("我看你好像张永超!");
pw.flush();
//6.关闭
pw.close();
br.close();
client.close();
server.close();
}
}
public class Client {
public static void main(String[] args)throws Exception {
//1.创建客户端,连接指定的IP+端口号
Socket client = new Socket("192.168.1.3",6666);
//2.获取输入输出流
InputStream is = client.getInputStream();
InputStreamReader isr = new InputStreamReader(is,"UTF-8");
BufferedReader br = new BufferedReader(isr);
OutputStream os = client.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os,"UTF-8");
PrintWriter pw = new PrintWriter(osw);
//3.发送数据
pw.println("服务端!");
pw.flush();
//4.接收响应数据
String message = br.readLine();
System.out.println("服务端说:"+message);
//5.关闭
pw.close();
br.close();
client.close();
}
}
客户端----服务端(发送)
服务端----线程处理类(收到)
线程处理(处理)
线程处理类-----客户端(响应)
178、tcp/ip网络模型(OSI七层)
- 物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后在转化为1、0,也就是我们常说的数模转换与模数转换),这一层的数据叫做比特;
- 数据链路层:定义了如何让格式化数据以进行传输,以及如何让控制对物理介质的访问,这一层通常还提供错误检测和纠正,以确保数据的可靠传输;
- 网络层:在位于不同地理位置的网络中的两个主机系统之间提供连接和路径选择,Internet的发展使得从世界各站点访问信息的用户数大大增加,而网络层正是管理这种连接的层;
- 传输层:定义了一些传输数据的协议和端口号(WWW端口80等),如:TCP(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据),UDP(用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的), 主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组,常常把这一层数据叫做段;
- 会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路,主要在你的系统之间发起会话或者接受会话请求(设备之间需要互相认识可以是IP也可以是MAC或者是主机名);
- 表示层:可确保一个系统的应用层所发送的信息可以被另一个系统的应用层读取。例如,PC程序与另一台计算机进行通信,其中一台计算机使用扩展二一十进制交换码(EBCDIC),而另一台则使用美国信息交换标准码(ASCII)来表示相同的字符。如有必要,表示层会通过使用一种通格式来实现多种数据格式之间的转换;
- 应用层: 是最靠近用户的OSI层,这一层为用户的应用程序(例如电子邮件、文件传输和终端仿真)提供网络服务;
179、struts2与springmvc的区别?
一、框架机制
- springmvc 的入口是一个 servlet 即前端控制器(DispatchServlet),而 struts2 入口 是一个 filter 过虑器(StrutsPrepareAndExecuteFilter);
- Filter在容器启动之后即初始化;服务停止以后坠毁,晚于Servlet。Servlet在是在调用时初始化,先于Filter调用,服务停止后销毁;
二、拦截机制
Struts2 是类级别的拦截, 一个类对应一个request上下文,SpringMVC是方法级别的拦截,一个方法对应一个request上下文,而方法同时又跟一个url对应,所以说从架构本身上SpringMVC就容易实现restful url,而struts2 的架构实现起来要费劲,因为 Struts2 中 Action 的一个方法可以对应一个 url,而其类属性却被所有方法共享,这也 就无法用注解或其他方式标识其所属方法了;
三、性能方面
SpringMVC实现了零配置,由于SpringMVC基于方法的拦截,有加载一次单例模式bean注入。而Struts2是类级别的拦截,每次请求对应实例一个新的Action,需要加载所有的属性值注入,所以,SpringMVC开发效率和性能高于Struts2;
四、拦截机制
Struts2有自己的拦截Interceptor机制,SpringMVC这是用的是独立的Aop方式,这样导致Struts2的配置文件量还是比SpringMVC大;
五、配置方面
spring MVC和Spring是无缝的。从这个项目的管理和安全上也比Struts2高(当然Struts2也可以通过不同的目录结构和相关配置做到SpringMVC一样的效果,但是需要xml配置的地方不少)。SpringMVC可以认为已经100%零配置;
六、设计思想
Struts2更加符合OOP的编程思想, SpringMVC就比较谨慎,在servlet上扩展;
七、集成方面
SpringMVC集成了Ajax,使用非常方便,只需一个注解@ResponseBody就可以实现,然后直接返回响应文本即可,而Struts2拦截器集成了Ajax,在Action中处理时一般必须安装插件或者自己写代码集成进去,使用起来也相对不方便;
180、如何解决session共享?
什么是session
session在计算机中,尤其是在网络应用中,称为”会话控制“。Session对象存储特定用户会话所需的属性及配置信息。
这样,当用户在应用程序的web页面之间跳转时,存储在session对象中的变量将不会 丢失,而在整个用户会话中一直存在下去;
产生session不一致原因
单台tomcat没有任何问题,但现在是集群的tomcat因此就存在session不一致问题。如
解决方案
(1)session复制
tomcat的session复制,可以实现session共享
优点:不需要额外开发,只需搭建tomcat集群即可
缺点:tomcat 是全局session复制,集群内每个tomcat的session完全同步(也就是任何时候都完全一样的) 在大规模应用的时候, 用户过多,集群内tomcat数量过多,session的全局复制会导致集群性能下降, 因此,tomcat的数量不能太多,5个以下为好;
(2)session绑定
当用户A第一次访问系统时,tomcat1对其进行服务,那么,下次访问时仍然让tomcat1对其进行服务;
(3)使用redis集中管理session
可以将用户的会话保存在redis中,每次从redis中查询用户信息,就可以很好的解决会话共享问题;
实际应用
(1)用户登录问题
对于大型分布式系统,可以使用单点登录系统进行登录,其中用户的session保存在redis缓存系统中;
(2)用户短信验证
当需要对用户短信进行校验前,调取第三方服务获取验证码,需要先将验证码保存在session中,然后与用户提交的验证码进行比对;
181、GC原理
GC 是垃圾收集的意思(GabageCollection),内存处理是编程人员容易出现问 题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目 的,Java 语言没有提供释放已分配内存的显示操作方法。
三个步骤:
- 需要回收的对象?
回收那些不可能再被任何途径使用的对象
1.1.怎样确定哪些对象是不再被引用的对象?(两次标记)
**首先:**通过可达性分析算法对GC-ROOT对象向下搜索,搜索到的对象到GC-ROOT的路径被称为引用链,如果一个对象没有到达GC-ROOT的引用链,则此对象被标记。
然后: 对已标记的对象进行筛选,筛选的条件是对象是否有必要执行finalized()方法。当对象没有覆盖finalized()方法,或者虚拟机已经调用过此方法,都被视为“没有必要执行”。
**最后:**若对象被判定为有必要执行finalized()方法,这些对象将被放置在F-Queue队列中,进行第二次标记。此时虚拟机自动创建的Finalizer线程来会出发finalized()方法,若对象在finalized()方法中逃逸,此对象将在第二次标记时被移除“即将回收”的集合,如果还没有逃脱,对象就被回收。
2.什么时候回收?
- 强引用:普遍存在的对象,类似new对象创建,只要强引用存在,垃圾回收器永远不会回收被引用的对象。
- 软引用:还有用但是非必须,在系统发生内存溢出之前之前,会将这部分对象列入回收范围中进行二次回收,若这轮垃圾回收之后内存还是不够用,则抛出内存溢出异常。(JDK1.2)
- 弱引用:非必须对象,被此关联的对象的生命周期只到下次垃圾回收之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象(JDK1.2,WeakReference类)
- 虚引用:不影响对象的生命周期,并且此引用将不会创建对象。只是在垃圾回收后有系统通知。
3.如何回收?
标记-清除算法
复制算法
标记整理算法
182、你知道的不可变类型有哪些?String是?
不可变数据类型: 当该数据类型的对应变量的值发生了改变,那么它对应的内存地址也会发生改变,对于这种数据类型,就称不可变数据类型。其中基本数据类型都是不可变数据类型,例如int,如果一个int类型的数据发生改变,那么它指向了内存中的另一个地址,但是需要注意的是java缓存了所有-128-127的值。
- 其状态不能在创建后再修改;
- 所有域都是final类型;
可变数据类型 :当该数据类型的对应变量的值发生了改变,那么它对应的内存地址不发生改变,对于这种数据类型,就称可变数据类型,当可变数据类型改变时它实际上是更改了内存中的内容。
String是不可变类型。这里的不可变是指,当编译器开辟堆内存来存储String内容后,这个堆内存无法再被外界修改;重新将引用指向一个新地址,新地址中为更改后的值。 可变数据类型则在原来的地址上直接更改对象值;
程序员可以创建一个String类型变量,通过赋值的方式,使之指向不同的堆内存,从而产生String字符串可变的假象。
183、终止线程的三种方式?
停止一个线程通常意味着在线程处理任务完成之前停掉正在做的操作,也就是放弃当前的操作;
在 Java 中有以下 3 种方法可以终止正在运行的线程:
- 使用退出标志,使线程正常退出,也就是当 run() 方法完成后线程中止;
- 使用 stop() 方法强行终止线程,但是不推荐使用这个方法,该方法已被弃用;
- 使用 interrupt 方法中断线程;
一、使用终止位终止线程
在 run() 方法执行完毕后,该线程就终止了。但是在某些特殊的情况下,run() 方法会被一直执行;比如在服务端程序中可能会使用 while(true) { ... }
这样的循环结构来不断的接收来自客户端的请求。此时就可以用修改标志位的方式来结束 run() 方法。
? //volatile修饰符用来保证其它线程读取的总是该变量的最新的值 public volatile boolean exit = false;
? 将true拿出来设置为一个变量,使程序运行结束后动态的变为false,从而结束程序,同时也达到了终止线程的目的;
二、使用stop()
通过查看 JDK 的 API,我们会看到 java.lang.Thread 类型提供了一系列的方法如 start()、stop()、resume()、suspend()、destory()等方法来管理线程。但是除了 start() 之外,其它几个方法都被声名为已过时(deprecated)。
虽然 stop() 方法确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且该方法已被弃用,最好不要使用它;
为什么弃用stop:
- 调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 语句中的,并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。
- 调用 stop() 方法会立即释放该线程所持有的所有的锁,导致数据得不到同步,出现数据不一致的问题。
例如,存在一个对象 u 持有 ID 和 NAME 两个字段,假如写入线程在写对象的过程中,只完成了对 ID 的赋值,但没来得及为 NAME 赋值,就被 stop() 导致锁被释放,那么当读取线程得到锁之后再去读取对象 u 的 ID 和 Name 时,就会出现数据不一致的问题;
三、使用interrupt()中断线程
- interrupt()方法并不像for循环语句中的break方法,可以立即终止循环,调用interrupt()方法只是在当前线程中打一个停止的标记,并没有真的停止线程,也就是说,调用此中断线程的方法时,只是通知目标线程,希望终止,至于终止与否,交给目标线程决定,如果interrupt()方法立即中断线程的话,则与stop()遇到的问题又一致了
- interrupt()方法是一种中断状态,如果要停止线程,那么需要使用isInterrupt()方法先判断线程是否中断,如果处于中断状态,则停止正在运行的逻辑,那么线程就会被终止;
184、jsp中的九个对象及其对应类型
- request :请求对象作⽤域(JSP页面),用户端请求,此请求会包含来自 GET/POST请求的参数
- response :表示服务器端对客户端的回应。主要用于设置头信息、跳转、Cookie 等
- pageContext :用于存取其他隐含对象,如 request、 reponse、session、application 等对 象。(实际上,pageContext 对象提供 了对 JSP 页面所有的对象及命名空间的 访问。
- session :用于存储特定的用户会话所需的信息
- application :用于存储和访问来自任何页面的变量 所有的用户分享一个 Application 对象
- out :用于在 Web 浏览器内输出信息,用来传送回应的输出
- config :取得服务器的配置信息
- page :page 对象代表 JSP 本身(对应 this), 只有在 JSP 页面内才是合法的
- exception :显示异常信息,必须在 page 指令中设 定< %@ page isErrorPage=“true” %>才能 使用,在一般的 JSP 页面中使用该对象 将无法编译 JSP 文件
185、get与post区别?
从表面现像上面看 GET 和 POST 的区别:
- GET 请求的数据会附在 URL 之后(就是把数据放置在 HTTP 协议头中),以?分割 URL 和传输数据,参数之间以&相连,如:login.action?name=zhagnsan&password=123456。POST 把提交的数据则放置在是 HTTP 包的包体中。
- GET 方式提交的数据最多只能是 1024 字节,理论上 POST 没有限制,可传较大量的数据。其实这样说是错误 的,不准确的: “GET 方式提交的数据最多只能是 1024 字节",因为 GET 是通过 URL 提交数据,那么 GET 可提交的数据量就跟 URL 的长度有直接关系了。而实际上,URL 不存在参数上限的问题,HTTP 协议规范没有对 URL 长度进行限制。这个 限制是特定的浏览器及服务器对它的限制。IE对URL长度的限制是2083字节(2K+35)。对于其他浏览器,如Netscape、 FireFox 等,理论上没有长度限制,其限制取决于操作系统的支持。
- POST 的安全性要比 GET 的安全性高。注意:这里所说的安全性和上面 GET 提到的“安全”不是同个概念。上 面“安全”的含义仅仅是不作数据修改,而这里安全的含义是真正的 Security 的含义,比如:通过 GET 提交数据,用 户名和密码将明文出现在 URL 上,因为(1)登录页面有可能被浏览器缓存,(2)其他人查看浏览器的历史纪录,那么别 人就可以拿到你的账号和密码了,除此之外,使用 GET 提交数据还可能会造成 Cross-site request forgery 攻击。
- Get 是向服务器发索取数据的一种请求,而 Post 是向服务器提交数据的一种请求,在 FORM(表单)中,Method 默认为"GET",实质上,GET 和 POST 只是发送机制不同,并不是一个取一个发!
get⽅式 参数在地址栏中显示 通过?name=“”&id=""这种形式传递的 不安全 只能传递2kb的能容 post⽅式 底层是通过流的形式传递 不限制⼤⼩ 上传的时候必须⽤Post⽅式 doGet:路径传参。效率⾼,安全性差 doPOST:实体传参。效率第,安全性好;
186、sql查询中in和exist的区别
in()适合B表比A表数据小的情况;
exist()适合B表比A表数据大的情况;
当A表与B表数据一样大时,二者没有区别;
187、数组和链表的区别 ?
数组是将元素在内存中连续存储的;它的优点:因为数据是连续存储的,内存地址连续,所以在查找数据的时候效率比较高;它的缺点:在存储之前,我们需要申请一块连续的内存空间,并且在编译的时候就必须确定好它的空间的大小。在运行的时候空间的大小是无法随着你的需要进行增加和减少而改变的,当数据两比较大的时候,有可能会出现越界的情况,数据比较小的时候,又有可能会浪费掉内存空间。在改变数据个数时,增加、插入、删除数据效率比较低;
链表是动态申请内存空间,不需要像数组需要提前申请好内存的大小,链表只需在用的时候申请就可以,根据需要来动态申请或者删除内存空间,对于数据增加和删除以及插入比数组灵活。还有就是链表中数据在内存中可以在任意的位置,通过应用来关联数据(就是通过存在元素的指针来联系);
188、IO流的分类
按照读写的单位大小来分:
字符流 :以字符为单位,每次次读入或读出是16位数据。其只能读取字符类型数据。 (Java代码接收数据为一般为 char数组,也可以是别的 )
字节流:以字节为单位,每次次读入或读出是8位数据。可以读任何类型数据,图片、文件、音乐视频等。 (Java代码接收数据只能为 byte数组 )
按照实际IO操作来分:
输出流:从内存读出到文件。只能进行写操作。
输入流:从文件读入到内存。只能进行读操作。
注意:输出流可以帮助我们创建文件,而输入流不会;
189、同步与异步,阻塞与非阻塞的区别
- 同步:一个任务的完成之前不能做其他操作,必须等待(等于在打电话);
- 异步:一个任务的完成之前,可以进行其他操作(等于在聊QQ);
- 阻塞:是相对于CPU来说的, 挂起当前线程,不能做其他操作只能等待;
- 非阻塞:无须挂起当前线程,可以去执行其他操作;
190、说一下IO、BIO、NIO和AIO?
IO
Java中I/O是以流为基础进行数据的输入输出的,所有数据被串行化(所谓串行化就是数据要按顺序进行输入输出)写入输出流。简单来说就是java通过io流方式和外部设备进行交互;在Java类库中,IO部分的内容是很庞大的,因为它涉及的领域很广泛:标准输入输出,文件的操作,网络上的数据传输流,字符串流,对象流等等;
- 比如程序从服务器上下载图片,就是通过流的方式从网络上以流的方式到程序中在到硬盘中;
BIO
BIO同步并阻塞,服务器实现一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,没处理完之前此线程不能做其他操作(如果是单线程的情况下,我传输的文件很大呢?),当然可以通过线程池机制改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解;
NIO
NIO同步非阻塞,服务器实现一个连接一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4之后开始支持;
AIO
AIO异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由操作系统先完成了再通知服务器应用去启动线程进行处理,AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用操作系统参与并发操作,编程比较复杂,JDK1.7之后开始支持;
- AIO属于NIO包中的类实现,其实IO主要分为BIO和NIO,AIO只是附加品,解决IO不能异步的实现在以前很少有Linux系统支持AIO,Windows的IOCP就是该AIO模型。但是现在的服务器一般都是支持AIO操作;
BIO、NIO和AIO的区别
- BIO是阻塞的,NIO是非阻塞的;
- BIO是面向流的,只能单向读写,NIO是面向缓冲的, 可以双向读写;
- 使用BIO做Socket连接时,由于单向读写,当没有数据时,会挂起当前线程,阻塞等待,为防止影响其它连接,,需要为每个连接新建线程处理.,然而系统资源是有限的,,不能过多的新建线程,线程过多带来线程上下文的切换,从来带来更大的性能损耗,因此需要使用NIO进行BIO多路复用,使用一个线程来监听所有Socket连接,使用本线程或者其他线程处理连接;
- AIO是非阻塞 以异步方式发起 I/O 操作。当 I/O 操作进行时可以去做其他操作,由操作系统内核空间提醒IO操作已完成;
191、Spring中用到的设计模式
- 单例模式:spring中两种代理方式,若目标对象实现了若干接口,spring 使用 jdk 的 java.lang.reflect.Proxy类代理。若目标兑现没有实现任何接口,spring 使用 CGLIB 库生成目标类的子类;
- 模板方式模式:用来解决代码重复的问题。比如:RestTemplate、JmsTemplate、JpaTemplate;
- 前端控制器模式:spring 提供了前端控制器 DispatherServlet 来对请求进行分发;
- 依赖注入:贯穿于 BeanFactory/ApplacationContext 接口的核心理念;
- 工厂模式:在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用同一个接口来指向新创建的对象。Spring中使用 beanFactory 来创建对象的实例;
192、feign是咋么进行服务间调用
Feign可以帮助我们实现面向接口编程,它使得写Http客户端变得更简单;使用Feign只需要创建一个接口并注解。它具有可插拔的注解特性,可使用Feign注解和JAX-RS注解。Feign默认集成了Ribbon和Eureka结合实现了负载均衡的效果;
1、添加Feign的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、修改启动文件
在启动文件中加入@EnableEurekaClient用于启动Eureka客户端注册功能,@EnableFeignClients用于开启feign客户端
3、编写接口(最重要)
我觉得主要是想问FeignClient这个注解
@FeignClient("producer-service1") //设置调用的服务名称
public interface FeignRemoteService {
@RequestMapping(value = "/proMsg",method = RequestMethod.GET)
public String remoteMsg(@RequestParam(value = "msg") String msg);
}
193、什么是Nginx?为什么要用Nginx?
Nginx是一个 轻量级/高性能的反向代理Web服务器,他实现非常高效的反向代理、负载平衡,他可以处理2-3万并发连接数,官方监测能支持5万并发,现在中国使用nginx网站用户有很多,例如:新浪、网易、 腾讯等;
之所以用Nginx是因为跨平台、配置简单、方向代理、高并发连接:处理2-3万并发连接数,官方监测能支持5万并发,内存消耗小:开启10个nginx才占150M内存 ,nginx处理静态文件好,耗费内存少,而且Nginx内置的健康检查功能:如果有一个服务器宕机,会做一个健康检查,再发送的请求就不会发送到宕机的服务器了。重新将请求提交到其他的节点上;
使用Nginx的话还能:
- 节省宽带:支持GZIP压缩,可以添加浏览器本地缓存;
- 稳定性高:宕机的概率非常小;
- 接收用户请求是异步的;
- Nginx性能这么高是因为他的事件处理机制:异步非阻塞事件处理机制:运用了epoll模型,提供了一个队列,排队解
决;
194、Nginx的优缺点?
优点:
- 占内存小,可实现高并发连接,处理响应快
- 可实现http服务器、虚拟主机、方向代理、负载均衡
- Nginx配置简单
- 可以不暴露正式的服务器IP地址
缺点:
动态处理差:nginx处理静态文件好,耗费内存少,但是处理动态页面则很鸡肋,现在一般前端用nginx作为反向代理抗住压力;
195、Git中常用的命令
git status :查看变更情况
git add . :将当前目录及其子目录下所有变更都加入到暂存区
git add -A :将仓库内所有变更都加入到暂存区
git add 文件1 文件2 文件3 :将指定文件添加到暂存区
git diff :比较工作区和暂存区的所有差异
git commit -m "提交描述信息" :提交文件
git branch -v :查看当前工作分支及本地分支
git branch -rv :查看远端分支
git checkout 指定分支:切换到指定分支
git remote add origin 远程仓库的地址:关联远程仓库
git remote -v :查看远程仓库地址
git push -u origin master:将本地的master分支上传到远程的master分支上
git clone 克隆的远程仓库地址: 克隆远程仓库
git pull origin master :拉取远程仓库代码
git merge 分支a :分支合并
196、什么是RabbitMQ?为什么要用它?
RabbitMQ是一个在AMQP协议标准基础上完整的,可服用的企业消息系统,遵循Mozilla Public License开源协议,服务器端用Erlang语言编写,支持多种客户端的工业级的消息队列(MQ)服务器,RabbitMQ 是建立在Erlang OTP平台上;
MQ有很多优点:
- 在分布式系统下具备异步,削峰,负载均衡等一系列高级功能;
- 拥有持久化的机制,进程消息,队列中的信息也可以保存下来;
- 实现消费者和生产者之间的解耦;
- 对于高并发场景下,利用消息队列可以使得同步访问变为串行访问达到一定量的限流,利于数据库的操作
- 可以使用消息队列达到异步下单的效果,排队中,后台进行逻辑下单;
197、Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点?
198、elasticsearch的倒排索引是什么?
倒排索引是相对于正排索引而言的,可以有效的解决该问题;
- 正排索引可以通过id查找到对应的文章,但是无法通过给的部分内容如elasticsearch,找出含有该关键字的文档;
- 倒排索引会先对文档进行分析将其拆分成单个Term, 并存储包含该Term的文档id,这样便可以实现通过内容查找对应文档,如包含elasticsearch的文档为文档1;
倒排索引的过程:
- 通过倒排索引获得“搜索引擎”对应的文档id列表,有1和3;
- 通过正排索引查询1和3的完整内容;
- 返回最终结果;
倒排索引的底层实现是基于:FST(Finite State Transducer)数据结构;
(1)空间占用小。通过对词典中单词前缀和后缀的重复利用,压缩了存储空间;
(2)查询速度快。O(len(str))的查询时间复杂度;
199、Ikanalyzer的分词模式
Ik分词器有ik_max_word和ik_smart两种模式
- ik_max_word:会将文本做最细粒度的拆分;
- ik_smart:会做最粗粒度的拆分;
200、VUE的生命周期
- beforeCreate:是new Vue()之后触发的第一个钩子,在当前阶段data、methods、computed以及watch上的数据和方法都不能被访问;
- created:在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数。可以做一些初始数据的获取,在当前阶段无法与Dom进行交互,如果非要想,可以通过vm.$nextTick来访问Dom;
- beforeMount:发生在挂载之前,在这之前template模板已导入渲染函数编译。而当前阶段虚拟Dom已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发updated;
- mounted 在挂载完成后发生,在当前阶段,真实的Dom挂载完毕,数据完成双向绑定,可以访问到Dom节点,使用$refs属性对Dom进行操作;
- beforeUpdate:发生在更新之前,也就是响应式数据发生更新,虚拟dom重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染;
- updated:发生在更新完成之后,当前阶段组件Dom已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新;
- beforeDestroy:发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器;
- destroyed 发生在实例销毁之后,这个时候只剩下了dom空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁;
201、VUE咋么传值?
父组件给子组件传值
1.父组件调用子组件的时候动态绑定属性
<parent :dataList='dataList'></parent>
2.子组件定义props接收动态绑定的属性
props: ['dataList']
3.子组件使用数据
子组件主动获取父子间的属性和方法
在子组件中使用this.$parent.属性
或者this.$parent.方法
子组件给父组件传值
1、使用ref属性
父组件调用子组件时绑定属性ref
<parent :ref='parent'></parent>
在父组件中使用this.
r
e
f
s
.
p
a
r
e
n
t
.
属
性
/
t
h
i
s
.
refs.parent.属性/this.
refs.parent.属性/this.refs.parent.方法
2、使用$emit方法
- 子组件调用this.$emit('方法名‘,传值);
- 父组件通过子组件绑定的’方法名’获取传值;
3、vue页面级组件之间传值
- 使用vue-router通过跳转链接带参数传参;
- 使用本地缓存localStorge;
- 使用vuex数据管理传值;
202、v-if和v-show有啥区别?
共同点:都能控制元素的显示和隐藏;
不同点:实现本质方法不同;
- v-show本质就是通过控制css中的display设置为none,控制隐藏,只会编译一次;
- v-if是动态的向DOM树内添加或者删除DOM元素,若初始值为false,就不会编译了。而且v-if不停的销毁和创建比较消耗性能;
总结:如果要频繁切换某节点,使用v-show(切换开销比较小,初始开销较大)。如果不需要频繁切换某节点使用v-if(初始渲染开销较小,切换开销比较大);
203、接口请求一般放在哪个生命周期中?
接口请求一般放在mounted中,但需要注意的是服务端渲染时不支持mounted,需要放到created中;
204、nacos心跳机制?
在微服务启动后每过5秒,会由微服务内置的 Nacos 客户端主动向 Nacos 服务器发起心跳包(HeartBeat)。心跳包会包含当前服务实例的名称、IP、端口、集群名、权重等信息;
naming模块心跳处理流程
- naming 模块收到心跳包,首先根据 IP 与端口判断 Nacos 是否存在该服务实例?如果实例信息不存在,在 Nacos 中注册登记该实例。而注册的本质是将新实例对象存储在“实例 Map”集合中;
- 如果实例信息已存在,记录本次心跳包发送时间;
- 设置实例状态为“健康”;
- 推送“微服务状态变更”消息;
- naming 模块返回心跳包时间间隔;
到这里一次完整的心跳包处理已完成。
处理不健康心跳
当出现无效实例Nacos是咋么剔除的?Nacos Server内置的逻辑是每过 20 秒对“实例 Map”中的所有“非健康”实例进行扫描,如发现“非健康”实例,随即从“实例 Map”中将该实例删除;
205、Java 容器都有哪些?
详见第71条;
Collection
list:ArrayList、LinkedList、Vector
set:HashSet、TreeSet
Map
HashMap、HashTable、TreeMap
206、说一下Nginx的正向代理和反向代理
正向代理
一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理;
正向代理总结就一句话:代理端代理的是客户端;
反向代理
反向代理(Reverse Proxy)方式是指以代理服务器来接受 internet 上的连接请求,然后将请求,发给内部网络上的服务器并将从服务器上得到的结果返回给 internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器;
反向代理总结就一句话:代理端代理的是服务端;
207、什么是 Swagger?你用 Spring Boot 实现了它吗?
Swagger 广泛用于可视化 API,使用 Swagger UI 为前端开发人员提供在线沙箱。Swagger 是用于生成 RESTful Web 服务的可视化表示的工具,规范和完整框架实现。它使文档能够以与服务器相同的速度更新。当通过 Swagger 正确定义时,消费者可以使用最少量的实现逻辑来理解远程服务并与其进行交互。因此,Swagger消除了调用服务时的猜测;
208、遍历 D 盘下面所有的文件
publicvoidtestprint(Filefile){
Stringname=file.getName();
booleanb=file.isDirectory();//判断是否为文件夹
if(b){
//是文件夹
Filefiles[]=file.listFiles();
for(Filef:files){
testprint(f);
//System.out.println(name);//打印文件夹名字
}
}else{
System.out.println(name);
}
}
publicstaticvoidmain(String[]args){
Filefile=newFile("d:\\");
testfilefi=newtestfile();
fi.testprint(file);
}
209、异常的体系结构
210、BS 与 CS 的联系与区别
C/S 是 Client/Server 的缩写。服务器通常采用高性能的 PC、工作站或小型机,并采用大型数据库系统,如 Oracle、Sybase、InFORMix 或 SQLServer。客户端需要安装专用的客户端软件;
B/S是 Brower/Server 的缩写,客户机上只要安装一个浏览器(Browser),如 NetscapeNavigator 或 InternetExplorer,服务器安装 Oracle、Sybase、InFORMix 或 SQLServer 等数据库。在这种结构下,用户界面完全通过 WWW浏览器实现,一部分事务逻辑在前端实现,但是主要事务逻辑在服务器端实现。浏览器通过WebServer 同数据库进行数据交互;
C/S 与 B/S 区别:
1、硬件环境不同
- C/S 一般建立在专用的网络上,小范围里的网络环境,局域网之间再通过专门服务器提供连接和数据交换服务;
- B/S 建立在广域网之上的,不必是专门的网络硬件环境,例与电话上网,租用设备.信息自己管理.有比 C/S 更强的适应范围,一般只要有操作系统和浏览器就行;
2、对安全要求不同
- C/S 一般面向相对固定的用户群,对信息安全的控制能力很强.一般高度机密的信息系统采用 C/S 结构适宜.可以通过 B/S 发布部分可公开信息;
- B/S 建立在广域网之上,对安全的控制能力相对弱,可能面向不可知的用户;
3、对程序架构不同
- C/S 程序可以更加注重流程,可以对权限多层次校验,对系统运行速度可以较少考虑;
- B/S 对安全以及访问速度的多重的考虑,建立在需要更加优化的基础之上比 C/S 有更高的要求 B/S 结构的程序架构是发展的趋势,从 MS 的.Net系列的 BizTalk2000Exchange2000 等,全面支持网络的构件搭建的系统.SUN和 IBM 推的 JavaBean 构件技术等,使 B/S 更加成熟;
211、项目的生命周期
大概分为12个步骤
- 需求分析
- 概要设计
- 详细设计(用例图,流程图,类图)
- 数据库设计(powerdesigner)
- 代码开发(编写)
- 单元测试(junit 白盒测试)(开发人员)
- Git或者SVN版本管理工具(提交,更新代码,文档)
- 集成测试(黑盒测试,loadrunner(编写测试脚本)(高级测试))
- 上线试运行(用户自己体验)
- 压力测试(loadrunner)
- 正式上线
- 维护
212、什么是 UML?UML 中有哪些常用的图?
UML 是统一建模语言(Unified Modeling Language)的缩写,发表于1997年,综合了当时已经存在的面向对象的建模语言、方法和过程,是一个支持模型化和软件系统开发的图形化语言,为软件开发的所有阶段提供模型化和可视化支持。使用 UML 可以帮助沟通与交流,辅助应用设计和文档的生成,还能够阐释系统的结构和行为;
UML 定义了多种图形化的符号来描述软件系统部分或全部的静态结构和动态结构,包括:用例图(use case diagram)、类图(class diagram)、时序图(sequencediagram)、协作图(collaboration diagram)、状态图(statechart diagram)、活动图(activity diagram)、构件图(component diagram)、部署图(deployment
diagram)等。用例图、类图、时序图这三种图最为重要;
用例图
用来捕获需求,描述系统的功能,通过该图可以迅速的了解系统的功能模块及其关系;
类图
描述类以及类与类之间的关系,通过该图可以快速了解系统;
时序图
描述执行特定任务时对象之间的交互关系以及执行顺序,通过该图可以了解对象能接收的消息也就是说对象能够向外界提供的服务;
写到最后
历时2个月整理出来的干货,还是有很多想要分享的但这篇文章篇幅已经很长了,我后期还会不断的补充的;其实我认为在面试的时候除了自身的知识储备还有一点就是态度,态度决定成败,在整个面试过程中表现的很自信也是有必要的但不是过度自信;
最后祝大家能成功上岸!!!