河间市网站建设,wordpress代码高亮是什么意思,企业建设网站企业,谷歌商店安卓版下载目录
第一章-Java基础篇
1、你是怎样理解OOP面向对象 难度系数#xff1a;⭐
2、重载与重写区别 难度系数#xff1a;⭐
3、接口与抽象类的区别 难度系数#xff1a;⭐
4、深拷贝与浅拷贝的理解 难度系数#xff1a;⭐
5、sleep和wait区别 难度系数⭐
2、重载与重写区别 难度系数⭐
3、接口与抽象类的区别 难度系数⭐
4、深拷贝与浅拷贝的理解 难度系数⭐
5、sleep和wait区别 难度系数⭐
6、什么是自动拆装箱 int和Integer有什么区别 难度系数⭐
7、和equals区别 难度系数⭐
8、String能被继承吗 为什么用final修饰 难度系数⭐
9、String buffer和String builder区别 难度系数⭐
10、final、finally、finalize 难度系数⭐
11、Object中有哪些方法 难度系数⭐
12、说一下集合体系 难度系数⭐
13、ArrarList和LinkedList区别 难度系数⭐
14、HashMap底层是 数组链表红黑树为什么要用这几类结构 难度系数⭐⭐
15、HashMap和HashTable区别 难度系数⭐
16、线程的创建方式 难度系数⭐
17、线程的状态转换有什么生命周期 难度系数⭐
18、Java中有几种类型的流 难度系数⭐
19、请写出你最常见的5个RuntimeException 难度系数⭐
20、谈谈你对反射的理解 难度系数⭐
21、什么是 java 序列化如何实现 java 序列化 难度系数⭐
22、Http 常见的状态码 难度系数⭐
23、GET 和POST 的区别 难度系数⭐
24、Cookie 和Session 的区别 难度系数⭐
第二章-Java高级篇
1、HashMap底层源码 难度系数⭐⭐⭐
2、JVM内存分哪几个区每个区的作用是什么 难度系数⭐⭐
3、Java中垃圾收集的方法有哪些 难度系数⭐
4、如何判断一个对象是否存活(或者GC对象的判定方法) 难度系数⭐
5、什么情况下会产生StackOverflowError栈溢出和OutOfMemoryError堆溢出怎么排查 难度系数⭐⭐
6、什么是线程池线程池有哪些创建 难度系数⭐
7、为什么要使用线程池 难度系数⭐
8、线程池底层工作原理 难度系数⭐
9、ThreadPoolExecutor对象有哪些参数 怎么设定核心线程数和最大线程数 拒绝策略有哪些 难度系数⭐
10、常见线程安全的并发容器有哪些 难度系数⭐
11、Atomic原子类了解多少 原理是什么 难度系数⭐
12、synchronized底层实现是什么 lock底层是什么 有什么区别 难度系数⭐⭐⭐
13、了解ConcurrentHashMap吗 为什么性能比HashTable高说下原理 难度系数⭐⭐
14、ConcurrentHashMap底层原理 难度系数⭐⭐⭐
15、了解volatile关键字不 难度系数⭐
16、synchronized和volatile有什么区别 难度系数⭐⭐
17、Java类加载过程 难度系数⭐
18、什么是类加载器类加载器有哪些 难度系数⭐
19、简述java内存分配与回收策略以及Minor GC和Major GCfull GC 难度系数⭐⭐
20、如何查看java死锁 难度系数⭐
21、Java死锁如何避免 难度系数⭐
第三章-java框架篇
1、简单的谈一下SpringMVC的工作流程 难度系数⭐
2、说出Spring或者SpringMVC中常用的5个注解 难度系数⭐
3、简述SpringMVC中如何返回JSON数据 难度系数⭐
4、谈谈你对Spring的理解 难度系数⭐
5、Spring中常用的设计模式 难度系数⭐
6、Spring循环依赖问题 难度系数⭐⭐
常见问法
什么是循环依赖?
两种注入方式对循环依赖的影响?
相关概念
三级缓存
四个关键方法
debug源代码过程
总结
其他衍生问题
7、介绍一下Spring bean 的生命周期、注入方式和作用域 难度系数⭐
8、请描述一下Spring 的事务管理 难度系数⭐
9、MyBatis中 #{}和${}的区别是什么 难度系数⭐
10、Mybatis 中一级缓存与二级缓存 难度系数⭐
11、MyBatis如何获取自动生成的(主)键值 难度系数⭐
12、简述Mybatis的动态SQL列出常用的6个标签及作用 难度系数⭐
13、Mybatis 如何完成MySQL的批量操作 难度系数⭐
14、谈谈怎么理解SpringBoot框架 难度系数⭐⭐
15、Spring Boot 的核心注解是哪个 它主要由哪几个注解组成的 难度系数⭐
16、Spring Boot自动配置原理是什么 难度系数⭐
17、SpringBoot配置文件有哪些 怎么实现多环境配置 难度系数⭐
18、SpringBoot和SpringCloud是什么关系 难度系数⭐
19、SpringCloud都用过哪些组件 介绍一下作用 难度系数⭐
20、Nacos作用以及注册中心的原理 难度系数⭐⭐
21、Feign工作原理 难度系数⭐⭐
第四章-MySQL
1、Select 语句完整的执行顺序 难度系数⭐
2、MySQL事务 难度系数⭐⭐
3、MyISAM和InnoDB的区别 难度系数⭐
4、悲观锁和乐观锁的怎么实现 难度系数⭐⭐
5、聚簇索引与非聚簇索引区别 难度系数⭐⭐
6、什么情况下mysql会索引失效 难度系数⭐
7、Btree 与 B-tree区别 难度系数⭐⭐
8、以MySQL为例Linux下如何排查问题 难度系数⭐⭐
9、如何处理慢查询 难度系数⭐⭐
10、数据库分表操作 难度系数⭐
11、MySQL优化 难度系数⭐
12、SQL语句优化案例 难度系数⭐
13、你们公司有哪些数据库设计规范 难度系数⭐
14、有没有设计过数据表?你是如何设计的 难度系数⭐
15、常见面试SQL 难度系数⭐
第五章-Redis
1、介绍下Redis Redis有哪些数据类型 难度系数⭐
2、Redis提供了哪几种持久化方式 难度系数⭐
3、Redis为什么快 难度系数⭐
4、Redis为什么是单线程的 难度系数⭐
5、Redis服务器的的内存是多大 难度系数⭐
6、为什么Redis的操作是原子性的怎么保证原子性的 难度系数⭐
7、Redis有事务吗 难度系数⭐
8、Redis数据和MySQL数据库的一致性如何实现 难度系数⭐⭐
9、缓存击穿缓存穿透缓存雪崩的原因和解决方案(或者说使用缓存的过程中有没有遇到什么问题怎么解决的 难度系数⭐
10、哨兵模式是什么样的 难度系数⭐⭐
11、Redis常见性能问题和解决方案 难度系数⭐
12、MySQL里有大量数据如何保证Redis中的数据都是热点数据 难度系数⭐⭐
13、Redis集群方案应该怎么做 都有哪些方案 难度系数⭐⭐
14、说说Redis哈希槽的概念 难度系数⭐⭐
15、Redis有哪些适合的场景 难度系数⭐
16、Redis在项目中的应用 难度系数⭐
第六章-分布式技术篇
第七章-Git
1、工作中git开发使用流程命令版描述
开发一个新功能流程: master线上分支dev测试分支
2、Reset 与Rebase,Pull 与 Fetch 的区别
3、git merge和git rebase的区别
4、git如何解决代码冲突
5、项目开发时git分支情况
第八章-Linux
1、Linux常用命令
2、如何查看测试项目的日志
3、如何查看最近1000行日志
4、Linux中如何查看某个端口是否被占用
5、查看当前所有已经使用的端口情况
第九章-电商项目篇之尚品汇商城
1、介绍下最近做的项目
1.1 项目背景
1.2 项目功能
1.3 技术栈
1.4 自己负责的功能模块
1.5 项目介绍参考
1.6 项目架构图
1.7 整体业务介绍
1.8 后台管理系统功能
1.8.1 后台主页
1.8.2 商品模块
1).商品管理
2).商品分类管理
3).商品平台属性管理
4).品牌管理
5).商品评论管理
1.8.3 销售模块
1).促销秒杀管理
2).礼券、积分管理
3).关联/推荐管理
1.8.4 订单模块
1).订单管理
2).支付
3).结算
1.8.5 库存模块
1).库存管理
2).查看库存明细记录。
3).备货/发货
4).退/换货
1.8.6 内容模块
1).内容管理
2).广告管理
3).可自由设置商城导航栏目以及栏目内容、栏目链接。
1.8.7 客户模块
1).客户管理
2).反馈管理
3).消息订阅管理
4).会员资格
1.8.8 系统模块
1).安全管理
2).系统属性管理
3).运输与区域
4).支付管理
5).包装管理
6).数据导入管理
1.8.9 报表模块
2、项目开发周期
3、项目参与人数
4、公司开发相关各岗位职责
4.1 项目经理PM
4.2 产品PD
4.3 界面设计UI
4.4 开发组长TL
4.5 测试QA
4.6 运维SRE
5、项目开发流程:
5.1 需求分析
5.2 系统设计
5.3 编码开发
5.4 系统测试
5.5 部署实施
6、项目版本控制
7、一般项目服务器数量
开发测试阶段
生产环境
8、上线后QPS并发量用户量、同时在线人数并发数等问题
9、你们项目的微服务是怎么拆分的拆分了多少
10、如何解决并发问题的
11、如何保证接口的幂等性
12、你们项目中有没有用到什么设计模式
13、生产环境出问题你们是怎么排查的
14、你做完这个项目后有什么收获
15、在做这个项目的时候你碰到了哪些问题你是怎么解决的
第十章-数据结构和算法
1、怎么理解时间复杂度和空间复杂度
2、数组和链表结构简单对比
3、怎么遍历一个树
4、冒泡排序Bubble Sort
5、快速排序Quick Sort
6、二分查找Binary Search
1、你所知道的设计模式有哪些
2、单例模式Binary Search
2.1 单例模式定义
2.2 单例模式的特点
2.3 单例的四大原则
2.4 实现单例模式的方式
1饿汉式立即加载
2懒汉式延迟加载
3同步锁解决线程安全问题
4双重检查锁提高同步锁的效率
5 静态内部类
6内部枚举类实现防止反射和反序列化攻击
3、工厂设计模式Factory
3.1 什么是工厂设计模式
3.2 简单工厂Simple Factory
3.3 工厂方法Factory Method
3.4 抽象工厂Abstract Factory
3.5 三种工厂方式总结
4、代理模式Proxy
4.1 什么是代理模式
4.2 为什么要用代理模式
4.3 有哪几种代理模式
4.4 静态代理Static Proxy
4.5 JDK动态代理Dynamic Proxy
4.6 CGLib动态代理CGLib Proxy
4.7 简述动态代理的原理 常用的动态代理的实现方式 第一章-Java基础篇
1、你是怎样理解OOP面向对象 难度系数⭐
面向对象是利于语言对现实事物进行抽象。面向对象具有以下特征
继承继承是从已有类得到继承信息创建新类的过程封装封装是把数据和操作数据的方法绑定起来对数据的访问只能通过已定义的接口多态性多态性是指允许不同子类型的对象对同一消息作出不同的响应
2、重载与重写区别 难度系数⭐
重载发生在本类重写发生在父类与子类之间重载的方法名必须相同重写的方法名相同且返回值类型必须相同重载的参数列表不同重写的参数列表必须相同重写的访问权限不能比父类中被重写的方法的访问权限更低构造方法不能被重写
3、接口与抽象类的区别 难度系数⭐
抽象类要被子类继承接口要被类实现接口可多继承接口但类只能单继承抽象类可以有构造器、接口不能有构造器抽象类除了不能实例化抽象类之外它和普通Java类没有任何区别抽象类抽象方法可以有public、protected和default这些修饰符、接口只能是public抽象类可以有成员变量接口只能声明常量
4、深拷贝与浅拷贝的理解 难度系数⭐
深拷贝和浅拷贝就是指对象的拷贝一个对象中存在两种类型的属性一种是基本数据类型一种是实例对象的引用。
浅拷贝是指只会拷贝基本数据类型的值以及实例对象的引用地址并不会复制一份引用地址所指向的对象也就是浅拷贝出来的对象内部的类属性指向的是同一个对象深拷贝是指既会拷贝基本数据类型的值也会针对实例对象的引用地址所指向的对象进行复制深拷贝出来的对象内部的类执行指向的不是同一个对象
5、sleep和wait区别 难度系数⭐
sleep方法
属于Thread类中的方法
释放cpu给其它线程 不释放锁资源
sleep(1000) 等待超过1s被唤醒
wait方法
属于Object类中的方法
释放cpu给其它线程同时释放锁资源
wait(1000) 等待超过1s被唤醒
wait() 一直等待需要通过notify或者notifyAll进行唤醒
wait 方法必须配合 synchronized 一起使用不然在运行时就会抛出IllegalMonitorStateException异常 #### 锁释放时机代码演示
public static void main(String[] args) {Object o new Object();Thread thread new Thread(() - {synchronized (o) {System.out.println(新线程获取锁时间 LocalDateTime.now() 新线程名称 Thread.currentThread().getName());try {//wait 释放cpu同时释放锁o.wait(2000);//sleep 释放cpu不释放锁//Thread.sleep(2000);System.out.println(新线程获取释放锁锁时间 LocalDateTime.now() 新线程名称 Thread.currentThread().getName());} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(主线程获取锁时间 LocalDateTime.now() 主线程名称 Thread.currentThread().getName());synchronized (o){System.out.println(主线程获取释放锁锁时间 LocalDateTime.now() 主线程名称 Thread.currentThread().getName());}
} Java 6、什么是自动拆装箱 int和Integer有什么区别 难度系数⭐
基本数据类型如int,float,double,boolean,char,byte,不具备对象的特征不能调用方法。
装箱将基本类型转换成包装类对象拆箱将包装类对象转换成基本类型的值
java为什么要引入自动装箱和拆箱的功能主要是用于java集合中ListInteter listnew ArrayListInteger();
list集合如果要放整数的话只能放对象不能放基本类型因此需要将整数自动装箱成对象。
实现原理javac编译器的语法糖底层是通过Integer.valueOf()和Integer.intValue()方法实现。
区别
Integer是int的包装类int则是java的一种基本数据类型Integer变量必须实例化后才能使用而int变量不需要Integer实际是对象的引用当new一个Integer时实际上是生成一个指针指向此对象而int则是直接存储数据值Integer的默认值是nullint的默认值是0
7、和equals区别 难度系数⭐ 如果比较的是基本数据类型那么比较的是变量的值
如果比较的是引用数据类型那么比较的是地址值两个对象是否指向同一块内存
equals
如果没重写equals方法比较的是两个对象的地址值
如果重写了equals方法后我们往往比较的是对象中的属性的内容
equals方法是从Object类中继承的默认的实现就是使用
8、String能被继承吗 为什么用final修饰 难度系数⭐
不能被继承因为String类有final修饰符而final修饰的类是不能被继承的。String 类是最常用的类之一为了效率禁止被继承和重写。为了安全。String 类中有native关键字修饰的调用系统级别的本地方法调用了操作系统的 API如果方法可以重写可能被植入恶意代码破坏程序。Java 的安全性也体现在这里。
9、String buffer和String builder区别 难度系数⭐
StringBuffer 与 StringBuilder 中的方法和功能完全是等价的只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰因此是线程安全的而 StringBuilder 没有这个修饰可以被认为是线程不安全的。 在单线程程序下StringBuilder效率更快因为它不需要加锁不具备多线程安全而StringBuffer则每次都需要判断锁效率相对更低
10、final、finally、finalize 难度系数⭐
final修饰符关键字有三种用法修饰类、变量和方法。修饰类时意味着它不能再派生出新的子类即不能被继承因此它和abstract是反义词。修饰变量时该变量使用中不被改变必须在声明时给定初值在引用中只能读取不可修改即为常量。修饰方法时也同样只能使用不能在子类中被重写。finally通常放在try…catch的后面构造最终执行代码块这就意味着程序无论正常执行还是发生异常这里的代码只要JVM不关闭都能执行可以将释放外部资源的代码写在finally块中。finalizeObject类中定义的方法Java中允许使用finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的通过重写finalize() 方法可以整理系统资源或者执行其他清理工作。
11、Object中有哪些方法 难度系数⭐
protected Object clone()---创建并返回此对象的一个副本。boolean equals(Object obj)---指示某个其他对象是否与此对象“相等protected void finalize()---当垃圾回收器确定不存在对该对象的更多引用时由对象的垃圾回收器调用此方法。Class? extendsObject getClass()---返回一个对象的运行时类。int hashCode()---返回该对象的哈希码值。void notify()---唤醒在此对象监视器上等待的单个线程。void notifyAll()---唤醒在此对象监视器上等待的所有线程。String toString()---返回该对象的字符串表示。void wait()---导致当前的线程等待直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。 void wait(long timeout)---导致当前的线程等待直到其他线程调用此对象的 notify() 方法或 notifyAll()方法或者超过指定的时间量。 void wait(long timeout, int nanos)---导致当前的线程等待直到其他线程调用此对象的 notify()
12、说一下集合体系 难度系数⭐ 13、ArrarList和LinkedList区别 难度系数⭐
ArrayList是实现了基于动态数组的数据结构LinkedList基于链表的数据结构。对于随机访问get和setArrayList效率优于LinkedList因为LinkedList要移动指针。对于新增和删除操作add和removeLinkedList比较占优势因为ArrayList要移动数据。 这一点要看实际情况的。若只对单条数据插入或删除ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据要移动插入点及之后的所有数据。
14、HashMap底层是 数组链表红黑树为什么要用这几类结构 难度系数⭐⭐
数组 NodeK,V[] table ,哈希表根据对象的key的hash值进行在数组里面是哪个节点 链表的作用是解决hash冲突将hash值取模之后的对象存在一个链表放在hash值对应的槽位红黑树 JDK8使用红黑树来替代超过8个节点的链表主要是查询性能的提升从原来的O(n)到O(logn),通过hash碰撞让HashMap不断产生碰撞那么相同的key的位置的链表就会不断增长当对这个Hashmap的相应位置进行查询的时候就会循环遍历这个超级大的链表性能就会下降所以改用红黑树
15、HashMap和HashTable区别 难度系数⭐
线程安全性不同
HashMap是线程不安全的HashTable是线程安全的其中的方法是Synchronized在多线程并发的情况下可以直接使用HashTable但是使用HashMap时必须自己增加同步处理。
是否提供contains方法
HashMap只有containsValue和containsKey方法HashTable有contains、containsKey和containsValue三个方法其中contains和containsValue方法功能相同。
key和value是否允许null值
Hashtable中key和value都不允许出现null值。HashMap中null可以作为键这样的键只有一个可以有一个或多个键所对应的值为null。
数组初始化和扩容机制
HashTable在不指定容量的情况下的默认容量为11而HashMap为16Hashtable不要求底层数组的容量一定要为2的整数次幂而HashMap则要求一定为2的整数次幂。
Hashtable扩容时将容量变为原来的2倍加1而HashMap扩容时将容量变为原来的2倍。
16、线程的创建方式 难度系数⭐
继承Thread类创建线程实现Runnable接口创建线程使用Callable和Future创建线程 有返回值使用线程池创建线程 #### 代码演示
import java.util.concurrent.*;
public class threadTest{public static void main(String[] args) throws ExecutionException, InterruptedException {//继承threadThreadClass thread new ThreadClass();thread.start();Thread.sleep(100);System.out.println(#####################);//实现runnableRunnableClass runnable new RunnableClass();new Thread(runnable).start();Thread.sleep(100);System.out.println(#####################);//实现callableFutureTask futureTask new FutureTask(new CallableClass());futureTask.run();System.out.println(callable返回值 futureTask.get());Thread.sleep(100);System.out.println(#####################);//线程池ThreadPoolExecutor threadPoolExecutor new ThreadPoolExecutor(1, 1, 2, TimeUnit.SECONDS, new ArrayBlockingQueue(10));threadPoolExecutor.execute(thread);threadPoolExecutor.shutdown();Thread.sleep(100);System.out.println(#####################);//使用并发包ExecutorsExecutorService executorService Executors.newFixedThreadPool(5);executorService.execute(thread);executorService.shutdown();}
}class ThreadClass extends Thread{Overridepublic void run() {System.out.println(我是继承thread形式 Thread.currentThread().getName());}
}class RunnableClass implements Runnable{Overridepublic void run(){System.out.println(我是实现runnable接口 Thread.currentThread().getName());}
}class CallableClass implements CallableString {Overridepublic String call(){System.out.println(我是实现callable接口);return 我是返回值可以通过get方法获取;}
} Java 17、线程的状态转换有什么生命周期 难度系数⭐ 新建状态(New) 线程对象被创建后就进入了新建状态。例如Thread thread new Thread()。就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后其它线程调用了该对象的start()方法从而来启动该线程。例如thread.start()。处于就绪状态的线程随时可能被CPU调度执行。运行状态(Running)线程获取CPU权限进行执行。需要注意的是线程只能从就绪状态进入到运行状态。阻塞状态(Blocked)阻塞状态是线程因为某种原因放弃CPU使用权暂时停止运行。直到线程进入就绪状态才有机会转到运行状态。阻塞的情况分三种 等待阻塞 -- 通过调用线程的wait()方法让线程等待某工作的完成。同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用)它会进入同步阻塞状态。其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时线程会进入到阻塞状态。当sleep()状态超时、join(等待线程终止或者超时、或者I/O处理完毕时线程重新转入就绪状态。死亡状态(Dead)线程执行完了或者因异常退出了run()方法该线程结束生命周期。
18、Java中有几种类型的流 难度系数⭐ 19、请写出你最常见的5个RuntimeException 难度系数⭐
java.lang.NullPointerException
空指针异常出现原因调用了未经初始化的对象或者是不存在的对象。
java.lang.ClassNotFoundException
指定的类找不到出现原因类的名称和路径加载错误通常都是程序试图通过字符串来加载某个类时可能引发异常。
java.lang.NumberFormatException
字符串转换为数字异常出现原因字符型数据中包含非数字型字符。
java.lang.IndexOutOfBoundsException
数组角标越界异常常见于操作数组对象时发生。
java.lang.IllegalArgumentException
方法传递参数错误。
java.lang.ClassCastException
数据类型转换异常。
20、谈谈你对反射的理解 难度系数⭐
反射机制
所谓的反射机制就是java语言在运行时拥有一项自观的能力。通过这种能力可以彻底了解自身的情况为下一步的动作做准备。
Java的反射机制的实现要借助于4个类classConstructorFieldMethod;其中class代表的时类对 象Constructor类的构造器对象Field类的属性对象Method类的方法对象。通过这四个对象我们可以粗略的看到一个类的各个组成部分。
Java反射的作用
在Java运行时环境中对于任意一个类可以知道这个类有哪些属性和方法。对于任意一个对象可以调用它的任意一个方法。这种动态获取类的信息以及动态调用对象的方法的功能来自于Java 语言的反射Reflection机制。
Java 反射机制提供功能
在运行时判断任意一个对象所属的类。
在运行时构造任意一个类的对象。
在运行时判断任意一个类所具有的成员变量和方法。
在运行时调用任意一个对象的方法
21、什么是 java 序列化如何实现 java 序列化 难度系数⭐
序列化是一种用来处理对象流的机制所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。序 列 化 的 实 现 将 需 要 被 序 列 化 的 类 实 现 Serializable 接 口 该 接 口 没 有 需 要 实 现 的 方 法 implements Serializable 只是为了标注该对象是可被序列化的然后使用一个输出流(如FileOutputStream)来构造一个ObjectOutputStream(对象流)对象接着使用 ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将参数为 obj 的对象写出(即保存其状态)要恢复的话则用输入流。
22、Http 常见的状态码 难度系数⭐
200 OK //客户端请求成功301 Permanently Moved 永久移除)请求的 URL 已移走。Response 中应该包含一个 Location URL, 说明资源现在所处的位置302 Temporarily Moved 临时重定向400 Bad Request //客户端请求有语法错误不能被服务器所理解401 Unauthorized //请求未经授权这个状态代码必须和 WWW-Authenticate 报头域一起使用403 Forbidden //服务器收到请求但是拒绝提供服务404 Not Found //请求资源不存在eg输入了错误的 URL500 Internal Server Error //服务器发生不可预期的错误503 Server Unavailable //服务器当前不能处理客户端的请求一段时间后可能恢复正常
23、GET 和POST 的区别 难度系数⭐
GET 请求的数据会附在URL 之后就是把数据放置在 HTTP 协议头中以?分割URL 和传输数据参数之间以相连如login.action?namezhagnsanpassword123456。POST 把提交的数据则放置在是 HTTP 包的包体中。GET 方式提交的数据最多只能是 1024 字节理论上POST 没有限制可传较大量的数据。其实这样说是错误的不准确的“GET 方式提交的数据最多只能是 1024 字节因为 GET 是通过 URL 提交数据那么 GET 可提交的数据量就跟URL 的长度有直接关系了。而实际上URL 不存在参数上限的问题HTTP 协议规范没有对 URL 长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE 对URL 长度的限制是2083 字节(2K35)。对于其他浏览器如Netscape、FireFox 等理论上没有长度限制其限制取决于操作系统的支持。POST 的安全性要比GET 的安全性高。注意这里所说的安全性和上面 GET 提到的“安全”不是同个概念。上面“安全”的含义仅仅是不作数据修改而这里安全的含义是真正的 Security 的含义比如通过 GET 提交数据用户名和密码将明文出现在 URL 上因为(1)登录页面有可能被浏览器缓存(2)其他人查看浏览器的历史纪录那么别人就可以拿到你的账号和密码了除此之外使用 GET 提交数据还可能会造成 Cross-site request forgery 攻击。Get 是向服务器发索取数据的一种请求而 Post 是向服务器提交数据的一种请求在 FORM表单中Method默认为GET实质上GET 和 POST 只是发送机制不同并不是一个取一个发
24、Cookie 和Session 的区别 难度系数⭐
Cookie 是 web 服务器发送给浏览器的一块信息浏览器会在本地一个文件中给每个 web 服务器存储 cookie。以后浏览器再给特定的 web 服务器发送请求时同时会发送所有为该服务器存储的 cookieSession 是存储在 web 服务器端的一块信息。session 对象存储特定用户会话所需的属性及配置信息。当用户在应用程序的 Web 页之间跳转时存储在 Session 对象中的变量将不会丢失而是在整个用户会话中一直存在下去Cookie 和session 的不同点
无论客户端做怎样的设置session 都能够正常工作。当客户端禁用 cookie 时将无法使用 cookie
在存储的数据量方面session 能够存储任意的java 对象cookie 只能存储 String 类型的对象
第二章-Java高级篇
1、HashMap底层源码 难度系数⭐⭐⭐
HashMap的底层结构在jdk1.7中由数组链表实现在jdk1.8中由数组链表红黑树实现以数组链表的结构为例。 JDK1.8之前Put方法 JDK1.8之后Put方法 HashMap基于哈希表的Map接口实现是以key-value存储形式存在即主要用来存放键值对。HashMap 的实现不是同步的这意味着它不是线程安全的。它的key、value都可以为null。此外HashMap中的映射不是有序的。
JDK1.8 之前 HashMap 由 数组链表 组成的数组是 HashMap 的主体链表则是主要为了解决哈希冲突(两个对象调用的hashCode方法计算的哈希码值一致导致计算的数组索引值相同)而存在的“拉链法”解决冲突.JDK1.8 以后在解决哈希冲突时有了较大的变化当链表长度大于阈值或者红黑树的边界值默认为 8并且当前数组的长度大于64时此时此索引位置上的所有数据改为使用红黑树存储。
补充将链表转换成红黑树前会判断即使阈值大于8但是数组长度小于64此时并不会将链表变为红黑树。而是选择进行数组扩容。
这样做的目的是因为数组比较小尽量避开红黑树结构这种情况下变为红黑树结构反而会降低效率因为红黑树需要进行左旋右旋变色这些操作来保持平衡 。同时数组长度小于64时搜索时间相对要快些。所以综上所述为了提高性能和减少搜索时间底层在阈值大于8并且数组长度大于64时链表才转换为红黑树。具体可以参考 treeifyBin方法。
当然虽然增了红黑树作为底层数据结构结构变得复杂了但是阈值大于8并且数组长度大于64时链表转换为红黑树时效率也变的更高效。
注意可以结合百度hashmap源码解析进行更深入的了解。
史上最详细的 JDK 1.8 HashMap 源码解析_程序员囧辉的博客-CSDN博客
2、JVM内存分哪几个区每个区的作用是什么 难度系数⭐⭐ java虚拟机主要分为以下几个区
方法区 有时候也成为永久代在该区内很少发生垃圾回收但是并不代表不发生GC在这里进行的GC主要是对方法区里的常量池和对类型的卸载方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。该区域是被线程共享的。方法区里有一个运行时常量池用于存放静态编译产生的字面量和符号引用。该常量池具有动态性也就是说常量并不一定是编译时确定运行时生成的常量也会存在这个常量池中。虚拟机栈 虚拟机栈也就是我们平常所称的栈内存,它为java方法服务每个方法在执行的时候都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接和方法出口等信息。虚拟机栈是线程私有的它的生命周期与线程相同。局部变量表里存储的是基本数据类型、returnAddress类型指向一条字节码指令的地址和对象引用这个对象引用有可能是指向对象起始地址的一个指针也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定操作数栈的作用主要用来存储运算结果以及运算的操作数它不同于局部变量表通过索引来访问而是压栈和出栈的方式每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用持有这个引用是为了支持方法调用过程中的动态连接.动态链接就是将常量池中的符号引用在运行期转化为直接引用。本地方法栈
本地方法栈和虚拟机栈类似只不过本地方法栈为Native方法服务。
堆
java堆是所有线程所共享的一块内存在虚拟机启动时创建几乎所有的对象实例都在这里创建因此该区域经常发生垃圾回收操作。
程序计数器
内存空间小字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个java虚拟机规范没有规定任何OOM情况的区域。
JEP 122: Remove the Permanent Generation 介绍 静态变量、字符串常量从永久代移动到堆中
3、Java中垃圾收集的方法有哪些 难度系数⭐
采用分区分代回收思想
复制算法 年轻代中使用的是Minor GC这种GC算法采用的是复制算法(Copying)
a) 效率高缺点需要内存容量大比较耗内存
b) 使用在占空间比较小、刷新次数多的新生区
标记-清除 老年代一般是由标记清除或者是标记清除与标记整理的混合实现
a) 效率比较低会差生碎片。
标记-整理 老年代一般是由标记清除或者是标记清除与标记整理的混合实现
a) 效率低速度慢需要移动对象但不会产生碎片。
4、如何判断一个对象是否存活(或者GC对象的判定方法) 难度系数⭐
引用计数法
所谓引用计数法就是给每一个对象设置一个引用计数器每当有一个地方引用这个对象时就将计数器加一引用失效时计数器就减一。当一个对象的引用计数器为零时说明此对象没有被引用也就是“死对象”,将会被垃圾回收.
引用计数法有一个缺陷就是无法解决循环引用问题也就是说当对象A引用对象B对象B又引用者对象A那么此时A,B对象的引用计数器都不为零也就造成无法完成垃圾回收所以主流的虚拟机都没有采用这种算法。
可达性算法(引用链法) 该算法的基本思路就是通过一些被称为引用链GC Roots的对象作为起点从这些节点开始向下搜索搜索走过的路径被称为Reference Chain)当一个对象到GC Roots没有任何引用链相连时即从GC Roots节点到该节点不可达则证明该对象是不可用的。在java中可以作为GC Roots的对象有以下几种虚拟机栈中引用的对象、方法区类静态属性引用的对象、方法区常量池引用的对象、本地方法栈JNI引用的对象。
5、什么情况下会产生StackOverflowError栈溢出和OutOfMemoryError堆溢出怎么排查 难度系数⭐⭐
引发 StackOverFlowError 的常见原因有以下几种 无限递归循环调用最常见执行了大量方法导致线程栈空间耗尽方法内声明了海量的局部变量native 代码有栈上分配的逻辑并且要求的内存还不小比如 java.net.SocketInputStream.read0 会在栈上要求分配一个 64KB 的缓存64位 Linux。引发 OutOfMemoryError的常见原因有以下几种 内存中加载的数据量过于庞大如一次从数据库取出过多数据集合类中有对对象的引用使用完后未清空使得JVM不能回收代码中存在死循环或循环产生过多重复的对象实体启动参数内存值设定的过小排查可以通过jvisualvm进行内存快照分析
参考https://www.cnblogs.com/boboooo/p/13164071.html 栈溢出、堆溢出案例演示 public class StackOverFlowTest { private static int count 1; public static void main(String[] args) { //模拟栈溢出 //getDieCircle(); //模拟堆溢出 getOutOfMem(); } public static void getDieCircle(){ System.out.println(count); getDieCircle(); } public static void getOutOfMem(){ while (true) { Object o new Object(); System.out.println(o); } } } Java 6、什么是线程池线程池有哪些创建 难度系数⭐
线程池就是事先将多个线程对象放到一个容器中当使用的时候就不用 new 线程而是直接去池中拿线程即可节省了开辟子线程的时间提高的代码执行效率
在 JDK 的 java.util.concurrent.Executors 中提供了生成多种线程池的静态方法。
ExecutorService newCachedThreadPool Executors.newCachedThreadPool();
ExecutorService newFixedThreadPool Executors.newFixedThreadPool(4);
ScheduledExecutorService newScheduledThreadPool Executors.newScheduledThreadPool(4);
ExecutorService newSingleThreadExecutor Executors.newSingleThreadExecutor();
然后调用他们的 execute 方法即可。
这4种线程池底层 全部是ThreadPoolExecutor对象的实现阿里规范手册中规定线程池采用ThreadPoolExecutor自定义的实际开发也是。
newCachedThreadPool
创建一个可缓存线程池如果线程池长度超过处理需要可灵活回收空闲线程若无可回收则新建线程。这种类型的线程池特点是
工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
如果长时间没有往线程池中提交任务即如果工作线程空闲了指定的时间(默认为1分钟)则该工作线程将自动终止。终止后如果你又提交了新的任务则线程池重新创建一个工作线程。
在使用CachedThreadPool时一定要注意控制任务的数量否则由于大量线程同时运行很有会造成系统瘫痪。
newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程如果工作线程数量达到线程池初始的最大数则将提交的任务存入到池队列中。FixedThreadPool是一个典型且优秀的线程池它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是在线程池空闲时即线程池中没有可运行任务时它不会释放工作线程还会占用一定的系统资源。
newSingleThreadExecutor
创建一个单线程化的Executor即只创建唯一的工作者线程来执行任务它只会用唯一的工作线程来执行任务保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束会有另一个取代它保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务并且在任意给定的时间不会有多个线程是活动的。
newScheduleThreadPool
创建一个定长的线程池而且支持定时的以及周期性的任务执行。例如延迟3秒执行。
7、为什么要使用线程池 难度系数⭐
线程池做的工作主要是控制运行的线程数量处理过程中将任务放入队列然后在线程创建后启动这些任务如果线程数量超过了最 大数量超出数量的线程排队等候等其它线程执行完毕再从队列中取出任务来执行。主要特点:线程复用;控制最大并发数:管理线程。
第一:降低资源消耗。通过重复利用己创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时任务可以不需要的等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源如果无限制的创建不仅会消耗系统资源还会降低系统的稳定性使用线程池可以进 行统一的分配调优和监控
8、线程池底层工作原理 难度系数⭐ 第一步线程池刚创建的时候里面没有任何线程等到有任务过来的时候才会创建线程。当然也可以调用 prestartAllCoreThreads() 或者 prestartCoreThread() 方法预创建corePoolSize个线程第二步调用execute()提交一个任务时如果当前的工作线程数corePoolSize直接创建新的线程执行这个任务第三步如果当时工作线程数量corePoolSize会将任务放入任务队列中缓存第四步如果队列已满并且线程池中工作线程的数量maximumPoolSize还是会创建线程执行这个任务第五步如果队列已满并且线程池中的线程已达到maximumPoolSize这个时候会执行拒绝策略JAVA线程池默认的策略是AbortPolicy即抛出RejectedExecutionException异常
9、ThreadPoolExecutor对象有哪些参数 怎么设定核心线程数和最大线程数 拒绝策略有哪些 难度系数⭐
参数与作用共7个参数
corePoolSize核心线程数
在ThreadPoolExecutor中有一个与它相关的配置allowCoreThreadTimeOut默认为false当allowCoreThreadTimeOut为false时核心线程会一直存活哪怕是一直空闲着。而当allowCoreThreadTimeOut为true时核心线程空闲时间超过keepAliveTime时会被回收。
maximumPoolSize最大线程数
线程池能容纳的最大线程数当线程池中的线程达到最大时此时添加任务将会采用拒绝策略默认的拒绝策略是抛出一个运行时错误RejectedExecutionException。值得一提的是当初始化时用的工作队列为LinkedBlockingDeque时这个值将无效。
keepAliveTime存活时间
当非核心空闲超过这个时间将被回收同时空闲核心线程是否回收受allowCoreThreadTimeOut影响。
unitkeepAliveTime的单位。workQueue任务队列
常用有三种队列即SynchronousQueue,LinkedBlockingDeque无界队列,ArrayBlockingQueue有界队列。
threadFactory线程工厂
ThreadFactory是一个接口用来创建worker。通过线程工厂可以对线程的一些属性进行定制。默认直接新建线程。
RejectedExecutionHandler拒绝策略
也是一个接口只有一个方法当线程池中的资源已经全部使用添加新线程被拒绝时会调用RejectedExecutionHandler的rejectedExecution法。默认是抛出一个运行时异常。
线程池大小设置
需要分析线程池执行的任务的特性 CPU 密集型还是 IO 密集型每个任务执行的平均时长大概是多少这个任务的执行时长可能还跟任务处理逻辑是否涉及到网络传输以及底层系统资源依赖有关系
如果是 CPU 密集型主要是执行计算任务响应时间很快cpu 一直在运行这种任务 cpu的利用率很高那么线程数的配置应该根据 CPU 核心数来决定CPU 核心数最大同时执行线程数加入 CPU 核心数为 4那么服务器最多能同时执行 4 个线程。过多的线程会导致上下文切换反而使得效率降低。那线程池的最大线程数可以配置为 cpu 核心数1 如果是 IO 密集型主要是进行 IO 操作执行 IO 操作的时间较长这是 cpu 出于空闲状态导致 cpu 的利用率不高这种情况下可以增加线程池的大小。这种情况下可以结合线程的等待时长来做判断等待时间越高那么线程数也相对越多。一般可以配置 cpu 核心数的 2 倍。
一个公式线程池设定最佳线程数目 线程池设定的线程等待时间线程 CPU 时间/ 线程 CPU 时间 * CPU 数目
这个公式的线程 cpu 时间是预估的程序单个线程在 cpu 上运行的时间通常使用 loadrunner测试大量运行次数求出平均值
拒绝策略
AbortPolicy直接抛出异常默认策略CallerRunsPolicy用调用者所在的线程来执行任务DiscardOldestPolicy丢弃阻塞队列中靠最前的任务并执行当前任务DiscardPolicy直接丢弃任务当然也可以根据应用场景实现 RejectedExecutionHandler 接口自定义饱和策略如记录日志或持久化存储不能处理的任务
10、常见线程安全的并发容器有哪些 难度系数⭐
CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMapCopyOnWriteArrayList、CopyOnWriteArraySet采用写时复制实现线程安全ConcurrentHashMap采用分段锁的方式实现线程安全
11、Atomic原子类了解多少 原理是什么 难度系数⭐
Java 的原子类都存放在并发包 java.util.concurrent.atomic下如下图 基本类型
使用原子的方式更新基本类型AtomicInteger整型原子类AtomicLong长整型原子类AtomicBoolean布尔型原子类
数组类型
使用原子的方式更新数组里的某个元素AtomicIntegerArray整形数组原子类AtomicLongArray长整形数组原子类AtomicReferenceArray引用类型数组原子类
引用类型
AtomicReference引用类型原子类AtomicStampedReference原子更新引用类型里的字段原子类AtomicMarkableReference 原子更新带有标记位的引用类型AtomicIntegerFieldUpdater原子更新整形字段的更新器AtomicLongFieldUpdater原子更新长整形字段的更新器AtomicStampedReference原子更新带有版本号的引用类型。该类将整数值与引用关联起来可用于解决原子的更新数据和数据的版本号以及解决使用 CAS 进行原子更新时可能出现的 ABA 问题
AtomicInteger 类利用 CAS (Compare and Swap) volatile native 方法来保证原子操作从而避免 synchronized 的高开销执行效率大为提升。CAS 的原理是拿期望值和原本的值作比较如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是个本地方法这个方法是用来拿“原值”的内存地址返回值是 valueOffset另外value 是一个 volatile 变量因此 JVM 总是可以保证任意时刻的任何线程总能拿到该变量的最新值。
12、synchronized底层实现是什么 lock底层是什么 有什么区别 难度系数⭐⭐⭐
Synchronized原理
方法级的同步是隐式即无需通过字节码指令来控制的它实现在方法调用和返回操作之中。JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置如果设置了执行线程将先持有monitor虚拟机规范中用的是管程一词然后再执行方法最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。
代码块的同步是利用monitorenter和monitorexit这两个字节码指令。它们分别位于同步代码块的开始和结束位置。当jvm执行到monitorenter指令时当前线程试图获取monitor对象的所有权如果未加锁或者已经被当前线程所持有就把锁的计数器1当执行monitorexit指令时锁计数器-1当锁计数器为0时该锁就被释放了。如果获取monitor对象失败该线程则会进入阻塞状态直到其他线程释放锁。
参考一篇文章讲透synchronized底层实现原理_忘了带罗盘的船夫的博客-CSDN博客
Lock原理
Lock的存储结构一个int类型状态值用于锁的状态变更一个双向链表用于存储等待中的线程Lock获取锁的过程本质上是通过CAS来获取状态值修改如果当场没获取到会将该线程放在线程等待链表中。Lock释放锁的过程修改状态值调整等待链表。Lock大量使用CAS自旋。因此根据CAS特性lock建议使用在低锁冲突的情况下。
Lock与synchronized的区别
Lock的加锁和解锁都是由java代码配合native方法调用操作系统的相关方法实现的而synchronize的加锁和解锁的过程是由JVM管理的当一个线程使用synchronize获取锁时若锁被其他线程占用着那么当前只能被阻塞直到成功获取锁。而Lock则提供超时锁和可中断等更加灵活的方式在未能获取锁的 条件下提供一种退出的机制。一个锁内部可以有多个Condition实例即有多路条件队列而synchronize只有一路条件队列同样Condition也提供灵活的阻塞方式在未获得通知之前可以通过中断线程以 及设置等待时限等方式退出条件队列。synchronize对线程的同步仅提供独占模式而Lock即可以提供独占模式也可以提供共享模式 synchronized Lock 关键字 类 自动加锁和释放锁 需要手动调用unlock方法释放锁 jvm层面的锁 API层面的锁 非公平锁 可以选择公平或者非公平锁 锁是一个对象,并且锁的信息保存在了对象中 代码中通过int类型的state标识 有一个锁升级的过程 无 13、了解ConcurrentHashMap吗 为什么性能比HashTable高说下原理 难度系数⭐⭐
ConcurrentHashMap是线程安全的Map容器JDK8之前ConcurrentHashMap使用锁分段技术将数据分成一段段存储每个数据段配置一把锁即segment类这个类继承ReentrantLock来保证线程安全JKD8的版本取消Segment这个分段锁数据结构底层也是使用Node数组链表红黑树从而实现对每一段数据就行加锁也减少了并发冲突的概率。
hashtable类基本上所有的方法都是采用synchronized进行线程安全控制高并发情况下效率就降低 ConcurrentHashMap是采用了分段锁的思想提高性能锁粒度更细化
14、ConcurrentHashMap底层原理 难度系数⭐⭐⭐
Java7 中 ConcurrentHashMap 使用的分段锁也就是每一个 Segment 上同时只有一个线程可以操作每一个 Segment 都是一个类似 HashMap 数组的结构它可以扩容它的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变。 public V put(K key, V value) {SegmentK,V s;if (value null)throw new NullPointerException();int hash hash(key);// hash 值无符号右移 28位初始化时获得然后与 segmentMask15 做与运算// 其实也就是把高4位与segmentMask1111做与运算// this.segmentMask ssize - 1;//对hash值进行右移segmentShift位计算元素对应segment中数组下表的位置//把hash右移segmentShift相当于只要hash值的高32-segmentShift位右移的目的是保留了hash值的高位。然后和segmentMask与操作计算元素在segment数组中的下表int j (hash segmentShift) segmentMask;//使用unsafe对象获取数组中第j个位置的值后面加上的是偏移量if ((s (SegmentK,V)UNSAFE.getObject // nonvolatile; recheck(segments, (j SSHIFT) SBASE)) null) // in ensureSegment// 如果查找到的 Segment 为空初始化s ensureSegment(j);//插入segment对象return s.put(key, hash, value, false);
}/*** Returns the segment for the given index, creating it and* recording in segment table (via CAS) if not already present.** param k the index* return the segment*/
SuppressWarnings(unchecked)
private SegmentK,V ensureSegment(int k) {final SegmentK,V[] ss this.segments;long u (k SSHIFT) SBASE; // raw offsetSegmentK,V seg;// 判断 u 位置的 Segment 是否为nullif ((seg (SegmentK,V)UNSAFE.getObjectVolatile(ss, u)) null) {SegmentK,V proto ss[0]; // use segment 0 as prototype// 获取0号 segment 里的 HashEntryK,V 初始化长度int cap proto.table.length;// 获取0号 segment 里的 hash 表里的扩容负载因子所有的 segment 的 loadFactor 是相同的float lf proto.loadFactor;// 计算扩容阀值int threshold (int)(cap * lf);// 创建一个 cap 容量的 HashEntry 数组HashEntryK,V[] tab (HashEntryK,V[])new HashEntry[cap];if ((seg (SegmentK,V)UNSAFE.getObjectVolatile(ss, u)) null) { // recheck// 再次检查 u 位置的 Segment 是否为null因为这时可能有其他线程进行了操作SegmentK,V s new SegmentK,V(lf, threshold, tab);// 自旋检查 u 位置的 Segment 是否为nullwhile ((seg (SegmentK,V)UNSAFE.getObjectVolatile(ss, u)) null) {// 使用CAS 赋值只会成功一次if (UNSAFE.compareAndSwapObject(ss, u, null, seg s))break;}}}return seg;
}final V put(K key, int hash, V value, boolean onlyIfAbsent) {// 获取 ReentrantLock 独占锁获取不到scanAndLockForPut 获取。HashEntryK,V node tryLock() ? null : scanAndLockForPut(key, hash, value);V oldValue;try {HashEntryK,V[] tab table;// 计算要put的数据位置int index (tab.length - 1) hash;// CAS 获取 index 坐标的值HashEntryK,V first entryAt(tab, index);for (HashEntryK,V e first;;) {if (e ! null) {// 检查是否 key 已经存在如果存在则遍历链表寻找位置找到后替换 valueK k;if ((k e.key) key ||(e.hash hash key.equals(k))) {oldValue e.value;if (!onlyIfAbsent) {e.value value;modCount;}break;}e e.next;}else {// first 有值没说明 index 位置已经有值了有冲突链表头插法。if (node ! null)node.setNext(first);elsenode new HashEntryK,V(hash, key, value, first);int c count 1;// 容量大于扩容阀值小于最大容量进行扩容if (c threshold tab.length MAXIMUM_CAPACITY)rehash(node);else// index 位置赋值 nodenode 可能是一个元素也可能是一个链表的表头setEntryAt(tab, index, node);modCount;count c;oldValue null;break;}}} finally {unlock();}return oldValue;
} Java Java8 中的 ConcurrentHashMap 使用的 Synchronized 锁加 CAS 的机制。结构Node 数组 链表 / 红黑树Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树在冲突小于一定数量时又退回链表。 public V put(K key, V value) {return putVal(key, value, false);
}/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {// key 和 value 不能为空if (key null || value null) throw new NullPointerException();int hash spread(key.hashCode());int binCount 0;for (NodeK,V[] tab table;;) {// f 目标位置元素NodeK,V f; int n, i, fh;// fh 后面存放目标位置的元素 hash 值if (tab null || (n tab.length) 0)// 数组桶为空初始化数组桶自旋CAS)tab initTable();else if ((f tabAt(tab, i (n - 1) hash)) null) {// 桶内为空CAS 放入不加锁成功了就直接 break 跳出if (casTabAt(tab, i, null,new NodeK,V(hash, key, value, null)))break; // no lock when adding to empty bin}else if ((fh f.hash) MOVED)tab helpTransfer(tab, f);else {V oldVal null;// 使用 synchronized 加锁加入节点synchronized (f) {if (tabAt(tab, i) f) {// 说明是链表if (fh 0) {binCount 1;// 循环加入新的或者覆盖节点for (NodeK,V e f;; binCount) {K ek;if (e.hash hash ((ek e.key) key ||(ek ! null key.equals(ek)))) {oldVal e.val;if (!onlyIfAbsent)e.val value;break;}NodeK,V pred e;if ((e e.next) null) {pred.next new NodeK,V(hash, key,value, null);break;}}}else if (f instanceof TreeBin) {// 红黑树NodeK,V p;binCount 2;if ((p ((TreeBinK,V)f).putTreeVal(hash, key,value)) ! null) {oldVal p.val;if (!onlyIfAbsent)p.val value;}}}}if (binCount ! 0) {if (binCount TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal ! null)return oldVal;break;}}}addCount(1L, binCount);return null;
} Java 15、了解volatile关键字不 难度系数⭐
volatile是Java提供的最轻量级的同步机制保证了共享变量的可见性被volatile关键字修饰的变量如果值发生了变化其他线程立刻可见避免出现脏读现象。volatile禁止了指令重排可以保证程序执行的有序性但是由于禁止了指令重排所以JVM相关的优化没了效率会偏弱
16、synchronized和volatile有什么区别 难度系数⭐⭐
volatile本质是告诉JVM当前变量在寄存器中的值是不确定的需要从主存中读取synchronized则是锁定当前变量只有当前线程可以访问该变量其他线程被阻塞住。volatile仅能用在变量级别而synchronized可以使用在变量、方法、类级别。volatile仅能实现变量的修改可见性不能保证原子性而synchronized则可以保证变量的修改可见性和原子性。volatile不会造成线程阻塞synchronized可能会造成线程阻塞。volatile标记的变量不会被编译器优化synchronized标记的变量可以被编译器优化。
17、Java类加载过程 难度系数⭐
加载 加载时类加载的第一个过程在这个阶段将完成一下三件事情
通过一个类的全限定名获取该类的二进制流。
将该二进制流中的静态存储结构转化为方法去运行时数据结构。
在内存中生成该类的Class对象作为该类的数据访问入口。
验证 验证的目的是为了确保Class文件的字节流中的信息不回危害到虚拟机.在该阶段主要完成以下四钟验证:
文件格式验证验证字节流是否符合Class文件的规范如主次版本号是否在当前虚拟机范围内常量池中的常量是否有不被支持的类型.
元数据验证:对字节码描述的信息进行语义分析如这个类是否有父类是否集成了不被继承的类等。
字节码验证是整个验证过程中最复杂的一个阶段通过验证数据流和控制流的分析确定程序语义是否正确主要针对方法体的验证。如方法中的类型转换是否正确跳转指令是否正确等。
符号引用验证这个动作在后面的解析过程中发生主要是为了确保解析动作能正确执行。
准备
准备阶段是为类的静态变量分配内存并将其初始化为默认值这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存实例变量将会在对象实例化时随着对象一起分配在Java堆中。
解析
该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前也有可能在初始化之后。
初始化
初始化时类加载的最后一步前面的类加载过程除了在加载阶段用户应用程序可以通过自定义类加载器参与之外其余动作完全由虚拟机主导和控制。到了初始化阶段才真正开始执行类中定义的Java程序代码。
18、什么是类加载器类加载器有哪些 难度系数⭐
类加载器就是把类文件加载到虚拟机中也就是说通过一个类的全限定名来获取描述该类的二进制字节流。
主要有以下四种类加载器
启动类加载器(Bootstrap ClassLoader)用来加载java核心类库无法被java程序直接引用
扩展类加载器(extension class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类
系统类加载器system class loader也叫应用类加载器它根据 Java 应用的类路径CLASSPATH来加载 Java 类。一般来说Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它
用户自定义类加载器通过继承 java.lang.ClassLoader类的方式实现
什么时候会使用到加载器java中的加载器是按需加载什么时候用到什么时候加载 new对象的时候访问某个类或者接口的静态变量或者对该静态变量赋值时调用类的静态方法时反射初始化一个类的子类时其父类首先会被加载JVM启动时标明的启动类也就是文件名和类名相同的那个类
19、简述java内存分配与回收策略以及Minor GC和Major GCfull GC 难度系数⭐⭐
内存分配
栈区栈分为java虚拟机栈和本地方法栈
堆区堆被所有线程共享区域在虚拟机启动时创建唯一目的存放对象实例。堆区是gc的主要区域通常情况下分为两个区块年轻代和年老代。更细一点年轻代又分为Eden区主要放新创建对象From survivor 和 To survivor 保存gc后幸存下的对象默认情况下各自占比 8:1:1。
方法区被所有线程共享区域用于存放已被虚拟机加载的类信息常量静态变量等数据。被Java虚拟机描述为堆的一个逻辑部分。习惯是也叫它永久代permanment generation
程序计数器当前线程所执行的行号指示器。通过改变计数器的值来确定下一条指令比如循环分支跳转异常处理线程恢复等都是依赖计数器来完成。线程私有的。
回收策略以及Minor GC和Major GC 对象优先在堆的Eden区分配大对象直接进入老年代长期存活的对象将直接进入老年代
当Eden区没有足够的空间进行分配时虚拟机会执行一次Minor GC.Minor GC通常发生在新生代的Eden区在这个区的对象生存期短往往发生GC的频率较高回收速度比较快;Full Gc/Major GC 发生在老年代一般情况下触发老年代GC的时候不会触发Minor GC,但是通过配置可以在Full GC之前进行一次Minor GC这样可以加快老年代的回收速度。
20、如何查看java死锁 难度系数⭐ ####演示死锁
package com.ssg.mst;
public class 死锁 {private static final String lock1 lock1;private static final String lock2 lock2;public static void main(String[] args) {Thread thread1 new Thread(() - {while (true) {synchronized (lock1) {try {System.out.println(Thread.currentThread().getName() lock1);Thread.sleep(1000);synchronized (lock2){System.out.println(Thread.currentThread().getName() lock2);}} catch (InterruptedException e) {throw new RuntimeException(e);}}}});Thread thread2 new Thread(() - {while (true) {synchronized (lock2) {try {System.out.println(Thread.currentThread().getName() lock2);Thread.sleep(1000);synchronized (lock1){System.out.println(Thread.currentThread().getName() lock1);}} catch (InterruptedException e) {throw new RuntimeException(e);}}}});thread1.start();thread2.start();}
} Java 死锁代码演示
程序运行进程没有停止。 通过jps查看java进程找到没有停止的进程 通过jstack 9060 查看进程具体执行信息 21、Java死锁如何避免 难度系数⭐
造成死锁的几个原因
1.一个资源每次只能被一个线程使用
2.一个线程在阻塞等待某个资源时不释放已占有资源
3.一个线程已经获得的资源在未使用完之前不能被强行剥夺
4.若干线程形成头尾相接的循环等待资源关系
这是造成死锁必须要达到的4个条件如果要避免死锁只需要不满足其中某一个条件即可。而其中前3个条件是作为锁要符合的条件所以要避免死锁就需要打破第4个条件不出现循环等待锁的关系。
在开发过程中
1.要注意加锁顺序保证每个线程按同样的顺序进行加锁
2.要注意加锁时限可以针对锁设置一个超时时间
3.要注意死锁检查这是一种预防机制确保在第一时间发现死锁并进行解决
第三章-java框架篇
1、简单的谈一下SpringMVC的工作流程 难度系数⭐
用户发送请求至前端控制器DispatcherServletDispatcherServlet收到请求调用HandlerMapping处理器映射器。处理器映射器找到具体的处理器生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。DispatcherServlet调用HandlerAdapter处理器适配器HandlerAdapter经过适配调用具体的处理器(Controller也叫后端控制器)。Controller执行完成返回ModelAndViewHandlerAdapter将controller执行结果ModelAndView返回给DispatcherServletDispatcherServlet将ModelAndView传给ViewReslover视图解析器ViewReslover解析后返回具体ViewDispatcherServlet根据View进行渲染视图即将模型数据填充至视图中。DispatcherServlet响应用户
2、说出Spring或者SpringMVC中常用的5个注解 难度系数⭐
Component 基本注解标识一个受Spring管理的组件Controller 标识为一个表示层的组件Service 标识为一个业务层的组件Repository 标识为一个持久层的组件Autowired 自动装配Qualifier() 具体指定要装配的组件的id值RequestMapping() 完成请求映射PathVariable 映射请求URL中占位符到请求处理方法的形参
只要说出几个注解并解释含义即可如上答案只做参考
3、简述SpringMVC中如何返回JSON数据 难度系数⭐
Step1在项目中加入json转换的依赖例如jacksonfastjsongson等
Step2在请求处理方法中将返回值改为具体返回的数据的类型 例如数据的集合类ListEmployee等
Step3在请求处理方法上使用ResponseBody注解
4、谈谈你对Spring的理解 难度系数⭐
Spring 是一个开源框架为简化企业级应用开发而生。Spring 可以是使简单的JavaBean 实现以前只有EJB 才能实现的功能。Spring 是一个 IOC 和 AOP 容器框架。
Spring 容器的主要核心是
控制反转IOC传统的 java 开发模式中当需要一个对象时我们会自己使用 new 或者 getInstance 等直接或者间接调用构造方法创建一个对象。而在 spring 开发模式中spring 容器使用了工厂模式为我们创建了所需要的对象不需要我们自己创建了直接调用spring 提供的对象就可以了这是控制反转的思想。
依赖注入DIspring 使用 javaBean 对象的 set 方法或者带参数的构造方法为我们在创建所需对象时将其属性自动设置所需要的值的过程就是依赖注入的思想。
面向切面编程AOP在面向对象编程oop思想中我们将事物纵向抽成一个个的对象。而在面向切面编程中我们将一个个的对象某些类似的方面横向抽成一个切面对这个切面进行一些如权限控制、事物管理记录日志等公用操作处理的过程就是面向切面编程的思想。AOP 底层是动态代理如果是接口采用 JDK 动态代理如果是类采用CGLIB 方式实现动态代理。
5、Spring中常用的设计模式 难度系数⭐
代理模式——spring 中两种代理方式若目标对象实现了若干接口spring 使用jdk 的java.lang.reflect.Proxy类代理。若目标兑现没有实现任何接口spring 使用 CGLIB 库生成目标类的子类。单例模式——在 spring 的配置文件中设置 bean 默认为单例模式。模板方式模式——用来解决代码重复的问题。
比如RestTemplate、JmsTemplate、JpaTemplate
工厂模式——在工厂模式中我们在创建对象时不会对客户端暴露创建逻辑并且是通过使用同一个接口来指向新创建的对象。Spring 中使用 beanFactory 来创建对象的实例。
6、Spring循环依赖问题 难度系数⭐⭐
常见问法
请解释一下spring中的三级缓存
三级缓存分别是什么?三个Map有什么异同?
什么是循环依赖?请你谈谈?看过spring源码吗?
如何检测是否存在循环依赖?实际开发中见过循环依赖的异常吗?
多例的情况下,循环依赖问题为什么无法解决?
什么是循环依赖? 两种注入方式对循环依赖的影响?
官方解释
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-dependency-resolution
相关概念
实例化:堆内存中申请空间 初始化:对象属性赋值 三级缓存 名称 对象名 含义 一级缓存 singletonObjects 存放已经经历了完整生命周期的Bean对象 二级缓存 earlySingletonObjects 存放早期暴露出来的Bean对象Bean的生命周期未结束属性还未填充完) 三级缓存 singletonFactories 存放可以生成Bean的工厂 四个关键方法 package org.springframework.beans.factory.support;
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { /** 单例对象的缓存:bean名称—bean实例即:所谓的单例池。 表示已经经历了完整生命周期的Bean对象 第一级缓存 */ private final MapString, Object singletonObjects new ConcurrentHashMap(256); /** 早期的单例对象的高速缓存: bean名称—bean实例。 表示 Bean的生命周期还没走完Bean的属性还未填充就把这个 Bean存入该缓存中也就是实例化但未初始化的 bean放入该缓存里 第二级缓存 */ private final MapString, Object earlySingletonObjects new HashMap(16); /** 单例工厂的高速缓存:bean名称—ObjectFactory 表示存放生成 bean的工厂 第三级缓存 */ private final MapString, ObjectFactory? singletonFactories new HashMap(16);
}
debug源代码过程
需要22个断点(可选)
1A创建过程中需要B于是A将自己放到三级缓里面去实例化B
2B实例化的时候发现需要A于是B先查一级缓存没有再查二级缓存还是没有再查三级缓存找到了A然后把三级缓存里面的这个A放到二级缓存里面并删除三级缓存里面的A
3B顺利初始化完毕将自己放到一级缓存里面(此时B里面的A依然是创建中状态)
然后回来接着创建A此时B已经创建结束直接从一级缓存里面拿到B然后完成创建并将A自己放到一级缓存里面。
总结
1Spring创建 bean主要分为两个步骤创建原始bean对象接着去填充对象属性和初始化。
2每次创建 bean之前我们都会从缓存中查下有没有该bean因为是单例只能有一个。
3当创建 A的原始对象后并把它放到三级缓存中接下来就该填充对象属性了这时候发现依赖了B接着就又去创建B同样的流程创建完B填充属性时又发现它依赖了A又是同样的流程不同的是这时候可以在三级缓存中查到刚放进去的原始对象A。
所以不需要继续创建用它注入 B完成 B的创建既然 B创建好了所以 A就可以完成填充属性的步骤了接着执行剩下的逻辑闭环完成
Spring解决循环依赖依靠的是Bean的中间态这个概念而这个中间态指的是已经实例化但还没初始化的状态—半成品。实例化的过程又是通过构造器创建的如果A还没创建好出来怎么可能提前曝光所以构造器的循环依赖无法解决
其他衍生问题
问题1:为什么构造器注入属性无法解决循环依赖问题? 由于spring中的bean的创建过程为先实例化 再初始化(在进行对象实例化的过程中不必赋值)将实例化好的对象暴露出去,供其他对象调用,然而使用构造器注入,必须要使用构造器完成对象的初始化的操作,就会陷入死循环的状态
问题2:一级缓存能不能解决循环依赖问题? 不能 在三个级别的缓存中存储的对象是有区别的 一级缓存为完全实例化且初始化的对象 二级缓存实例化但未初始化对象 如果只有一级缓存,如果是并发操作下,就有可能取到实例化但未初始化的对象,就会出现问题
问题3:二级缓存能不能解决循环依赖问题? 理论上二级缓存可以解决循环依赖问题,但是需要注意,为什么需要在三级缓存中存储匿名内部类(ObjectFactory),原因在于 需要创建代理对象 eg:现有A类,需要生成代理对象 A是否需要进行实例化(需要) 在三级缓存中存放的是生成具体对象的一个匿名内部类,该类可能是代理类也可能是普通的对象,而使用三级缓存可以保证无论是否需要是代理对象,都可以保证使用的是同一个对象,而不会出现,一会儿使用普通bean 一会儿使用代理类
7、介绍一下Spring bean 的生命周期、注入方式和作用域 难度系数⭐
Bean的生命周期
1默认情况下IOC容器中bean的生命周期分为五个阶段:
调用构造器 或者是通过工厂的方式创建Bean对象给bean对象的属性注入值调用初始化方法进行初始化 初始化方法是通过init-method来指定的.使用IOC容器关闭时 销毁Bean对象.
2当加入了Bean的后置处理器后IOC容器中bean的生命周期分为七个阶段:
调用构造器 或者是通过工厂的方式创建Bean对象给bean对象的属性注入值执行Bean后置处理器中的 postProcessBeforeInitialization调用初始化方法进行初始化 初始化方法是通过init-method来指定的.x执行Bean的后置处理器中 postProcessAfterInitialization 使用IOC容器关闭时 销毁Bean对象
只需要回答出第一点即可第二点也回答可适当 加分。
注入方式
通过 setter 方法注入
通过构造方法注入
Bean的作用域
总共有四种作用域:
Singleton 单例的Prototype 原型的RequestSession
8、请描述一下Spring 的事务管理 难度系数⭐
1声明式事务管理的定义用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。这样的好处是事务管理不侵入开发的组件具体来说业务逻辑对象就不会意识到正在事务管理之中事实上也应该如此因为事务管理是属于系统层面的服务而不是业务逻辑的一部分如果想要改变事务管理策划的话也只需要在定义文件中重新配置即可这样维护起来极其方便。
基于 TransactionInterceptor 的声明式事务管理两个次要的属性 transactionManager用来指定一个事务治理器 并将具体事务相关的操作请托给它 其他一个是 Properties 类型的transactionAttributes 属性该属性的每一个键值对中键指定的是方法名方法名可以行使通配符 而值就是表现呼应方法的所运用的事务属性。
2基于 Transactional 的声明式事务管理Spring 2.x 还引入了基于 Annotation 的体式格式具体次要触及Transactional 标注。Transactional 可以浸染于接口、接口方法、类和类方法上。算作用于类上时该类的一切public 方法将都具有该类型的事务属性。
3编程式事物管理的定义在代码中显式挪用 beginTransaction()、commit()、rollback()等事务治理相关的方法 这就是编程式事务管理。Spring 对事物的编程式管理有基于底层 API 的编程式管理和基于 TransactionTemplate 的编程式事务管理两种方式。
9、MyBatis中 #{}和${}的区别是什么 难度系数⭐
#{}是预编译处理${}是字符串替换
Mybatis在处理#{}时会将sql中的#{}替换为?号调用PreparedStatement的set方法来赋值
Mybatis在处理${}时就是把${}替换成变量的值
使用#{}可以有效的防止SQL注入提高系统安全性。
10、Mybatis 中一级缓存与二级缓存 难度系数⭐
MyBatis的缓存分为一级缓存和 二级缓存。
一级缓存是SqlSession级别的缓存默认开启。
二级缓存是NameSpace级别(Mapper)的缓存多个SqlSession可以共享使用时需要进行配置开启。
缓存的查找顺序二级缓存 一级缓存 数据库
11、MyBatis如何获取自动生成的(主)键值 难度系数⭐
在insert标签中使用 useGeneratedKeys和keyProperty 两个属性来获取自动生成的主键值。
示例: insert id”insertname” usegeneratedkeys”true” keyproperty”id” insert into names (name) values (#{name}) /insert Java 12、简述Mybatis的动态SQL列出常用的6个标签及作用 难度系数⭐
动态SQL是MyBatis的强大特性之一 基于功能强大的OGNL表达式。
动态SQL主要是来解决查询条件不确定的情况在程序运行期间根据提交的条件动态的完成查询
常用的标签:
if : 进行条件的判断
where在if判断后的SQL语句前面添加WHERE关键字并处理SQL语句开始位置的AND 或者OR的问题
trim可以在SQL语句前后进行添加指定字符 或者去掉指定字符.
set: 主要用于修改操作时出现的逗号问题
choose when otherwise类似于java中的switch语句.在所有的条件中选择其一
foreach迭代操作
13、Mybatis 如何完成MySQL的批量操作 难度系数⭐
MyBatis完成MySQL的批量操作主要是通过foreach标签来拼装相应的SQL语句
例如: insert** idinsertBatch insert into tbl_employee(last_name,email,gender,d_id) values foreach** collectionemps itemcurr_emp separator,** (#{curr_emp.lastName},#{curr_emp.email},#{curr_emp.gender},#{curr_emp.dept.id}) /foreach /insert Java 14、谈谈怎么理解SpringBoot框架 难度系数⭐⭐
Spring Boot 是 Spring 开源组织下的子项目是 Spring 组件一站式解决方案主要是简化了使用 Spring 的难度简省了繁重的配置提供了各种启动器开发者能快速上手。 Spring Boot的优点
独立运行
Spring Boot而且内嵌了各种servlet容器Tomcat、Jetty等现在不再需要打成war包部署到容器中Spring Boot只要打成一个可执行的jar包就能独立运行所有的依赖包都在一个jar包内。
简化配置
spring-boot-starter-web启动器自动依赖其他组件简少了maven的配置。除此之外还提供了各种启动器开发者能快速上手。
自动配置
Spring Boot能根据当前类路径下的类、jar包来自动配置bean如添加一个spring-boot-starter-web启动器就能拥有web的功能无需其他配置。
无代码生成和XML配置
Spring Boot配置过程中无代码生成也无需XML配置文件就能完成所有配置工作这一切都是借助于条件注解完成的这也是Spring4.x的核心功能之一。
应用监控
Spring Boot提供一系列端点可以监控服务及应用做健康检测。
Spring Boot缺点
Spring Boot虽然上手很容易但如果你不了解其核心技术及流程所以一旦遇到问题就很棘手而且现在的解决方案也不是很多需要一个完善的过程。
15、Spring Boot 的核心注解是哪个 它主要由哪几个注解组成的 难度系数⭐
启动类上面的注解是SpringBootApplication它也是 Spring Boot 的核心注解主要组合包含了以下 3 个注解
SpringBootConfiguration组合了 Configuration 注解实现配置文件的功能。EnableAutoConfiguration打开自动配置的功能也可以关闭某个自动配置的选项 如关闭数据源自动配置功能 SpringBootApplication(exclude { DataSourceAutoConfiguration.class })。ComponentScanSpring组件扫描。
16、Spring Boot自动配置原理是什么 难度系数⭐
注解 EnableAutoConfiguration, Configuration, ConditionalOnClass 就是自动配置的核心
首先它得是一个配置文件其次根据类路径下是否有这个类去自动配置。
EnableAutoConfiguration是实现自动配置的注解
Configuration表示这是一个配置文件
具体参考文档
Spring Boot自动配置原理、实战
17、SpringBoot配置文件有哪些 怎么实现多环境配置 难度系数⭐
Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件。
application 配置文件这个容易理解主要用于 Spring Boot 项目的自动化配置。
bootstrap配置文件的特性
bootstrap 由父 ApplicationContext 加载比 applicaton 优先加载bootstrap 里面的属性不能被覆盖
bootstrap 配置文件有以下几个应用场景
使用 Spring Cloud Config 配置中心时这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息一些固定的不能被覆盖的属性一些加密/解密的场景
提供多套配置文件如
applcation.properties application-dev.properties application-test.properties application-prod.properties
运行时指定具体的配置文件具体请看这篇文章《Spring Boot Profile 不同环境配置》。
18、SpringBoot和SpringCloud是什么关系 难度系数⭐
Spring Boot 是 Spring 的一套快速配置脚手架可以基于Spring Boot 快速开发单个微服务Spring Cloud是一个基于Spring Boot实现的开发工具Spring Boot专注于快速、方便集成的单个微服务个体Spring Cloud关注全局的服务治理框架 Spring Boot使用了默认大于配置的理念很多集成方案已经帮你选择好了能不配置就不配置Spring Cloud很大的一部分是基于Spring Boot来实现必须基于Spring Boot开发。 可以单独使用Spring Boot开发项目但是Spring Cloud离不开 Spring Boot。
19、SpringCloud都用过哪些组件 介绍一下作用 难度系数⭐
Nacos--作为注册中心和配置中心实现服务注册发现和服务健康监测及配置信息统一管理Gateway--作为网关作为分布式系统统一的出入口进行服务路由统一鉴权等OpenFeign--作为远程调用的客户端实现服务之间的远程调用Sentinel--实现系统的熔断限流Sleuth--实现服务的链路追踪
20、Nacos作用以及注册中心的原理 难度系数⭐⭐
Nacos英文全称Dynamic Naming and Configuration ServiceNa为naming/nameServer即注册中心,co为configuration即注册中心service是指该注册/配置中心都是以服务为核心。
Nacos注册中心分为server与clientserver采用Java编写为client提供注册发现服务与配置服务。而client可以用多语言实现client与微服务嵌套在一起nacos提供sdk和openApi如果没有sdk也可以根据openApi手动写服务注册与发现和配置拉取的逻辑。 服务注册原理
服务注册方法以Java nacos client v1.0.1 为例子服务注册的策略的是每5秒向nacos server发送一次心跳心跳带上了服务名服务ip服务端口等信息。同时 nacos server也会向client 主动发起健康检查支持tcp/http检查。如果15秒内无心跳且健康检查失败则认为实例不健康如果30秒内健康检查失败则剔除实例。 21、Feign工作原理 难度系数⭐⭐
主程序入口添加了EnableFeignClients注解开启对FeignClient扫描加载处理。根据Feign Client的开发规范定义接口并加FeignClient注解。当程序启动时会进行包扫描扫描所有FeignClient的注解的类并且讲这些信息注入Spring IOC容器中当定义的的Feign接口中的方法被调用时通过JDK的代理方式来生成具体的RequestTemplate.当生成代理时Feign会为每个接口方法创建一个RequestTemplate。当生成代理时Feign会为每个接口方法创建一个RequestTemplate对象该对象封装HTTP请求需要的全部信息如请求参数名请求方法等信息都是在这个过程中确定的。然后RequestTemplate生成Request,然后把Request交给Client去处理这里指的时Client可以时JDK原生的URLConnection,Apache的HttpClient,也可以时OKhttp最后Client被封装到LoadBalanceClient类这个类结合Ribbon负载均衡发器服务之间的调用。 第四章-MySQL
1、Select 语句完整的执行顺序 难度系数⭐
SQL Select 语句完整的执行顺序
1from 子句组装来自不同数据源的数据
2where 子句基于指定的条件对记录行进行筛选
3group by 子句将数据划分为多个分组
4使用聚集函数进行计算
5使用 having 子句筛选分组
6计算所有的表达式
7select 的字段
8使用order by 对结果集进行排序。
2、MySQL事务 难度系数⭐⭐
事务的基本要素ACID
原子性Atomicity事务开始后所有操作要么全部做完要么全部不做不可能停滞在中间环节。事务执行过程中出错会回滚到事务开始前的状态所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体就像化学中学过的原子是物质构成的基本单位一致性Consistency事务开始前和结束后数据库的完整性约束没有被破坏 。比如A向B转账不可能A扣了钱B却没收到。隔离性Isolation同一时间只允许一个事务请求同一数据不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱在A取钱的过程结束前B不能向这张卡转账。持久性Durability事务完成后事务对数据库的所有更新将被保存到数据库不能回滚。
MySQL事务隔离级别 事务隔离级别 脏读 不可重复读 幻读 读未提交read-uncommitted 是 是 是 读提交read-committed 否 是 是 可重复读repeatable-read 否 否 是 串行化serializable 否 否 否 事务的并发问题
脏读事务A读取了事务B更新的数据然后B回滚操作那么A读取到的数据是脏数据不可重复读事务 A 多次读取同一数据事务 B 在事务A多次读取的过程中对数据作了更新并提交导致事务A多次读取同一数据时结果 不一致幻读系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级但是系统管理员B就在这个时候插入了一条具体分数的记录当系统管理员A改结束后发现还有一条记录没有改过来就好像发生了幻觉一样这就叫幻读。
如何解决脏读、幻读、不可重复读
脏读 隔离级别为 读提交、可重复读、串行化可以解决脏读不可重复读隔离级别为可重复读、串行化可以解决不可重复读幻读隔离级别为串行化可以解决幻读、通过MVCC 区间锁可以解决幻读
小结
不可重复读的和幻读很容易混淆不可重复读侧重于修改幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行解决幻读需要锁表
3、MyISAM和InnoDB的区别 难度系数⭐ MyISAM InnoDB 事务 不支持 支持 锁 表锁 表锁、行锁 文件存储 3个 1个 外键 不支持 支持 4、悲观锁和乐观锁的怎么实现 难度系数⭐⭐
悲观锁select...for update是MySQL提供的实现悲观锁的方式。 例如select price from item where id100 for update
此时在items表中id为100的那条数据就被我们锁定了其它的要执行select price from items where id100 for update的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。MySQL有个问题是select...for update语句执行中所有扫描过的行都会被锁上因此在MySQL中用悲观锁务必须确定走了索引而不是全表扫描否则将会将整个数据表锁住。
乐观锁乐观锁相对悲观锁而言它认为数据一般情况下不会造成冲突所以在数据进行提交更新的时候才会正式对数据的冲突与否进行检测如果发现冲突了则让返回错误信息让用户决定如何去做。
利用数据版本号version机制是乐观锁最常用的一种实现方式。一般通过为数据库表增加一个数字类型的 “version” 字段当读取数据时将version字段的值一同读出数据每更新一次对此version值1。当我们提交更新的时候判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对如果数据库表当前版本号与第一次取出来的version值相等则予以更新否则认为是过期数据返回更新失败。
举例
//1: 查询出商品信息
select (quantity,version) from items where id100;
//2: 根据商品信息生成订单
insert into orders(id,item_id) values(null,100);
//3: 修改商品的库存
update items set quantityquantity-1,versionversion1 where id100 and version#{version};
5、聚簇索引与非聚簇索引区别 难度系数⭐⭐
都是B树的数据结构
聚簇索引:将数据存储与索引放到了一块、并且是按照一定的顺序组织的找到索引也就找到了数据数据的物理存放顺序与索引顺序是一致的即:只要索引是相邻的那么对应的数据一定也是相邻地存放在磁盘上的
非聚簇索引叶子节点不存储数据、存储的是数据行地址也就是说根据索引查找到数据行的位置再取磁盘查找数据这个就有点类似一本书的目录比如我们要找第三章第一节那我们先在这个目录里面找找到对应的页码后再去对应的页码看文章。
优势:
1、查询通过聚簇索引可以直接获取数据相比非聚簇索引需要第二次查询(非覆盖索引的情况下)效率要高
2、聚簇索引对于范围查询的效率很高因为其数据是按照大小排列的
3、聚簇索引适合用在排序的场合非聚簇索引不适合
劣势;
1、维护索引很昂贵特别是插入新行或者主键被更新导至要分页(pagesplit)的时候。建议在大量插入新行后选在负载较低的时间段通过OPTIMIZETABLE优化表因为必须被移动的行数据可能造成碎片。使用独享表空间可以弱化碎片
2、表因为使用uuId(随机ID)作为主键使数据存储稀疏这就会出现聚簇索引有可能有比全表扫面更慢所以建议使用int的auto_increment作为主键
3、如果主键比较大的话那辅助索引将会变的更大因为辅助索引的叶子存储的是主键值过长的主键值会导致非叶子节点占用占用更多的物理空间
6、什么情况下mysql会索引失效 难度系数⭐
失效条件
where 后面使用函数使用or条件模糊查询 %放在前边类型转换组合索引 最佳左前缀匹配原则 #查询条件用到了计算或者函数explain SELECT * from test_slow_query where age 20explain SELECT * from test_slow_query where age 10 30#模糊查询EXPLAIN SELECT * from test_slow_query where NAME like %吕布EXPLAIN SELECT * from test_slow_query where NAME like %吕布%EXPLAIN SELECT * from test_slow_query where NAME like 吕布#用到了or条件EXPLAIN SELECT * from test_slow_query where NAME 吕布 or name aaa#类型不匹配查询explain SELECT * from test_slow_query where NAME 11explain SELECT * from test_slow_query where NAME 11 SQL 7、Btree 与 B-tree区别 难度系数⭐⭐
原理:分批次的将磁盘块加载进内存中进行检索,若查到数据,则直接返回,若查不到,则释放内存,并重新加载同等数据量的索引进内存,重新遍历
结构: 数据 向下的指针 指向数据的指针
特点: 1节点排序 2 .一个节点了可以存多个元索多个元索也排序了 结构: 数据 向下的指针
特点: 1.拥有B树的特点 2.叶子节点之间有指针 3.非叶子节点上的元素在叶子节点上都冗余了也就是叶子节点中存储了所有的元素并且排好顺序 从结构上看,BTree 相较于 B-Tree 而言 缺少了指向数据的指针 也就红色小方块;
Mysq|索引使用的是B树因为索引是用来加快查询的而B树通过对数据进行排序所以是可以提高查询速度的然后通过一个节点中可以存储多个元素从而可以使得B树的高度不会太高在Mysql中一个Innodb页就是一个B树节点一个Innodb页默认16kb所以一般情况下一颗两层的B树可以存2000万行左右的数据然后通过利用B树叶子节点存储了所有数据并且进行了排序并且叶子节点之间有指针可以很好的支持全表扫描范围查找等SQL语句
文章推荐B-Tree和BTree的区别 - 简书
8、以MySQL为例Linux下如何排查问题 难度系数⭐⭐
类似提问方式:如果线上环境出现问题比如网站卡顿重则瘫痪 如何是好?
---linux---mysql/redis/nacos/sentinel/sluth---可以从以上提到的技术点中选择一个自己熟悉单技术点进行分析
以mysql为例
1,架构层面 是否使用主从
2,表结构层面 是否满足常规的表设计规范(大量冗余字段会导致查询会变得很复杂)
3,sql语句层面(⭐)
前提:由于慢查询日志记录默认是关闭的,所以开启数据库mysql的慢查询记录 的功能 从慢查询日志中去获取哪些sql语句时慢查询 默认10S ,从中获取到sql语句进行分析
3.1 explain 分析一条sql Id:执行顺序 如果单表的话,无参考价值 如果是关联查询,会据此判断主表 从表
Select_type:simple
Table:表
Type: ALL 未创建索引 、const、 常量ref其他索引 、eq_ref 主键索引、
Possible_keys
Key 实际是到到索引到字段
Key_len 索引字段数据结构所使用长度 与是否有默认值null 以及对应字段到数据类型有关有一个理论值 有一个实际使用值也即key_len的值
Rows 检索的行数 与查询返回的行数无关
Extra 常见的值usingfilesort 使用磁盘排序算法进行排序事关排序 分组 的字段是否使用索引的核心参考值
还可能这样去提问sql语句中哪些位置适合建索引/索引建立在哪个位置
Select id,name,age from user where id1 and name”xxx” order by age
总结: 查询字段 查询条件(最常用) 排序/分组字段
补充:如何判断是数据库的问题?可以借助于top命令 9、如何处理慢查询 难度系数⭐⭐
在业务系统中除了使用主键进行的查询其他的都会在测试库上测试其耗时慢查询的统计主要由运维在做会定期将业务中的慢查询反馈给我们。
慢查询的优化首先要搞明白慢的原因是什么?是查询条件没有命中索引?是加载了不需要的数据列?还是数据量太大?
所以优化也是针对这三个方向来的
首先分析语句看看是否加载了额外的数据可能是查询了多余的行并且抛弃掉了可能是加载了许多结果中并不需要的列对语句进行分析以及重写。
分析语句的执行计划然后获得其使用索引的情况之后修改语句或者修改索引使得语句可以尽可能的命中索引。
如果对语句的优化已经无法进行可以考虑表中的数据量是否太大如果是的话可以进行横向或者纵向的分表。
具体处理流程 阿里云RDS为例
1.开启慢查询设置 日志管理导出慢查询文件 测试环境通过explain执行sql主要关心以下字段 type连接类型
key MYSQL使用的索引
rows显示MYSQL执行查询的行数简单且重要数值越大越不好说明没有用好索引
extra该列包含MySQL解决查询的详细信息。 10、数据库分表操作 难度系数⭐
水平分表
步长法1000万一张表拆分
取模法举例根据用户id取模落入不能的表
垂直分表大表拆小表。商品信息 spu_info spu_image ... 可以说使用Mycat或者ShardingSphere等中间件来做具体怎么做就要结合具体的场景进行分析了。可以参考MySQL分库分表写得太好了-mysql分库分表
11、MySQL优化 难度系数⭐
1尽量选择较小的列
2将where中用的比较频繁的字段建立索引
3select子句中避免使用‘*’
4避免在索引列上使用计算、not in 和等操作
5当只需要一行数据的时候使用limit 1
6保证单表数据不超过200W适时分割表。针对查询较慢的语句可以使用explain 来分析该语句具体的执行情况。
7避免改变索引列的类型。
8选择最有效的表名顺序from字句中写在最后的表是基础表将被最先处理在from子句中包含多个表的情况下你必须选择记录条数最少的表作为基础表。
9避免在索引列上面进行计算。
10尽量缩小子查询的结果
12、SQL语句优化案例 难度系数⭐
例1where 子句中可以对字段进行 null 值判断吗
可以比如 select id from t where num is null 这样的 sql 也是可以的。但是最好不要给数据库留NULL尽可能的使用 NOT NULL 填充数据库。不要以为 NULL 不需要空间比如char(100) 型在字段建立时空间就固定了 不管是否插入值NULL 也包含在内都是占用 100 个字符的空间的如果是 varchar 这样的变长字段null 不占用空间。可以在 num 上设置默认值 0确保表中 num 列没有 null 值然后这样查询select id from t where num 0。
例2如何优化?下面的语句
select * from admin left join log on admin.admin_id log.admin_id where log.admin_id10
优化为select * from (select * from admin where admin_id10) T1 lef join log on T1.admin_id log.admin_id。
使用 JOIN 时候应该用小的结果驱动大的结果left join 左边表结果尽量小如果有条件应该放到左边先处理 right join 同理反向同时尽量把牵涉到多表联合的查询拆分多个 query多个连表查询效率低容易到之后锁表和阻塞。
例3limit 的基数比较大时使用 between
例如select * from admin order by admin_id limit 100000,10
优化为select * from admin where admin_id between 100000 and 100010 order by admin_id。
例4尽量避免在列上做运算这样导致索引失效
例如select * from admin where year(admin_time)2014
优化为 select * from admin where admin_time 2014-01-01′
13、你们公司有哪些数据库设计规范 难度系数⭐
一基础规范
1、表存储引擎必须使用InnoD表字符集默认使用utf8必要时候使用utf8mb4
解读
1通用无乱码风险汉字3字节英文1字节
2utf8mb4是utf8的超集有存储4字节例如表情符号时使用它
2、禁止使用存储过程视图触发器Event
解读
1对数据库性能影响较大互联网业务能让站点层和服务层干的事情不要交到数据库层
2调试排错迁移都比较困难扩展性较差
3、禁止在数据库中存储大文件例如照片可以将大文件存储在对象存储系统数据库中存储路径
4、禁止在线上环境做数据库压力测试
5、测试开发线上数据库环境必须隔离
二命名规范
1、库名表名列名必须用小写采用下划线分隔
解读abcAbcABC都是给自己埋坑
2、库名表名列名必须见名知义长度不要超过32字符
解读tmpwushan谁知道这些库是干嘛的
3、库备份必须以bak为前缀以日期为后缀
4、从库必须以-s为后缀
5、备库必须以-ss为后缀
三表设计规范
1、单实例表个数必须控制在2000个以内
2、单表分表个数必须控制在1024个以内
3、表必须有主键推荐使用UNSIGNED整数为主键
潜在坑删除无主键的表如果是row模式的主从架构从库会挂住
4、禁止使用外键如果要保证完整性应由应用程式实现
解读外键使得表之间相互耦合影响update/delete等SQL性能有可能造成死锁高并发情况下容易成为数据库瓶颈
5、建议将大字段访问频度低的字段拆分到单独的表中存储分离冷热数据
四列设计规范
1、根据业务区分使用tinyint/int/bigint分别会占用1/4/8字节
2、根据业务区分使用char/varchar
解读
1字段长度固定或者长度近似的业务场景适合使用char能够减少碎片查询性能高
2字段长度相差较大或者更新较少的业务场景适合使用varchar能够减少空间
3、根据业务区分使用datetime/timestamp
解读前者占用5个字节后者占用4个字节存储年使用YEAR存储日期使用DATE存储时间使用datetime
4、必须把字段定义为NOT NULL并设默认值
解读
1NULL的列使用索引索引统计值都更加复杂MySQL更难优化
2NULL需要更多的存储空间
3NULL只能采用IS NULL或者IS NOT NULL而在/!/in/not in时有大坑
5、使用INT UNSIGNED存储IPv4不要用char(15)
6、使用varchar(20)存储手机号不要使用整数
解读
1牵扯到国家代号可能出现/-/()等字符例如86
2手机号不会用来做数学运算
3varchar可以模糊查询例如like ‘138%’
7、使用TINYINT来代替ENUM
解读ENUM增加新值要进行DDL操作
五索引规范
1、唯一索引使用uniq_[字段名]来命名
2、非唯一索引使用idx_[字段名]来命名
3、单张表索引数量建议控制在5个以内
解读
1互联网高并发业务太多索引会影响写性能
2生成执行计划时如果索引太多会降低性能并可能导致MySQL选择不到最优索引
3异常复杂的查询需求可以选择ES等更为适合的方式存储
4、组合索引字段数不建议超过5个
解读如果5个字段还不能极大缩小row范围八成是设计有问题
5、不建议在频繁更新的字段上建立索引
6、非必要不要进行JOIN查询如果要进行JOIN查询被JOIN的字段必须类型相同并建立索引
解读踩过因为JOIN字段类型不一致而导致全表扫描的坑么
7、理解组合索引最左前缀原则避免重复建设索引如果建立了(a,b,c)相当于建立了(a), (a,b), (a,b,c)
六SQL规范
1、禁止使用select *只获取必要字段
解读
1select *会增加cpu/io/内存/带宽的消耗
2指定字段能有效利用索引覆盖
3指定字段查询在表结构变更时能保证对应用程序无影响
2、insert必须指定字段禁止使用insert into T values()
解读指定字段插入在表结构变更时能保证对应用程序无影响
3、隐式类型转换会使索引失效导致全表扫描
4、禁止在where条件列使用函数或者表达式
解读导致不能命中索引全表扫描
5、禁止负向查询以及%开头的模糊查询
解读导致不能命中索引全表扫描
6、禁止大表JOIN和子查询
7、同一个字段上的OR必须改写问ININ的值必须少于50个
8、应用程序必须捕获SQL异常
解读方便定位线上问题
说明本规范适用于并发量大数据量大的典型互联网业务可直接参考。
14、有没有设计过数据表?你是如何设计的 难度系数⭐ 第一范式 每一列属性(字段)不可分割的,字段必须保证原子性 两列的属性值相近或者一样的,尽量合并到一列或者分表,确保数据不冗余 第二范式 每一行的数据只能与其中一行有关 即 主键 一行数据只能做一件事情或者表达一个意思, 只要数据出现重复,就要进行表的拆分 第三范式 数据不能存在传递关系,每个属性都跟主键有直接关联而不是间接关联 15、常见面试SQL 难度系数⭐
例1
用一条SQL语句查询出每门课都大于80分的学生姓名
name kecheng fenshu 张三 语文 81 张三 数学 75 李四 语文 76 李四 数学 90 王五 语文 81 王五 数学 100 王五 英语 90答1
select distinct name from table where name not in (select distinct name from table where fenshu80) 答2
select name from table group by name having min(fenshu)80
例2
学生表 如下: 自动编号 学号 姓名 课程编号 课程名称 分数 1 2005001 张三 0001 数学 69 2 2005002 李四 0001 数学 89 3 2005001 张三 0001 数学 69 删除除了自动编号不同其他都相同的学生冗余信息答
delete tablename where 自动编号 not in(select min(自动编号) from tablename group by学号, 姓名, 课程编号, 课程名称, 分数)
例3
一个叫team的表里面只有一个字段name,一共有4条纪录分别是a,b,c,d,对应四个球队现在四个球队进行比赛用一条sql语句显示所有可能的比赛组合.
答
select a.name, b.name from team a, team b where a.name b.name 例4
怎么把这样一个表 year month amount 1991 1 1.1 1991 2 1.2 1991 3 1.3 1991 4 1.4 1992 1 2.1 1992 2 2.2 1992 3 2.3 1992 4 2.4 查成这样一个结果 year m1 m2 m3 m4 1991 1.1 1.2 1.3 1.4 1992 2.1 2.2 2.3 2.4 答
select year, (select amount from aaa m where month1 and m.yearaaa.year) as m1, (select amount from aaa m where month2 and m.yearaaa.year) as m2, (select amount from aaa m where month3 and m.yearaaa.year) as m3, (select amount from aaa m where month4 and m.yearaaa.year) as m4 from aaa group by year
例5
说明复制表(只复制结构,源表名a新表名b)
答 SQL:
select * into b from a where 11 (where11拷贝表结构和数据内容)
ORACLE: create table b As Select * from a where 12 [不等于(SQL Server Compact)
比较两个表达式。 当使用此运算符比较非空表达式时如果左操作数不等于右操作数则结果为 TRUE。 否则结果为 FALSE。] 例6
原表 courseid coursename score 1 java 70 2 oracle 90 3 xml 40 4 jsp 30 5 servlet 80
为了便于阅读,查询此表后的结果显式如下(及格分数为60): courseid coursename score mark 1 java 70 pass 2 oracle 90 pass 3 xml 40 fail 4 jsp 30 fail 5 servlet 80 pass 写出此查询语句
答
select courseid, coursename ,score ,if(score60, pass,fail) as mark from course
例7
表名购物信息
购物人 商品名称 数量
A 甲 2
B 乙 4
C 丙 1
A 丁 2
B 丙 5
给出所有购入商品为两种或两种以上的购物人记录
答
select * from 购物信息 where 购物人 in (select 购物人 from 购物信息 group by 购物人 having count(*) 2); 例8
info 表
date result
2005-05-09 win
2005-05-09 lose
2005-05-09 lose
2005-05-09 lose
2005-05-10 win
2005-05-10 lose
2005-05-10 lose
如果要生成下列结果, 该如何写sql语句?
date win lose
2005-05-09 2 2
2005-05-10 1 2
答1
select date, sum(case when result win then 1 else 0 end) as win, sum(case when result lose then 1 else 0 end) as lose from info group by date;
答2
select a.date, a.result as win, b.result as lose from (select date, count(result) as result from info where result win group by date) as a join (select date, count(result) as result from info where result lose group by date) as b on a.date b.date; 例9 mysql 创建了一个联合索引(a,b,c) 以下 索引生效 的是(1,2,4
1、where a 1 and b 1 and c 1
2、where a 1 and c 1
3、where b 1 and c 1,
4、where b 1 and a 1 and c 1 第五章-Redis
1、介绍下Redis Redis有哪些数据类型 难度系数⭐
Redis全称Remote Dictionary Server本质上是一个Key-Value类型的内存数据库整个数据库统统加载在内存当中进行操作定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作Redis的性能非常出色每秒可以处理超过 10万次读写操作是已知性能最快的Key-Value DB。 Redis的出色之处不仅仅是性能Redis最大的魅力是支持保存多种数据结构此外单个value的最大限制是1GB不像 memcached只能保存1MB的数据因此Redis可以用来实现很多有用的功能比方说用他的List来做FIFO双向链表实现一个轻量级的高性 能消息队列服务用他的Set可以做高性能的tag系统等等。另外Redis也可以对存入的Key-Value设置expire时间因此也可以被当作一 个功能加强版的memcached来用。 Redis的主要缺点是数据库容量受到物理内存的限制不能用作海量数据的高性能读写因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
常用基本数据类型如下 string 字符串一个字符串类型最大存储容量为512M list 可以重复的集合 set 不可以重复的集合 hash 类似于MapString,String zset(sorted set 带分数的set 2、Redis提供了哪几种持久化方式 难度系数⭐
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储。
AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
如果你只希望你的数据在服务器运行的时候存在你也可以不使用任何持久化方式。
你也可以同时开启两种持久化方式在这种情况下当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
1RDB持久化
每隔一段时间将内存中的数据集写到磁盘
Redis会单独创建fork一个子进程来进行持久化会先将数据写入到个临时文件中待持久化过程都结束了再用这个临时文件替换上次持久化好的文件。整个过程中主进程是不进行任何IO操作的这就确保了极高的性能如果需要进行大规模数据的恢复且对于数据恢复的完整性不是非常敏感那RDB方式要比AOF方式更加的高效。
保存策略
save 9 00 1 900 秒内如果至少有 1 个 key 的值变化则保存
save 300 10 300 秒内如果至少有 10 个 key 的值变化则保存
save 60 1 0000 60 秒内如果至10000 个 key 的值变化则保存
2AOF 持久化: 以日志形式记录每个更新(总结、改操作
Redis重新启动时读取这个文件重新执行新建、修改数据的命令恢复数据。
保存策略
appendfsync always每次产生一条新的修改数据的命令都执行保存操作效率低但是安全
appendfsync everysec每秒执行一次保存操作。如果在未保存当前秒内操作时发生了断电仍然会导致一部分数据丢失即1秒钟的数据。
appendfsync no从不保存将数据交给操作系统来处理。更快也更不安全的选择。
推荐并且也是默认的措施为每秒 fsync 一次 这种 fsync 策略可以兼顾速度和安全性。
缺点
1 比起RDB占用更多的磁盘空间
2 恢复备份速度要慢
3 每次读写都同步的话有一定的性能压力
4 存在个别Bug造成恢复不能
3选择策略
可读的日志文本通过操作AOF
官方推荐
如果对数据不敏感可以选单独用RDB不建议单独用AOF因为可能出现Bug;如果只是做纯内存缓存可以都不用
3、Redis为什么快 难度系数⭐
1)完全基于内存绝大部分请求是纯粹的内存操作非常快速。数据存在内存中类似于HashMapHashMap的优势就是查找和操作的时间复杂度都是O(1)
2)数据结构简单对数据操作也简单Redis中的数据结构是专门进行设计的
3)采用单线程避免了不必要的上下文切换和竞争条件也不存在多进程或者多线程导致的切换而消耗 CPU不用去考虑各种锁的问题不存在加锁释放锁操作没有因为可能出现死锁而导致的性能消耗
4)使用I/O多路复用模型非阻塞IO
IO输入/输出
多路多个输入/输出通道socket
复用通过一种机制同时管理多个I/O操作
I/O多路复用是一种操作IO的技术我们可以理解采用单线程同时管理多个Socket
多种I/O多路复用技术select、poll、epoll
Redis在linux使用epoll机制 I/O 多路复用体现在计算机通信的操作系统内核层具体来说是在处理网络通信的过程中。操作系统内核层使用 I/O 多路复用技术来同时监视和管理多个套接字Socket的状态以实现高效的并发通信。以下是一些关键环节 接收数据和分发当有多个客户端连接到服务器时每个连接都需要进行数据的接收。操作系统内核使用 I/O 多路复用来同时监听多个套接字的读取事件一旦某个套接字有数据可读内核会通知应用程序并将数据从内核缓冲区复制到应用程序的内存中。 发送数据和缓冲类似地内核使用 I/O 多路复用来监视套接字的写入事件以确保可以高效地将数据从应用程序发送到套接字。当套接字可写时内核会将应用程序提供的数据从内存缓冲区复制到内核缓冲区然后通过网络传输。 连接管理在服务器端当有新的客户端连接请求时操作系统内核可以使用 I/O 多路复用来监听连接事件以便及时接受新的连接并进行处理。 多客户端并发处理I/O 多路复用还可以帮助服务器同时处理多个客户端连接的读写操作而不需要为每个连接创建独立的线程或进程。这有助于提高服务器的性能和并发处理能力。 5)使用底层模型不同它们之间底层实现方式以及与客户端之间通信的应用协议不一样Redis直接自己构建了VM 机制 因为一般的系统调用系统函数的话会浪费一定的时间去移动和请求
4、Redis为什么是单线程的 难度系数⭐
官方FAQ表示因为Redis是基于内存的操作CPU不是Redis的瓶颈Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现而且CPU不会成为瓶颈那就顺理成章地采用单线程的方案了Redis利用队列技术将并发访问变为串行访问
1绝大部分请求是纯粹的内存操作
2采用单线程,避免了不必要的上下文切换和竞争条件
5、Redis服务器的的内存是多大 难度系数⭐
配置文件中设置redis内存的参数。
该参数如果不设置或者设置为0则redis默认的内存大小为
32位下默认是3G
64位下不受限制
一般推荐Redis设置内存为最大物理内存的四分之三也就是0.75
命令行设置config set maxmemory 内存大小单位字节服务器重启失效
config get maxmemory获取当前内存大小
永久则需要设置maxmemory参数maxmemory是bytes字节类型注意转换
6、为什么Redis的操作是原子性的怎么保证原子性的 难度系数⭐
对于Redis而言命令的原子性指的是一个操作的不可以再分操作要么执行要么不执行。
Redis的操作之所以是原子性的是因为Redis是单线程的。
Redis本身提供的所有API都是原子操作Redis中的事务其实是要保证批量操作的原子性。
多个命令在并发中也是原子性的吗
不一定 将get和set改成单命令操作incr 。使用Redis的事务或者使用RedisLua的方式实现.
7、Redis有事务吗 难度系数⭐
Redis是有事务的redis中的事务是一组命令的集合这组命令要么都执行要不都不执行
redis事务的实现需要用到MULTI事务的开始和EXEC事务的结束命令 ; 当输入MULTI命令后服务器返回OK表示事务开始成功然后依次输入需要在本次事务中执行的所有命令每次输入一个命令服务器并不会马上执行而是返回”QUEUED”这表示命令已经被服务器接受并且暂时保存起来最后输入EXEC命令后本次事务中的所有命令才会被依次执行可以看到最后服务器一次性返回了两个OK这里返回的结果与发送的命令是按顺序一一对应的这说明这次事务中的命令全都执行成功了。
Redis的事务除了保证所有命令要不全部执行要不全部不执行外还能保证一个事务中的命令依次执行而不被其他命令插入。同时redis的事务是不支持回滚操作的。
8、Redis数据和MySQL数据库的一致性如何实现 难度系数⭐⭐
一、 延时双删策略 在写库前后都进行redis.del(key)操作并且设定合理的超时时间。具体步骤是 1先删除缓存 2再写数据库 3休眠500毫秒根据具体的业务时间来定 4再次删除缓存。 那么这个500毫秒怎么确定的具体该休眠多久呢 需要评估自己的项目的读数据业务逻辑的耗时。这么做的目的就是确保读请求结束写请求可以删除读请求造成的缓存脏数据。 当然这种策略还要考虑 redis 和数据库主从同步的耗时。最后的写数据的休眠时间则在读数据业务逻辑的耗时的基础上加上几百ms即可。比如休眠1秒。
二、设置缓存的过期时间 从理论上来说给缓存设置过期时间是保证最终一致性的解决方案。所有的写操作以数据库为准只要到达缓存过期时间则后面的读请求自然会从数据库中读取新值然后回填缓存 结合双删策略缓存超时设置这样最差的情况就是在超时时间内数据存在不一致而且又增加了写请求的耗时。
三、如何写完数据库后再次删除缓存成功 上述的方案有一个缺点那就是操作完数据库后由于种种原因删除缓存失败这时可能就会出现数据不一致的情况。这里我们需要提供一个保障重试的方案。
1、方案一具体流程 1更新数据库数据 2缓存因为种种问题删除失败 3将需要删除的key发送至消息队列 4自己消费消息获得需要删除的key 5继续重试删除操作直到成功。 然而该方案有一个缺点对业务线代码造成大量的侵入。于是有了方案二在方案二中启动一个订阅程序去订阅数据库的binlog获得需要操作的数据。在应用程序中另起一段程序获得这个订阅程序传来的信息进行删除缓存操作。
2、方案二具体流程 1更新数据库数据 2数据库会将操作信息写入binlog日志当中 3订阅程序提取出所需要的数据以及key 4另起一段非业务代码获得该信息 5尝试删除缓存操作发现删除失败 6将这些信息发送至消息队列 7重新从消息队列中获得该数据重试操作。
9、缓存击穿缓存穿透缓存雪崩的原因和解决方案(或者说使用缓存的过程中有没有遇到什么问题怎么解决的 难度系数⭐
1. 缓存穿透
是指查询一个不存在的数据由于缓存无法命中将去查询数据库但是数据库也无此记录并且出于容错考虑我们没有将这次查询的null写入缓存这将导致这个不存在的数据每次请求都要到存储层去查询失去了缓存的意义。在流量大时可能DB就挂掉了要是有人利用不存在的key频繁攻击我们的应用这就是漏洞。
解决方案空结果也进行缓存可以设置一个空对象但它的过期时间会很短最长不超过五分钟。 或者用布隆过滤器也可以解决Redisson框架中有布隆过滤器。
2. 缓存雪崩
是指在我们设置缓存时采用了相同的过期时间导致缓存在某一时刻同时失效请求全部转发到DBDB瞬时压力过重雪崩。
解决方案原有的失效时间基础上增加一个随机值比如1-5分钟随机这样每一个缓存的过期时间的重复率就会降低就很难引发集体失效的事件。
3. 缓存击穿
是指对于一些设置了过期时间的key如果这些key可能会在某些时间点被超高并发地访问是一种非常“热点”的数据。这个时候需要考虑一个问题如果这个key在大量请求同时进来之前正好失效那么所有对这个key的数据查询都落到DB我们称为缓存击穿。
解决方案在分布式的环境下应使用分布式锁来解决分布式锁的实现方案有多种比如使用Redis的setnx、使用Zookeeper的临时顺序节点等来实现
10、哨兵模式是什么样的 难度系数⭐⭐
如果Master异常则会进行Master-Slave切换将其中一Slae作为Master将之前的Master作为Slave
下线
①主观下线Subjectively Down简称 SDOWN指的是当前 Sentinel 实例对某个redis服务器做出的下线判断。
②客观下线Objectively Down 简称 ODOWN指的是多个 Sentinel 实例在对Master Server做出 SDOWN 判断并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后得出的Master Server下线判断然后开启failover.
工作原理
1每个Sentinel以每秒钟一次的频率向它所知的MasterSlave以及其他 Sentinel 实例发送一个 PING 命令
2如果一个实例instance距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值 则这个实例会被 Sentinel 标记为主观下线
3如果一个Master被标记为主观下线则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态
4当有足够数量的 Sentinel大于等于配置文件指定的值在指定的时间范围内确认Master的确进入了主观下线状态 则Master会被标记为客观下线
5在一般情况下 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有MasterSlave发送 INFO 命令
6当Master被 Sentinel 标记为客观下线时Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次
7若没有足够数量的 Sentinel 同意 Master 已经下线 Master 的客观下线状态就会被移除
若 Master 重新向 Sentinel 的 PING 命令返回有效回复 Master 的主观下线状态就会被移除 11、Redis常见性能问题和解决方案 难度系数⭐
(1) Master最好不要做任何持久化工作如RDB内存快照和AOF日志文件
(2) 如果数据比较重要某个Slave开启AOF备份数据策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性Master和Slave最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5) 主从复制不要用图状结构用单向链表结构更为稳定即Master - Slave1 - Slave2 - Slave3...
这样的结构方便解决单点故障问题实现Slave对Master的替换。如果Master挂了可以立刻启用Slave1做Master其他不变。
12、MySQL里有大量数据如何保证Redis中的数据都是热点数据 难度系数⭐⭐
Redis内存淘汰策略
redis内存数据集大小上升到一定大小的时候就会施行数据淘汰策略。
数据淘汰策略
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令大部分的写入指令但DEL和几个例外
allkeys-lru: 尝试回收最少使用的键LRU使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键LRU但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键并且优先回收存活时间TTL较短的键,使得新添加的数据有空间存放。
13、Redis集群方案应该怎么做 都有哪些方案 难度系数⭐⭐
1twemproxy大概概念是它类似于一个代理方式使用方法和普通redis无任何区别设置好它下属的多个redis实例后使用时在本需要连接redis的地方改为连接twemproxy它会以一个代理的身份接收请求并使用一致性hash算法将请求转接到具体redis将结果再返回twemproxy。使用方式简便(相对redis只需修改连接端口)对旧项目扩展的首选。 问题twemproxy自身单端口实例的压力使用一致性hash后对redis节点数量改变时候的计算值的改变数据无法自动移动到新的节点。
2codis目前用的最多的集群方案基本和twemproxy一致的效果但它支持在 节点数量改变情况下旧节点数据可恢复到新hash节点。
3redis cluster3.0自带的集群特点在于他的分布式算法不是一致性hash而是hash槽的概念以及自身支持节点设置从节点。具体看官方文档介绍。
4在业务代码层实现起几个毫无关联的redis实例在代码层对key 进行hash计算然后去对应的redis实例操作数据。 这种方式对hash层代码要求比较高考虑部分包括节点失效后的替代算法方案数据震荡后的自动脚本恢复实例的监控等等。
14、说说Redis哈希槽的概念 难度系数⭐⭐
Redis集群没有使用一致性hash,而是引入了哈希槽的概念Redis集群有16384个哈希槽每个key通过CRC16校验后对16384取模来决定放置哪个槽集群的每个节点负责一部分hash槽。 15、Redis有哪些适合的场景 难度系数⭐
1会话缓存Session Cache
最常用的一种使用Redis的情景是会话缓存session cache。用Redis缓存会话比其他存储如Memcached的优势在于Redis提供持久化。当维护一个不是严格要求一致性的缓存时如果用户的购物车信息全部丢失大部分人都会不高兴的现在他们还会这样吗
幸运的是随着 Redis 这些年的改进很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。
2全页缓存FPC
除基本的会话token之外Redis还提供很简便的FPC平台。回到一致性问题即使重启了Redis实例因为有磁盘的持久化用户也不会看到页面加载速度的下降这是一个极大改进类似PHP本地FPC。
再次以Magento为例Magento提供一个插件来使用Redis作为全页缓存后端。
此外对WordPress的用户来说Pantheon有一个非常好的插件 wp-redis这个插件能帮助你以最快速度加载你曾浏览过的页面。
3队列
Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作就类似于本地程序语言如Python对 list 的 push/pop 操作。
如果你快速的在Google中搜索“Redis queues”你马上就能找到大量的开源项目这些项目的目的就是利用Redis创建非常好的后端工具以满足各种队列需求。例如Celery有一个后台就是使用Redis作为broker你可以从这里去查看。
4排行榜/计数器
Redis在内存中对数字进行递增或递减的操作实现的非常好。集合Set和有序集合Sorted Set也使得我们在执行这些操作的时候变的非常简单Redis只是正好提供了这两种数据结构。所以我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”我们只需要像下面一样执行即可
当然这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数你需要这样执行
ZRANGE user_scores 0 10 WITHSCORES
Agora Games就是一个很好的例子用Ruby实现的它的排行榜就是使用Redis来存储数据的你可以在这里看到。
5发布/订阅
最后但肯定不是最不重要的是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用还可作为基于发布/订阅的脚本触发器甚至用Redis的发布/订阅功能来建立聊天系统不这是真的你可以去核实。
16、Redis在项目中的应用 难度系数⭐
Redis一般来说在项目中有几方面的应用
1. 作为缓存将热点数据进行缓存减少和数据库的交互提高系统的效率
2. 作为分布式锁的解决方案解决缓存击穿等问题
3. 作为消息队列使用Redis的发布订阅功能进行消息的发布和订阅
具体的使用场景要结合项目去说比如说项目中有哪些场景用到Redis来作为缓存以及分布式锁等等。
第六章-分布式技术篇
第七章-Git
1、工作中git开发使用流程命令版描述
开发一个新功能流程: master线上分支dev测试分支
git clone 注释1
git checkout -b product 新建一个product 分支并且切换到product 分支
git add ./ 提交开发需求到暂存区域
git commit -m 开发商品模块
git push origin pengyu
git co test //切换到test分支
git merge pengyu //带你开发的业务代码合并到test分支
git push origin test //带你开发的业务代码推送到远端的test分支 2、Reset 与Rebase,Pull 与 Fetch 的区别
git reset 不修改commit相关的东西只会去修改.git目录下的东西。
git rebase 会试图修改你已经commit的东西比如覆盖commit的历史等但是不能使用rebase来修改已经push过的内容容易出现兼容性问题。rebase还可以来解决内容的冲突解决两个人修改了同一份内容然后失败的问题。
git pull pullfetchmerge,
使用git fetch是取回远端更新不会对本地执行merge操作不会去动你的本地的内容。 pull会更新你本地代码到服务器上对应分支的最新版本 3、git merge和git rebase的区别
git merge把本地代码和已经取得的远程仓库代码合并。
git rebase是复位基底的意思gitmerge会生成一个新的节点之前的提交会分开显示而rebase操作不会生成新的操作将两个分支融合成一个线性的提交。
4、git如何解决代码冲突
第一种
git stash
git pull
git stash pop
这个操作就是把自己修改的代码隐藏然后把远程仓库的代码拉下来然后把自己隐藏的修改的代码释放出来让gie自动合并。
如果要代码库的文件完全覆盖本地版本。
git reset –hard
git pull
第二种通过开发工具 idea进行merge代码合并
5、项目开发时git分支情况
主干分支master主要负责管理正在运行的生产环境代码。永远保持与正在运行的生产环境完全一致。
开发分支develop主要负责管理正在开发过程中的代码。一般情况下应该是最新的代码。
bug修理分支hotfix要负责管理生产环境下出现的紧急修复的代码。 从主干分支分出修理完毕并测试上线后并回主干分支。并回后视情况可以删除该分支。
发布版本分支release较大的版本上线前会从开发分支中分出发布版本分支进行最后阶段的集成测试。该版本上线后会合并到主干分支。生产环境运行一段阶段较稳定后可以视情况删除。
功能分支feature为了不影响较短周期的开发工作一般把中长期开发模块会从开发分支中独立出来。 开发完成后会合并到开发分支。
[注释1] 克隆远端仓库代码到本地
第八章-Linux
1、Linux常用命令 序号 命令 命令解释 1 top 查看内存 2 df -h 查看磁盘存储情况 3 iotop 查看磁盘IO读写(yum install iotop安装 4 iotop -o 直接查看比较高的磁盘读写程序 5 netstat -tunlp | grep 端口号 查看端口占用情况 6 uptime 查看报告系统运行时长及平均负载 7 ps aux 查看进程 2、如何查看测试项目的日志
一般测试的项目里面有个logs的目录文件会存放日志文件有个xxx.out的文件可以用tail -f 动态实时查看后端日志
先cd 到logs目录(里面有xx.out文件)
tail -f xx.out
这时屏幕上会动态实时显示当前的日志ctrc停止
3、如何查看最近1000行日志
tail -1000 xx.out
4、Linux中如何查看某个端口是否被占用
netstat -anp | grep 端口号 图中主要看监控状态为LISTEN表示已经被占用最后一列显示被服务mysqld占用查看具体端口号只要有如图这一行就表示被占用了 查看82端口的使用情况如图
netstat -anp |grep 82 可以看出并没有LISTEN那一行所以就表示没有被占用。此处注意图中显示的LISTENING并不表示端口被占用不要和LISTEN混淆哦查看具体端口时候必须要看到tcp端口号LISTEN那一行才表示端口被占用了 5、查看当前所有已经使用的端口情况
如图
第九章-电商项目篇之尚品汇商城
1、介绍下最近做的项目
可以从2个方向出发
介绍项目背景、项目功能和自己负责的功能模块
介绍项目背景、项目使用技术栈和自己负责的功能模块
1.1 项目背景
可以介绍项目是什么类型的B2C、B2B2C、O2O这类为什么要做这个项目
有工作经验的找工作一般这样介绍项目是自己公司开发自己运营的然后不断加功能进行迭代和维护或者是项目定制的给甲方客户开发的一个项目上线后不负责维护和迭代这样避免了很多后期问题这两种都可以。
咱们可以借鉴以上的方式去介绍刨除公司情况以学习为主。
1.2 项目功能
结合项目进行主要的功能模块阐述可以结合电商项目的核心购物流程去说后台管理系统商品的管理、商品详情、商品搜索、购物车、单点登录社交登录、订单、支付、秒杀等等。
1.3 技术栈
使用springboot整合SpringCloud 以及MyBatis-Plus进行微服务构建使用nacos作为注册中心和配置中心使用feign进行服务远程调用使用gateway网关进行请求负载、请求过滤、统一鉴权和限流使用Sentinel进行服务的熔断和降级使用Spring Cloud Sleuth进行链路追踪针对于项目图片文件资源较多采用FastDFS进行文件资源存储使用redis数据库进行数据缓存以及分布式锁的实现使用ElasticSearch进行商品的搜索业务实现…..这块基础架构说完后主要结合自己负责的功能模块去说技术点的应用
1.4 自己负责的功能模块 已简历为主简历上写了哪几个就说那几个一定要知道自己简历写的什么内容。
1.5 项目介绍参考
尚品汇商城是B2C模式的综合性在线销售平台。商城分为后台管理部分与用户前台使用部分。后台管理部分包括商品管理模块商品分类、品牌、平台属性、SPU与SKU以及销售属性、商品上下架和商品评论管理等、内容广告模块、库存管理模块、订单管理模块、促销管理秒杀等商品设置、客户模块、统计报表模块和系统基础权限等模块。
用户前台使用部分商城首页、商品搜索可按条件查询展示、商品详情信息展示、购物车、用户单点登录和社交登录微信登录、用户会员中心、订单的创建修改、展示以及在线支付支付宝、微信、物流模块、商品评论以及秒杀活动等功能。
1.6 项目架构图 1.7 整体业务介绍 首页 静态页面包含了商品分类搜索栏商品广告位。 全文搜索 通过搜索栏填入的关键字进行搜索并列表展示 分类查询 根据首页的商品类目进行查询 商品详情 商品的详细信息展示 购物车 将有购买意向的商品临时存放的地方 单点登录 用户统一登录的管理 结算 将购物车中勾选的商品初始化成要填写的订单 下单 填好的订单提交 支付服务 下单后用户点击支付负责对接第三方支付系统。 订单服务 负责确认订单是否付款成功并对接仓储物流系统。 仓储物流 独立的管理系统负责商品的库存。 后台管理 主要维护类目、商品、库存单元、广告位等信息。 秒杀 秒杀抢购完整方案 1.8 后台管理系统功能
电子商务网站整个系统的后端管理按功能划分为九大模块包括商品组织管理、订单处理、内容发布管理等模块。
1.8.1 后台主页
各类主要信息的概要统计包括客户信息、 订单信息、商品信息、库存信息、评论和最近反馈等。
1.8.2 商品模块
1).商品管理
商品SPU和SKU的添加、修改、 删除、复制、批处理、商品计划上下架、SEO、商品多媒体上传等可以定义商品是实体还是虚拟可以定义是否预订、是否缺货销售等。
2).商品分类管理
树形的商品目录组织管理并可以设置品类关联与商品推荐。
3).商品平台属性管理
定义商品的属性类型设置自定义属性项。
4).品牌管理
添加、修改、删除、上传品牌 LOGO。
5).商品评论管理
商品评论的搜索、条件查询列表展示、回复、删除等功能。
1.8.3 销售模块
1).促销秒杀管理
设置秒杀商品、购物车促销和 优惠券促销三类可以随意定义不同的促销规则满足日常促销活动购物折扣、购物赠送积分、购物赠送优惠券、购物免运输费、特价商品、特定会员购买特定商品、折上折、买二送一等。
2).礼券、积分管理
比如添加、发送礼券和积分
3).关联/推荐管理
基于规则引擎可以支持多种推荐类型可手工添加或者自动评估商品。
1.8.4 订单模块
1).订单管理
可以编辑、解锁、取消订单、 拆分订单、添加商品、移除商品、确认可备货等也可对因促销规则发生变化引起的价格变化进行调整。订单处理完可发起退货、换货流程。
2).支付
常用于订单支付信息的查看和手工 支付两种功能。手工支付订单常用于“款到发货”类型的订单可理解为对款到发货这类订单的一种补登行为。
3).结算
提供商家与第三方物流公司的结算 功能通常是月结。同时结算功能也是常用来对“货到付款”这一类型订单支付后的数据进行对帐
1.8.5 库存模块
1).库存管理
引入库存的概念不包括销售规则为永远可售的商品一个SKU对应一个库存量。库存管理提供增加、减少等调整库存量的功能;另外也可对具具体的SKU设置商品的保留数量、最小库存量、再进货数量。每条SKU商品的具体库存操作都会记录在库存明细记录里边。
2).查看库存明细记录。
3).备货/发货
创建备货单、打印备货单、打印发货单、打印快递单、完成发货等一系列物流配送的操作。
4).退/换货
对退/换货的订单进行收货流程的处理。
1.8.6 内容模块
1).内容管理
包括内容管理以及内容目录管理。内容目录由树形结构组织管理。类似于商品目录的树形结构可设置目录是否为链接目录。
2).广告管理
添加、修改、删除、上传广告、 定义广告有效时限。
3).可自由设置商城导航栏目以及栏目内容、栏目链接。
1.8.7 客户模块
1).客户管理
添加、删除、修改、重设密码、 发送邮件等。
2).反馈管理
删除、回复。
3).消息订阅管理
添加、删除、修改消息组 和消息、分配消息组、查看订阅人。
4).会员资格
添加、删除、修改。
1.8.8 系统模块
1).安全管理
管理员、角色权限分配和安全日志
2).系统属性管理
用于管理自定义属性。可关联模块包括商品管理、商品目录管理、内容管理、客户管理。
3).运输与区域
运输公司、运输方式、运输 地区。
4).支付管理
支付方式、支付历史。
5).包装管理
添加、修改、删除。
6).数据导入管理
商品目录导入、商品导入、 会员资料导入。
1.8.9 报表模块 缺省数个统计报表支持时间段过滤、支持按不同状态过滤、支持HTML、PDF和Excel格式的导出和打印。
1.用户注册统计 2.低库存汇总 3.缺货订单 4.订单汇总 5.退换货
2、项目开发周期
开发、维护和运营一体化的开发周期8个月左右后期维护与迭代时间会更长
项目定制前期架构数据库设计编码开发测试解bug共7个月左右进行项目交付
培训学习项目: 20天教程咱们是学习20天课程
3、项目参与人数
一般公司:项目经理PM1人、产品PD2人、界面设计UI2人、前端 3人、Java后台DE6人其中1人是开发组长、测试QA2人、运维SRE1人
培训学习项目根据课程内容编写代码自己实现部分功能
4、公司开发相关各岗位职责
4.1 项目经理PM
企业建立以项目经理责任制为核心对项目实行质量、安全、进度、成本管理的责任保证体系和全面提高项目管理水平设立的重要管理岗位。职责
1、负责软件项目管理及计划实施
2、具备较强管理、协调及沟通能力帮助开发人员解决开发过程中遇到的技术问题做好日常的开发团队管理工作
3、与各团队协同工作确保开发工作正常顺利的开展
4.2 产品PD
企业中专门负责产品管理的职位负责调查并根据用户的需求确定开发何种产品选择何种技术、商业模式等。并推动相应产品的开发组织他还要根据产品的生命周期协调研发、营销、运营等确定和组织实施相应的产品策略以及其他一系列相关的产品管理活动。职责 1. 根据公司产品及用户需求结合市场调研情况进行产品规划
2. 负责用户沟通、需求分析诊断
3. 负责产品定位、用户体验流程定位及产品设计
4. 推动、协调与控制产品策划及研发工作保证产品需求的有效实现
5. 负责产品持续升级不断提升用户满意度及忠诚度
6. 对行业及竞争产品的分析跟踪最新发展趋势并提交分析报告。
4.3 界面设计UI
对软件的人机交互、操作逻辑、界面美观的整体设计。职责
1、负责公司产品PC端和移动端的UI界面设计工作
2、配合完成校样修改和界面调整
3、深入了解负责的产品并通过各种设计形式和视觉语言让用户感受到产品的优点和特性
4、跟进设计的变化和需求注重相关文档的整理、资料的收集能独立完成界面设计工作。
4.4 开发组长TL
其实就是个更小一点的项目经理。其职责1、 参与软件的设计负责系统需求的分析进行系统设计和数据库设计
2、 解决开发过程中技术问题和提供解决办法
3、 能够带领小组负责模块的功能开发
4、 负责项目组代码的审查工作有效地控制项目的质量风险。
4.5 测试QA
测试工程师软件质量的把关者工作起点高发展空间大。职责 1.理解、分析需求文档挖掘、细化需求;
2.根据软件需求及设计文档编写测试用例参与文档评审并维护相关文档;
3.准备测试数据执行测试用例记录测试结果整理测试报告;
4.负责BUG的提交、跟踪、验证、关闭;
5.负责测试部门测试环境及BUG系统管理与维护。
6.对产品进行必要的功能性能安全兼容性及其它方面的测试工作
7.公司安排的其它工作。
4.6 运维SRE
运维工程师最基本的职责都是负责服务的稳定性。
1. 产品发布前负责参与并审核架构设计的合理性和可运维性以确保在产品发布之后能高效稳定的运行。
2. 产品发布阶段负责用自动化的技术或者平台确保产品可以高效的发布上线之后可以快速稳定迭代。
3. 产品运行维护阶段负责保障产品7*24H稳定运行在此期间对出现的各种问题可以快速定位并解决在日常工作中不断优化系统架构和部署的合理性以提升系统服务的稳定性。
5、项目开发流程:
5.1 需求分析 项目前期主要指的是项目业务需求调研、包括配合用户制定项目建设方案、技术规范书、配合市场人员进行售前技术交流等环节此阶段应该组织由售前工程师、需求分析师以及系项目经理等组成一个临时小组负责跟踪项目。这个小组根据项目的大小和客户的要求确定小组成员。
项目前期小组的工作是项目的开始这个小组工作成绩的优劣、工作质量的高低将直接影响项目的成败。因此从管理层的角度一定要重视这个环节。
项目前期小组需要完成的工作包括以下方面
1、 客户的各种项目前期要求如方案介绍、业务需求编写等
2、 提交项目可行性分析报告包括成本/效益分析
3、 提交项目建议方案
4、 提交业务需求说明书或需求分析说明书
5.2 系统设计 系统设计是决定项目或软件系统“怎样做”的过程这个过程回答了系统应该如何实现的问题。从软件工程的角度设计阶段大约是整个项目开发成本的25%所以设计团队以及该团队的工作成绩对于整个系统来说至关重要。
人员需求
设计团队一般由3—8名设计人员组成从这个阶段起项目需要一名项目经理行使项目组的各种管理职能。设计团队的成员具体包括
1名项目经理
包括1—2名项目前期成员
1名系统构架师
1名数据库设计人员
1名用户界面设计人员组成
设计团队需要完成的工作包括
1、项目开发计划
2、确定系统软硬件配置最佳方案
3、确定系统开发平台以及开发工具
4、确定系统软件结构
5、确定系统功能模块以及各个模块之间的关系
6、确定系统测试方案
7、提交系统数据库设计方案
8、提交系统概要设计文档
由于应用软件需求经常变化因此设计需要考虑系统可扩展性并需要在设计过程中对于重要的环节和用户进行及时沟通。
5.3 编码开发 将用户的需求变成真正可用的软件系统是通过编码和系统实现阶段来完成的。虽然软件的质量主要取决于系统设计的质量但是编码的途径和实现的具体方法对程序的可靠性、可读性、可测试性和可维护性产生深远的影响。
人员需求
这个阶段要根据用户对项目进度的要求灵活组织开发团队。为了工作的连贯性同时也为了解决在开发过程中用户需求有可能变化的因素开发团队应该保留1—3名设计团队的成员。
人员分工
开发过程中项目经理的角色非常重要项目经理负责项目组开发人员的日常管理控制项目的进度负责和设计部门、市场部门以及客户之间进行必要的沟通。这个阶段通常是多个部门的人员共同组成一个项目组因此项目管理的一定要保证统一管理理想状态是项目经理全权负责项目组人员的人员工作安排、业绩考核、工资奖金等因为项目经理最了解项目组成员的工作态度和工作业绩。
一般在大型项目开发团队中应该设立专门的技术经理岗位负责对项目组的技术方案进行管控技术经理最好是由设计团队中抽调出来。技术经理在项目开发过程中需要注意程序风格、编码规范等问题并必须进行有效的代码管理版本管理。
开发过程还应该进行系统的单元测试工作确保各个独立模块功能的正确性和性能满足需求说明书的要求。
开发团队应该完成的工作包括
1、 系统的实现代码编写
2、 单元测试
3、 提交源代码清单
4、 提交单元测试报告
5.4 系统测试 5.5 部署实施 由于从事的应用软件的开发因此在开发完成之后经常会有系统集成、软件的安装等工作。这个阶段还经常伴随着新的业务需求和本地化需求的产生因此将会有一部分的开发工作需要在这个阶段完成。
6、项目版本控制 使用git进行版本控制剩下的主要是对git仓库的一些列操作要记住操作命令以及分支切换等等。
7、一般项目服务器数量
开发测试阶段 开发在自己电脑上开发代码提交到git仓库gitlab、gitee等的开发分支到测试时公司有1-2台测试服务器把开发分支代码合并到测试分支使用jenkins进行测试环境代码部署测试人员进行测试。
生产环境 为了保证高可用首先每个服务都要进行 集群部署包括项目中依赖的第三方服务这样的话服务器的数量是很庞大的以尚品汇商城所学功能实现为例如下 Nginx2台主备Nacos 3台官方推荐最少3台项目中有服务数量*219*238共需要38台redis无中心化集群6台3主3从如果是主从哨兵集群5台mysql数据库读写分离的情况下1主2从 4*3 12共12台ES集群3台rabbitmq集群2台fastDFS集群保证高可用情况下traker2台storage2组4台共6台。 以上所述 服务器数量一共为72台数量很多以目前市场硬件服务器平均一台5万块钱来计算72*5360万投入成本和维护成本很大在项目前期没有大用户量的情况下不建议。 针对于这个问题如果是项目定制外包的话可以由甲方客户自己去部署这种情况也是存在的。 还有一种情况是在项目前期没有那么大的用户量情况下可以采用单机主备部署方案把所有服务部署同一台服务器上进行资源节省。
8、上线后QPS并发量用户量、同时在线人数并发数等问题
一般情况下,给客户定制的项目这些数据是拿不到的项目给客户做的甲方客户的运营数据是拿不到的。
但是要了解以下数据关键词
1QPSTPS每秒钟request/事务 数量
2并发数 系统同时处理的request/事务数
3响应时间 一般取平均响应时间
QPSTPS 并发数/平均响应时间或者并发数 QPS*平均响应时间 一个典型的上班签到系统早上8点上班7点半到8点的30分钟的时间里用户会登录签到系统进行签到。公司员工为1000人平均每个员上登录签到系统的时长为5分钟。可以用下面的方法计算。 QPS 1000/(30*60) 事务/秒 平均响应时间为 5*60 秒 并发数 QPS*平均响应时间 1000/(30*60) *(5*60)166.7
说明
一个系统吞吐量通常由QPSTPS、并发数两个因素决定每套系统这两个值都有一个相对极限值在应用场景访问压力下只要某一项达到系统最高值系统的吞吐量就上不去了如果压力继续增大系统的吞吐量反而会下降原因是系统超负荷工作上下文切换、内存等等其它消耗导致系统性能下降。
我们做项目要排计划可以多人同时并发做多项任务也可以一个人或者多个人串行工作始终会有一条关键路径这条路径就是项目的工期。系统一次调用的响应时间跟项目计划一样也有一条关键路径这个关键路径是就是系统影响时间 关键路径是有CPU运算、IO、外部系统响应等等组成。
如果非要说以下提供一套参数但是仅供参考可以根据自己设计适当调整
用户总量 几万日活 3000月活 12W一个月PV 30W并发量 500
9、你们项目的微服务是怎么拆分的拆分了多少
根据功能模块进行拆分有多少功能模块就基本上拆出来多少个服务。
10、如何解决并发问题的 尚品汇商城是微服务架构构建集群部署数据库的分库分表读写分离Redis缓存RabbitMQ消息异步解耦页面静态化等这些都是解决并发的手段。
开发层面微服务架构、缓存Redis、异步MQ、队排好限流和削峰
部署层面集群高可用和负载均衡----Nginx、Gateway、Feign
硬件层面CPU性能、硬盘SSD性能、内存大小
网络层面增加网络带宽、网络加速器
11、如何保证接口的幂等性
1. 根据状态机很多时候业务表是有状态的比如订单表中有1-下单、2-已支付、3-完成、4-撤销等状态。如果这些状态的值是有规律的按照业务节点正好是从小到大我们就能通过它来保证接口的幂等性。假如id123的订单状态是已支付现在要变成完成状态。update order set status3 where id123 and status2;第一次请求时该订单的状态是已支付值是2所以该update语句可以正常更新数据sql执行结果的影响行数是1订单状态变成了3。后面有相同的请求过来再执行相同的sql时由于订单状态变成了3再用status2作为条件无法查询出需要更新的数据所以最终sql执行结果的影响行数是0即不会真正的更新数据。但为了保证接口幂等性影响行数是0时接口也可以直接返回成功。
具体步骤
1 用户通过浏览器发起请求服务端收集数据。
2 根据id和当前状态作为条件更新成下一个状态
3 判断操作影响行数如果影响了1行说明当前操作成功可以进行其他数据操作。
4 如果影响了0行说明是重复请求直接返回成功。
主要特别注意的是该方案仅限于要更新的表有状态字段并且刚好要更新状态字段的这种特殊情况并非所有场景都适用。
2. 加分布式锁其实前面介绍过的加唯一索引或者加防重表本质是使用了数据库的分布式锁也属于分布式锁的一种。但由于数据库分布式锁的性能不太好我们可以改用redis或zookeeper。鉴于现在很多公司分布式配置中心改用apollo或nacos已经很少用zookeeper了我们以redis为例介绍分布式锁。目前主要有三种方式实现redis的分布式锁
1 setNx命令
2 set命令
3 Redission框架
具体步骤
1 用户通过浏览器发起请求服务端会收集数据并且生成订单号code作为唯一业务字段。
2 使用redis的set命令将该订单code设置到redis中同时设置超时时间。
3 判断是否设置成功如果设置成功说明是第一次请求则进行数据操作。
4 如果设置失败说明是重复请求则直接返回成功。
3. 获取token
除了上述方案之外还有最后一种使用token的方案。该方案跟之前的所有方案都有点不一样需要两次请求才能完成一次业务操作。
第一次请求获取token
第二次请求带着这个token完成业务操作。
具体步骤
1 用户访问页面时浏览器自动发起获取token请求。
2 服务端生成token保存到redis中然后返回给浏览器。
3 用户通过浏览器发起请求时携带该token。
4 在redis中查询该token是否存在如果不存在说明是第一次请求做则后续的数据操作。
5 如果存在说明是重复请求则直接返回成功。
6 在redis中token会在过期时间之后被自动删除。
12、你们项目中有没有用到什么设计模式
这个建议提前去了解几种常见的设计模式及其应用场景将其套用到项目的某个场景中以下提供几种常见的设计模式及其应用场景
1) 单例模式。
单例模式是一种常用的软件设计模式。
在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问从而方便对实例个数的控制并节约系统资源。
应用场景如果希望在系统中某个类的对象只能存在一个单例模式是最好的解决方案。
2) 工厂模式。
工厂模式主要是为创建对象提供了接口。
应用场景如下
a、 在编码时不能预见需要创建哪种类的实例。
b、 系统不应依赖于产品类实例如何被创建、组合和表达的细节。
3) 策略模式。
策略模式定义了算法族分别封装起来让它们之间可以互相替换。此模式让算法的变化独立于使用算法的客户。
应用场景如下。
a、 一件事情有很多方案可以实现。
b、我可以在任何时候决定采用哪一种实现。
c.、未来可能增加更多的方案。
d、 策略模式让方案的变化不会影响到使用方案的客户。
举例业务场景如下。
系统的操作都要有日志记录通常会把日志记录在数据库里面方便后续的管理但是在记录日志到数据库的时候可能会发生错误比如暂时连不上数据库了那就先记录在文件里面。日志写到数据库与文件中是两种算法但调用方不关心只负责写就是。
4) 观察者模式。
观察者模式又被称作发布/订阅模式定义了对象间一对多依赖当一个对象改变状态时它的所有依赖者都会收到通知并自动更新。
应用场景如下
a、对一个对象状态的更新需要其他对象同步更新而且其他对象的数量动态可变。
b、对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节。
5) 迭代器模式。
迭代器模式提供一种方法顺序访问一个聚合对象中各个元素而又不暴露该对象的内部表示。
应用场景如下
当你需要访问一个聚集对象而且不管这些对象是什么都需要遍 历的时候就应该考虑用迭代器模式。其实stl容器就是很好的迭代器模式的例子。
6) 模板方法模式。
模板方法模式定义一个操作中的算法的骨架将一些步骤延迟到子类中模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些步骤。
应用场景如下
对于一些功能在不同的对象身上展示不同的作用但是功能的框架是一样的。
13、生产环境出问题你们是怎么排查的
生产环境出问题一般由多方面引起可以由多方面进行排查可以借鉴以下链接文章进行参考面试的时候只需提出大概的思路即可
https://blog.csdn.net/GitChat/article/details/79019454
14、你做完这个项目后有什么收获
首先在数据库方面我现在是真正地体会到数据库的设计真的是一个程序或软件设计的重要和根基。因为数据库怎么设计直接影响到一个程序或软件的功能的实现方法、性能和维护。由于我做的模块是要对数据库的数据进行计算和操作的所以我对数据库的设计对程序的影响是深有体会就是因为我们的数据库设计得不好搞得我在对数据库中的数据进行获取和计算利润、总金时非常困难而且运行效率低时间和空间的复杂也高而且维护起来很困难过了不久即使自己有注释但是也要认真地看自己的代码才能明白自己当初的想法和做法。加上师兄的解说让我对数据库的重要的认识更深一层数据库的设计真的是重中之重。
其次就是分工的问题。虽然这次的项目我们没有在四人选出一个组长但是由于我跟其他人都比较熟也有他们的号码然后我就像一个小组长一样也是我对他们进行了分工。俗话也说分工合作分好了工才能合作。但是这次项目我们的分工却非常糟糕我们在分工之前分好了模块每个 责哪些模块。本以为我们的分工是明确的后来才发现我们的分工是那么的一踏糊涂一些功能上紧密相连的模块分给了两个人来完成使两个人都感到迷惘不知道自己要做什么因为两个人做的东西差不多。我做的他也在做那我是否要继续做下去总是有这样的疑问。从而导致了重复工作浪费时间和精力并打击了队员的激情因为自己辛辛苦苦写的代码最后可能没有派上用场。我也知道没有一点经验的我犯这样的错是在所难免我也不过多地怪责自己吸取这次的教训就好。分工也是一门学问。 再者就是命名规范的问题。可能我们以前都是自己一个人在写代码写的代码都是给自己看的所以我们都没有注意到这个问题。就像师兄说的那样我们的代码看上去很上难看很不舒服也不知道我们的变量是什么类型的也不知道是要来做什么的。但是我觉得我们这一组人的代码都写得比较好看每个人的代码都有注释和分隔就是没有一个统一的规范每个人都人自己的一个命名规则和习惯也不能见名知义。还有就是没有定义好一些公共的部分使每个人都有一个自己的“公共部分”从而在拼起来时第一件事就是改名字。而这些都应该是在项目一开始还没开始写代码时应该做的。 然后我自己在计算时竟然太大意算错了利润这不能只一句我不小心就敷衍过去也是我的责任而且这也是我们的项目的核心部分以后在做完一个模块后一定要测试多次不能过于随便地用一个数据测试一下能成功就算了要用可能出现的所有情况去测试程序让所有的代码都有运行过一次确认无误。
最后也是我比较喜欢的东西就是大家一起为了一个问题去讨论和去交流。因为我觉得无论是谁他能想的东西都是有限的别人总会想到一些自己想不到的地方。跟他人讨论和交流能知道别人的想法、了解别人是怎样想一个问题的对于同样的问题自己又是怎样想的是别人的想法好还是自己的想法好好在什么地方。因为我发现问题的能力比较欠缺所以我也总是喜欢别人问我问题也喜欢跟别人去讨论一个问题因为他们帮我发现了我自己没有发现的问题。在这次项目中我跟植荣的讨论就最多了很多时候都是不可开交的那种不过我觉得他总是能够想到很多我想不到的东西他想的东西也比我深入很多虽然很多时候我们好像闹得很僵但是我们还是很要好的! 嘻嘻而且在以后的学习和做项目的过程中我们遇到的问题可能会多很多复杂很多我们一个人也不能解决或者是没有想法但是懂得与他人讨论与交流就不怕这个问题总有人的想法会给我们带来一片新天地。相信我能做得更好。 还有就是做项目时要抓准客户的要求不要自以为是自己觉得这样好那样好就把客户的需求改变项目就是项目就要根据客户的要求来完成。
15、在做这个项目的时候你碰到了哪些问题你是怎么解决的
1开发SpringBoot接口出现客户端和服务端不同步导致接口无法测试产生的原因沟通不畅。
2订单提交时由于本地bug或者意外故障导致用户钱支付了但是订单不成功采用对账方式来解决。
3上线的时候一定要把支付的假接口换成真接口。
4项目中用到了曾经没有用过的技术解决方式用自己的私人时间主动学习
5在开发过程中与测试人员产生一些问题本地环境ok但是测试环境有问题环境的问题产生的浏览器环境差异服务器之间的差异
6系统运行环境问题有些问题是在开发环境下OK但是到了测试环境就问题比如说系统文件路径问题、导出报表中的中文问题报表采用POI需要在系统jdk中添加相应的中文字体才能解决
第十章-数据结构和算法
1、怎么理解时间复杂度和空间复杂度
时间复杂度和空间复杂度一般是针对算法而言是衡量一个算法是否高效的重要标准。先纠正一个误区时间复杂度并不是算法执行的时间再纠正一个误区算法不单单指冒泡排序之类的一个循环甚至是一个判断都可以称之为算法。其实理解起来并不冲突八大排序甚至更多的算法本质上也是通过各种循环判断来实现的。
时间复杂度指算法语句的执行次数。O(1),O(n),O(logn),O(n2)
空间复杂度就是一个算法在运行过程中临时占用的存储空间大小换句话说就是被创建次数最多的变量它被创建了多少次那么这个算法的空间复杂度就是多少。有个规律如果算法语句中就有创建对象那么这个算法的时间复杂度和空间复杂度一般一致很好理解算法语句被执行了多少次就创建了多少对象。
2、数组和链表结构简单对比
数组相同数据类型的元素按一定顺序排列的集合就是把有限个类型相同的变量用一个名字命名然后用编号区分他们的变量的集合这个名字称为数组名编号称为下标
数组的特性
1.数组必须先定义固定长度不能适应数据动态增减
2.当数据增加时可能超出原先定义的元素个数当数据减少时造成内存浪费
3.数组查询比较方便根据下标就可以直接找到元素时间复杂度O(1)增加和删除比较复杂需要移动操作数所在位置后的所有数据时间复杂度为O(N)
链表是一种物理存储单元上非连续非顺序的存储结构数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表的特性
1.链表动态进行存储分配可适应数据动态增减
2.插入、删除数据比较方便,时间复杂度O(1)查询必须从头开始找起十分麻烦时间复杂度O(N)
常见的链表:
1.单链表通常链表每一个元素都要保存一个指向下一个元素的指针
2.双链表每个元素既要保存到下一个元素的指针还要保存一个上一个元素的指针
3.循环链表在最后一个元素中下一个元素指针指向首元素
链表和数组都是在堆里分配内存
应用
如果需要快速访问数据很少或不插入和删除元素就应该用数组相反 如果需要经常插入和删除元素就需要用链表数据结构了
3、怎么遍历一个树
四种遍历概念
先序遍历先访问根节点再访问左子树最后访问右子树。
后序遍历先左子树再右子树最后根节点。
中序遍历先左子树再根节点最后右子树。
层序遍历每一层从左到右访问每一个节点。
每一个子树遍历时依然按照此时的遍历顺序。可以采用递归实现遍历。
4、冒泡排序Bubble Sort
算法描述
比较相邻的元素。如果第一个比第二个大就交换它们两个对每一对相邻元素作同样的工作从开始第一对到结尾的最后一对这样在最后的元素应该会是最大的数针对所有的元素重复以上的步骤除了最后一个重复步骤1~3直到排序完成。 如果两个元素相等不会再交换位置所以冒泡排序是一种稳定排序算法。
代码实现 5、快速排序Quick Sort
算法描述
使用分治法来把一个串list分为两个子串sub-lists。具体算法描述如下
从数列中挑出一个元素称为 “基准”pivot重新排序数列所有元素比基准值小的摆放在基准前面所有元素比基准值大的摆在基准的后面相同的数可以到任一边。在这个分区退出之后该基准就处于数列的中间位置。这个称为分区partition操作递归地recursive把小于基准值元素的子数列和大于基准值元素的子数列排序。 key值的选取可以有多种形式例如中间数或者随机数分别会对算法的复杂度产生不同的影响。
代码实现 package com.atguigu.interview.chapter02; /** * author atguigu * since 2019/7/22 * 快速排序 */ public class QuickSort { public static void quickSort(int[] data, int low, int high) { int i, j, temp, t; if (low high) { return; } i low; j high; //temp就是基准位 temp data[low]; System.out.println(基准位 temp); while (i j) { //先看右边依次往左递减 while (temp data[j] i j) { j--; } //再看左边依次往右递增 while (temp data[i] i j) { i; } //如果满足条件则交换 if (i j) { System.out.println(交换 data[i] 和 data[j]); t data[j]; data[j] data[i]; data[i] t; System.out.println(java.util.Arrays.toString(data)); } } //最后将基准位与i和j相等位置的数字交换 System.out.println(基准位 temp 和i、j相遇的位置 data[i] 交换); data[low] data[i]; data[i] temp; System.out.println(java.util.Arrays.toString(data)); //递归调用左半数组 quickSort(data, low, j - 1); //递归调用右半数组 quickSort(data, j 1, high); } public static void main(String[] args) { int[] data {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48}; System.out.println(排序之前\n java.util.Arrays.toString(data)); quickSort(data, 0, data.length - 1); System.out.println(排序之后\n java.util.Arrays.toString(data)); } } 6、二分查找Binary Search
算法描述
二分查找也称折半查找它是一种效率较高的查找方法要求列表中的元素首先要进行有序排列。首先假设表中元素是按升序排列将表中间位置记录的关键字与查找关键字比较如果两者相等则查找成功否则利用中间位置记录将表分成前、后两个子表如果中间位置记录的关键字大于查找关键字则进一步查找前一子表否则进一步查找后一子表。重复以上过程直到找到满足条件的记录使查找成功或直到子表不存在为止此时查找不成功。
代码实现 package com.atguigu.interview.chapter02; /** * author atguigu * since 2019/7/22 */ public class BinarySearch { /** * 二分查找 时间复杂度O(log2n);空间复杂度O(1) * * param arr 被查找的数组 * param left * param right * param findVal * return 返回元素的索引 */ public static int binarySearch(int[] arr, int left, int right, int findVal) { if (left right) {//递归退出条件找不到返回-1 return -1; } int midIndex (left right) / 2; if (findVal arr[midIndex]) {//向左递归查找 return binarySearch(arr, left, midIndex, findVal); } else if (findVal arr[midIndex]) {//向右递归查找 return binarySearch(arr, midIndex, right, findVal); } else { return midIndex; } } public static void main(String[] args){ //注意需要对已排序的数组进行二分查找 int[] data {-49, -30, -16, 9, 21, 21, 23, 30, 30}; int i binarySearch(data, 0, data.length, 21); System.out.println(i); } } 拓展需求
当一个有序数组中有多个相同的数值时如何将所有的数值都查找到。
代码实现 package com.atguigu.interview.chapter02; import java.util.ArrayList;
import java.util.List; /** * author atguigu* since 2019/7/22 */
public class BinarySearch2 { /** * {1, 8, 10, 89, 1000, 1000, 1234} * 一个有序数组中有多个相同的数值如何将所有的数值都查找到比如这里的 1000. * 分析 * 1. 返回的结果是一个列表 list * 2. 在找到结果时向左边扫描向右边扫描 [条件] * 3. 找到结果后就加入到ArrayBuffer * * return */ public static ListInteger binarySearch2(int[] arr, int left, int right, int findVal) { //找不到条件? ListInteger list new ArrayList(); if (left right) {//递归退出条件找不到返回-1 return list; } int midIndex (left right) / 2; int midVal arr[midIndex]; if (findVal midVal) {//向左递归查找 return binarySearch2(arr, left, midIndex - 1, findVal); } else if (findVal midVal) { //向右递归查找 return binarySearch2(arr, midIndex 1, right, findVal); } else { System.out.println(midIndex midIndex); //向左边扫描 int temp midIndex - 1; while (true) { if (temp 0 || arr[temp] ! findVal) { break; } if (arr[temp] findVal) { list.add(temp); } temp - 1; } //将中间这个索引加入 list.add(midIndex); //向右边扫描 temp midIndex 1; while (true) { if (temp arr.length - 1 || arr[temp] ! findVal) { break; } if (arr[temp] findVal) { list.add(temp); } temp 1; } return list; } } public static void main(String[] args){ //注意需要对已排序的数组进行二分查找 int[] data {1, 8, 10, 89, 1000, 1000, 1234}; ListInteger list binarySearch2(data, 0, data.length, 1000); System.out.println(list); }
} 第十一章-设计模式
1、你所知道的设计模式有哪些
Java 中一般认为有 23 种设计模式我们不需要所有的都会但是其中常用的几种设计模式应该去掌握。下面列出了所有的设计模式。需要掌握的设计模式我单独列出来了当然能掌握的越多越好。
总体来说设计模式分为三大类
创建型模式共5种工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式共7种适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式共11种策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
2、单例模式Binary Search
2.1 单例模式定义
单例模式确保某个类只有一个实例而且自行实例化并向整个系统提供这个实例。在计算机系统中线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机但只能有一个Printer Spooler以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口系统应当集中管理这些通信端口以避免一个通信端口同时被两个请求同时调用。总之选择单例模式就是为了避免不一致状态。
2.2 单例模式的特点
单例类只能有一个实例。单例类必须自己创建自己的唯一实例。单例类必须给所有其他对象提供这一实例。
单例模式保证了全局对象的唯一性比如系统启动读取配置文件就需要单例保证配置的一致性。
2.3 单例的四大原则
构造私有以静态方法或者枚举返回实例确保实例只有一个尤其是多线程环境确保反序列换时不会重新构建对象
2.4 实现单例模式的方式
1饿汉式立即加载
饿汉式单例在类加载初始化时就创建好一个静态的对象供外部使用除非系统重启这个对象不会改变所以本身就是线程安全的。
Singleton通过将构造方法限定为private避免了类在外部被实例化在同一个虚拟机范围内Singleton的唯一实例只能通过getInstance()方法访问。事实上通过Java反射机制是能够实例化构造方法为private的类的会使Java单例实现失效 package com.atguigu.interview.chapter02; /** * author atguigu * * 饿汉式立即加载 */ public class Singleton { /** * 私有构造 */ private Singleton() { System.out.println(构造函数Singleton1); } /** * 初始值为实例对象 */ private static Singleton single new Singleton(); /** * 静态工厂方法 * return 单例对象 */ public static Singleton getInstance() { System.out.println(getInstance); return single; } public static void main(String[] args){ System.out.println(初始化); Singleton instance Singleton.getInstance(); } } 2懒汉式延迟加载
该示例虽然用延迟加载方式实现了懒汉式单例但在多线程环境下会产生多个Singleton对象 package com.atguigu.interview.chapter02; /** * author atguigu * * 懒汉式延迟加载 */ public class Singleton2 { /** * 私有构造 */ private Singleton2() { System.out.println(构造函数Singleton2); } /** * 初始值为null */ private static Singleton2 single null; /** * 静态工厂方法 * return 单例对象 */ public static Singleton2 getInstance() { if(single null){ System.out.println(getInstance); single new Singleton2(); } return single; } public static void main(String[] args){ System.out.println(初始化); Singleton2 instance Singleton2.getInstance(); } } 3同步锁解决线程安全问题
在方法上加synchronized同步锁或是用同步代码块对类加同步锁此种方式虽然解决了多个实例对象问题但是该方式运行效率却很低下下一个线程想要获取对象就必须等待上一个线程释放锁之后才可以继续运行。 package com.atguigu.interview.chapter02; /** * author atguigu * * 同步锁解决线程安全问题 */ public class Singleton3 { /** * 私有构造 */ private Singleton3() {} /** * 初始值为null */ Private static Singleton3 single null; Public synchronized static Singleton3 getInstance() { if(single null){ single new Singleton3(); } return single; } } 4双重检查锁提高同步锁的效率
使用双重检查锁进一步做了优化可以避免整个方法被锁只对需要锁的代码部分加锁可以提高执行效率。 package com.atguigu.interview.chapter02; /** * author atguigu * 双重检查锁提高同步锁的效率 */ public class Singleton4 { /** * 私有构造 */ private Singleton4() {} /** * 初始值为null * 加volatile关键字是为了防止 创建对象时的指令重排问题导致其他线程使用对象时造成空指针问题。 */ Private volatile static Singleton4 single null; /** * 双重检查锁 * return 单例对象 */ public static Singleton4 getInstance() { if (single null) { // 解决高并发问题 synchronized (Singleton4.class) { if (single null) { // 判断是否为null single new Singleton4(); // 不是原子操作 分配空间 初始化赋值 引用地址 } } } return single; } } 5 静态内部类
这种方式引入了一个内部静态类static class静态内部类只有在调用时才会加载它保证了Singleton 实例的延迟初始化又保证了实例的唯一性。它把singleton 的实例化操作放到一个静态内部类中在第一次调用getInstance() 方法时JVM才会去加载InnerObject类同时初始化singleton 实例所以能让getInstance() 方法线程安全。
特点是即能延迟加载也能保证线程安全。
静态内部类虽然保证了单例在多线程并发下的线程安全性但是在遇到序列化对象时默认的方式运行得到的结果就是多例的。 package com.atguigu.interview.chapter02; /** * author atguigu * * 静态内部类延迟加载线程安全 */ public class Singleton5 { /** * 私有构造 */ private Singleton5() {} /** * 静态内部类 */ private static class InnerObject{ private static Singleton5 single new Singleton5(); } public static Singleton5 getInstance() { return InnerObject.single; } } 6内部枚举类实现防止反射和反序列化攻击
事实上通过Java反射机制是能够实例化构造方法为private的类的。这也就是我们现在需要引入的枚举单例模式。 package com.atguigu.interview.chapter02; /** * author atguigu */ public class SingletonFactory { /** * 内部枚举类 */ private enum EnumSingleton{ Singleton; private Singleton6 singleton; //枚举类的构造方法在类加载是被实例化 private EnumSingleton(){ singleton new Singleton6(); } public Singleton6 getInstance(){ return singleton; } } public static Singleton6 getInstance() { return EnumSingleton.Singleton.getInstance(); } } class Singleton6 { public Singleton6(){} } 3、工厂设计模式Factory
3.1 什么是工厂设计模式
工厂设计模式顾名思义就是用来生产对象的在java中万物皆对象这些对象都需要创建如果创建的时候直接new该对象就会对该对象耦合严重假如我们要更换对象所有new对象的地方都需要修改一遍这显然违背了软件设计的开闭原则如果我们使用工厂来生产对象我们就只和工厂打交道就可以了彻底和对象解耦如果要更换对象直接在工厂里更换该对象即可达到了与对象解耦的目的所以说工厂模式最大的优点就是解耦
3.2 简单工厂Simple Factory
定义
一个工厂方法依据传入的参数生成对应的产品对象角色 1、抽象产品 2、具体产品 3、具体工厂 4、产品使用者使用说明
先将产品类抽象出来比如苹果和梨都属于水果抽象出来一个水果类Fruit苹果和梨就是具体的产品类然后创建一个水果工厂分别用来创建苹果和梨。代码如下
水果接口 public interface Fruit { void whatIm(); } 苹果类 public class Apple implements Fruit { Override public void whatIm() { System.out.println(苹果); } } 梨类 public class Pear implements Fruit { Override public void whatIm() { System.out.println(梨); } } 水果工厂 public class FruitFactory { public Fruit createFruit(String type) { if (type.equals(apple)) {//生产苹果 return new Apple(); } else if (type.equals(pear)) {//生产梨 return new Pear(); } return null; } } 使用工厂生产产品 public class FruitApp { public static void main(String[] args) { FruitFactory mFactory new FruitFactory(); Apple apple (Apple) mFactory.createFruit(apple);//获得苹果 Pear pear (Pear) mFactory.createFruit(pear);//获得梨 apple.whatIm(); pear.whatIm(); } } 以上的这种方式每当添加一种水果就必然要修改工厂类违反了开闭原则
所以简单工厂只适合于产品对象较少且产品固定的需求对于产品变化无常的需求来说显然不合适。
3.3 工厂方法Factory Method
定义
将工厂提取成一个接口或抽象类具体生产什么产品由子类决定角色 1、抽象产品 2、具体产品 3、抽象工厂 4、具体工厂使用说明
和上例中一样产品类抽象出来这次我们把工厂类也抽象出来生产什么样的产品由子类来决定。代码如下水果接口、苹果类和梨类
代码和上例一样
抽象工厂接口 public interface FruitFactory { Fruit createFruit();//生产水果 } 苹果工厂 public class AppleFactory implements FruitFactory { Override public Apple createFruit() { return new Apple(); } } 梨工厂 public class PearFactory implements FruitFactory { Override public Pear createFruit() { return new Pear(); } } 使用工厂生产产品 public class FruitApp { public static void main(String[] args){ AppleFactory appleFactory new AppleFactory(); PearFactory pearFactory new PearFactory(); Apple apple appleFactory.createFruit();//获得苹果 Pear pear pearFactory.createFruit();//获得梨 apple.whatIm(); pear.whatIm(); } } 以上这种方式虽然解耦了也遵循了开闭原则但是如果我需要的产品很多的话需要创建非常多的工厂所以这种方式的缺点也很明显。
3.4 抽象工厂Abstract Factory
定义
为创建一组相关或者是相互依赖的对象提供的一个接口而不需要指定它们的具体类。角色
抽象产品 2、具体产品 3、抽象工厂 4、具体工厂
使用说明
抽象工厂和工厂方法的模式基本一样区别在于工厂方法是生产一个具体的产品而抽象工厂可以用来生产一组相同有相对关系的产品重点在于一组一批一系列举个例子假如生产小米手机小米手机有很多系列小米note、红米note等假如小米note生产需要的配件有825的处理器6英寸屏幕而红米只需要650的处理器和5寸的屏幕就可以了。用抽象工厂来实现
cpu接口和实现类 public interface Cpu { void run(); class Cpu650 implements Cpu { Override public void run() { System.out.println(650 也厉害); } } class Cpu825 implements Cpu { Override public void run() { System.out.println(825 更强劲); } } } 屏幕接口和实现类 public interface Screen { void size(); class Screen5 implements Screen { Override public void size() { System.out.println( 5寸); } } class Screen6 implements Screen { Override public void size() { System.out.println(6寸); } } } 抽象工厂接口 public interface PhoneFactory { Cpu getCpu();//使用的cpu Screen getScreen();//使用的屏幕 } 小米手机工厂 public class XiaoMiFactory implements PhoneFactory { Override public Cpu.Cpu825 getCpu() { return new Cpu.Cpu825();//高性能处理器 } Override public Screen.Screen6 getScreen() { return new Screen.Screen6();//6寸大屏 } } 红米手机工厂 public class HongMiFactory implements PhoneFactory { Override public Cpu.Cpu650 getCpu() { return new Cpu.Cpu650();//高效处理器 } Override public Screen.Screen5 getScreen() { return new Screen.Screen5();//小屏手机 } } 使用工厂生产产品 public class PhoneApp { public static void main(String[] args){ HongMiFactory hongMiFactory new HongMiFactory(); XiaoMiFactory xiaoMiFactory new XiaoMiFactory(); Cpu.Cpu650 cpu650 hongMiFactory.getCpu(); Cpu.Cpu825 cpu825 xiaoMiFactory.getCpu(); cpu650.run(); cpu825.run(); Screen.Screen5 screen5 hongMiFactory.getScreen(); Screen.Screen6 screen6 xiaoMiFactory.getScreen(); screen5.size(); screen6.size(); } } 以上例子可以看出抽象工厂可以解决一系列的产品生产的需求对于大批量多系列的产品用抽象工厂可以更好的管理和扩展。
3.5 三种工厂方式总结
1、对于简单工厂和工厂方法来说两者的使用方式实际上是一样的如果对于产品的分类和名称是确定的数量是相对固定的推荐使用简单工厂模式
2、抽象工厂用来解决相对复杂的问题适用于一系列、大批量的对象生产。
4、代理模式Proxy
4.1 什么是代理模式
代理模式给某一个对象提供一个代理对象并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
举个例子来说明假如说我现在想买一辆二手车虽然我可以自己去找车源做质量检测等一系列的车辆过户流程但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢于是我就通过中介公司来买车他们来给我找车源帮我办理车辆过户流程我只是负责选择自己喜欢的车然后付钱就可以了。用图表示如下 4.2 为什么要用代理模式
中介隔离作用
在某些情况下一个客户类不想或者不能直接引用一个委托对象而代理类对象可以在客户类和委托对象之间起到中介的作用其特征是代理类和委托类实现相同的接口。
开闭原则增加功能
代理类除了是客户类和委托类的中介之外我们还可以通过给代理类增加额外的功能来扩展委托类的功能这样做我们只需要修改代理类而不需要再修改委托类符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类以及事后对返回结果的处理等。代理类本身并不真正实现服务而是同过调用委托类的相关方法来提供特定的服务。真正的业务功能还是由委托类来实现但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能我们就可以使用代理类来完成而没必要修改已经封装好的委托类。
4.3 有哪几种代理模式
我们有多种不同的方式来实现代理。
如果按照代理创建的时期来进行分类的话可以分为两种静态代理、动态代理。
静态代理是由程序员创建或特定工具自动生成源代码再对其编译。在程序员运行之前代理类.class文件就已经被创建了。动态代理是在程序运行时通过反射机制动态创建的。
4.4 静态代理Static Proxy
第一步创建服务类接口 public interface BuyHouse { void buyHouse(); } 第二步实现服务接口 public class BuyHouseImpl implements BuyHouse { Override public void buyHouse() { System.out.println(我要买房); } } 第三步创建代理类 public class BuyHouseProxy implements BuyHouse { private BuyHouse buyHouse; public BuyHouseProxy(final BuyHouse buyHouse) { this.buyHouse buyHouse; } Override public void buyHouse() { System.out.println(买房前准备); buyHouse.buyHouse(); System.out.println(买房后装修); } } 第四步编写测试类 public class HouseApp { public static void main(String[] args) { BuyHouse buyHouse new BuyHouseImpl(); BuyHouseProxy buyHouseProxy new BuyHouseProxy(buyHouse); buyHouseProxy.buyHouse(); } } 静态代理总结
优点可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
缺点我们得为每一个服务创建代理类工作量太大不易管理。同时接口一旦发生改变代理类也得相应修改。
4.5 JDK动态代理Dynamic Proxy
在动态代理中我们不再需要再手动的创建代理类我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK在运行时为我们动态的来创建。
第一步创建服务类接口
代码和上例一样
第二步实现服务接口
代码和上例一样
第三步编写动态处理器 public class DynamicProxyHandler implements InvocationHandler { private Object object; public DynamicProxyHandler(final Object object) { this.object object; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(买房前准备); Object result method.invoke(object, args); System.out.println(买房后装修); return result; } } 第四步编写测试类 public class HouseApp { public static void main(String[] args) { BuyHouse buyHouse new BuyHouseImpl(); BuyHouse proxyBuyHouse (BuyHouse) Proxy.newProxyInstance( BuyHouse.class.getClassLoader(), new Class[]{BuyHouse.class}, new DynamicProxyHandler(buyHouse)); proxyBuyHouse.buyHouse(); } } Proxy是所有动态生成的代理的共同的父类这个类有一个静态方法Proxy.newProxyInstance()接收三个参数
ClassLoader loader指定当前目标对象使用的类加载器,获取加载器的方法是固定的Class?[] interfaces指定目标对象实现的接口的类型,使用泛型方式确认类型InvocationHandler指定动态处理器执行目标对象的方法时,会触发事件处理器的方法
JDK动态代理总结
优点相对于静态代理动态代理大大减少了开发任务同时减少了对业务接口的依赖降低了耦合度。
缺点Proxy是所有动态生成的代理的共同的父类因此服务类必须是接口的形式不能是普通类的形式因为Java无法实现多继承。
4.6 CGLib动态代理CGLib Proxy
JDK实现动态代理需要实现类通过接口定义业务方法对于没有接口的类如何实现动态代理呢这就需要CGLib了。CGLib采用了底层的字节码技术其原理是通过字节码技术为一个类创建子类并在子类中采用方法拦截的技术拦截所有父类方法的调用顺势织入横切逻辑。但因为采用的是继承所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
Cglib子类代理实现方法
1引入cglib的jar文件asm的jar文件
2代理的类不能为final
3目标业务对象的方法如果为final/static那么就不会被拦截即不会执行目标对象额外的业务方法
第一步创建服务类 public class BuyHouse2 { public void buyHouse() { System.out.println(我要买房); } } 第二步创建CGLIB代理类 public class CglibProxy implements MethodInterceptor { private Object target; public CglibProxy(Object target) { this.target target; } /** * 给目标对象创建一个代理对象 * return 代理对象 */ public Object getProxyInstance() { //1.工具类 Enhancer enhancer new Enhancer(); //2.设置父类 enhancer.setSuperclass(target.getClass()); //3.设置回调函数 enhancer.setCallback(this); //4.创建子类(代理对象) return enhancer.create(); } public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println(买房前准备); //执行目标对象的方法 Object result method.invoke(target, args); System.out.println(买房后装修); return result; } } 第三步创建测试类 public class HouseApp { public static void main(String[] args) { BuyHouse2 target new BuyHouse2(); CglibProxy cglibProxy new CglibProxy(target); BuyHouse2 buyHouseCglibProxy (BuyHouse2) cglibProxy.getProxyInstance(); buyHouseCglibProxy.buyHouse(); } } CGLib代理总结
CGLib创建的动态代理对象比JDK创建的动态代理对象的性能更高但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象因为无需频繁创建对象用CGLIB合适反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法对于final修饰的方法无法进行代理。
4.7 简述动态代理的原理 常用的动态代理的实现方式
动态代理的原理: 使用一个代理将对象包装起来然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。
代理对象决定是否以及何时将方法调用转到原始对象上
动态代理的方式
基于接口实现动态代理 JDK动态代理
基于继承实现动态代理 Cglib、Javassist动态代理