⭐面试
自我介绍(优势+岗位匹配度)
为什么来我们公司(对公司的了解)
讲讲做的项目(为什么这么做,思路和贡献)
跨部门涉案的业务流程
我们跨部门涉案业务主要是本系统、配合物流系统和罚没系统使用,使用线上线下相结合的方式,来实现对涉案财物的追踪和流程管理。
提供给公检法线上录入案件、物证信息等信息的平台,搭配子系统(罚没系统、物流管理系统)满足公检法对涉案财物的集中化管理和移交的流程追踪控制,主要业务流程为涉案财物被公安扣押后,开具给当事人,①决定入库到保管中心,由办案人员调用和归还,出库给办案人员由其自行处置或者移交专管部门处置上报给资产中心处置。②决定移送处置权给检查院、法院、资产中心处置。
SAAS项目
使用了Mybatisplus+SaToken+RabbitMQ+SpringBoot开发的一个股权管理系统,可以统计企业的股本、估值、期权池、权益人数、公司股权结构、公司估值变化等信息,分为企业端和员工端以及系统管理后台,并接入第三方系统提供短信验证码、邮件通知、实名认证、电子发票的服务。
SaToken
页面用拦截器来拦截,执行preHandle判断浏览器有没有token登录信息,判断token是否过期,过期/没有则返回登录页面,自己写的方法判断账号(BCrypt.hashpw)密码是否正确,登录成功后执行直接调用sa token的工具方法StpUtil.login登录 ,并设置用户数据(Session多级缓存),最后跳转到登录成功页
⭐登录退出
登录退出(引入satoken,登录时记录StpUtil.setLoginId(用户id);再写一个踢出的方法,调用StpUtil.logoutByLoginId(userId);
redis存的时候key(用户id,token)
//设置一个过期时间
// 获取验证码
int code = SendSMSUtil.getCode();
Map<String,Object> map=new HashMap<>();
// 将数据存入redis
map.put(phoneNumber,code);
//用phoneNumber来做键,可以做到唯一性
stringRedisTemplate.opsForHash().putAll(phoneNumber,map);
// 设置redis过期时间,这个时间是秒为单位的,
//我现在设置5分钟之内有效,过了就会自动删除
stringRedisTemplate.expire(phoneNumber, 60*5, TimeUnit.SECONDS);
return "OK";
⭐SpringCloud
Nacos https://www.saoniuhuo.com/article/detail-31700.html
Nacos安装配置教程
使用了SpringCloud + Nacos + SaToken +Sentinel + OpenFeign(OpenFeign工作原理)开发的一个鹰眼电影购票系统
Spring Cloud Gateway(是Spring Cloud 的二级子项目,提供了微服务网关功能,包含:权限安全、监控/指标等功能),Nacos主要做服务注册与发现以及系统配置的自动更新(Nacos=SpringCloudConfig+Euraka)
Sentinel主要做流量控制、实时监控,Spring Cloud Gateway 整合 sentinel 实现流控熔断【方便您快速了解目前系统的状态】(熔断降级、系统负载保护)(类似Hystrix;Ribbon;在服务代码中使用 @SentinelResource 注解定义资源名称,并在 blockHandler 属性指定一个限流函数,自定义服务限流信息【@SentinelResource(value = “testD-resource”, blockHandler = “blockHandlerTestD”)】)
SpringSecurity做登录注册的权限验证 SpringSecurity登录流程详解
redis做短信验证码的缓存(Jedis jedis = new Jedis(“127.0.0.1”, 6379);)
RabbitMQ(写一个RabbitMQConfig配件类,定义队列名称、交换器名称、路由键名称,创建队列和交换器以及其中的路由键,在发送类上添加
@Autowired
@Qualifier(“hello”)
private Queue queue;
接收类
类上加@RabbitListener(queues = “hello”)
方法加@RabbitHandler
用户注册时需要发送一封邮件到用户注册的邮箱来激活账号,因为是同步方法,所以响应很慢,得等到邮件发送成功才有响应,所以打算使用RabbiMQ来异步发送邮件,提高响应速度。)
⭐锁
同步锁、自旋锁和适应自旋锁
synchronized关键字,包括方法、代码段,代码段执行速度快
自旋锁:没有获取到锁的线程就一直循环等待判断该资源是否已经释放锁,不用将线程阻塞起来
互斥锁:把自己阻塞起来,等待重新调度请求
适应性自旋锁:自旋的时间不再固定了,而是由前一次在同一个锁上的自旋 时间及锁的拥有者的状态来决定的。如果在同一个锁对象上,自旋等待刚刚成功获取过锁,并且持有锁的线程正在运行中,那么JVM会认为该锁自旋获取到锁的可能性很大,会自动增加等待时间。
适应性自旋锁
package com.ruoyi.system.api.domain;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
//适应性自旋锁
public class MCSLock implements Lock {
//提供了对象引用的非阻塞原子性读写操作,并且提供了原子性的compareAndSet方法(CAS操作)
// 解决了多线程并发操作导致数据不一致的问题,确保其他线程可以看到锁的真实状态
private AtomicReference<QNode> tail;
//线程本地变量,用于解决多线程并发时访问共享变量的问题
private ThreadLocal<QNode> myNode;
//无参构造,自动执行
public MCSLock() {
tail = new AtomicReference<>(null);
myNode = ThreadLocal.withInitial(QNode::new);
}
private class QNode {
/**
* 是否被qNode所属线程锁定
*/
private volatile boolean locked = false;
/**
* 与CLHLock相比,多了这个真正的next
*/
// volatile是Java中的关键字,用来修饰会被不同线程访问和修改的变量
private volatile QNode next = null;
}
@Override
public void lock() {
QNode qnode = myNode.get();
QNode preNode = tail.getAndSet(qnode);
if (preNode != null) {
qnode.locked = false;
preNode.next = qnode;
//wait until predecessor gives up the lock
while (!qnode.locked) {
}
}
qnode.locked = true;
}
@Override
public void unlock() {
QNode qnode = myNode.get();
if (qnode.next == null) {
//后面没有等待线程的情况
if (tail.compareAndSet(qnode, null)) {
//真的没有等待线程,则直接返回,不需要通知
return;
}
//wait until predecessor fills in its next field
// 突然有人排在自己后面了,可能还不知道是谁,下面是等待后续者
while (qnode.next == null) {
}
}
//后面有等待线程,则通知后面的线程
qnode.next.locked = true;
qnode.next = null;
}
}
⭐Java
Java优点
Java 语言是一种分布式的面向对象语言,具有面向对象、平台无关性、简单性、解释执行、多线程、安
全性等
JDK和JRE和JVM的区别
JDK:Java标准的开发包,提供了编译、运行Java程序所需要的各种工具
和资源
JRE:Java运行时环境,用于解释执行Java的字节码文件
JVM:它是整个Java实现跨平台的核心,JVM是运行Java字节码的虚拟机
Java 程序从源代码到运行需要三步
总结
- JDK 用于开发,JRE 用于运行java程序 ;如果只是运行Java程序,可以只安装JRE,无序安装JDK。
微信搜索公众号:Java专栏,获取最新面试手册 - JDk包含JRE,JDK 和 JRE 中都包含 JVM。
- JVM 是 Java 编程语言的核心并且具有平台独立性。
Java有哪些数据类型
Java中有 8 种基本数据类型,分别为:
6 种数字类型 (四个整数形,两个浮点型):byte、short、int、long、float、double
1 种字符类型:char
1 种布尔型:boolean。
这八种基本类型都有对应的包装类分别为:Byte、Short、Integer、Long、Float、Double、
Character、Boolean
堆栈
栈:在函数中定义的基本类型的变量和对象的引用变量都是在函数的栈内存中分配。
堆:堆内存用于存放由new创建的对象和数组。
从通俗化的角度来说,堆是用来存放对象的,栈是用来存放执行程序的
//基本数据类型作为方法参数被调用
public class Main{
public static void main(String[] args){
//基本数据类型
int i = 1;
int j = 1;
double d = 1.2;
//引用数据类型
String str = "Hello";
String str1= "Hello";
}
}
Java中的自动装箱与拆箱
什么是自动装箱拆箱?
//自动装箱
Integer total = 99;
//自定拆箱
int totalprim = total;
装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。
坑点1
public class Main {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
结果
true
false
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
坑点2
public class Main {
public static void main(String[] args) {
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
结果
false
false
在某个范围内的整型数值的个数是有限的,而浮点数却不是
a=a+b与a+=b有什么区别吗
+= 操作符会进行隐式自动类型转换,a=a+b则不会自动进行类型转换
byte a = 127;
byte b = 127;
b = a + b; // 报编译错误:cannot convert from int to byte
b += a;
改成如下不报错
short s1= 1;
s1 += 1;
final和static
final
特征:凡是引用final关键字的地方皆不可修改!
(1)修饰类:表示该类不能被继承;
(2)修饰方法:表示方法不能被重写;
(3)修饰变量:表示变量只能一次赋值以后值不能被修改(常量)
static
(1)修饰静态变量和静态方法.也就是被static所修饰的变量/方法都属于类的静态资源,类实例所共享
(2)静态块,多用于初始化操作
(3)修饰内部类,此时称之为静态内部类
(4)静态导包(在JDK 1.5之后引入的新特性,可以用来指定导入某个类中的静态资源,并且不需要使用类名,可以直接使用资源名)
import static java.lang.Math.*;
什么是内部类?内部类的作用
内部类的定义
将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
内部类的作用:
1、成员内部类 成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态
成员)。 当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问
的是成员内部类的成员。
2、局部内部类 局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局
部内部类的访问仅限于方法内或者该作用域内。
3、匿名内部类 匿名内部类就是没有名字的内部类
4、静态内部类 指被声明为static的内部类,他可以不依赖内部类而实例,而通常的内部类需要实例化外
部类,从而实例化。静态内部类不可以有与外部类有相同的类名。不能访问外部类的普通成员变量,但
是可以访问静态成员变量和静态方法(包括私有类型) 一个 静态内部类去掉static 就是成员内部类,他
可以自由的引用外部类的属性和方法,无论是静态还是非静态。但是不可以有静态属性和方法
try {}里有一个return语句,那么紧跟在这个try后的finally{}里的code会不会被执行,什么时候被执行,在return前还是后
public classTest {
public static void main(String[]args) {
System.out.println(newTest().test());;
}
static int test()
{
intx = 1;
try
{
return x;
}
finally
{
++x;
}
}
}
结果为1
== 和 equals 的区别
== | equals | |
---|---|---|
基本类型 | 比较值 | equals 不能作用于基本类型的比较 |
引用类型 | 先比较类型,再比较虚地址 | 比如String、Integer类重写了equals方法(变成了值比较),其他的是继承Object中的equals,所以比较值是否相等 |
集合
map存key,value,Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的
HashMap可以通过下面的语句进行同步
Map m = Collections.synchronizeMap(hashMap);
Collections.synchronizedMap()方法来获取一个线程安全的集合(Collections.synchronizedMap()实现原理是Collections定义了一个SynchronizedMap的内部类,这个类实现了Map接口,在调用方法时使用synchronized来保证线程同步,当然了实际上操作的还是我们传入的HashMap实例
简单的说就是Collections.synchronizedMap()方法帮我们在操作HashMap时自动添加了synchronized来实现线程同步,类似的其它Collections.synchronizedXX方法也是类似原理)'。
Java 中 IO 流分为几种?
按功能来分:输入流(input)、输出流(output)。
按类型来分:字节流和字符流。
字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符
为单位输入输出数据。
反射的四种方法
应用场景:
1、Spring 通过 XML 配置模式装载 Bean
2、JDBC 的数据库的连接
反射可以在运行时获取一个类的所有信息(有属性和方法,包括私有),这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制
jdbc就是典型的反射
Class.forName('com.mysql.jdbc.Driver.class');//加载MySQL的驱动类
类名.class
对象名.getClass()
Class<?> clazz = Integer.TYPE;//基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象
String 、 StringBuffer 、 StringBuilder区别
String | StringBuffer | StringBuilder | |
---|---|---|---|
不可变,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象 | 在原有对象的基础上进行操作 | 在原有对象的基础上进行操作 | |
线程安全(同步锁) | 线程不安全(没有同步锁) | ||
多线程环境下推荐使用 | 在单线程环境下推荐使用 | ||
底层 | 不可变的字符数组 private final char value[] | 继承了 AbstractStringBuilder,可变的字符char[] value | 继承了 AbstractStringBuilder,可变的字符数组char value[] |
抽象类(abstract)和接口的区别
- 抽象类的特性
- 抽象类可以继承一个类实现多个接口。
- 子类继承抽象类的时候,子类须实现抽象类中的抽象方法,如果不实现,则子类也变为抽象类。
- 抽象类不能被实例化,抽象类可以有非抽象方法。
- 一个类如果有抽象方法,则这个类一定是抽象类。当然抽象类也可以没有抽象方法!
- 接口的特性:
- 接口不可以被实例化(匿名内部类中实例化的是实现接口的类)
- 实现类必须实现接口中的所有的方法
- 实现类可以实现多个接口
- 接口中的常量都是静态常量
联系:接口是特殊的抽象类。
abstract class Emoplyee {
String name;
public Emoplyee(String name) {
this.name = name;
}
public void test() {
}
public abstract void work();
}
class Waiter extends Emoplyee {
public Waiter(String s) {
super(s);
}
public void work() {
System.out.println("我来搽桌子");
}
单例模式(Singleton)
- 最简单的设计模式之一,提供了一种创建对象的最佳方式
- 创建型模式
确保在一个应用程序中某个类只有一个实例
- 单例类只能有一个实例
- 必须自己创建自己的唯一实例
- 必须给所有其他对象提供这一实例
Java中实现单例模式可以通过两种形式实现
共有点:
- 构造方法私有化
- 提供一个静态的方法返回一个类对象
懒汉模式
应用场景:数据库连接池, 网站的计数器,日志应用,任务管理器、回收站
类加载时 不初始化
声明但不赋值 - 加入线程安全
//懒汉模式
class LazySingleton {
// 类加载,执行一次
private static LazySingleton singleton = null;
// 不能让外部直接调用构造方法,不让他直接实例化
private LazySingleton() {
// TODO Auto-generated constructor stub
}
// 加个synchronized
public static synchronized LazySingleton getInstance() {
// 判断
// 多个线程同时访问可能会产生多个实例,甚至破坏实例,违背单例设计的单例的设计原则
if (singleton == null) {
singleton = new HungarySingleton();
}
return singleton;
}
}
饿汉模式
类加载时 初始化,所以类加载比较 慢,但是获取对象更快
//饿汉模式
class HungarySingleton {
// 类加载,执行一次
private static HungarySingleton singleton = new HungarySingleton();
// 不能让外部直接调用构造方法,不让他直接实例化
private HungarySingleton() {
// TODO Auto-generated constructor stub
}
public static HungarySingleton getInstance() {
return singleton;
}
}
工厂模式
简单工厂
工厂方法
抽象工厂
当每个对象的创建逻辑都比较简单的时候,推荐使用简单工厂模式,将多个对象的创建逻辑放到一个工厂类中。当每个对象的创建逻辑都比较复杂的时候,为了避免设计一个过于庞大的简单工厂类,推荐使用工厂方法模式,将创建逻辑拆分得更细,每个对象的创建逻辑独立到各自的工厂类中。同理,对于第二种情况,因为单个对象本身的创建逻辑就比较复杂,所以,建议使用工厂方法模式。置于抽象工厂模式,使用场景不多。如果碰到一个工厂需要承担多种不同类型的对象创建时,可以考虑使用。
java几种常见的排序
https://www.cnblogs.com/hanease/p/15728835.html
冒泡排序
a、冒泡排序,是通过每一次遍历获取最大/最小值
b、将最大值/最小值放在尾部/头部
c、然后除开最大值/最小值,剩下的数据在进行遍历获取最大/最小值
public static void main(String[] args) {
int arr[] = {12, 42, 23, 52, 11, 3};
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
if (arr[j] < arr[j+1]) { //小于则排列成从大到小
int t = arr[j];
arr[j] = arr[j+1];
arr[j+1] = t;
}
}
}
for (int a : arr)
System.out.print(a + " ");
}
缺点也能看得出来:耗时,每次都需要从头开始比较
选择排序
a、 将第一个值看成最小值
b、然后和后续的比较找出最小值和下标
c、交换本次遍历的起始值和最小值
d、说明:每次遍历的时候,将前面找出的最小值,看成一个有序的列表,后面的看成无序的列表,然后每次遍历无序列表找出最小值。
public static void main(String[] args) {
int arr[] = {12, 42, 23, 52, 11, 3};
for (int i = 0; i < arr.length - 1; i++) {
//默认第一个是最小的。
int min = arr[i];
//记录最小的下标
int index = i;
//要在i位置的基础上,往后比较,所以是i+1,长度arr.length,数组0开始,最后刚好不等于arr.length
for (int j = i + 1; j < arr.length; j++) {
if (min > arr[j]) {
min = arr[j];
index = j;
}
}
//最后交换
int temp = arr[i];
arr[i] = min;
arr[index] = temp;
}
for (int a: arr) {
System.out.print(a + " ");
}
}
插入排序
a、默认从第二个数据开始比较。
b、如果第二个数据比第一个小,则交换。然后在用第三个数据比较,如果比前面小,则插入(狡猾)。否则,退出循环
c、说明:默认将第一数据看成有序列表,后面无序的列表循环每一个数据,如果比前面的数据小则插入(交换)。否则退出。
public static void main(String[] args) {
int arr[] = {12, 42, 23, 52, 11, 3};
for (int i = 1; i < arr.length; i++) {
//默认第二个开始
//外层循环,从第二个开始比较
for (int j = i; j > 0; j--) {
if (arr[j] < arr[j-1]) {
//最后交换
int temp = arr[j-1];
arr[j-1] = arr[j];
arr[j] = temp;
}else {
//如果不小于,说明插入完毕,退出内层循环
break;
}
}
}
for (int a: arr) {
System.out.print(a + " ");
}
}
希尔排序(插入排序变种版)
a、基本上和插入排序一样的道理
b、不一样的地方在于,每次循环的步长,通过减半的方式来实现
c、说明:基本原理和插入排序类似,不一样的地方在于。通过间隔多个数据来进行插入排序
public static void main(String[] args) {
int arr[] = {7, 5, 3, 2, 4};
//希尔排序(插入排序变种版)
for (int i = arr.length / 2; i > 0; i /= 2) {
//i层循环控制步长
for (int j = i; j < arr.length; j++) {
//j控制无序端的起始位置
for (int k = j; k > 0 && k - i >= 0; k -= i) {
if (arr[k] < arr[k - i]) {
int temp = arr[k - i];
arr[k - i] = arr[k];
arr[k] = temp;
} else {
break;
}
}
}
//j,k为插入排序,不过步长为i
}
}
快速排序
a、确认列表第一个数据为中间值,第一个值看成空缺(低指针空缺)。
b、然后在剩下的队列中,看成有左右两个指针(高低)。
c、开始高指针向左移动,如果遇到小于中间值的数据,则将这个数据赋值到低指针空缺,并且将高指针的数据看成空缺值(高指针空缺)。然后先向右移动一下低指针,并且切换低指针移动。
d、当低指针移动到大于中间值的时候,赋值到高指针空缺的地方。然后先高指针向左移动,并且切换高指针移动。重复c、d操作。
e、直到高指针和低指针相等时退出,并且将中间值赋值给对应指针位置。
f、然后将中间值的左右两边看成行的列表,进行快速排序操作。
public static void main(String[] args) {
int arr[] = {7, 5, 3, 2, 4, 1, 8, 9, 6};
//快速排序
int low = 0;
int high = arr.length - 1;
quickSort(arr, low, high);
}
public static void quickSort(int[] arr, int low, int high) {
//如果指针在同一位置(只有一个数据时),退出
if (high - low < 1) {
return;
}
//标记,从高指针开始,还是低指针(默认高指针)
boolean flag = true;
//记录指针的其实位置
int start = low;
int end = high;
//默认中间值为低指针的第一个值
int midValue = arr[low];
while (true) {
//高指针移动
if (flag) {
//如果列表右方的数据大于中间值,则向左移动
if (arr[high] > midValue) {
high--;
} else if (arr[high] < midValue) {
//如果小于,则覆盖最开始的低指针值,并且移动低指针,标志位改成从低指针开始移动
arr[low] = arr[high];
low++;
flag = false;
}
} else {
//如果低指针数据小于中间值,则低指针向右移动
if (arr[low] < midValue) {
low++;
} else if (arr[low] > midValue) {
//如果低指针的值大于中间值,则覆盖高指针停留时的数据,并向左移动高指针。切换为高指针移动
arr[high] = arr[low];
high--;
flag = true;
}
}
//当两个指针的位置相同时,则找到了中间值的位置,并退出循环
if (low == high) {
arr[low] = midValue;
break;
}
}
//然后出现有,中间值左边的小于中间值。右边的大于中间值。
//然后在对左右两边的列表在进行快速排序
quickSort(arr, start, low -1);
quickSort(arr, low + 1, end);
}
归并排序
a、将列表按照对等的方式进行拆分
b、拆分小最小快的时候,在将最小块按照原来的拆分,进行合并
c、合并的时候,通过左右两块的左边开始比较大小。小的数据放入新的块中
d、说明:简单一点就是先对半拆成最小单位,然后将两半数据合并成一个有序的列表。
public static void main(String[] args) {
int arr[] = {7, 5, 3, 2, 4, 1,6};
//归并排序
int start = 0;
int end = arr.length - 1;
mergeSort(arr, start, end);
}
public static void mergeSort(int[] arr, int start, int end) {
//判断拆分的不为最小单位
if (end - start > 0) {
//再一次拆分,知道拆成一个一个的数据
mergeSort(arr, start, (start + end) / 2);
mergeSort(arr, (start + end) / 2 + 1, end);
//记录开始/结束位置
int left = start;
int right = (start + end) / 2 + 1;
//记录每个小单位的排序结果
int index = 0;
int[] result = new int[end - start + 1];
//如果查分后的两块数据,都还存在
while (left <= (start + end) / 2 && right <= end) {
//比较两块数据的大小,然后赋值,并且移动下标
if (arr[left] <= arr[right]) {
result[index] = arr[left];
left++;
} else {
result[index] = arr[right];
right++;
}
//移动单位记录的下标
index++;
}
//当某一块数据不存在了时
while (left <= (start + end) / 2 || right <= end) {
//直接赋值到记录下标
if (left <= (start + end) / 2) {
result[index] = arr[left];
left++;
} else {
result[index] = arr[right];
right++;
}
index++;
}
//最后将新的数据赋值给原来的列表,并且是对应分块后的下标。
for (int i = start; i <= end; i++) {
arr[i] = result[i - start];
}
}
}
创建对象的6种方式
class GirlFriend {
private String name;
}
new 关键字
GirlFriend girlFriend = new GirlFriend("new一个对象"); //!!!!!
克隆 Clone
GirlFriend 类实现 Cloneable 接口,并且实现其 clone() 方法
好处:克隆的好处就是可以快速创建一个和原对象值一样的对象,对象的字段值一样,但是两个不同的引用
class GirlFriend implements Cloneable {
private String name;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
实现完后怎么使用(调方法呗)
@Test
public void girlFriend2() throws CloneNotSupportedException {
GirlFriend girlFriend1 = new GirlFriend("克隆一个对象");
GirlFriend girlFriend2 = (GirlFriend) girlFriend1.clone(); //!!!!!
System.out.println(girlFriend2);
}
类派发一个对象(反射)—— 类.class.newInstance()
@Test
public void girlFriend3() throws InstantiationException, IllegalAccessException {
GirlFriend girlFriend = GirlFriend.class.newInstance(); //!!!!!
girlFriend.setName("类派发一个对象");
System.out.println(girlFriend);
}
动态加载一个对象(反射)—— Class.forName(类路径).newInstance()
@Test
public void girlFriend4() throws InstantiationException,IllegalAccessException,ClassNotFoundException{
GirlFriend girlFriend =(GirlFriend)Class.forName("cn.javastack.test.jdk.core.GirlFriend").newInstance(); //!!!!!
}
构造一个对象(反射)—— 类.class.getConstructor().newInstance()
@Test
public void girlFriend5() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
GirlFriend girlFriend = GirlFriend.class.getConstructor().newInstance(); //!!!!!
}
反序列化一个对象
让类可序列化,实现 Serializable 接口
class GirlFriend implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private String name;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
@Test
public void girlFriend6() throws IOException, ClassNotFoundException {
GirlFriend girlFriend1 = new GirlFriend("反序列化一个对象");
// 序列化一个女朋友
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("gf.obj"));
objectOutputStream.writeObject(girlFriend1);
objectOutputStream.close();
// 反序列化出来
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("gf.obj"));
GirlFriend girlFriend2 = (GirlFriend) objectInputStream.readObject();
objectInputStream.close();
}
jdk1.8的新特性有哪些(朗姆达,接口,hashmap等)
Lambda 表达式
− Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
Lmabda表达式的语法总结: () -> ();
可以认为是对匿名内部类的一种简化,但不是所有的匿名内部类都可以简化为Lambda表达式
要求自定义的接口只能有一个抽象方法需要实现(当一个接口中存在多个抽象方法时,如果使用lambda表达式,并不能智能匹配对应的抽象方法,因此引入了函数式接口的概念),比如Runnable接口
前置 | 语法 |
---|---|
无参数无返回值 | () -> System.out.println(“Hello WOrld”) |
有一个参数无返回值 | (x) -> System.out.println(x) |
有且只有一个参数无返回值 | x -> System.out.println(x) |
有多个参数,有返回值,有多条lambda体语句 | (x,y) -> {System.out.println(“xxx”);return xxxx;}; |
有多个参数,有返回值,只有一条lambda体语句 | (x,y) -> xxxx |
// 类型声明 接受int类型a和b,返回a+b的和
MathOperation addition = (int a, int b) -> a + b;
// 不用类型声明 接受a和b,返回a-b的差
MathOperation subtraction = (a, b) -> a - b;
// 大括号中的返回语句 使用大括号要用return
MathOperation multiplication = (int a, int b) -> { return a * b; };
// 没有大括号及返回语句
MathOperation division = (int a, int b) -> a / b;
//
Runnable r1 = () -> {
System.out.println("Lambda+匿名内部类1111");
};
// 匿名对象
new Thread(r1).start();
方法引用
− 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码(System.out::println对象实例方法 类名::方法名 类名::实例方法)。
(a) 方法引用
三种表现形式:
对象::实例方法名
类::静态方法名
类::实例方法名 (lambda参数列表中第一个参数是实例方法的调用 者,第二个参数是实例方法的参数时可用)
public void test() {
/**
*注意:
* 1.lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
* 2.若lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method
*
*/
Consumer<Integer> con = (x) -> System.out.println(x);
con.accept(100);
// 方法引用-对象::实例方法
Consumer<Integer> con2 = System.out::println;
con2.accept(200);
// 方法引用-类名::静态方法名
BiFunction<Integer, Integer, Integer> biFun = (x, y) -> Integer.compare(x, y);
BiFunction<Integer, Integer, Integer> biFun2 = Integer::compare;
Integer result = biFun2.apply(100, 200);
// 方法引用-类名::实例方法名
BiFunction<String, String, Boolean> fun1 = (str1, str2) -> str1.equals(str2);
BiFunction<String, String, Boolean> fun2 = String::equals;
Boolean result2 = fun2.apply("hello", "world");
System.out.println(result2);
}
(b)构造器引用
格式:ClassName::new
public void test2() {
// 构造方法引用 类名::new
Supplier<Employee> sup = () -> new Employee();
System.out.println(sup.get());
Supplier<Employee> sup2 = Employee::new;
System.out.println(sup2.get());
// 构造方法引用 类名::new (带一个参数)
Function<Integer, Employee> fun = (x) -> new Employee(x);
Function<Integer, Employee> fun2 = Employee::new;
System.out.println(fun2.apply(100));
}
©数组引用
格式:Type[]::new
public void test(){
// 数组引用
Function<Integer, String[]> fun = (x) -> new String[x];
Function<Integer, String[]> fun2 = String[]::new;
String[] strArray = fun2.apply(10);
Arrays.stream(strArray).forEach(System.out::println);
}
默认方法
− 默认方法就是一个在接口里面有了一个实现的方法(接口中可以定义默认实现方法和静态方法)。
在接口中可以使用default和static关键字来修饰接口中定义的普通方法
public interface Interface {
default String getName(){
return "zhangsan";
}
static String getName2(){
return "zhangsan";
}
}
引入了default默认实现,static的用法是直接用接口名去调方法即可
新工具
− 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
Stream API
−新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
Stream操作的三个步骤
创建stream
中间操作(过滤、map)
终止操作
stream的创建:
// 1,校验通过Collection 系列集合提供的stream()或者paralleStream()
List<String> list = new ArrayList<>();
Strean<String> stream1 = list.stream();
// 2.通过Arrays的静态方法stream()获取数组流
String[] str = new String[10];
Stream<String> stream2 = Arrays.stream(str);
// 3.通过Stream类中的静态方法of
Stream<String> stream3 = Stream.of("aa","bb","cc");
// 4.创建无限流
// 迭代
Stream<Integer> stream4 = Stream.iterate(0,(x) -> x+2);
//生成
Stream.generate(() ->Math.random());
Stream的中间操作:
/**
* 筛选 过滤 去重
*/
emps.stream()
.filter(e -> e.getAge() > 10)
.limit(4)
.skip(4)
// 需要流中的元素重写hashCode和equals方法
.distinct()
.forEach(System.out::println);
/**
* 生成新的流 通过map映射
*/
emps.stream()
.map((e) -> e.getAge())
.forEach(System.out::println);
/**
* 自然排序 定制排序
*/
emps.stream()
.sorted((e1 ,e2) -> {
if (e1.getAge().equals(e2.getAge())){
return e1.getName().compareTo(e2.getName());
} else{
return e1.getAge().compareTo(e2.getAge());
}
})
.forEach(System.out::println);
```
Stream的终止操作:
```java
/**
* 查找和匹配
* allMatch-检查是否匹配所有元素
* anyMatch-检查是否至少匹配一个元素
* noneMatch-检查是否没有匹配所有元素
* findFirst-返回第一个元素
* findAny-返回当前流中的任意元素
* count-返回流中元素的总个数
* max-返回流中最大值
* min-返回流中最小值
*/
/**
* 检查是否匹配元素
*/
boolean b1 = emps.stream()
.allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b1);
boolean b2 = emps.stream()
.anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b2);
boolean b3 = emps.stream()
.noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b3);
Optional<Employee> opt = emps.stream()
.findFirst();
System.out.println(opt.get());
// 并行流
Optional<Employee> opt2 = emps.parallelStream()
.findAny();
System.out.println(opt2.get());
long count = emps.stream()
.count();
System.out.println(count);
Optional<Employee> max = emps.stream()
.max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(max.get());
Optional<Employee> min = emps.stream()
.min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(min.get());
两个终止操作 reduce和collect
reduce操作: reduce:(T identity,BinaryOperator)/reduce(BinaryOperator)-可以将流中元素反复结合起来,得到一个值
/**
* reduce :规约操作
*/
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer count2 = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(count2);
Optional<Double> sum = emps.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
System.out.println(sum);
collect操作:Collect-将流转换为其他形式,接收一个Collection接口的实现,用于给Stream中元素做汇总的方法
/**
* collect:收集操作
*/
List<Integer> ageList = emps.stream()
.map(Employee::getAge)
.collect(Collectors.toList());
ageList.stream().forEach(System.out::println);
Date Time API
− 加强对日期与时间的处理。优点:== LocalDate和LocalTime和最基本的String一样,是不变类型,不但线程安全,而且不能修改==
表示日期的LocalDate
表示时间的LocalTime
表示日期时间的LocalDateTime
// 从默认时区的系统时钟获取当前的日期时间。不用考虑时区差
LocalDateTime date = LocalDateTime.now();
//2018-07-15T14:22:39.759
// 手动创建一个LocalDateTime实例
LocalDateTime date2 = LocalDateTime.of(2017, 12, 17, 9, 31, 31, 31);
// 进行加操作,得到新的日期实例
LocalDateTime date3 = date2.plusDays(12);
// 进行减操作,得到新的日期实例
LocalDateTime date4 = date3.minusYears(2);
* 之前使用的java.util.Date月份从0开始,我们一般会+1使用,很不方便,java.time.LocalDate月份和星期都改成了enum
* java.util.Date和SimpleDateFormat都不是线程安全的,而LocalDate和LocalTime和最基本的String一样,是不变类型,不但线程安全,而且不能修改。
* java.util.Date是一个“万能接口”,它包含日期、时间,还有毫秒数,更加明确需求取舍
* 新接口更好用的原因是考虑到了日期时间的操作,经常发生往前推或往后推几天的情况。用java.util.Date配合Calendar要写好多代码,而且一般的开发人员还不一定能写对。
Optional 类
− Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
Nashorn, JavaScript 引擎
− Java 8提供了一个新的Nashorn
javascript引擎
它允许我们在JVM上运行特定的javascript应用。
A a = new B();
class A{
int i=10;
public void show(){
System.out.println(i);
}
}
class B extends A{
int i= 100;
public void show (){
System.out.println(i);
}
}
public class Test {
public static void main(String []args){
A a= new B();//A的引用指向B的对象。
a.show();/*结果是100,结果是可预料的,不管引用是什么,表现将是子类的样子。比如我需要一个动物,结果得到了一只公鸡
,那么我让这只动物叫,就会得到公鸡的叫声。*/
//如果我们直接访问成员变量,看看得到什么。
System.out.println(a.i);
/*结果是10,我们发现如果方法被重写了,他是成员变量也重复定义了,父类的引用指向
子类的对象,调用的方法将是子类的,而成员变量的值将是父类的。这是java的编译机制造成的。方法时在运行的时候取值,
所以方法时动态绑定的,而成员变量的初值是编译的时候就绑定的。这种结果挺让人郁闷的,为了避免这类问题,java程序员通常会使用
方法,而不是直接使用成员变量。*/
}
}
总的来说一句话,“编译看左边,运行看右边 ”也就是编译的时候,会看左边引用类型是否能正确编译通过,运行的时候是调用右边的对象的方法。
⭐多线程
简述线程、程序、进程的基本概念。以及他们之间关系是什么?
程序>进程>线程
程序:是含有指令和数据的文件,存储在磁盘和其他数据存储结构中,是静态的代码
进程:是程序的一次执行过程,是系统运行程序的基本单位。进程就是运行的程序,程序执行时会被加载到内存中。进程属于操作系统的范畴,同一段时间内可以执行一个以上的程序;线程是在同一程序中几乎同时执行一个以上的程序
线程:是比进程更小执行的单位,一个进程在执行过程中可以产生多个线程,多个线程共享一块内存区域和一组系统资源,切换的负担比进程小,称为轻量级进程
为什么加上synchronized就是同步?
Synchronized是java的关键字,是一种同步锁
在执行synchronized代码块时,会锁定当前的对象,只有执行完该代码块才能释放对象锁,下一个线程才能执行和锁定该对象锁
synchronized修饰的对象有哪些?
修饰代码块,同步代码块,作用于{}内的代码,作用于调用代码段的对象
修饰方法,同步方法,作用于整个方法,作用于调用方法的对象
修饰静态方法, 作用于整个静态方法,作用于这个类的所有对象
修饰类, 作用于{}内的代码,作用于这个类的所有对象
线程的三种创建方式
- 继承Thread,重写run方法
- 实现Runnable接口,重写run方法
- 实现Callable接口,重写call方法
如何确保线程安全
- 对非安全的代码进行加锁控制
- 使用线程安全的类
- 多线程并发情况下,线程共享的变量改为方法级的局部变量(线程本地变量ThreadLocal)
//创建一个ThreadLocal变量
static ThreadLocal<String> localVariable = new ThreadLocal<>();
线程池定义
线程池(Thread Pool):把一个或多个线程通过统一的方式进行调度和重复使用的技术,避免了因为线程过多而带来使用上的开销
线程池场景
异步发送邮件通知
发送一个任务,然后注入到线程池中异步发送。
心跳请求任务
创建一个任务,然后定时发送请求到线程池中。
线程池优点
- 降低资源消耗
- 提高响应速度
- 无需反复创建线程
自带的线程池
方法 | 作用 |
---|---|
newCacheThreadPool() | 创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程 |
newCachedThreadPool(ThreadFactory threadFactory) | 创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程,并在需要时使用提供的ThreadFactory创建新线程 |
newFixedThreadPool(int nThreads) | 创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程 |
newFixedThreadPool(int nThreads,ThreadFactory threadFactory) | 创建一个线程池,重用固定数量的线程,从共享无界队列中运行,使用提供的ThreadFactory在需要时创建新线程 |
newSingleThreadExecutor | 创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,如果这个线程异常结束,会有另一个取代它,保证顺序执行 |
newScheduleThreadPool | 创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer |
线程同步、线程调度相关的方法
wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理
InterruptedException异常;
notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待
状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞
争,只有获得锁的线程才能进入就绪状态;
线程的基本状态
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
1)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
3)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
yeild和sleep的区别
(区别) | yeild | sleep |
---|---|---|
作用 | 使线程回到就绪状态 | 使线程进入阻塞状态 |
阻塞时长 | 不需要提供(使线程直接进入就绪状态,没有阻塞时长) | 需要提供 |
是否考虑线程优先级 | 可以使优先级高的线程得到执行的机会,而且只能使相同或更高优先级的线程有执行的机会,甚至于某些时候JVM认为不符合最优资源调度的情况下会忽略该方法的调用(类似于System.gc()) | 因为不考虑线程的优先级, 可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会 |
共同点 | 不会释放锁资源 | 不会释放锁资源 |
Thread.yield(); | Thread t = new Thread(); t.sleep(1000) | |
参数 | 没有参数 | sleep(long ms),sleep(long ms,int nanos) |
是否抛出异常 | 无 | 抛出InterruptedException |
注:
- 执行sleep()的线程在指定的时间内肯定不会执行
- 不会释放锁资源: 即如果正在运行的线程占有某个资源的同步锁,它不会释放掉这个同步锁,其他线程仍然不能访问该资源
- Java并不保证线程在阻塞给定的时间后能够马上执行(事实上这几乎是不可能的事情),在阻塞时间到了之后,线程进入就绪状态,继续执行的时机取决于Java虚拟机的线程调度机制,唯一能够确定的是,线程中断执行的时间是大于等于给定的阻塞时长的,因此不要将sleep用作精确度要求非常高的定时任务调度
Lock接口和Synchronized区别
区别 | |
---|---|
Lock接口和Synchronized | Lock接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞 |
Lock接口提供lock和unlock方法获得和释放锁 | |
Lock接口具有比Synchronized更广泛的锁定操作 |
⭐Jquery
插件:validate
⭐vue
生命周期
从一个组件创建、数据初始化、挂载、更新、销毁,这就是一个组件所谓的生命周期
beforeCreate :创建vue实例 data computed watch methods不能访问;
created: 可以对data数据进行操作, 可进行一些请求,但不易过多,避免白屏时间太长
beforeMount:判断是否有template进行渲染保存到内存当中,但还未挂载在页面上;
mounted: 将内存中的模块挂载到页面上 ,此时可以操作页面上的DOM节点,但还未挂载在页面上
beforeUpdate: 页面显示的数据是旧的,此时data里的数据是最新,页面数据和data数据还没同步;
updated : 根据data里的最新数据渲染出最新的DOM树 然后挂载到页面
beforeDestroy:此时组件从运行进入到销毁阶段 data和methods可用 销毁还未执行;
destroyed : 组件已经完全销毁,所有的方法指令等都不可使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head>
<body>
<div id="app">
<p>{{ message }}</p> //数据绑定的文本插值
<button @click="changeMessage">改变Message</button>
</div>
</body>
<script>
var app = new Vue({
el: "#app",
data: {
message: "Hello Vue!"
},
methods: {
changeMessage() {
this.message = 'goodbye world'
}
},
beforeCreate() {
console.log("~~~~~~ 初始化前 ~~~~~~");
console.log(this.message);
console.log(this.$el);
},
created() {
console.log("~~~~~~ 初始化完成 ~~~~~~");
console.log(this.message);
console.log(this.$el);
},
beforeMount() {
console.log("~~~~~~ 挂载前 ~~~~~~");
console.log(this.message);
console.log(this.$el);
},
mounted() {
console.log("~~~~~~ 挂载完成 ~~~~~~");
console.log(this.message);
console.log(this.$el);
},
beforeUpdate() {
console.log("~~~~~~ 更新前 ~~~~~~");
console.log(this.message);
console.log(this.$el);
},
updated() {
console.log("~~~~~~ 更新完成 ~~~~~~");
console.log(this.message);
console.log(this.$el);
}
});
</script>
</html>
在组件中具体
beforeCreate、created、 beforeMount、mounted、beforeUpdate、 updated、beforeDestroy、destroyed
Vue钩子函数
就是指在一个Vue实例从创建到销毁的过程自动执行的函数
beforeCreate() 实例创建前触发
created() 实例创建完成
beforeMount() 模板渲染前,可以访问数据,模板编译完成,虚拟DOM已经存在
mounted() 模板渲染完成,可以拿到DOM节点和数据
beforeUpdate() 更新前
updated() 更新完成
activated() 激活前
deactivated() 激活后
beforeDestroy() 销毁前
destroyed() 销毁后
watch是在监控的数据变化时就会自动执行对应的方法
computed是在数据变化时再次计算数据。
自定义指令directives的钩子函数
bind() 绑定指令到元素上,只执行一次。
inserted() 绑定了指令的元素插入到页面中展示时调用,很常用。
update() 所有组件节点更新时调用
componentUpdated 指令所在的节点及其子节点全部更新完成后调用。
unbind() 解除指令和元素的绑定,只执行一次。
路由导航 / 路由守卫 钩子函数
全局守卫
前置:router.beforeEach((to,from,next)=>{ })
后置:router.afterEach((to,from)=>{ })
路由独享守卫
beforeEnter:(to,from,next)=>{ }
导航守卫
beforeRouteEnter(to,from,next){ }
beforeRouteLeave(to,from,next){ }
v-if 和 v-show的区别是什么? 什么时候使用v-if更好? 什么时候用v-show更好?
v-show 可以操作display属性.主要用于频繁操作
v-if 销毁和创建元素,主要是用于大量数据渲染到页面时使用符合条件就将数据渲染,频繁使用会消耗性能
vue-router路由的传参方式(重要)
第一种:使用router的name属性也就是params来传递参数
传值页面:
this.router.push(name:“路由配置中对应的name名”,para ms:参数)
取值页面 this.router.push({name:“路由配置中对应的name名”,params:{参数}}) 取值页面 this.router.push(name:“路由配置中对应的name名”,params:参数)取值页面this.route.params.userId
第二种:使用query来传递参数
传值页面
this.r o u t e r . p u s h ( p a t h : " / l o g i n " , q u e r y : 参数 )
取值页面 t h i s . router.push({path:“/login”,query:{参数}}) 取值页面 this.router.push(path:“/login”,query:参数)取值页面this.route.query.userId
第三种:使用vue里的标签来传递参数
传值页面
Hi页面1
取值页面
this.$route.params.userId
第四种 : 动态路由传参
this.r o u t e r . p u s h ( ′ . / . / ′ + i d )
取值页面 t h i s . router.push(‘././’ + id)
取值页面 this.router.push(
′
././
′
+id)
取值页面this.route.params.id
post和get的区别?
get 的参数会显示在地址栏,不安全. 可传的数据量小
post传的参数不会在地址栏显示,相对安全,可传的数据量大
ES6的新特性有哪些?(必背)
列举常用的ES6特性:
let、const
箭头函数
类的支持
字符串模块
symbols
Promises
数据解构
展开运算符
什么是闭包?(必背)
答: 函数嵌套函数,函数内部可以访问外部变量,外部不能直接访问该变量闭包保存了自己的私有变量,通过提供的接口给外部使用 延申了作用范围
JS作用域和变量提升?(必背)
作用域:变量起作用的范围 变量访问会层层往上级作用域访问直到window,称为作用域链
变量提升:JS编译阶段会将文件中所有var,function声明的变量提升到当前作用域最顶端
cookie/localstorage/session区别(重要)
localstorage 本地存储,只有手动删除才会销毁
session数据保存在服务器端,生命周期由服务器端决定
cookie数据保存在客户端 只有4k左右
session和cookie 都是用来跟踪浏览器用户身份的会话方式
⭐Docker
查看Linux内核 uname -r
安装Docker yum -y install docker
启动docker服务 service docker start/systemctl start docker
用docker启动服务 docker start 服务
启动镜像 docker run hello-world镜像
远程载入一个镜像 docker pull training/webapp
查看正在运行的容器 docker ps
停止容器 docker stop ——+CONTAINER ID
docker run -d -P springbootdocker
-d参数是让容器后台运行
-p 是做端口映射,此时将服务器中的8080端口映射到容器中的8888(项目中端口配置的是8080)端口
-P 自动映射
⭐数据库优化之分库分表
(mysql单表存储量推荐是百万级,mysql单表数据太大,会导致性能变慢)
分库分表是解决数据过大问题的良方,目的就是为了缓解数据库的压力,最大限度减轻数据库压力,提高数据处理的效率。
分库、分表一般方式:
【垂直拆分(先)】分库是把一个数据库,按实际场景切分多个库,再将数据表分散到多个库中。
【水平拆分(后)】分表是把一个数据库中的数据表拆分成多张表,防止单表过大。
⭐数据库
MYSQL
3.1 SELECT
SELECT 表,函数(主键列的字段名) as 字段名,字段名
FROM 表
WHERE 条件
GROUP BY 字段名
Having 条件 - 分组之后的帅选
ORDER BY 字段名,字段名 DESC/ASC
limit 部分
时间大小也是可以比较的
where 字段 < '2020-10-10'
3.1.1 count函数
- count(主键列);count(*)
- count统计的是非null的列(如果()里面是null则不会统计,所以要写上主键列,因为它不允许为空)
3.1.2 orderby
- 当字段名1相同时,会按照第二个列进行排序
- 两个字段可以是不一样的排序方式
select 字段名 from 表 ORDER BY 字段名 DESC
3.1.3 limilt
显示的记录行有n个
select 字段名 from 表 limit n;
a表示起始位置(不包括a),b表示参数行数,从a开始,显示有b个记录行
select 字段名 from 表 limit a,b;
3.1.4 like
%代表任意长度/任意字符
_代表表示一个字符
- 查找以n开头的
select 字段名 from 表 where 字段名 like 'n%'
- 查找以n结尾的
select 字段名 from 表 where 字段名 like '%n'
- 查找包含n的
select 字段名 from 表 where 字段名 like '%n%'
- 查找第二个字符是这个n的数据(可以多加 _)
select 字段名 from 表 where 字段名 like '_n'
3.1.5 between … and …
在a到b之间,包含 a,b这个临界值
select 字段名 from 表 where 字段名 between a and b
3.1.6 not in/in
字段名取值为1,4,5的
select 字段名 from 表 where 字段名 IN (1,4,5)
字段名取值不为1,4,5的
select 字段名 from 表 where 字段名 NOT IN (1,4,5)
3.1.7 having
在OrderBy分组后用having再筛选
select 字段名 from 表 ORDER BY 字段名 having 条件
3.1.8 正则表达式
查询以大开头
select 字段名 from 表 where 字段名 regexp '^大'
查询以A结尾
select 字段名 from 表 where 字段名 regexp 'A$'
查询以A或者B或者D结尾
select 字段名 from 表 where 字段名 regexp '[ABD]$'
查询以A或者B或者D至少出现一次在结尾
select 字段名 from 表 where 字段名 regexp '[ABD][1]$'
3.2 INSERT
Insert into 表 values(字段名,字段名)
Insert into 字段名,字段名 values(对应字段名,对应字段名)
3.3 DELETE
Delete from 表 where ...
truncate table 表
不能加条件
删除表数据 = delete from 表
drop table 表
删除整个表(包括表数据和表结构)
3.4 UPDATE
Update 表 set 操作 where 条件
Oracle
desc emp表 查看表结构
多了number数据类型,是数字类型
❤优化慢sql查询
慢查询的原因,原因主要有三:
-
加载了不需要的数据列
-
查询条件没有命中索引
-
数据量太大
第一步.开启mysql慢查询
方法一:修改配置文件
在 my.ini 增加几行: 主要是慢查询的定义时间(超过2秒就是慢查询),以及慢查询log日志记录( slow_query_log)
方法二:通过MySQL数据库开启慢查询
第二步:分析慢查询日志
利用explain关键字可以模拟优化器执行SQL查询语句,来分析sql慢查询语句
EXPLAIN SELECT * FROM res_user ORDER BYmodifiedtime LIMIT 0,1000
table | type | possible_keys | key |key_len | ref | rows | Extra EXPLAIN列的解释:
table 显示这一行的数据是关于哪张表的
type 这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、range、indexhe和ALL
rows 显示需要扫描行数
key 使用的索引
第三步:常见的慢查询优化
索引没起作用的情况
使用LIKE关键字的查询语句,如果匹配字符串的第一个字符为“%”,索引不会起作用。只有“%”不在第一个位置索引才会起作用
- 以“%” 开头的 LIKE 语句, 模糊匹配(在字符串like时左边是通配符.类似于’%aaa’)
- OR 语句前后没有同时使用索引
- 数据类型出现隐式转化( 如 varchar 不加单引号的话可能会自动转换为 int 型)
- 使用!=查询,
- 列参与了数学运算或者函数
- 当mysql分析全表扫描比使用索引快的时候不使用索引.
- 当使用联合索引,前面一个条件为范围查询,后面的即使符合最左前缀原则,也无法使用索引.
- 空值不在索引中存储,如果where 条件中有a is null的条件,那么就不会使用到索引
优化数据库结构
- 将字段很多的表分解成多个表
对于字段比较多的表,如果有些字段的使用频率很低,可以将这些字段分离出来形成新表。因为当一个表的数据量很大时,会由于使用频率低的字段的存在而变慢。 - 增加中间表
对于需要经常联合查询的表,可以建立中间表以提高查询效率。通过建立中间表,把需要经常联合查询的数据插入到中间表中,然后将原来的联合查询改为对中间表的查询,以此来提高查询效率
分解关联查询
将一个大的查询分解为多个小查询是很有必要的。
很多高性能的应用都会对关联查询进行分解,就是可以对每一个表进行一次单表查询,然后将查询结果在应用程序中进行关联,很多场景下这样会更高效,例如:
SELECT * FROM tag
JOIN tag_post ON tag_id = tag.id
JOIN post ON tag_post.post_id = post.id
WHERE tag.tag = 'mysql';
分解为:
SELECT * FROM tag WHERE tag = 'mysql';
SELECT * FROM tag_post WHERE tag_id = 1234;
SELECT * FROM post WHERE post.id in (123,456,567);
优化LIMIT分页
select id,title from collect limit 90000,10;
该语句存在的最大问题在于limit M,N中偏移量M太大(我们暂不考虑筛选字段上要不要添加索引的影响),导致每次查询都要先从整个表中找到满足条件 的前M条记录,之后舍弃这M条记录并从第M+1条记录开始再依次找到N条满足条件的记录。如果表非常大,且筛选字段没有合适的索引,且M特别大那么这样的代价是非常高的。
- 方法1:“关延迟联”
Select news.id, news.description from news inner join (select id from news order by title limit 50000,5) as myNew using(id);
- 方法2:先查询出主键id值
select id,title from collect where id>=(select id from collect order by id limit 90000,1) limit 10;
原理:先查询出90000条数据对应的主键id的值,然后直接通过该id的值直接查询该id后面的数据。
- 方法3:建立复合索引 acct_id和create_time
select * from acct_trans_log WHERE acct_id = 3095 order by create_time desc limit 0,10
注意sql查询慢的原因都是:引起filesort
分析具体的SQL语句(sql优化)
select * from a where id in (select id from b );
- SELECT子句中避免使用 ‘ * ‘
- 在where子句中应把最具限制性的条件放在最前面
select * from test_001 where id<=10000 and id>=1;
select * from test_001 where id>=1 and id<=10000;
- where子句中字段的顺序应和复合索引中字段顺序一致
select * from test_002 where a=… and b=… and c=…;
若有索引index(a,b,c),则where子句中字段的顺序应和索引中字段顺序一致
- 使用>=来替代>,使用<=替代<
前者比后者更加快速的定位到索引
高效:
SELECT * FROM EMP WHERE DEPTNO >=4
- 用IN来替换OR
低效:
SELECT…. FROM LOCATION WHERE LOC_ID = 10 OR LOC_ID = 20 OR LOC_ID = 30
高效
SELECT… FROM LOCATION WHERE LOC_IN IN (10,20,30);
- 1.2.10优化GROUP BY
提高GROUP BY 语句的效率, 可以通过将不需要的记录在GROUP BY 之前过滤掉
低效:
SELECT JOB , AVG(SAL)
FROM EMP
GROUP by JOB
HAVING JOB = ‘PRESIDENT'
OR JOB = ‘MANAGER'
高效:
SELECT JOB , AVG(SAL)
FROM EMP
WHERE JOB = ‘PRESIDENT'
OR JOB = ‘MANAGER'
GROUP by JOB
- 用EXISTS替代IN、用NOT EXISTS替代NOT IN
例如:
SELECT …FROM EMPWHERE DEPT_NO NOT IN (SELECT DEPT_NOFROM DEPTWHERE DEPT_CAT=’A’);
为了提高效率。改写为:
(方法一:高效)
SELECT ….FROM EMP A,DEPT BWHERE A.DEPT_NO = B.DEPT(+)AND B.DEPT_NO IS NULL AND B.DEPT_CAT(+) = ‘A’
(方法二:最高效)
SELECT ….FROM EMP E WHERE NOT EXISTS (SELECT ‘X’FROM DEPT DWHERE D.DEPT_NO = E.DEPT_NOAND DEPT_CAT = ‘A’);
- sql语句用大写的
先解析sql语句,把小写的字母转换成大写的再执行 - 1.2.15避免在索引列上使用计算
WHERE子句中,如果索引列是函数的一部分.优化器将不使用索引而使用全表扫描
- 空值不在索引中存储
如果where 条件中有a is null的条件,那么就不会使用到索引
因此在数据库设置中,可以考虑用一个默认值来代替空值 - 索引值过大
索引值过大(如在一个char(40)的字段上建索引),会造成大量的I/O开销 - 子查询
尽量少用子查询,特别是相关子查询。因为这样会导致效率下降。
查询嵌套层次越多,效率越低,因此应当尽量避免子查询。如果子查询不可避免,那么要在子查询中过滤掉尽可能多的行。
sql语句写的不要太复杂,有时候简单的语句就可以实现
❤索引
是一种数据结构,可以帮助我们快速的进行数据的查找
在MySQL中使用较多的索引有Hash索引,B+树索引等,InnoDB存储引擎的默认索引实现为:B+树索引
类型
- 单值索引:即一个索引只包含单个列,一个表可以有多个单列索引
建表时,加上 key(列名) 指定
create index 索引名 on 表名(列名)
alter table 表名 add index 索引名(列名)
- 唯一索引:索引列的值必须唯一,但允许有 null 且 null 可以出现多次
建表时,加上 unique(列名) 指定
create unique index idx_表名_列名 on 表名(列名)
alter table 表名 add unique 索引名(列名)
- 主键索引:设定为主键后数据库会自动建立索引,innodb 为聚簇索引,值必须唯一且不能为 null
建表时,加上 primary key(列名) 指定
- 复合索引:即一个索引包含多个列
建表时,加上 key(列名列表) 指定
create index 索引名 on 表名(列名列表)
alter table 表名 add index 索引名(列名列表)
假设现在建立了"name,age,school"的联合索引,那么索引的排序为: 先
按照name排序,如果name相同,则按照age排序,如果age的值也相等,则按照school进行排序
复合索引的使用
MySQL执行计划(执行优化器),在我们写的select语句前加一个Explain关键字
type表示对表访问方式,表示MySQL在表中找到所需行的方式,又称“访问类型”。
常用的类型有: ALL、index、range、 ref、eq_ref、const、system、NULL(从左到右,性能从差到好)
ALL — MySQL将遍历全表以找到匹配的行(全表扫描)
index —index与ALL区别为index类型只遍历索引树
range —只检索给定范围的行,使用一个索引来选择行
ref —表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
eq_ref —类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件
const —当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量。
system —system是const类型的特例,当查询的表只有一行的情况下,使用system
NULL —MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`aid` varchar(20) NOT NULL DEFAULT '' COMMENT 'aid',
`bid` varchar(20) NOT NULL DEFAULT '' COMMENT 'bid',
`cid` varchar(20) NOT NULL DEFAULT '' COMMENT 'cid',
PRIMARY KEY (`id`),
KEY `abc` (`aid`,`bid`,`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
INSERT INTO test VALUES('a01','b01','c01');
INSERT INTO test VALUES('a02','b02','c02');
INSERT INTO test VALUES('a03','b03','c03');
UPDATE `test` SET `aid` = 'a01', `bid` = 'b01', `cid` = 'c01' WHERE `id` = 1;
UPDATE `test` SET `aid` = 'a02', `bid` = 'b02', `cid` = 'c02' WHERE `id` = 2;
UPDATE `test` SET `aid` = 'a03', `bid` = 'b02', `cid` = 'c03' WHERE `id` = 3;
#组合结果为 abc ab ac bc 四种组合结果
#abc 能用到索引
explain select * from test where aid='a01' and bid='b01' and cid='c01';
#bca能用到索引
explain select * from test where bid='b01' and cid='c01'and aid='a01';
#cab能用到索引
explain select * from test where cid='c01'and aid='a01' and bid='b01';
#ab 能用到索引
explain select * from test where aid='a01' and bid='b01';
#ac 能用到索引
explain select * from test where aid='a01' and cid='c01';
#ba 能用到索引
explain select * from test where bid='b01' and aid='a01';
#ca 能用到索引
explain select * from test where cid='c01' and aid='a01';
#bc 不能用到索引
explain select * from test where bid='b01' and cid='c01';
#b 用不到索引(都是全表扫描,没有走索引)
explain select * from test where bid='b01';
#c 用不到索引(都是全表扫描,没有走索引)
explain select * from test where cid='c01';
#cb 用不到索引(都是全表扫描,没有走索引)
explain select * from test where cid='c01' and bid='b01';
结论:
复合索引(abc) 所有情况的罗列,bca和 cab 也能用到索引是mysql优化器优化的结果(修改查询列顺序,发现结果一样。是以为MySQL会通过优化器,自动优化索引顺序),最根本的还是最左原则
最左原则:
最左优先,以最左边的为起点任何连续的索引都能匹配上。同时遇到范围查询(>、<、between、like)就会停止匹配
创建索引注意事项
-
限制表的索引数目。对一个存在大量更新操作的表,3<=所建索引的数目<=5。索引会提高了访问速度,但太多索引会影响数据的更新速度。
-
避免在取值朝一个方向增长的字段(例如:日期类型的字段)上,建立索引;对复合索引,避免将这种类型的字段放置在最前面。由于字段的取值总是朝一个方向增长,新记录总是存放在索引的最后一个叶页中,从而不断地引起该叶页的访问竞争、新叶页的分配、中间分支页的拆分。此外,如果所建索引是聚集索引,表中数据按照索引的排列顺序存放,所有的插入操作都集中在最后一个数据页上进行,从而引起插入“热点”。
-
对复合索引,按照字段在查询条件中出现的频度建立索引。在复合索引中,记录首先按照第一个字段排序。对于在第一个字段上取值相同的记录,系统再按照第二个字段的取值排序,以此类推。因此只有复合索引的第一个字段出现在查询条件中,该索引才可能被使用。因此将应用频度高的字段,放置在复合索引的前面,会使系统最大可能地使用此索引,发挥索引的作用。
-
删除不再使用,或者很少被使用的索引。表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再被需要。数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。
-
对于那些在查询中很少使用或者参考的列不应该创建索引。这是因为,既然这些列很少使用到,因此有索引或者无索引,并不能提高查 询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求;
-
不要在有大量相同取值的字段上,建立索引。这是因为,由于这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加 快检索速度;
-
对于那些定义为text, image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少;
-
当修改性能远远大于检索性能时,不应该创建索引。这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因此,当修改性能远远大于检索性能时,不应该创建索引。
⭐AJAX
$.ajax({
url : '${basepath}/api/front/buyTickets/' + id,
type : 'get',
data : {
'id' : id
},
success : function(res) {
//上部分
$(".avatar").attr("src",
"${basepath}/front/picture/" + res.icon);
$(".typename").html(res.type.typename);
$(".duration").html(res.duration + "分钟");
$(".name").html(res.name);
$(".dra").html(res.info);
$(".stonefont").html(res.price);
}
});
⭐JSP
JSP的九种内置对象
Linux命令
shutdown -h now 立刻关机
shutdown -r now 立刻重启
su - root 切换成管理员
ls 查看目录的所有目录和文件
ls -a 以及隐藏文件
ll 显示更多信息如权限
ls /dir 查看指定目录和文件
增
mkdir aaa 创建一个名为aaa的目录
mkdir /user/aaa
touch 文件名 新增一个文件
删
rm文件 删除文件
rm -f 文件 删除文件不询问
rm -r 目录 删除目录
rm -rf 目录 删除目录不询问
rm -rf删除该目录下所有
rm -rf/删除所有文件和目录
改
mv 当前目录/文件/压缩包 新目录/文件/压缩包 把当前目录改成新目录
mv 目录名称 目录位置 (mv /user/aaa /user)剪切到新位置
cp -r 目录名称 拷贝到目标位置(-r表示递归)
vi 文件名 进入文件编辑
a或者i或者o进入编辑模式
保存文件:ESC退出进入命令行->wq/q!不保存
查
find /usr/tmp -name ‘a*’ 查找/usr/tmp目录下的所有以a开头的目录或文件
cat sudo.conf 看最后一屏内容
more sudo.conf 显示百分比,回车可以下一行,空格下一页
less sudo.conf 翻页查看pageUp q结束
tail -10 sudo.conf 查看sudo.conf的后10行,ctrl+c结束
修改权限
R w x 4可读,2可写,1可执行
chmod 100 aaa.txt 设置成拥有者可执行,不可读和写
打包和压缩
tar -zrcf ab.tar * 打包文件夹下所有文件命名为ab.tar
解压
Tar -zxvf ab.tar 解压在目录下
Tar -zxvf ab.tar -C /usr/ 解压在usr目录下
grep
ps -ef | grep sshd 查找指定ssh服务进程
Su
su不足:如果某个用户需要使用root权限、则必须要把root密码告诉此用户。
Su - root 切换到root用户,路径是/root
Su - test 切换到test用户,路径/home/test
Sudo
Linux sudo命令以系统管理者的身份执行指令,也就是说,经由 sudo 所执行的指令就好像是 root 亲自执行。
权限列表
sudo -l
版本资讯
sudo -V
以root权限执行上一条命令
$ sudo !!
指定用户执行命令
# sudo -u userb ls -l
以特定用户身份进行编辑文本
$ sudo -u uggc vi ~www/index.html//以 uggc 用户身份编辑 home 目录下www目录中的 index.html 文件
系统服务
Service iptables restart/start/stop/status
开启关闭服务
Chkconfig iptables off/on 开关服务的自启动
Pwd 查看当前目录路径
Ps -ef 查看正在运行的进程
Kill pid 杀死进程pid进程
Ifconfig
Ping ip
Netstart -an|grep 8080 查看当前系统端口(搜索指定端口)
Clear 清屏
service mysql start
yum install #全部安装 yum install package1 #安装指定的安装包package1
systemctl start redis
systemctl stop firewalld
添加
firewall-cmd --zone=public --add-port=80/tcp --permanent (–permanent永久生效,没有此参数重启后失效)
重新载入
firewall-cmd --reload
查看
firewall-cmd --zone=public --query-port=80/tcp
删除
firewall-cmd --zone=public --remove-port=80/tcp --permanent
项目点回答
你如何使用沙箱支付?
首先拉取官方的AlipayConfig配置文件,修改支付宝账号的APPID、商户私钥和支付宝公钥,以及支付成功跳转的通知页面的路径
写一个控制器方法,参数有电影名、价格和数量,实例化DefaultAlipayClient获得初始化的AlipayClient,调用配置类设置返回地址。最后在支付按钮上绑定控制器地址就可以了。
你如何使用榛子云短信完成短信验证和注册登录?
首先写一个发送短信的controller方法,配置模板ID、AppId和AppleSercret参数来初始化ZhenziSmsClient,定义一个字符数组接收随机生成的验证码和提示信息,并把验证码和创建时间存入Session,根据前台获取的用户电话号码、验证码跟取出的session值比对,相同则注册成功;登录类似,验证码与session值相同则登录成功
如何实现选座功能
用localStorage.getItem获取座位信息,用ajax发送带有座位和订单信息(总价和数量)的url请求,后台将这些信息存到session中,供订单支付时取值。支付成功后调用添加座位的方法,以字符串拼接的方式放入数据库;日后显示的时候先split分隔逗号,再循环输出座位信息
如何使用Spring的事务管理完成失败回滚
使用@Transactional注解加入到订单生成的service实现类的方法上,若事务失败,则不添加座位信息,并提示失败。
⭐Spring
SpringBean的生命周期
- 先实例化bean对象 然后是进行对bean的一个属性进行设置
- 接着是对BeanNameAware(其实就是为了让Spring容器来获取bean的名称)、BeanFactoryAware(让bean的BeanFactory调用容器
的服务)、ApplicationContextAware(让bean当前的applicationContext可以来取调用Spring容器的服务) - 然后是实现BeanPostProcessor 这个接口中的两个方法,主要是对调用接口的前置初始化postProcessBeforeInitialization 这里是主要是对xml中自己定义的初始化方法 init-method = “xxxx”进行调用 然后是继续对BeanPostProcessor 这个接口中的后置初始化方法进行一个调用postProcessAfterInitialization()
- 基本上这个bean的初始化基本已经完成,就处于就绪状态
- 当Spring容器中如果使用完毕的话,就会调用destory()方法 最后会去执行我们自己定义的销毁方法来进行销毁,然后结束生命周期。
Spring的几种作用域
Singleton
自动实例化所有singleton的bean并缓存于容器中,主要是为了提前发现潜在的配置问题和加快运行效率
Prototype
容器启动不实例化bean,在接受bean请求提供bean给调用者后就不再管理生命周期
request
每个http请求都会创建一个新的bean,处理完http请求后,bean会被销毁
Session
同一个Session共享一个bean,不同Session用不一样的bean,httpSession结束后才销毁bean
GlobalSession
同一个全局session共享一个bean,一般用于portlet应用环境
Spring几个核心模块
SpringAop
底层实现?
基于JDK的动态代理和cglib动态创建类来动态代理所实现的
场景:前后加上日志
1、切面(Aspect):被抽取的公共模块,可能会横切多个对象。 在Spring AOP中,切面可以使用通用
类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。
2、连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。
3、通知(Advice):在切面的某个特定的连接点(Join point)上执行的动作。通知有各种类型,其中
包括“around”、“before”和“after”等通知。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并
维护一个以连接点为中心的拦截器链。
4、切入点(Pointcut):切入点是指 我们要对哪些Join point进行拦截的定义。通过切入点表达式,指
定拦截的方法,比如指定拦截add、search。 5、引入(Introduction):(也被称为内部类型声明(inter-type declaration))。声明额外的方法
或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,
你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
6、目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。也有人把
它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被
代理(proxied) 对象。
7、织入(Weaving):指把增强应用到目标对象来创建新的代理对象的过程。Spring是在运行时完成
织入。
切入点(pointcut)和连接点(join point)匹配的概念是AOP的关键,这使得AOP不同于其它仅仅提供
拦截功能的旧技术。 切入点使得定位通知(advice)可独立于OO层次。 例如,一个提供声明式事务管
理的around通知可以被应用到一组横跨多个对象中的方法上(例如服务层的所有业务操作)。
SpringAOP应用
@Aspect
public class Audience {
//使用@Pointcut注解声明频繁使用的切入点表达式
@Pointcut("execution(* com.wqh.concert.Performance.perform(..))")
public void performance(){}
@Before("performance()")
public void silenceCellPhones(){
System.out.println("Sillencing cell phones");
}
@Before("performance()")
public void takeSeats(){
System.out.println("Task Seat");
}
@AfterReturning("performance()")
public void applause(){
System.out.println("CLAP CLAP CLAP");
}
@AfterThrowing("performance()")
public void demandRefund(){
System.out.println("Demand a Refund");
}
}
SpringDao
SpringContext
SpringORM
SpringCore(核心容器)
** SpringIOC的理解?**
IOC就是控制反转,是指创建对象权力转移到Spring容器中,由容器根据配置文件的去创建实例和管理实例间的依赖关系,实现了松散耦合,利用功能复用。
** SpringIOC的实现原理?**
工厂模式加反射机制
SpringWeb
SpringWebMVC
谈谈你对SpringMVC的理解
MVC中
M是模型,有javabean构成,有entity、service和dao构成
V是视图,做页面展示,有jsp和html、vue构成
C是控制器,接收请求,调用返回页面结果
SpringMVC是MVC的开源框架,是Struts2+Spring,是在Spring的基础上,新加了web应用的MVC模块,是Spring的子模块
简化工作流程:
1.客户端发送请求到DispacherServlet(分发器)
2.由DispacherServlet控制器查询HanderMapping,找到处理请求的Controller
3.Controller调用业务逻辑处理后,返回ModelAndView
4.DispacherSerclet查询视图解析器(ViewReslover),找到ModelAndView指定的视图
5.视图负责将结果显示到客户端
详细工作流程:
1、 用户发送请求至前端控制器DispatcherServlet。
2、 DispatcherServlet收到请求调用HandlerMapping 处理器映射器。
3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器
拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet调用HandlerAdapter处理器适配器。
5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、 Controller执行完成返回ModelAndView。
7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9、 ViewReslover解析后返回具体View。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、 DispatcherServlet响应用户。
数据库事务特性(ACID)
原子性:事务是不可分隔的最小工作单元,要么全部改变,要不全部不作改变
一致性:事务在执行前和后数据库都处于正确的状态,数据库的完整性约束,如银行转钱
持久性:事务一旦提交成功,对数据库的改变是永久的,不会因为一些故障丢失数据
隔离性:在一个事务的内部操作事务是对其他事务不影响的
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = RuntimeException.class)
Spirng事务的传播级别(PROPAGATION)
PROPAGATION_NEW:存在事务,加入事务,不存在事务,则新建事务
PROPAGATION_REQUIRE:存在事务与否,都新建事务,新老事务相互独立,外部的事务不会影响内部事务的正常提交
PROPAGATION_NESTED:没有事务,新建事务,存在事务则嵌套到当前事务中运行
PROPAGATION_SUPPORTS:支持当前事务,若没有事务,则按非事务方式运行
PROPAGATION_NOT_SUPPORTS:以非事务的方式运行,若没有事务则挂起
PROPAGATION_MANDARY:强制事务执行,若不存在事务,则抛出异常
PROPAGATION_NEVER:以非事务方式运行,若不存在事务,则抛出异常
Spring事务的隔离级别( Isolation )
Read_Uncommitted未提及读:运行脏读,可能会读取到未提及事务的数据
Read_Committed提交读:只能读取到已提交的数据,是Oracle的默认级别
Reaped_Read可重复读:消除了不可重复读的问题,但是可能存在幻读(多个事务同时提交数据,后面的事务数据覆盖前面的事务,前面的事务好像没有发生过一样)
Serilizable可串行化:完全串行化的读,每次读都需要获取表级共享锁,读写都会阻塞
Spring事务的实现方式
编程事务管理:通过可编程的方式管理事务
声明事务管理:将事务管理和逻辑代码分离,通过xml或者注解的方式配置管理事务
事务三要素是什么
数据源
事务管理器
事务应用和属性配置
Autowired和Resource关键字的区别
同:
可以放在字段和setter方法上,在字段上,就不需要setter方法
异:
@Autowired是Spring提供的注解,org.framework.bean.factory.annation.Autowired,是按类型(bytype装配的),要求依赖对象必须存在不能为null,如果想byname,可以配置@Qualifier
@Resouce是J2EE提供的,是Javax.anatation.Resouce,默认按照byname自动注入,有name和type属性,分别可以按照byname和bytype注入,或者使用反射机制使用byname自动注入(最好放在setter方法上,符合面向对象思想,通过get、set方式操作数据)
依赖注入的方式有几种,各是什么?
一、构造器注入 将被依赖对象通过构造函数的参数注入给依赖对象,并且在初始化对象的时候注
入。
优点: 对象初始化完成后便可获得可使用的对象。
缺点: 当需要注入的对象很多时,构造器参数列表将会很长; 不够灵活。若有多种注入方式,每种
方式只需注入指定几个依赖,那么就需要提供多个重载的构造函数,麻烦。
public class TestServiceImpl {
// 下面两种@Resource只要使用一种即可
@Resource(name=“userDao”)
private UserDao userDao; // 用于字段上
@Resource(name=“userDao”)
public void setUserDao(UserDao userDao) { // 用于属性的setter方法上
this.userDao = userDao;
}
}
二、setter方法注入 IoC Service Provider通过调用成员变量提供的setter函数将被依赖对象注入给
依赖类。
优点: 灵活。可以选择性地注入需要的对象。
缺点: 依赖对象初始化完成后由于尚未注入被依赖对象,因此还不能使用。
三、接口注入 依赖类必须要实现指定的接口,然后实现该接口中的一个函数,该函数就是用于依赖
注入。该函数的参数就是要注入的对象。
优点 接口注入中,接口的名字、函数的名字都不重要,只要保证函数的参数是要注入的对象类型即
可。
缺点: 侵入行太强,不建议使用。
PS:什么是侵入行? 如果类A要使用别人提供的一个功能,若为了使用这功能,需要在自己的类中
增加额外的代码,这就是侵入性。
⭐Redis
什么是Redis?
redis是key-value形式的支持内存的非关系型数据库,把整个数据库加载到内存中进行操作,通过异步操作把数据库的数据flush到硬盘上进行保存
Redis优点
- 高性能
用户直接访问数据库的数据是比较慢的,因为是读取硬盘上的数据。而数据存在缓存上的话,下次访问可以直接从缓存取值,也就是直接操作内存,所以速度会快 - 高并发
直接操作缓存能承受的请求比直接操作数据库要大得多,所以可以把常用的数据存在缓存中,这样部分请求不经过数据库,降低数据库压力
Redis 支持哪几种数据类型?
String、List、Set、Sorted Set、hash
- String
常用命令: set,get,decr,incr,mget 等。
String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。
常规key-value缓存应用; <br**> 常规计数:微博数,粉丝数等。 - Hash
常用命令: hget,hset,hgetall 等。
hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,
你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以 hash 数据结构来存储用户信息,商品
信息等等。 - List
常用命令: lpush,rpush,lpop,rpop,lrange等
list 就是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,
粉丝列表,消息列表等功能都可以用Redis的 list 结构来实现。
Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内
存开销。
另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很
棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一
页的往下走),性能高。 - Set
常用命令: sadd,spop,smembers,sunion 等 set 对外提供的功能与list类似是一个列表的功能,特殊之
处在于 set 是可以自动排重的。
当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某
个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并
集、差集的操作。
比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。
Redis可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程,具
体命令如下: - Sorted Set
常用命令: zadd,zrange,zrem,zcard等 和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。
举例: 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解
为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 Sorted Set 结构进行存储。
Redis面试题
http://www.redis.cn/
1什么是redis?
谈谈对redis理解? 切入点 =>概念+应用
Redis 就是一个使用 C 语言开发的NoSQL数据库, Redis 的数据是存在内存中的 ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。
另外,Redis 除了做缓存之外,Redis 也经常用来做分布式锁,消息队列。
Redis 提供了多种数据类型来支持不同的业务场景。Redis 还支持事务 、持久化、Lua 脚本、多种集群方案。
2分布式缓存怎么选型?
分布式缓存常见的是 Memcached 和 Redis。
共同点 :
都是基于内存的数据库,一般都用来当做缓存使用。
都有过期策略。
两者的性能都非常高。
区别 :
Redis 支持更丰富的数据类型(支持更复杂的应用场景)。Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。Memcached 只支持最简单的 k/v 数据类型。
Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memecache 把数据全部存在内存之中。
Redis 有灾难恢复机制。 因为可以把缓存中的数据持久化到磁盘上。
Redis 在服务器内存使用完之后,可以将不用的数据放到磁盘上。但是,Memcached 在服务器内存使用完之后,就会直接报异常。
Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的.
Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的多路 IO 复用模型。 (Redis 6.0 引入了多线程 IO )
Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且,Redis 支持更多的编程语言。
Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除。
3缓存数据请求过程?
如果用户请求的数据在缓存中就直接返回。
缓存中不存在的话就看数据库中是否存在。
数据库中存在的话就更新缓存中的数据。
数据库中不存在的话就返回空数据。
4Redis 常见数据结构以及使用场景分析
string: 一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量.
list: 发布与订阅或者说消息队列、慢查询。
hash: hash 类似于 JDK1.8 前的 HashMap,应用场景为系统中对象数据的存储
set: set 类似于 Java 中的 HashSet; 需要存放的数据不能重复
sorted set:和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合;需要对数据根据某个权重进行排序的场景
bitmap: bitmap 存储的是连续的二进制数字(0 和 1)
5Redis是多线程还是单线程?
虽然说 Redis 是单线程模型,但是, 实际上,Redis 在 4.0 之后的版本中就已经加入了对多线程的支持。
6为什么要给Redis数据设置过期时间?
因为内存是有限的,如果缓存中的所有数据都是一直保存的话,会导致内存溢出 Out of memory。
Redis 自带了给缓存数据设置过期时间的功能:
exp key 60 # 数据在 60s 后过期
setex key 60 value # 数据在 60s 后过期
ttl key # 查看数据还有多久过期
7Redis持久化机制?
支持两种不同的持久化操作。Redis 的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file, AOF)。这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。
快照(snapshotting)持久化(RDB)
Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。
快照持久化是 Redis 默认采用的持久化方式,在 Redis.conf 配置文件中默认有此下配置:
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
AOF(append-only file)持久化
与快照持久化相比,AOF 持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化,可以通过 appendonly 参数开启:
appendonly yes
开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入硬盘中的 AOF 文件。AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 appendonly.aof。
在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no #让操作系统决定何时进行同步
Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。
如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。
8Redis 事务
Redis 可以通过 MULTI,EXEC,DISCARD 和 WATCH 等命令来实现事务(transaction)功能。
使用 MULTI命令后可以输入多个命令。Redis 不会立即执行这些命令,而是将它们放到队列,当调用了EXEC命令将执行所有命令。
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR A
QUEUED
127.0.0.1:6379> INCR B
QUEUED
127.0.0.1:6379> EXEC
- (integer) 1
- (integer) 1
127.0.0.1:6379>
Redis 是不支持 roll back 的,因而不满足原子性的(而且不满足持久性)。将 Redis 中的事务就理解为 :Redis 事务提供了一种将多个命令请求打包的功能。然后,再按顺序执行打包的所有命令,并且不会被中途打断。
9缓存穿透
缓存穿透就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。例如:恶意向redis服务器发送不存在key 发起大量请求,导致大量请求直接请求MySQL数据库。
解决方案1:
如果缓存和数据库都查不到某个 key 的数据就写一个到 Redis 中去并设置过期时间,具体命令如下: SET key value EX 123 。这种方式可以解决请求的 key 变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求 key,会导致 Redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟.
public Object getObject(Integer id) {
// 从缓存中获取数据
Object cacheValue = cache.get(id);
// 缓存为空
if (cacheValue == null) {
// 从数据库中获取
Object storageValue = storage.get(key);
// 缓存空对象
cache.set(key, storageValue);
// 如果存储数据为空,需要设置一个过期时间(300秒)
if (storageValue == null) {
// 设置过期时间,方式被攻击的风险
cache.expire(key, 60 * 5);
}
return storageValue;
}
return cacheValue;
}
解决方案2:
布隆过滤器是一个非常神奇的数据结构,通过它我们可以非常方便地判断一个给定数据是否存在于海量数据中。我们需要的就是判断 key 是否合法,有没有感觉布隆过滤器就是我们想要找的那个“人”。
具体是这样做的:把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。
10雪崩:
缓存在同一时间大面积的失效,后面的请求都直接落到了数据库上,造成数据库短时间内承受大量请求。 这就好比雪崩一样,数据库可能直接就被这么多请求弄宕机了。
举个例子:系统的缓存模块出了问题比如宕机导致不可用。造成系统的所有访问,都要走数据库。
针对 Redis 服务不可用的情况:
采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。
限流,避免同时处理大量的请求。
你怎么解决多线程并发线程不安全?
- 把需要同步的代码放到同步代码块中执行
设置同步代码块(同步锁)。
synchronized(共享资源){
同步执行代码;
}
同步方法。
synchronized 方法名{
方法体(同步代码);
}
注意:因为运行同步代码的效率不高,尽量减小synchronized代码块的大小
- 使用Lock锁机制。
public interface Lock
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。不过,某些锁可能允许对共享资源并发访问,如 ReadWriteLock 的读取锁。
代码层面:
通过synchronized锁来实现线程同步,当线程进入同步代码块后,jvm的计数器将锁的标记置为1,其他线程看到标记为1,会去线程池等待,锁的标记会置为0,之后cpu会随机分配一个线程再次进入同步代码块
通过lock锁的机制,进行手动lock,和unlock,但是这种很容易出现死锁。
注意加锁以及解锁的顺序,就可以避免死锁
乐观:加版本号
加列version int 初始值为0
提交数据更新的时候,需要将版本号更新,版本号+1,修改语句的条件也要加上 And version = #{version,jdbcType=INTEGER}
悲观:加sql——加上for update
建项目要添加的 SQL Mybatis Framework MysqlDriver
悲观锁&乐观锁——适用场景:
悲观锁:比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。
总结:两种所各有优缺点,读取频繁使用乐观锁,写入频繁使用悲观锁。
HashMap和HashTable的区别
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
}
public synchronized V remove(Object key) {
}
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
}
public V remove(Object key) {
}
相同点:
都实现了Map接口、cloneable和serializable
HashMap | HashTable | |
---|---|---|
线程不安全 | 大部分方法有synchronized修饰,线程安全 | |
key、value都可以为null值,但key的null不允许重复, | key、value都不可以为空,key不可以重复 | |
继承的是abstractMap | 继承的是Dictionary | |
底层是基于数组+单向链表+红黑树(jdk1.8) | 底层是基于哈希表实现 | |
初始16,一次扩充到2倍 | 初始是11,扩充2n+1 | |
JDK1.2出现 | JDK1.0出现 |
servlet生命周期
Servlet启动时,开始加载servlet生命周期开始。Servlet被服务器实例化后,容器运行其init方法,请求到达时运行其service方法,service方法自动派遣运行与请求对应的doXXX方法(doGet,doPost)等,当服务器决定将实例销毁的时候(服务器关闭)调用其destroy方法。
SpringBean的生命周期
通过这张图能大致看懂spring的生命周期,详解:
首先会先进行实例化bean对象 然后是进行对bean的一个属性进行设置 接着是对BeanNameAware(其
实就是为了让Spring容器来获取bean的名称)、BeanFactoryAware(让bean的BeanFactory调用容器
的服务)、ApplicationContextAware(让bean当前的applicationContext可以来取调用Spring容器的
服务)
然后是实现BeanPostProcessor 这个接口中的两个方法,主要是对调用接口的前置初始化
postProcessBeforeInitialization 这里是主要是对xml中自己定义的初始化方法 init-method = “xxxx”进
行调用 然后是继续对BeanPostProcessor 这个接口中的后置初始化方法进行一个调用
postProcessAfterInitialization() 其实到这一步,基本上这个bean的初始化基本已经完成,就处于就
绪状态 然后就是当Spring容器中如果使用完毕的话,就会调用destory()方法 最后会去执行我们自己
定义的销毁方法来进行销毁,然后结束生命周期。
22.。。。。。
首先说一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;
Spring上下文中的Bean生命周期也类似,如下:
(1)实例化Bean:
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注
入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容
器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。
(2)设置对象属性(依赖注入):
实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以 及 通过BeanWrapper提供的设置属性的接口完成依赖注入。
(3)处理Aware接口:
接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:
①如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String
beanId)方法,此处传递的就是Spring配置文件中Bean的id值;
②如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传
递的是Spring工厂自身。
③如果这个Bean已经实现了ApplicationContextAware接口,会调用
setApplicationContext(ApplicationContext)方法,传入Spring上下文;
(4)BeanPostProcessor:
如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会
调用postProcessBeforeInitialization(Object obj, String s)方法。
(5)InitializingBean 与 init-method:
如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。
(6)如果这个Bean实现了BeanPostProcessor接口,将会调用
postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调
用的,所以可以被应用于内存或缓存技术;
以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。
(7)DisposableBean: 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现
的destroy()方法;
(8)destroy-method:
最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方
法。
用户注册登录的实现(密码加密MD5)
对spring的了解
对SpringAOP、IOC的认识
Spring使用了哪些设计模式
简单工厂:
简单工厂模式又叫静态工厂方法模式,就是建立一个工厂类,对实现了同一接口的一些类进行
实例的创建。比如,一台咖啡机就可以理解为一个工厂模式,你只需要按下想喝的咖啡品类的按钮
(摩卡或拿铁),它就会给你生产一杯相应的咖啡,你不需要管它内部的具体实现,只要告诉它你
的需求即可。
优点:
无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可
通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一
定程度上提高了系统的灵活性
缺点:
不易拓展,一旦添加新的产品类型,就不得不修改工厂的创建逻辑;
产品类型较多时,工厂的创建逻辑可能过于复杂,一旦出错可能造成所有产品的创建失败,不
利于系统的维护
工厂
模型模板
原型
观察者
责任链
单例
代理模式有哪些?两种动态代理及其区别
JDK动态代理和Cglib动态创建代理类
如果目标类是接口,则使用JDK动态代理技术,如果目标对象没有实现接口,则默认会采用CGLIB代理
JDK动态代理
1、因为利用JDKProxy生成的代理类实现了接口,所以目标类中所有的方法在代理类中都有。
2、生成的代理类的所有的方法都拦截了目标类的所有的方法。而拦截器中invoke方法的内容正好就是代理类的各个方法的组成体。
3、利用JDKProxy方式必须有接口的存在。
4、invoke方法中的三个参数可以访问目标类的被调用方法的API、被调用方法的参数、被调用方法的返回类型。
cglib动态代理
1、 CGlib是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。
2、 用CGlib生成代理类是目标类的子类。
3、 用CGlib生成 代理类不需要接口
4、 用CGLib生成的代理类重写了父类的各个方法。
5、 拦截器中的intercept方法内容正好就是代理类中的方法体
Linux的使用,常用命令
怎么对防火墙进行开关
防火墙这个单词的最后一个字母是什么
Linux怎么看线程,看线程是用来干什么的
Docker的使用
谈谈对dubbo的认识
分布式、微服务、ZooKeeper的认识
Vue的使用认知(有做demo吗)
Oracle、Mysql用哪个,分页怎么实现,如果要显示第二页5条记录怎么写
问有一个字段里面有值重复,要怎么查出这些相同的记录
数据库怎么优化
怎么实现动态sql
添加索引,哪些字段适合添加索引等等索引问题,用SQL怎么显示一个表的索引
谈谈对Redis的认识(就问一些,基础内容自己复习),用redis的实现(做了什么功能),为什么使用redis等
springsecurity相关,问项目的角色有哪些,密码怎么设置
项目中的角色,权限,关系表等关系
使用maven,有分模块吗
问熟悉哪个框架(我讲ssm)然后一直问springboot内容
springboot的注解有哪些,说一下常用的注解
事务的实现方式,用@transaction,怎么实现回滚,为什么rollback=exception
我说了一些注解,问哪些spring的哪些是springboot,想知道你对springboot的了解
当数据库的数据很多怎么确保不会出错
你觉得你的什么技术学的最好
你对springcloud了解多少
用的是什么的注册中心
异常有哪些,运行时异常和非运行时异常,然后空指针,数学运算,超出范围等等,问能用英语讲吗
数学异常ArithmeticException
输入类型转换异常InputMismatchException
空指针异常NullPointerException
数组下标越界异常ArrayIndexOutOfBoundsException
类型转换异常ClassCastException
mybatis和mybatis-plus用过感觉怎么样,喜欢用哪一种,有什么区别
springboot的文件后缀名知道有哪些,properties和yml优先级哪个高,平常有用这两个做一个demo吗
⭐git命令
远程克隆到本地
git clone 地址
$ git clone https://github.com/jquery/jquery.git
git status 查看状态
git add 添加到本地库
git push 如果当前分支只有一个追踪分支,连远程主机名都可以省略。
$ git pull origin
git push
如果当前分支与远程分支之间存在追踪关系,则本地分支和远程分支都可以省略。
$ git push origin
git commit
Spring Bean 的作用域
singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
prototype : 每次请求都会创建一个新的 bean 实例。
request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
session : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
图片上传
写一个参数带有图片的控制器方法,request.getRealPath获取文件路径,用文件.getOrigialFilename获取文件名,拼接得到实际路径放在new出来的File对象里面,调用service方法完成用户的增加,将File对象作为操作,调用myfile.tranferTo(file)完成文件上传操作
重载和重写
分页如何实现?
一、数据在Java代码中进行分页,然后取得当前页数据(物理分页);
二、在数据库中直接取得当前页数据(逻辑分页)。
Java中做分页的实现代码只管拿数据库中的当前页数据即可,数据分页功能交由SQL处理,在分页实现中Java最多只实现总页数的计算,总页数的算法,(数据总行数+每页数据行数-1)/每页数据行数。
Mybatis动态sql有什么用?执行原理?有哪些动态sql?
Mybatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值 完成逻
辑判断并动态拼接sql的功能。
Mybatis提供了9种动态sql标签: trim|where|set|foreach|if|choose|when|otherwise|bind 。
其执行原理为,使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来
完成动态 sql 的功能。
Integer类型以及其他类型能否被继承?
是final类不能被继承
List、Map、Set三个接口,存取元素时,各有什么特点?
List以特定次序来持有元素,可有重复元素,add添加新值,只能以Iterator遍历元素。Set无法拥有重复元素,内部排序,add添加新值,可以通过iterator遍历,也可以get(index i)。Map保存key-value值,get(Object key)返回值为key所对应的value
value可多值。
switch支持哪些类型?
- 最早时,只支持int、char、byte、short这样的整型的基本类型或对应的包装类型Integer、Character、Byte、Short常量,包装类型最终也会经过拆箱为基本类型,本质上还是只支持基本类型
- JDK1.5开始支持enum,原理是给枚举值进行了内部的编号,进行编号和枚举值的映射
- JDK1.7开始支持String,但不允许为null,原理是借助 hashcode( ) 来实现。
什么是事务?
事务是逻辑上的一组操作,要么都执行,要么都不执行。
事务的特性:数据库事务特性:原子性(Atomic)、一致性(Consistency)、隔离性(Isolation)、持久性
(Durabiliy)。简称ACID。
原子性:组成一个事务的多个数据库操作是一个不可分割的原子单元,只有所有操作都成功,
整个事务才会提交。任何一个操作失败,已经执行的任何操作都必须撤销,让数据库返回初始
状态。
一致性:事务操作成功后,数据库所处的状态和它的业务规则是一致的。即数据不会被破坏。
如A转账100元给B,不管操作是否成功,A和B的账户总额是不变的。
隔离性:在并发数据操作时,不同的事务拥有各自的数据空间,它们的操作不会对彼此产生干
扰
持久性:一旦事务提交成功,事务中的所有操作都必须持久化到数据库中。
jsp传值方式
<!-- html -->
<li class="typename">${param.typename}</li>
<!-- js -->
$(".typename").html(res.type.typename);
<!-- js -->
success : function(data) {
$.each(data.data,function(n, value) {
console.log("value.id:"+ value.id);
$(".schedulelist").append(
"<div class=\"cinema-cell\">"
+ "<div class=\"cinema-info\">"
+ "<a href=\"javascript:void(0)\" "
+ " class=\"cinema-name\" data-act=\"cinema-name-click\" "
+ " data-bid=\"b_wek7vrx9\" data-val=\"{city_id: 121, cinema_id: 7349}\">"
+ value.room.roomname
+ "</a> "
+ " <p class=\"period\">"
+ value.period
+ "</p> "
+ "</div>"
+ "<div class=\"buy-btn\">"
+ "<a href=\"selectSeat.jsp?id="
+ value.id
+ "&movieid="
+ value.movieid
+ "\" data-act=\"buy-btn-click\" "
+ "data-val=\"{city_id: 121, cinema_id: 7349}\" data-bid=\"b_wek7vrx9\">选座购票</a>"
+ "</div>"
+ "<div class=\"price\">"
+ "<span class=\"rmb red\">¥"
+ value.movie.price
+ "</span> <span class=\"price-num red\"></span> <span></span>"
+ "</div>"
+ "</div>")
}
纵横
1.Cookie和Session的区别?
1.1存储的位置不同
Session:服务,Cookie:客户端
1.2存储的数据格式不同
Session:value为对象,Object类型
Cookie:value为字符串,如果我们存储一个对象,这个时候,就需要将对象转换为JSON
1.3存储的数据大小
Session:受服务器内存控制
Cookie:一般来说,最大为4k
1.4生命周期不同
Session:服务器端控制,默认是30分钟,注意,当用户关闭了浏览器,session并不会消失Cookie:客户端控制,其实是客户端的一个文件,分两种情况1,默认的是会话级的cookie,这种随着浏览器的关闭而消失,比如保存sessionId的cookie2,非会话级cookie,通过设置有效期来控制,比如这种“7天免登录”这种功能,就需要设置有效期,setMaxAge。
2.Mysql索引的原理,以及何时无法使用索引?
2.1原理
B+Tree是数据库系统实现索引的首选数据结构。
索引的原理:就是把无序的数据变成有序的查询
- 把创建了索引的列的内容进行排序
- 对排序结果生成倒排表
- 在倒排表内容上拼上数据地址链
- 在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据
2.2何时无法使用索引
1.更新频繁字段不适合创建索引
2.若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)
3.尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
4.对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
5.对于定义为text、image和bit的数据类型的列不要建立索引。
模糊查询不能使用索引
创建全文索引的sql语法是:
ALTER TABLE table_name
ADD FULLTEXT INDEX idx_user_name
(user_name
);
使用全文索引的sql语句是:
select id,fnum,fdst from table_name where match(user_name) against(‘zhangsan’ in boolean mode);
3.HashMap的底层实现(红黑树),以及如何扩容?
1.初始化大小是16,如果事先知道数据量的大小,建议修改默认初始化大小。 减少扩容次数,提高性能。
2.最大的装载因子默认是0.75,当HashMap中元素个数达到容量的0.75时,就会扩容。容量是原先的两倍。
3.HashMap底层采用链表法来解决冲突。 但是存在一个问题,就是链表也可能会过长,影响性能,于是JDK1.8,对HashMap做了进一步的优化,引入了红黑树。当链表长度超过8,且数组容量大于64时,链表就会转换为红黑树当红黑树的节点数量小于6时,会将红黑树转换为链表。因为在数据量较小的情况下,红黑树要维护自身平衡,比链表性能没有优势。
4.了解哪些设计模式,简单的描述一种?
1.工厂模式:工厂类可以根据条件生成不同的子类实例,这些子类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作(多态方法)。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。
2.代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引用。实际开发中,按照使用目的的不同,代理可以分为:远程代理、虚拟代理、保护代理、Cache代理、防火墙代理、同步化代理、智能引用代理等。
3.适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起使用的类能够一起工作。
单例模式(Singleton)
最简单的设计模式之一,提供了一种创建对象的最佳方式
创建型模式
确保在一个应用程序中某个类只有一个实例
1.单例类只能有一个实例
2.必须自己创建自己的唯一实例
3.必须给所有其他对象提供这一实例
Java中实现单例模式可以通过两种形式实现
共有点:
构造方法私有化
提供一个静态的方法返回一个类对象
加入线程安全
5.什么是Spring IOC,为什么要使用IOC,简述IOC初始化过程?
5.1什么是Spring IOC
Spring IoC负责创建对象、管理对象(通过依赖注入(DI)、装配对象、配置对象,并且管理这些对象的整个生命周期。
5.2为什么要使用IOC
传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
5.3IOC初始化过程
Resource定位(Bean的定义文件定位)
将Resource定位好的资源载入到BeanDefinition
将BeanDefiniton注册到容器中
- Resource资源定位。这个Resouce指的是BeanDefinition的资源定位。这个过程就是容器找数据的过程,就像水桶装水需要先找到水一样。
ClassPathResource resource =new ClassPathResource(“beans.xml”);
//@SpringJUnitConfig = @ContextConfiguration +@RunWith
@SpringJUnitConfig(locations = {“classpath:beans.xml”})
- BeanDefinition的载入过程。这个载入过程是把用户定义好的Bean表示成Ioc容器内部的数据结构,而这个容器内部的数据结构就是BeanDefition。
- 向IOC容器注册这些BeanDefinition的过程,这个过程就是将前面的BeanDefition保存到HashMap中的过程。
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap(256);
纵横
1、对框架了解多少?
2、Hibernate是什么?跟mybatis的区别?优点?
3、SpringMVC中你使用过哪些注解?
4、SpringMVC怎么实现一对多的?
5、SpringMVC注解select查询,得到的是什么对象?
6、做过什么项目,大概介绍一下?
7、项目中哪里用到了Aop?
8、对springcloud了解多少?
9、对springboot了解多少?
10、Springboot要用tomcat吗?
11、存储器、触发器是什么?
12、Group by 和 order by哪个先执行?
13、怎么理解事物,都用在哪里?
14、Linux了解多少?
15、Git命令了解多少?
16、有脱离开发工具(eclipse、IDEA)怎么部署项目?
17、Maven了解多少?
18、List和Set的区别?
19、怎么使用Set?
20、List和Set安全性问题?
21、内置对象有哪些?为什么这些叫内置对象?
22、Session和cookie的区别?
23、Cookie能不能在控制器中操作?
24、什么是代理?
25、jsp怎么跟数据库关联的?
26、Jsp中怎么请求控制器?
27、Ajax熟悉吗?
28、怎么理解同步、异步?
29、有自学什么Java相关的技术吗?
1.string和stringbuffer的区别,为什么可变不可变
2.list和set,map的区别
3.主要用了哪个map
4.for循环删除集合指定数字能成功吗?为什么
5.两个子项目如何互相调用数据
6.拦截器
7.访问控制
8.如何改编码格式
9.linux了解吗?常见的命令
10.端口
11.问项目功能?
12.如何获取字符串指定字母的位置
13.了解事务吗?什么是事务
14.如果两个同时访问一个数据怎么解决?
15.三大范式,第三范式?
1Oracle性能优化建议:
在生产环境中,SQL执行效率直接决定前段程序页面展示的效率,而影响SQL执行效率的原因是多方面。
1.1、语句脚本代码本身执行效率不高;
1.2、代码中相关表统计信息不全或者过时导致执行计划偏差;
1.3、RAC并行设置引发的性能问题;
1.4、缺少索引
查询数据库当前ACTIVE进程的SQL语句:
select A.SQL_ID,A.SQL_TEXT, A.SQL_FULLTEXT, sysdate
from V
s
q
l
a
r
e
a
A
w
h
e
r
e
A
.
S
Q
L
I
D
i
n
(
s
e
l
e
c
t
S
.
S
Q
L
I
D
f
r
o
m
V
sqlarea A where A.SQL_ID in (select S.SQL_ID from V
sqlareaAwhereA.SQLIDin(selectS.SQLIDfromVsession S
where S.status = ‘ACTIVE’
AND S.USERNAME IS NOT NULL AND S.USERNAME NOT IN (‘SYS’, ‘SYSMAN’, ‘DBSNMP’))
1.1基本工作:
1、表空间充足;
2、初始化参数配置合理;
3、磁盘空间充足(包括归档空间)
4、权限分配是否合理;
5、访问控制机制是否合理;数据库性能指标是否正常;
6、分区表设置是否合理;
7、其他
1.2 SQL语句的优化:
数据库的调优工作是基础调优与SQL调优的互相配合,只有前台语句与后台数据库配合好了才能发挥数据库的最大性能。
基础调优:SGA、数据库配置和I/O问题、优化排序操作、诊断LATCH竞争情况、Rollback Segment优化、锁情况分析、应用的优化、网络安全检查
本文档以SQL优化为主:
SQL调优:SQL语句的优化
1.2.1SELECT子句中避免使用 ‘ * ‘
ORACLE在解析的过程中, 会将’*’ 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间.
1.2.2删除重复记录:
最高效的删除重复记录方法 ( 因为使用了ROWID)例子:
DELETE FROM EMP E WHERE E.ROWID > (SELECT MIN(X.ROWID)
FROM EMP X WHERE X.EMP_NO = E.EMP_NO);
1.2.3尽量多commit:
只要有可能,在程序中尽量多使用COMMIT, 这样程序的性能得到提高,需求也会因为COMMIT所释放的资源而减少:
COMMIT所释放的资源:
a. 回滚段上用于恢复数据的信息.
b. 被程序语句获得的锁
c. redo log buffer 中的空间
d. ORACLE为管理上述3种资源中的内部花费
1.2.4Where后的条件
在where子句中应把最具限制性的条件放在最前面。
在下面两条select语句中()
select * from test_001 where id<=10000 and id>=1;
select * from test_001 where id>=1 and id<=10000;
如果数据表中的数据id都>=0,则第一条select语句要比第二条select语句效率高的多,因为第二条select语句的第一个条件耗费了大量的系统资源。
1.2.5Where字句:
where子句中字段的顺序应和复合索引中字段顺序一致
select * from test_002 where a=… and b=… and c=…;
若有索引index(a,b,c),则where子句中字段的顺序应和索引中字段顺序一致
Oracle的复合索引使用必须严格按照复合索引的顺序进行.
1.2.6Where替代having:
避免使用HAVING子句, HAVING 只会在检索出所有记录之后才对结果集进行过滤. 这个处理需要排序,总计等操作. 如果能通过WHERE子句限制记录的数目,那就能减少这方面的开销. (非oracle中)on、where、having这三个都可以加条件的子句中,on是最先执行,where次之,having最后,因为on是先把不 符合条件的记录过滤后才进行统计,它就可以减少中间运算要处理的数据,按理说应该速度是最快的,where也应该比having快点的,因为它过滤数据后 才进行sum,在两个表联接时才用on的,所以在一个表的时候,就剩下where跟having比较了。在这单表查询统计的情况下,如果要过滤的条件没有涉及到要计算字段,那它们的结果是一样的,只是where可以使用rushmore技术,而having就不能,在速度上后者要慢如果要涉及到计算的字 段,就表示在没计算之前,这个字段的值是不确定的,根据上篇写的工作流程,where的作用时间是在计算之前就完成的,而having就是在计算后才起作 用的,所以在这种情况下,两者的结果会不同。在多表联接查询时,on比where更早起作用。系统首先根据各个表之间的联接条件,把多个表合成一个临时表 后,再由where进行过滤,然后再计算,计算完后再由having进行过滤。由此可见,要想过滤条件起到正确的作用,首先要明白这个条件应该在什么时候起作用,然后再决定放在那里
1.2.7大于和大于等于:
使用>=来替代>,使用<=替代<
前者比后者更加快速的定位到索引
高效:
SELECT * FROM EMP WHERE DEPTNO >=4
低效:
SELECT * FROM EMP WHERE DEPTNO >3
两者的区别在于, 前者DBMS将直接跳到第一个DEPT等于4的记录而后者将首先定位到DEPTNO=3的记录并且向前扫描到第一个DEPT大于3的记录.
1.2.8用IN来替换OR
这是一条简单易记的规则,但是实际的执行效果还须检验,在ORACLE8i下,两者的执行路径似乎是相同的.
低效:
SELECT…. FROM LOCATION WHERE LOC_ID = 10 OR LOC_ID = 20 OR LOC_ID = 30
高效
SELECT… FROM LOCATION WHERE LOC_IN IN (10,20,30);
1.2.9Select Count ()和Select Count(1) 区别
一般情况下,Select Count ()和Select Count(1)两着返回结果是一样的
假如表沒有主键(Primary key), 那么count(1)比count()快,
如果有主键的話,那主键作为count的条件时候count(主键)最快
如果你的表只有一个字段的话那count()就是最快的
count() 跟 count(1) 的结果一样,都包括对NULL的统计,而count(column) 是不包括NULL的统计
selelct 常量 from … 对应所有行,返回的永远只有一个值,即常量 。所以正常只会用来判断是否有还是没有(比如exists子句)。而select * from … 是返回所有行的所有列。
性能上的差异,关键看你的from和where子句。比如说如果你的where条件中可以通过索引,那显然 select 1 from … 的性能比 select * from … 好。
select count()返回所有满足条件的记录数,此时同select sum(1)
但是sum()可以传任意数字,负数、浮点数都可以,返回的值是传入值n*满足条件记录数m
1.2.10优化GROUP BY
提高GROUP BY 语句的效率, 可以通过将不需要的记录在GROUP BY 之前过滤掉.下面两个查询返回相同结果但第二个明显就快了许多.
低效:
SELECT JOB , AVG(SAL)
FROM EMP
GROUP by JOB
HAVING JOB = ‘PRESIDENT’
OR JOB = ‘MANAGER’
高效:
SELECT JOB , AVG(SAL)
FROM EMP
WHERE JOB = ‘PRESIDENT’
OR JOB = ‘MANAGER’
GROUP by JOB
1.2.11用EXISTS替代IN、用NOT EXISTS替代NOT IN
在子查询中,NOT IN子句将执行一个内部的排序和合并。无论在哪种情况下,NOT IN都是最低效的 (因为它对子查询中的表执行了一个全表遍历)。为了避免使用NOT IN,我们可以把它改写成外连接(Outer Joins)或NOT EXISTS。
例如:
SELECT …FROM EMPWHERE DEPT_NO NOT IN (SELECT DEPT_NOFROM DEPTWHERE DEPT_CAT=’A’);
为了提高效率。改写为:
(方法一:高效)
SELECT ….FROM EMP A,DEPT BWHERE A.DEPT_NO = B.DEPT(+)AND B.DEPT_NO IS NULLAND B.DEPT_CAT(+) = ‘A’
(方法二:最高效)
SELECT ….FROM EMP EWHERE NOT EXISTS (SELECT ‘X’FROM DEPT DWHERE D.DEPT_NO = E.DEPT_NOAND DEPT_CAT = ‘A’);
20.用表连接替换EXISTS
通常来说,采用表连接的方式比EXISTS更有效率
SELECT ENAMEFROM EMP EWHERE EXISTS (SELECT ‘X’FROM DEPTWHERE DEPT_NO = E.DEPT_NOAND DEPT_CAT = ‘A’);
(更高效)
SELECT ENAMEFROM DEPT D,EMP EWHERE E.DEPT_NO = D.DEPT_NOAND DEPT_CAT = ‘A’ ;
(译者按:在RBO的情况下,前者的执行路径包括FILTER,后者使用NESTED LOOP)
1.2.12用EXISTS替换DISTINCT
当提交一个包含一对多表信息(比如部门表和雇员表)的查询时,避免在SELECT子句中使用DISTINCT. 一般可以考虑用EXIST替换, EXISTS 使查询更为迅速,因为RDBMS核心模块将在子查询的条件一旦满足后,立刻返回结果.
例子:
低效:
SELECT DISTINCT DEPT_NO,DEPT_NAME FROM DEPT D , EMP E
WHERE D.DEPT_NO = E.DEPT_NO
高效:
SELECT DEPT_NO,DEPT_NAME FROM DEPT D WHERE EXISTS ( SELECT ‘X’
FROM EMP E WHERE E.DEPT_NO = D.DEPT_NO);
1.2.13函数的索引:
函数的使用,使用函数索引。作为条件尽量不使用函数。
函数做为条件不使用索引,但是可以通过创建函数索引来提高查询效率
如果一个表的记录在十万以上,尽量不要使用函数作为条件,那样效率很低。
1.2.14sql语句用大写的
因为oracle总是先解析sql语句,把小写的字母转换成大写的再执行.
1.2.15避免在索引列上使用计算
WHERE子句中,如果索引列是函数的一部分.优化器将不使用索引而使用全表扫描.
举例:
低效:
SELECT … FROM DEPT WHERE SAL * 12 > 25000;
高效:
SELECT … FROM DEPT WHERE SAL > 25000/12;
1.2.16Like模糊检索:
使用like运算符的时候,“a%”将会使用索引,而“a%c”和“%a”则会使用全表扫描,因此“a%c”和“%a”不能被有效的评估匹配的数量。
带通配符(%)的like语句
同样以上面的例子来看这种情况。目前的需求是这样的,要求在职工表中查询名字中包含cliton的人。可以采用如下的查询SQL语句:
select * from employee where last_name like ‘%cliton%’;
这里由于通配符(%)在搜寻词首出现,所以Oracle系统不使用last_name的索引。在很多情况下可能无法避免这种情况,但是一定要心中有底,通配符如此使用会降低查询速度。然而当通配符出现在字符串其他位置时,优化器就能利用索引。在下面的查询中索引得到了使用:
select * from employee where last_name like ‘c%’;
1.2.17空值不在索引中存储。
如果where 条件中有a is null的条件,那么就不会使用到索引
因此在数据库设置中,可以考虑用一个默认值来代替空值
不能用null作索引,任何包含null值的列都将不会被包含在索引中。即使索引有多列这样的情况下,只要这些列中有一列含有null,该列就会从索引中排除。也就是说如果某列存在空值,即使对该列建索引也不会提高性能。
任何在where子句中使用is null或is not null的语句优化器是不允许使用索引的。
1.2.18索引值过大:
索引值过大(如在一个char(40)的字段上建索引),会造成大量的I/O开销(甚至会超过表扫描的I/O开销)
这就为什么有时候索引所占有的空间远远超过了数据所占有的空间。
在这里就要提一下索引表空间与数据表空间分离的好处,有助于快速恢复备份数据,通过RMAN单独备份表空间
1.2.19子查询:
尽量少用子查询,特别是相关子查询。因为这样会导致效率下降。
查询嵌套层次越多,效率越低,因此应当尽量避免子查询。如果子查询不可避免,那么要在子查询中过滤掉尽可能多的行。
sql语句写的不要太复杂,有时候简单的语句就可以实现
2021-8-22面试
Maven是干什么用
发布项目信息以及一种简单的方式来在多个项目之间共享JAR(管多个jar包)
功能一:
Maven主要用于解决导入依赖于Java类的jar和编译Java项目的主要问题。(最早手动导入jar,并使用Ant编译Java项目)
依赖的jar包由pom.xml文件中的dependency属性管理,并且jar包包含类文件和一些必要的资源文件。当然,它可以构建项目,管理依赖关系并生成简单的单元测试报告。
功能二:防止副本导入到项目中,jar之间发生依赖关系的冲突
例如,上一个项目导入了jar。它通过副本导入到项目中,并且jar之间存在依赖关系和冲突。Maven解决了这些问题,但是当互联网速度不佳时,这很烦人。使用专用服务器关系解决此问题。
功能三:使用通用的maven配置文件,避免手动、重复下载jar
Jar包管理,以防止jar之间的依赖关系冲突。在组之间建立私有服务。每个人都使用通用的maven配置文件,而不是手动下载jar。pom文件将自动管理下载的jar包。
功能四:通过使用Maven关联jar来配置引用的jar的版本,以避免冲突(便于更改版本)
Maven是基于项目对象模型的软件项目管理工具,可以通过一小段描述信息来管理项目的构造,报告和文档。Maven可以轻松地帮助您管理项目报告,生成站点,管理jar文件等。例如:项目开发中的第三方jar引用。在开发过程中,合作成员引用的jar版本可能会有所不同,并且同一jar的不同版本可能会重复引用。可以通过使用Maven关联jar来配置引用的jar的版本,以避免冲突。
SpringBoot框架介绍
什么是springboot ?
简化开发、使用porperties或者yml的方式来配置、内置tomcat、简化maven配置、pom.xml文件自动配置Spring对应功能的启动类starter
Springboot 有哪些优点
(不需要手动配置 bean和引入jar包)
减少开发,测试时间和努力。
使用JavaConfig有助于避免使用XML
避免大量的Maven导入和各种版本冲突
没有单独的Web服务器需要。这意味着你不再需要启动Tomcat
需要更少的配置 因为没有web.xml文件,可以使用注解完成Spring的自动加载对象到依赖关系中
可以写多个配置文件方便切换环境
SSM 和 Springboot 的最大区别
- Springboot 将原有的 xml 配置,简化为 java 注解
- 使用 IDEA 可以很方便的搭建一个 springboot 项目,选择对应的 maven 依赖,简化Spring应用的初始搭建以及开发过程
- springboot 有内置的 tomcat 服务器,可以 jar 形式启动一个服务,可以快速部署发布 web 服务
- springboot 使用 starter 依赖自动完成 bean 配置,解决 bean 之间的冲突,并引入相关的 jar 包
Springboot 注解
启动类:@SpringBootApplication
实体类:@Component
Dao层:@Repository
业务层:@Service
控制层:@Controller
项目在启动的过程中,Spring Boot Starter根据约定的信息进行资源初始化、Spring Boot JPA 根据约定的方式自动生成sql
@scope
@configration
@RestController = @Controller + @ResponseBody
@Autowierd
四种常见的 Spring Bean 的作用域
singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
prototype : 每次请求都会创建一个新的 bean 实例。
request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
session : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
Spring Boot 配置加载顺序
Spring Boot 会涉及到各种各样的配置,如开发、测试、线上
可以配置多个配置文件,方便切换使用①
1、properties文件;
2、YAML文件;
3、系统环境变量;
4、命令行参数;
① 提供多套配置文件,如:
applcation.properties
application-dev.properties
application-test.properties
application-prod.properties
然后在applcation.properties文件中指定当前的环境spring.profiles.active=test,这时候读取的就是
application-test.properties文件。
SpringBoot常用的starter有哪些?
- spring-boot-starter-web (嵌入tomcat和web开发需要servlet与jsp支持)
- spring-boot-starter-data-jpa (数据库支持)
- spring-boot-starter-data-redis (redis数据库支持)
- spring-boot-starter-data-solr (solr搜索应用框架支持)
- mybatis-spring-boot-starter (第三方的mybatis集成starter)
Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下
3 个注解:
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源
自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
@ComponentScan:Spring组件扫描。
Swagger介绍
类上@Api(value = “电影控制器”, description = “电影控制器相关功能”)
方法上@ApiOperation(value = “电影列表”, httpMethod = “GET”, notes = “电影模糊查询”, response = AjaxResponse.class)
方法的参数@ApiParam(name = “name”, required = false, value = “查询关键字”) @RequestParam(value = “name”, required = true, defaultValue = “”) String name
Swagger 广泛用于可视化 API,使用 Swagger UI 为前端开发人员提供在线沙箱。Swagger 是用于生成
RESTful Web 服务的可视化表示的工具,规范和完整框架实现。它
运行SpringBoot的方式
运行 Spring Boot 有哪几种方式?
- 打包用命令或者放到容器中运行
- 用 Maven/ Gradle 插件运行
- 直接执行 main 方法运行
Spring Data-JPA怎么实现分页和排序
JPA——Java持久化API
@Query("select u from User u where u.username like %?1%")
Page<User> findByUsernameLike(String username, Pageable pageable);
@Query("select u from User u where u.username like %?1%")
List<User> findByUsernameAndSort(String username, Sort sort);
Spring Data Jpa可以在方法参数中直接传入Pageable或Sort来完成动态分页或排序,通常Pageable或Sort会是方法的最后一个参数
搭建 SSM 框架时,我们需要哪些步骤
- 加相关的 jar 包
- 配置 web.xml,加载 Spring,SpringMVC
- 配置数据库连接,spring 事务
- 配置加载配置文件的读取,开启注解
- 配置日志文件
- 配置完成,部署 tomcat 调试
SSM框架介绍
Spring:
SpringMVC:
Mybatis:
拦截器和过滤器的区别
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<servlet-name>/*</servlet-name>
</filter-mapping>
过滤器(Filter): 它依赖于servlet容器。在实现上,基于函数回调,它可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的,是用来做一些过滤操作,获取我们想要获取的数据,比如:在Javaweb中,对传入的request、response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者Controller进行业务逻辑操作。通常用的场景是:在过滤器中修改字符编码(CharacterEncodingFilter)、在过滤器中修改HttpServletRequest的一些参数(XSSFilter(自定义过滤器)),如:过滤低俗文字、危险字符等
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.scorpios.atcrowdfunding.web.LoginInterceptor"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.scorpios.atcrowdfunding.web.AuthInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
拦截器(Interceptor):它依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上,基于Java的反射机制,属于面向切面编程(AOP)的一种运用,就是在service或者一个方法前,调用一个方法,或者在方法后,调用一个方法,比如动态代理就是拦截器的简单实现,在调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在调用方法后打印出字符串,甚至在抛出异常的时候做业务逻辑的操作。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入
tomcat端口配置
在安装目录 编辑文件“安装目录\apache-tomcat-7.0.6\conf\server.xml”(可以用记事本打开)
安装目录
bin
包含启动/关闭脚本。
conf
包含不同的配置文件,包括server.xml(Tomcat的主要配置文件)和为不同的Tomcat配置的web应用设置缺省值的文件web.xml。
doc
包含各种Tomcat文档。
lib
包含Tomcat使用的jar文件,unix平台此目录下的任何文件都被加到Tomcat的classpath中。
logs
Tomcat摆放日志文件的地方。
src
ServletAPI源文件,先别高兴,这些只有些必须在Servlet容器内实现的空接口和抽象类。
webapps
包含web项目示例。
此外你可以Tomcat会创建如下目录:
work
Tomcat自动生成,放置Tomcat运行时的临时文件(如编译后的JSP文件),如在Tomcat运行时删除此目录.JSP页面将不能运行。
classes
你可以创建此目录来添加一些附加的类到类路径中,任何你加到此目录中的类都可在Tomcat的类路径中找到自身。
这里可以修改端口号
<Connector URIEncoding="utf-8" connectionTimeout="20000" port="8090" protocol="HTTP/1.1" redirectPort="8443"/>
tomcat容器是如何创建servlet类实例?用到了什么原理?
当容器启动时,会读取在webapps目录下所有的web应用中的web.xml文件,然后对xml文件进行解析,
并读取servlet注册信息。然后,将每个应用中注册的servlet类都进行加载,并通过反射的方式实例化。
事务放的位置
Service层
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = RuntimeException.class)
语言分类
- DDL:(数据定义语言):常用的有CREATE 和 DROP,用于在数据库中创建新表或删除表,以及为表加入索引等
- DML:Data Manipulation Language(数据操纵语言):主要用来对数据库的数据进行一些操作,常用的就是INSERT、UPDATE、DELETE
- DQL: Data Query Language(数据查询语言):数据检索语句,用于从表中获取数据。SELECT
- DPL:(事务处理语言):事务处理语句能确保被DML语句影响的表的所有行及时得以更新。TPL语句包括BEGIN TRANSACTION、COMMIT和ROLLBACK。
- DCL:(数据控制语言):通过GRANT和REVOKE,确定单个用户或用户组对数据库对象的访问权限。
- CCL:(指针控制语言):它的语句,像DECLARE CURSOR、FETCH INTO和UPDATE WHERE CURRENT用于对一个或多个表单独行的操作。
JAVA常见容器
1)Collection:一个独立元素的序列,这些元素都服从一条或者多条规则。 List必须按照插入的顺序保存元素,而set不能有重复的元素。Queue按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)。
2)Map:一组成对的“键值对”对象,允许你使用键来查找值。
|Collection
| ├List
| │–├LinkedList
| │–├ArrayList
| │–└Vector
| │ └Stack
| ├Set
| │–├HashSet
| │–├TreeSet
| │–└LinkedSet
|
|Map
├Hashtable
├HashMap
└WeakHashMap
2022-11-1天阳科技
自我介绍
选座买票出现多人操作应该怎么避免
jdk用的多少?jdk1.8新特性
谈谈数据库的优化
RabbitMQ在项目中的使用场景
优化数据库的方法
反射的四种方法
--------------------------二面分割线--------------------------
反射的四种方法,底层怎么实现?springBoot中哪些用到反射?
Redis的使用场景?五种数据类型(String,list,Set,Hash,SortSet[Zset])
springMVC的工作流程
Mysql的锁有哪些?有了解实现吗?
数据库优化包括哪些方面?
innerdb和mysql的其他数据库引擎有哪些不同?(不只是innerdb支持事务)
事务的注解是什么?事务的隔离级别和传播级别是哪些?
如果出现同时付款时怎么处理?票数有限,同时锁住同一个座位怎么处理?
线程池的使用?场景?
spring的理解?IOC的理解?为啥要有IOC、aop
@Transaction的使用,平时用有没有出现无效的情况
索引失效的场景?
平时前后端开发接口字段会对一遍吗
线程安全怎么解决高并发速度慢问题
2022-11-8吉联
OpenFeign和Feign
优化数据库(包括优化sql)
- 表设计层面:选取适用的字段属性,尽可能减少定义字段宽度,尽量把字段设置 NOTNULL;不常做修改的字段(where及order by涉及的列上建立索引)可以建立索引,加快查询
- SQL优化方面:使用连接(JOIN)来代替子查询;联合(UNION)来代替手动创建的临时表 ;优化查询语句(应尽量避免全表扫描【select * from A】;能用between就不要用in/not in了:Select id from t where num between 1 and 3;尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性 能,并会增加存储开销;使用表的别名(Alias),减少解析的时间并减少那些由Column歧义引起的语法错误;尽量使用“>=”,不要使用“>”;适量使用limit等)
事务失效的几种原因(数据库+java代码)
- 数据库:如使用mysql且引擎是MyISAM,则事务会不起作用,原因是MyISAM不支持事务,可以改成InnoDB引擎(ALTER TABLE mytable ENGINE = InnoDB;)
- java代码:spring的事务注解@Transactional只能放在public修饰的方法上才起作用
- java代码:默认设置下(默认是在RuntimeException和Error上回滚)、业务代码中如果抛出RuntimeException异常,事务回滚;但是抛出Exception,事务不回滚
- java代码:如果在加有事务的方法内,使用了try…catch…语句块对异常进行了捕获,而catch语句块没有throw new RuntimeExecption异常,事务也不会回滚
索引失效的几种原因
- 不建议使用%前缀模糊查询(只有在查询时字段最左侧加%和左右侧都加%才会导致索引失效)
- 不要在索引上做任何操作(计算、函数、自动/手动类型转换),不然会导致索引失效而转向全表扫描(对字段操作走不了索引)
- 索引字段上使用(!= 或者 < >)判断时,会导致索引失效而转向全表扫描
- 索引字段上使用 is null / is not null 判断时,会导致索引失效而转向全表扫描
- 索引字段使用 or 时,会导致索引失效而转向全表扫描(两个字段都有索引就不会失效,会走两个索引)
RabbitMQ应用
用户注册时需要发送一封邮件到用户注册的邮箱来激活账号,因为是同步方法,所以响应很慢,得等到邮件发送成功才有响应,所以打算使用RabbiMQ来异步发送邮件,提高响应速度。
Java基础遗漏
Java 构造方法Constructor
1、构造方法概述和作用:给对象的数据(属性)进行初始化,当一个类实例化一个对象时,类都会自动调用构造方法。
构造方法格式特点:
```java
方法名与类名相同(大小也要与类名一致)
没有返回值类型,连void都没有
没有具体的返回值return
2、对象初始化:
在构造方法中可以为成员变量赋值,这样当实例化一个本类的对象时,相应的成员变量也将被初始化。如果类中没有明确定义构造方法,则编译器会自动创建一个不带参数的默认构造方法。
注意:如果在类中定义的构造方法都不是无参的构造,则编译器不会为类设置了一个默认的无参构造方法,当试图调用无参构造方法实例化一个对象时,编译器会报错。所以只有在类中没有定义任何构造方法时,编译器才会在该类中自动创建一个不带参数的构造方法。
3、使用this关键字:this可以调用类的成员变量和成员方法,事实上this还可以调用类中的构造方法。
public class Mytest{
int i;
public Mytest(){
this(1);
}
public Mytest(int i){
this.i = i;
}
}
4、私有构造方法:构造方法同其他方法一样,也可以用private修改,私有的构造方法无法在本类外使用,也就导致本类无法用new实例化,这样可以控制对象的生成。(单例模式)
public class Book{
//私有构造方法
private Book(){}
//静态公开方法,向图书馆借书
public static Book libraryBorrow(){//创建静态方法,返回本类实例对象
return new Book();
}
}
在其他的类中:不可以使用new 的形式创建对象,这样就实现了使用private来控制对象的生成。
public static void main(){
//创建一个书的对象,不是new实例化的,而是通过方法从图书馆借来的
Book book = Book.libraryBorrow();
}
⭐SpringBoot
常见注解
@SpringBootApplication(@EnableAutoConfiguration自动化配置的核心,@SpringBootConfiguration声明为配置类)
@ComponentScan Ioc扫描的路径
@Component类、@Service业务层、@Controller控制层@Repository( dao)
@ResponseBody(返回json对象)@RestController(@Controller和@ResponseBody的结合体)
@AutoWired、@Qualifier、@Resource
@RequestMapping、@GetMapping、@PostMapping
@Value(读取application.yml配置)
@ConfigurationProperties
@PropertySource
@Configuration
@Bean
@RequestParam
@PathVariable
@RequestHeader
@Cookie
主要的配置文件
application.yml
application.properties
切换配置文件spring.profiles.active=dev
正常的情况是先加载yml,接下来加载properties文件。如果相同的配置存在于两个文件中。最后会使用properties中的配置。最后读取properties的优先集最高
⭐跨域(WebMvcConfigurer)
所谓跨域其实就是浏览器对某些请求进行拦截
//标注这个类是一个配置类
@Configuration
//实现 WebMvcConfigurer 接口
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") //设置允许跨域访问的路径
.allowedOriginPatterns("*") //设置允许跨域访问的源
.allowedMethods("*") //允许跨域请求的方法
.maxAge(168000) //预检间隔时间
.allowedHeaders("*") //允许头部设置
.allowCredentials(true); //是否发送 cookie
}
}
⭐Mybatis和Hibernate区别
hibernate | Mybatis | |
---|---|---|
自动化 | 全自动对象关系映射框架(通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成sql) | 半自动(字段映射,对象数据以及对象实际关系仍然需要通过手写sql来实现) |
可移植性 | hibernate通过它强大的映射结构和hql语言,大大降低了对象与数据库(oracle、mysql等)的耦合性 | mybatis由于需要手写sql,而如果是数据库特性的sql语句,不方便移植 |
日志系统 | 完整的日志系统(sql记录、关系异常、优化警告、缓存提示、脏数据警告等) | 除了基本记录功能外,功能薄弱很多 |
sql优化 | hibernate的sql很多都是自动生成的,无法直接维护sql | mybatis的sql都是写在xml里,因此优化sql比hibernate方便很多 |
BigDecimal与double
项目中,金额必须是完全精确的计算,故不能使用double或者float
还要注意的就是默认值,一定写成0.00,不要用默认的NULL,否则在进行加减排序等操作时,会带来转换的麻烦
在程序中存储金额的数据类型用:java.math.BigDecimal,在数据库中存储金额的数据类型用:decimal
对应的加减乘除
两个 BigDecimal 的值不能用 +、-、*、/ 来进行加减乘除、
两个BigDecimal值比较使用compareTo方法,比较结果有-1,0,1,分别表示小于, 等于, 大于
对于0,使用BigDecimal.ZERO表示
setScale方法的第一个参数是小数位数,这个示例是保留2位小数,后面是四舍五入规则
public static void main(String[] args){
BigDecimal x = new BigDecimal("1.3");
BigDecimal y = new BigDecimal("2.5");
// 加法 --> 3.8
BigDecimal add = x.add(y);
System.out.println(add);
// 减法 --> -1.2
BigDecimal subtract = x.subtract(y);
System.out.println(subtract);
// 乘法 --> 3.25
BigDecimal multiply = x.multiply(y);
System.out.println(multiply);
// 除法 --> 0.5 ,RoundingMode.HALF_UP 四舍五入
BigDecimal divide = x.divide(y, RoundingMode.HALF_UP);
System.out.println(divide);
}
BigDecimal num = new BigDecimal("-3");
if (num.compareTo(BigDecimal.ZERO) == -1) {
System.out.println("num 小于 0");
} else if (num.compareTo(BigDecimal.ZERO) == 1) {
System.out.println("num 大于 0");
} else if (num.compareTo(BigDecimal.ZERO) == 0) {
System.out.println("num 等于 0");
}
public static void main(String[] args){
BigDecimal num = new BigDecimal("10.2621684798165165");
System.out.println("原型 = " + num);
System.out.println("直接删除多余的小数位 = " + num.setScale(2, BigDecimal.ROUND_DOWN));
System.out.println("进位 = " + num.setScale(2, BigDecimal.ROUND_UP));
System.out.println("四舍五入,碰到5位进位 = " + num.setScale(2, BigDecimal.ROUND_HALF_UP));
System.out.println("四舍五入,碰到5位舍弃 = " + num.setScale(2, BigDecimal.ROUND_HALF_DOWN));
}
股权业务
MQTT
MQTT(消息队列遥测传输)是ISO协议下基于发布/订阅范式的消息协议
“MQTT”中的“MQ” 是来自于IBM的MQ系列消息队列产品线
MQTT应用:国内很多企业都广泛使用MQTT作为Android手机客户端与服务器端推送消息的协议
MQTT由于开放源代码,耗电量小等特点
MQTT协议是为大量计算能力有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议,它具有以下主要的几项特性:
1、使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合;
2、对负载内容屏蔽的消息传输;
3、使用 TCP/IP 提供网络连接;
4、有三种消息发布服务质量:
5、小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量;
6、使用 Last Will 和 Testament 特性通知有关各方客户端异常中断的机制。 [1]