面试大纲
2021-04-25 16:34:45 13 举报
AI智能生成
java相关面试
作者其他创作
大纲/内容
常用设计模式
单例模式
首先构造器要私有,然后在内部实现实例化,提供静态的获取实例的方法,这里分两种:<br>第一是饿汉式,类加载的同时实例就生成了,天生的线程安全<br>第二是懒汉式,调用静态方法的时候要先判断实例是否存在,可以在静态方法加synchronized关键字保证线程安全
线程池,spring中bean的singleton作用域
代理模式
代理类和服务类实现同一个接口,代理类的构造器需要传入服务类,这样代理类除了能调用服务类的功能,还能实现额外的功能;<br>静态代理:代理类指定服务类<br>动态代理:代理类不指定服务类,用Object替换,这样的话你传入什么类型的服务类,我代理类就代理什么类型的服务类
工厂模式
多个类实现同一个接口,工厂类通过传入的参数判断具体要创建哪个实例类
集合
List<br>
List(有序集合)
ArrayList(1.5n)
底层是<font color="#381e11" style=""><b>数组</b></font>,所以可以根据角标直接定位到元素因此查询比较快;<br>但是如果要增删改元素的话,需要复制修改后的数组到内存中新的一块区域,<br>涉及到整个数组的移动所以增删改元素比较慢<br>
线程不安全!那如何解决呢?
用Vector
用集合工具类提供的synchronizedList(在List上加了锁来保证线程安全)
用JUC包提供的CopyOnWriteArrayList<br>(写入时复制,如果有线程要做修改,会复制出一个新List,在复制的List上修改,在原List上读,<br>修改完了再把对象指向复制的List,相当于是读写分离)<br>
初始容量为0,添加第一个元素之后数组容量变成10,添加新元素时会在size属性上+1再跟数组长度比较,<br>如果超过数组长度就扩容成原来的<b>1.5倍</b><br>
LinkedList
底层是<b>双向链表</b>,查询的时候要从头节点挨个向往后找,所以查询比较慢;<br>但是如果做增删改的操作只用把这个元素的上一个节点指针和下一个节点的指针指向它或者不指向它就完成了修改的操作,所以增删改比较快
也是线程不安全!解决方案和ArrayList一样,只不过JUC提供的是CurrentLinkedQueue<br>
添加元素时会把元素封装到<b>node对象</b>中,node对象不仅包含数据还包含上一个节点的指针和下一个节点的指针
Vector
底层数组,由于<b>加了锁</b>所以线程安全但是效率低,初始容量是10,扩容成原来的2倍<br>
Set(无序集合)
HashSet
底层是<b>数组</b>,但是添加元素不是顺序添加的,而是先用hash值获取在数组中的位置,如果这个位置上没有元素就直接添加,<br>如果已经有元素或者链表就调用equal和每个数据作比较,<br>如果都返回flase就用尾插法添加到末尾,如果有返回了true,就用新元素替换这个旧元素<br>
LinkedHashSet
在HashSet上加了个双向链表
TreeSet
只能存同一个类对象
Map
HashMap(2n)
key-value结构,jdk1.8之后底层用数组+链表+红黑树实现<br>
由于添加元素的时候要调用key的hashCode和equals方法,所以key所在类要重写这两个方法<br>第一次添加元素会生成<b>长度16的node[]数组</b>,算出hash值获取在数组中的位置,如果这个位置上没有元素就直接添加,<br>如果已经有元素或者链表就调用key的equal方法和每个元素的key作比较,<br>如果都返回flase并且链表元素不超过8个就用尾插法添加到末尾,如果有返回了true,就用新元素替换这个旧元素,如果元素超过8就就转用红黑树存储<br>
线程不安全!想要安全就用ConconrrentHashMap<br>扩容机制是:首先添加元素的初始容量是16,默认加载因子是0.75,当添加元素的位置已经有元素了并且当前元素个达到了容量的0.75倍的时候会rehash,将容量扩大成原来的两倍<br>
LinkedHashMap
在HashMap上加了一对指针指向前后元素,所以可以按元素添加顺序遍历<br>
TreeMap
底层红黑树,按照key来排序遍历
HashTable
线程安全,效率低
properties和yml都是它的具体实现
普通for循环和增强for循环的区别?
普通for循环写起来比较复杂,但是他有个好处是能拿到元素的下标
集合排序?
compareable和comparator
compareable是排序接口,类如果实现compareable接口并且重写了compare()方法的话说明这个类的对象可以排序
comparator是比较器接口,如果一个类没有实现compareable接口,但是又想排序,可以用自定义这个类的比较器类实现comparator接口,重写compare()方法来实现排序
IO相关
4个抽象基类
1.InputStream 字节输入流<br>
2.OutputStream 字节输出流<br>
3.Reader 字符输入流<br>
4.writer 字符输出流<br>
序列化<br>
把对象转换成流进行存储和传输
如何实现?<br>要序列化的类实现Serializable接口,再用一个输出流构造一个对象输出流ObjectOutputStream对象,调用写出方法writeObject写出
多线程
创建线程的方式
1.创建一个类直接继承Thread类,重写run(),调用start()
2.创建一个类实现Runnable接口,重写run()方法,再把这个类丢给线程类执行
3.创建一个类实现Calleable接口,再通过futureTask包装这个类丢给线程类执行(线程类只能接收实现了Runnable接口的类,但是futureTask是Runnable的实现类,并且他的构造方法需要一个Calleable的实现类)
线程状态
新生态(刚new)——就绪态(start())——运行态——阻塞态(sleep,wait)——死亡态<br>
各种锁
公平锁:先来后到<br>非公平锁:允许插队<br>
可重入锁:外层的方法获得锁之后,内层的方法自动获取锁<br>
自旋锁:拿不到锁的时候不会阻塞,会不停循环尝试<br>
独享锁:只能一个线程使用<br>共享锁:可以多个线程使用
分段锁:针对ConcurrentHashap,put元素时不会对整个hashMap加锁,而是对需要加进去的那个链表加锁,<br>所以只要不是加在同一个链表,都是并行的
锁升级
一段同步代码块不停被一个线程访问,这个线程就会自动获得锁,也就是这个锁会更加偏向于它,所以叫<b>偏向锁</b>;当一个偏向锁被另一个线程试图访问的时候会升级成<b>轻量级锁</b>,访问的线程不会阻塞而是会自旋,从而提高效率;但当自旋到一定次数之后这个线程就会被阻塞了,这时候轻量级锁升级成<b>重量级锁</b>,其他线程试图访问时都会被阻塞,效率降低
synchronized和Lock区别<br>
1.一个是关键字,一个是类
2.前者是自动释放,后者要手动释放<br>
3.前者不能中断,后者可以中断<br>
4.前者是非公平锁,后者公不公平可以设置
线程池
使用方法:调用工具类Executors可以生成单个线程池,指定数量线程池或者缓存池<br>
执行流程:先调用核心线程,如果核心线程满了,就放入任务队列,如果任务队列满了就调用备用线程,<br>如果所有线程都在工作,就调用拒绝策略(1.抛异常 2.返回给调用者 3.丢弃 4.尝试竞争)<br>
JMM内存模型
每个线程都有自己的工作内存,修改数据时会把主存的数据读取并加载到自己的工作内存,<br>修改完后再写入给主存
CAS算法(比较并交换,如果当前工作内存的值是预期的值,就交换,如果不是就自旋)
存在ABA问题<br>
可以用乐观锁解决
关键字Volatile<br>
循环太耗时
一次只能保证一个变量的原子性<br>
线程之间有哪些通信方式
1.共享变量<br>多个线程监听一个变量,这个变量相当于是个标志位,可以是布尔类型,用volatile修饰,因为volatile的可见性,当这个变量发生改变的时候监听的线程会感知到然后执行自己对应的代码<br>比如说一个投票的代码片段,设置一个标志位起始是false,线程A是计票线程,线程B是关闭投票的线程,两个线程同时监听这个标志位,标志位是false的时候A计票,计票代码里面可能有个方法是当某一方票数达到100票的时候,将标志位设置成true,这时候线程B监听到标志位变成了B就开始执行自己的代码,关闭投票
2.用Object类提供的wait(),notify()方法<br>这里要和synchronized一起用,线程要拿到锁才能执行,比如一个线程A拿到锁,执行自己的代码,这时候B线程是等待状态的,当达到某个条件的时候调用notify()唤醒线程B,但是这时候B没有拿到锁,因为notify()是不是放锁的,只有当线程A代码执行完了之后,B才能拿到锁开始执行自己的代码
3.用JUC提供的CountDownLanch类<br>其实也是共享变量的思想,这个类里面有个state变量,当调用这个类的countDown方法的时候会改变这个变量吧(貌似是这样?不确定)
死锁现象
多个线程抢夺共享变量
产生死锁的四个必要条件
1.互斥:就是一个资源最多只能被一个线程占有
2.请求和保持:当线程因为争夺资源被阻塞的时候,对已经持有的资源保持不放
3.不被剥夺:线程持有的资源只能执行完成之后由自己释放,不能被其他线程剥夺
4.循环等待:每一个线程都占有另外一个线程所需要的资源
怎么避免死锁?
1.让线程执行的时候对已经使用完的锁做释放
2.当线程已经拿到了一些资源,但是在请求某个资源被阻塞的时候,要释放掉已经拿到的资源,等需要的时候再重新申请,但是这种中方式代价比较大,容易延长线程执行的周期
3.
常用类
Object类
clone() 复制一个新对象
equals() 比较相等,默认比较地址
toString() 返回对象的字符串表示
wait() 线程等待
notify() 线程唤醒
String类
length() 返回字符串长度
compareTo() 比较大小
StringBuffer:长度可变,线程安全<br>StringBuilder:长度可变,线程不安全<br>
Math类
abs() 返回绝对值
max() 获取两个数中最大的
random() 随机数
Date类
SimpleDateFormat()
LocalDate,LocalTime,LocalDateTime,DateTimeFormater
Java8新特性
Lamda表达式
用在简化匿名内部类:<br>以前的匿名内部类需要new 接口然后重写法方法,Lamda直接把new接口换成了()->的方式实现里面的方法
新时间类,LocalDate,LocalTime,LocalDateTime
Stream API,流式编程,用来处理集合比如查找,筛选<br>
新编译工具
Optional类来处理空指针异常<br>
spring全家桶
Spring
IOC
控制反转:将对象的创建和之间的依赖关系交由spring容器管理
自动装配和依赖注入<br>
依赖注入(bean中的引用对象实例化后注入)
1.通过set()
2.通过构造器
自动装配(将依赖注入自动化)
1.No手动装配
2.ByName<br>
3.ByType<br>
4.constructor<br>
循环依赖问题解决方案
1.用set()注入
2.@Lazy,加了这个注解的引用对象会被延迟加载
两大核心接口
BeanFactory
最底层接口,Bean的管理,加载,实例化,配置文件都是它做的,是通过延迟加载来注入Bean,就是用的时候才会实例化
ApplicationContext
beanFactory的子接口,容器启动的时候就加载好了所有Bean
Bean的作用域
singleton 单例,只有一个Bean
prototype 原型,每次创建都会一个新的Bean
request 每次请求对应一个Bean,Bean只在请求内有效
Session 每次会话对应一个Bean,Bean只在会话内有效
global-session 全局,浏览器内有效
AOP
在不影响业务代码的情况下增加额外的功能
实现方式
1.定义类直接继承切面类<br>
2.定义普通类,在xml中配置切面切点
3.通过@Aspect注解声明切面类和通知方法以及切点(要在xml开启注解支持)<br>
配置
<bean>自动装配<br>
<alis>取别名<br>
<context:component-scan>扫描包,使注解生效<br>
<context:annotation-config>开启注解驱动<br>
<aop:config>AOP配置
使用的设计模式
工厂模式 BeanFactoty
代理模式 AOP<br>
代理类和服务类实现同一个接口,代理类内部除了注入服务类,还可以添加自己额外的方法
单例模式
构造器私有,防止被其他人实例化,对外暴露一个静态的实例方法。这个方法里面可以通过对这个实例属性做判断存不存在来实现懒汉式的单例
模板方法模式 jdbcTemplate等
spring事务传播行为
支持当前事务,如果当前有事务就加入
如果没有就新建一个事务
如果没有就以非事务方式运行
如果没有就抛异常
不支持当前事务,如果当前有事务就挂起
如果没有就新建一个事务
如果没有就以非事务方式运行
如果有事务就抛异常
如果当前有事务就新建一个嵌套事务,没有就新建事务
常用注解
自动注入相关
@Autowired(ByType), @Qualifier(ByName), @Resource(ByName)<br>
@Component, @Repository, @Service, @Controller, @RestController<br>
@Value
@Configration, @Bean
切面编程相关
@Aspect
@Before, @After, @Around
@PointCut
SpringMVC
DispatcherServlet前端控制器,链接三个东西<br>
HandlerMapping处理器映射器,根据请求分配对应处理器<br>
HandlerAdapter处理器适配器,适配处理器后处理请求,并且返回MoudleAndView<br>
视图解析器,解析逻辑视图<br>
执行流程<br>
用户请求被前端控制器拦截,前端控制器调用处理器映射器(HandlerMapping)根据请求的url匹配对应的handler,<br>然后前端控制器再调用处理器适配器(HanlderAdapter)适配到handler,处理器处理请求返回moudleAndView给前端控制器,<br>前端控制器再调用视图解析器解析逻辑视图,然后渲染视图,把数据存到Request域,返回给用户
框架会自动根据前端的name匹配Controller的参数名
Rest风格:一个URL路径对应唯一的一个资源,同一个请求,可以根据不同的请求方式匹配到不同的处理器
springMVC的组件
前端控制器
处理器映射器
处理器适配器
处理器
视图解析器
视图
视图跳转的几种方式
通过视图解析器
转发forword:url不变,数据共享
重定向redirect:url改变,数据不共享,如果想数据共享,可以在参数里用RedirectAttributes代替model
如何拦截特定的请求方式?
在@RequestMapping设置method = RequestMethod.GET或者直接用@GetMapping等等...
如何实现拦截器?
定义类继承Intercepter接口,在类中实现重写那三个方法,然后再配置文件中注册拦截器并且配置拦截路径和需要排除的路径
常用注解
@RequestMapping, @GetMapping, @PostMapping
@RequestParam Controller的参数使用,匹配前端参数的name(一般不用,直接用形参的名字匹配就好了)<br>
配置
<Servlet-name>前端控制器
<mvc:annotation-drive>映射器和适配器驱动<br>
<bean>视图解析器<br>
Json格式乱码<br>
核心依赖
想要跑起来一个mvc,必须含有这些依赖
spring-web,spring-mvcweb
还有spring相关的比如spring-beans,spring-context,spring-core,aop
还有logging的依赖
Mybatis
执行过程
sqlSessionFactoryBuilder调用build()方法读取mybatis配置文件的流,生成sqlSessionFactory,再通过sqlSessionFactory获取sqlSession,然后sqlSession获取Mapper接口,Mapper接口调用sql方法
生命周期:<br>sqlSessionFactoryBuilder创建了Factory之后就不需要了;sqlSessionFactory以单例模式一直存在,直到程序停止;sqlSession所用域是一次请求,用完了要及时关闭<br>
核心配置
事务管理器,默认JDBC<br>
数据源,默认POOLED<br>
数据库链接的四个要素
username
password
url
driver
配置
核心配置
别名,通过包扫描默认包下的所有类都是首字母小写,大写也行;如果要指定别名,可以在类上加@Alias注解<br>
Mapper映射,注册.xml文件<br>
缓存
一级缓存,在同一个会话(sqlSession)中,查询同一个数据会走缓存,一级缓存是默认开启的
二级缓存,需要手动开启,将缓存标签设置成true;作用范围是一个命名空间内<br>
缓存查询顺序:二级缓存到一级缓存再到数据库<br>
结果集映射<br>
为了防止属性名和数据库字段不一致,可以用结果集映射ResultMap来显示的指定,property是实体类的属性名,<br>column是数据库字段名;主键字段用id标签,其他的就用result标签
动态sql
根据不同的条件生成不同的sql语句
实现分页
1.使用原生的sql语句的limit关键字:<br>需要定义分页对象Pager,属性page,size,List,count,通过传入的page和size执行分页查询语句,返回这一页的所有数据给List,然后把这个List赋给Pager对象,把Pager作为返回值返回<br>
2.使用Intercepter接口:<br>定义类继承Intercepter接口重写里面的方法,在配置中增加intercepter插件的配置,因为这种方式是拦截器会帮我们拼接limit条件,所以我们的sql语句要把后面的limit条件去掉;这种的好处是我们不用像第一种那样在传入Map的时候要事先对page做计算了
3.使用PageHelper:<br>其实是第二类的一种实现,只不过不用我们自己定义intercepter的实现类了,也是查询的时候拦截,然后通过调用startPage()方法设置分页参数,然后查询所有的时候会自动做分页处理,所以返回的结果就是分页后的结果
防止sql注入
1.#{} 井号加大括号的形式看可以防止sql注入,因为这这种方式的sql语句是先编译然后再传入参数执行的,整个sql语句的逻辑已经编译完成了,传入的参数仅仅作为要替换的值被替换进去
2.存储过程,存储过程也可以防止sql注入,因为存储过程是已经被编译好了存储在数据库的
3.前端做校验,前端对参数做格式的校验
${}和#{}的区别是?<br>${}只是简单的字符串替换;但#{}是预编译,将sql语句预编译,把#{}替换成?再用PreparedStatement的Set()方法赋值<br>
Mapper里面怎么传递多个参数
1.@Param注解:在接口参数前使用,指定接收的参数名字
2.在sql的条件中占位符用#{0} #{1}代表第一个参数,第二个参数..
3.多个参数封装成map
Mybatis Plus插件
使用流程:<br>引入依赖,配置数据库,实体类,mapper接口继承BaseMapper,就可以直接用了;如果想看到日志,可以在springboot配置文件里面配置<br>
<b>条件查询</b>是通过传入map实现的,key对应数据库字段,value对应实际查询参数<br><b>分页查询</b>是通过传入Page对象实现的<br><b>复杂查询</b>是通过new条件构造器QueryWrapper实现的<br>
重要注解<br>
<b>@TableId</b><br>主键策略:可以在实体类的主键属性上加@TableId通过type的值来指定主键是自增还是全局唯一还是其他...<br>
<b>@TableField</b><br>填充策略:在实体类属性上加@TableField表示需要自动填充,通过fill属性指定是插入时填充还是更新时填充还是其他时候填充,然后自定义类继承MetaObjectHandler ,重写对应的填充策略;一般用在字段创建时间和修改时间上<br>
<b>@Version</b><br>乐观锁:在实体类属性上加@Version标志版本信息
<b>@TableLogic<br></b>逻辑删除:在实体类属性上加@TableLogic标致是一个逻辑删除字段,然后在配置文件中配置逻辑删除的实现
SpringBoot
必须有一个入口类Application,且所有包都必须和它同级<br>
<b>@SpringBootApplication 核心注解!</b><br>
@SpringBootConfigration:用来标志入口类,并且实现配置文件功能<br>
@EnableAutoConfigration:核心注解!<b>自动配置</b>spring和第三方应用<br>
@ComponentScan:扫描组件<br>
配置文件
配置springboot应用的端口号,服务器名字,配置环境<br>
配置数据源的连接参数
配置前端控制器,视图解析器,xml文件位置,别名<br>
配置logback日志,开启热部署<br>
配置Redis
通过spring: profiles: active开启不同的配置环境
在注册中心注册服务,配置网关<br>
springboot的核心配置文件是哪几个
Application:用于springboot的自动配置
bootstrap
springboot中的starter是什么
是启动器,启动器里面包含了应用所需要的所有依赖,所以如果想引入某一个功能的话只用引入他的启动器就可以,不用再引入其他的依赖
实现热部署
springboot-devtools (官方)<br>
spring-loaded(官方)
jrebel(插件)
实现AOP
也是通过注解实现,在切面类加@Aspect注解标志切面类,然后在里面通过@After,@Before,@Around标志通知方法,通过@PointCut指定切点类,在通知方法的参数传入JoinPoint对象可以获取切点对象的属性<br>
实现拦截器
首先定义一个拦截器类继承HandlerIntercepter方法,重写preHandler(),postHandler(),afterCompletion()方法<br>
然后开发一个类用来注册拦截器,继承WebMvcConfigurerAdapter类,重写注册拦截器的方法,把自定义的拦截器类注册到springboot中,<br>然后设置拦截路径和排除的路径<br>
实现定时任务
自定义任务类,在类上加@EnableScheduling注解,标志这是个定时任务类,然后在里面用@Scheduled注解标志一个定时任务方法,<br>cron属性设置循环周期
用Quartz框架
Swagger
用来管理和维护接口的文档工具<br>
先要导入依赖,然后配置Config类,里面包含了扫描的接口包和一些swagger的ui页面的一些信息<br>@Api:加在Controller上可以表示接口名字<br>@ApiOperation:加在控制器的方法上,可以对接口方法做描述<br>
Shiro
三个关键对象:<b>Security Manager</b>安全管理器,<b>Realm</b>与数据库交互的认证授权类,<b>Subject</b>用户主体<br>
用户注册
自定义获取随机盐的类,一般是用shiro提供的获取随机盐做返回值,然后在<b>service层的注册方法</b>中设置注册用户的随机盐,存到数据库中,然后对用户密码做加密,最后把用户存到数据库中<br>
认证
<b>Controller封装token---login()---Realm 查询数据库密码信息返回Info,由密码匹配器校验</b><br>用户登录请求被拦截器拦截,由拦截器分发给控制器,控制器将用户的账号密码封装成Token传给用户主体调用login()方法登录,登录方法走我们自定的Realm中的认证方法,这个认证方法通过Token获取用户名,通过用户名去数据库或者缓存中查询对应的加密后的密码和散列次数,封装成一个认证的Info对象返回,由shiro对密码做校验,但是shiro是默认equals比较的,所以我们要在shiroConfig类里面创建一个Hash什么的密码匹配器,然后设置加密方式和散列次数<br>
授权
首先要在shiroConfig的过滤器里面通过map存储受保护的资源和公共资源,然后在Realm的授权方法中通过用户名查询拥有的角色遍历出来放到返回的Info中,然后通过角色查询对应的权限,遍历出来放到Info中返回<br>
缓存
EhCache<br>在配置类的Realm中开启EhCche缓存
Redis<br>先要自定义Redis缓存类继承Cache类,在里面实现添加移除的那些方法,然后自定义缓存管理器类继承默认的缓存管理器类,里面返回自己自己定义的Redis缓存,然后再配置类的Realm里面开启缓存<br>
属性注入(读取配置)有哪几种
@Value(${ })
@ConfigurationProperties(prefix = )根据前缀读取
JDBC
JDBC操作数据库的步骤:<br>1.加载数据库驱动driver 2.获取数据库链接(传入url,username,password) 3.通过链接获取statment对象 4.statment对象执行sql语句 5.处理结果集 6.关闭链接(结果集,statment,数据库链接)
execute,executeQuery,executeUpdate的区别
execute 可以用来执行任何sql
executeQuery 只能用来执行查询sql
executeUpdate 可以用来执行增删改sql
JDBC的ResultSet是什么?
ResultSet是查询后得到的结果集,里面是查询出来的数据,可以用next()方法遍历结果集,当返回是false的时候说明已经没有数据了
JDBC的批处理怎么做?
调用Preperstatment的executeBatch()方法就可以执行批处理操作
JDBC的事务管理?
JDBC默认自动开启事务,也就是每条语句都是自动提交的,如果想手动提交,可以调用setAutoCommit(boolean flag) 将参数设置成false就可以关闭自动提交
JDBC的回滚怎么做?
使用连接的rollback()方法可以使事务回滚,并且可以通过Savepoint设置回滚点
Servlet相关
Servlet的生命周期
初始化:Web容器加载servlet,调用init()方法
根据请求运行service()方法
服务结束后Web容器调用distroy(0方法销毁servlet
GET请求和POST请求区别
GET请求不安全,因为参数会在url上显示出来;POST是安全的,因为参数不会在url上显示出来
GET只能提交很小的文本信息;POST不仅能提交大文本信息还能提交大量的二进制数据
GET一般用来从服务器获取数据;POST一般用来给服务器发送数据
JSP相关
JSP和HTML的区别
JSP可以理解成能在里面写java代码的html文件
jsp是服务器端的比较偏向功能的动态页面;html是客户端的,是直接被浏览器翻译的,比较偏向静态的页面展示
JSP几个内置对象
request, response, session, pageContext, application, page, out, config, exception
JSP的几个作用域
request
session
page
application
forward和redirect
转发的url不会变,因为是服务器直接去目标的地址把数据加载给浏览器;重定向的url会变,因为浏览器会重新加载新的url
转发页面之间共享数据;重定向页面之间不共享数据,如果想数据共享,可以在用RedirectAttributes对象存数据
转发一般用在用户登录之后;重新向一般用在用户退出之后或者跳转到别的链接
Cookie和Session
cookie是存储在浏览器的,不安全;session存储在服务器,安全
mysql (3306)
整体架构:客户端(JDBC)---mysql服务端(连接器:链接客户端,校验权限,分析器:分析sql语句,优化器:优化sql,执行器:执行sql)---存储引擎(innoDB,myISAM)<br>
字段常见属性
auto_increment自增,一般用在主键上<br>
非空<br>
Unsigned:不能为负数<br>
0填充<br>
存储引擎<br>
innoDB
支持事务,外键,全文索引,锁是行锁;当使用事务的时候会自动开启行锁<br>
myISAM
不支持事务,外键,全文索引,锁是表锁
sql执行顺序<br>
先是from读取表数据,然后join连表,where筛选条件,group by分组,having再次筛选条件,然后才是select查询,查询完了通过order by排序,最后limit分页显示
存储过程
一组已经编译好的sql语句
create procedure xxx 创建存储过程并命名<br>begin end 开始 结束<br>declare 声明一个变量 set 给变量赋值 select xx 返回变量
索引相关
mysql有哪些索引
1.主键索引,每张表必有的
2.普通索引,一张表可以有多个
3.唯一索引,索引列不能重复,并且最多有一个null
4.复合索引,所列组合的索引,必须遵循最左前缀原则
创建索引的原则
一般加在经常查询并且不常修改的字段上
数据量小的表不要加索引
索引底层结构
<b>B+树</b><br>叶子节点存主键和数据并且根据主键从小到大排序,其他节点只存主键和子节点的指针,好处是每个非叶子节点能存很多索引<br>
怎么不用hash?<br>因为是通过hash值直接算出数据位置,所以只适合等值查询,不适合范围查询,时间复杂度是O(n)<br>
怎么不用二叉树?<br>二叉树是左小右大的结构,但是一般我们用主键自增的话,就会一直往右边加数据,造成和hash一样的情况
怎么不用平衡二叉树?<br>平衡二叉树数据量越大,树越高,查询次数就越多,IO次数就会增多<br>
怎么不用红黑树?<br>和平衡二叉树一样,也是树高,IO次数多
怎么不用B树?<br>B树每个节点都会存数据,而每次从磁盘读取的大小是固定的,这就导致每次从磁盘读取的数据很少<br>
sql调优
预防慢sql
避免select *,避免全表扫描,尽量创建索引
避免让索引列做计算,尽量避免Having
尽量给表起别名,可以减少sql的解析时间
update的时候更新哪些字段就写哪些字段,不更新的不要写
复现慢sql调优
先跑一下sql,开启慢查询日志,设置阈值(默认10s)来抓慢sql,,然后用Explain查sql的执行计划,然后根据explain出来的type,possible_key,key,rows这些看看哪些可以优化一下<br>
开启show profile,用show profiles查看sql的执行时间,然后用show profile命令对慢sql做诊断,就可以看到各个步骤的开销时间
mysql有哪些锁
从资源共享来说
读锁:其他线程能读但不能写
写锁:其他线程既不能读也不能写
从颗粒度来说
行锁
乐观锁:通过版本号实现<br>在操作数据的时候默认其他线程不会修改这个数据,提交数据前会通过比较版本号来判断是否被修改过
悲观锁:通过for update语句实现<br>操作数据的时候默认其他线程会修改这个数据,所以会对数据加锁
表锁:阻塞其他线程对表的读写,比如alter修改表的时候就是加表锁
事务
ACID
原子性:要么一起成功,要么一起失败;<br>一致性:事务前后保持一直;<br>隔离性:多个事务之间互不受影响;<br>持久性:提交之后会持久化到数据库<br>
隔离问题和隔离级别<br>
隔离级别
读未提交:啥隔离问题都没解决<br>
<b>读已提交</b>:只能读提交后的数据,解决了脏读问题<br>
<b>可重复读(mysql默认)</b>:读字段的时候不允许其他事务对这个字段做更新操作,解决了了脏读和不可重复读问题<br>
串行化:读取的时候保证表行数不变,不允许其他事务对这个表做更新,解决了脏读,不可测重复度和幻读问题<br>
隔离问题
脏读:读了另一个事务还没提交的数据 <b> 读已提交,可重复读,串行化</b>可以解决<br>
不可重复读:一个事务中前后两次读取的数据不一致 <b>可重复读,串行化</b>可以解决<br>
幻读:一个事务读到了别的事务插入的数据 <b>串行化</b>可以解决<br>
主从复制
一主一从,一主多从,主主复制,多主一从,连级复制<br>
同步原理:主库的log dump线程给从库的IO线程传递binlog,从库的IO线程去请求主库的binlog,写到本地的relay-log中,然后通过sql线程读取relay-log的日志,解析成sql语句执行<br>
函数
select count(*) from 'xxx' select count(1) from 'xxx'
select SUM/AVG/MAX(user) from 'xxx'<br>
select ABS 取绝对值
mysql优化建议
开启查询缓存
查看执行计划优化查询
如果只要一行数据的话要用limit 1
如果字段的值不多并且都知道的话要用枚举不要用varchar
分库分区分表
分表:把一张表根据一定规则拆分成多个表
分区:一张表内根据一定规则划分分区,但是还是一张表,数据可以存储在不同的设备上;一张表最多1024个分区<br>
Redis (6379)
key-value结构,基于内存所以效率高,1秒11万次读8万次写,存储的数据多样,有16个数据库,默认用第0个<br>redis是单线程的,因为是基于内存的,没有CPU瓶颈,而多线程是解决CPU瓶颈问题的
5个基本类型+3个特殊类型
<b>string</b>类型,可以用来统计人数(因为有自增incr和自减方法decr)或者存储验证码
<b>list</b>类型(有序),可以用来实现消息队列,rpush发消息,lpop消费消息<br>
<b>Set</b>类型(无序),可以用来实现共同关注(因为有交集,并集的方法)<br>
<b>hash</b>类型(key-map结构),可以用来存储对象<br>
<b>zset</b>有序集合,可以用来实现排行榜,因为添加元素时可以排序
geospatial地理位置,底层是zset,用来存储地理位置<br>
Hyperloglog统计基数,占用内存很小,可以用来统计访问人数<br>
Bitmap按位记录,所以占内存小,添加元素时可以按位添加,可以用来实现打卡记录或者实现布隆过滤器<br>
配置文件作用
配置文件里面可以配置包含别的文件,通用配置,rdb,aof,密码,客户端配置<br>
发布订阅
发布者发布消息,然后订阅者通过redis自动获取消息内容,可以用来实现订阅博主订阅公众号<br>
订阅者订阅频道:<b>SUBRIBE </b>xxx;发布者往频道发消息:<b>PUBLISH </b>xxx “Hi~”订阅者就会自动接收到这个消息<br>
事务<br>
<b>multi</b>开启事务,<b>exec</b>执行事务<br>
实现乐观锁
通过<b>watch</b>监视器监视资源<br>
实现持久化
RDB:redis会分一个子进程来做持久化,这个进程把修改的数据写到临时的RDB文件,写完了替换旧RDB,一旦满足保存的条件或者退出redis就会完成RDB,生成dump.rdb<br><b>性能更好</b>
AOF:用日志记录每次操作到appendonly.aof文件,然后开启redis的时候执行这些操作<br><b>更新频率高更安全更大,更适合还原数据</b>
部署的几种模式
一主多从
master负责写,slaver负责读
一般开3个端口,设置一主两从,这就要配置3个配置文件以不同的端口号来区分,配置文件里面配置端口号,pid文件写入位置,日志位置,持久化,然后两个从机slaveof设置主机地址和端口号,并且还要设置slave-read-only只能读,最后先启动主机再启动从机<br>
哨兵模式
上面简单的主从复制有一个问题,就是如果主机宕机了,还需要手动重新设置主机,这时候就需要哨兵模式了
哨兵通过服务器返回的主机运行状态检测到如果主机宕机,会主观认为主机宕机,只有当其他一定数量的哨兵都检测到主机宕机之后,才会有随机一个哨兵发起投票选举新主机,然后通过发布订阅,每个哨兵通知自己监控的从机修改配置文件配置新主机<br>
集群模式
分布式缓存
分布式缓存是在应用服务器之外的缓存
我们要自定义缓存类实现mybatis的<b>cache</b>接口重写添加,获取,清空等方法,具体用redisTemplate实现,然后再在.xml文件中引用自定义的缓存类<br>
怎么解决关联缓存问题?
关联缓存就是如果涉及到多表查询,一方的缓存更新了,另一方的没有更新,这样缓存就不同步了<br>
直接在一方的缓存引用那里写上另一方的命名空间,这样就使用了同一个命名空间,对应的数据就同步了
缓存三大问题
缓存穿透<br>
如果一直查询一个不存在的数据,缓存里面没有,就会一直去访问数据库,流量大了之后服务器可能会挂掉<br>
解决方案1:布隆过滤器,加一层bitmap,因为节省空间所以可以把所有可能的数据都放里面,那一定不存在的数据就会被排除在外<br>
解决方案2:redis存空值(失效时间设置比较短),这样就算查询为空也不会走数据库<br>
缓存雪崩
如果redis里面大量数据同时刷新或者同时失效,这时候并发访问到数据库的量一大就可能造成服务器挂掉
解决方案:往redis存数据的时候在key上加随机的失效时间<br>
缓存击穿
redis里面的热点数据如果失效了,并发直接访问数据库,可能会造成数据库崩掉
解决方案1:设置热点数据永不过期,但不是真的永远不过期,只是把过期时间设置在value里面,如果发现value里面的过期时间过期的话就用一个异步进程重新构建缓存<br>
解决方案2:加互斥锁,当发现缓存失效的时候加互斥锁并且重构缓存,其他访问会被阻塞直到缓存构建完
RabbitMQ (5672)
一个用户对应一个虚拟主机!<br>生产者:链接到服务,然后通过用户名密码链接对应的虚拟主机,然后把消息发给交换机<br>消费者:连接到服务,然后通过用户名密码链接对应虚拟主机,再从交换机中取数据
5种生产者消费者模型
没用交换机
1.直连模型<br>生产者:连接<b>虚拟机</b>,获取<b>连接对象</b>,通过连接对象获取<b>通道</b>,通道声明<b>队列</b>,由通道往队列中发消息<b>(队列名,消息内容)<br></b>消费者:链接虚拟机,获取连接对象,通过连接对象获取通道,通道绑定队列从队列中消费消息<b>(队列名,消息自动确认机制,回调函数)</b><br>
2.任务模型<br>和直连的区别在于消费者变成了多个,所以这里存在着一个问题就是消费者不同的消费快慢会导致程序变慢或者<b>消息丢失<br></b>因此,消费者在消费消息的时候要把消息自动确认关掉,并且手动设置每次消费的消息个数<br>
用到交换机
3.广播模型<b>fanout</b><br>生产者:获取<b>通道对象</b>后,由通道声明<b>交换机</b>,然后通道发布消息<b>(交换机名,消息内容)<br></b>消费者:获取<b>通道</b>后,由通道声明<b>交换机</b>,再创建一个自己的<b>临时队列</b>,把临时队列和交换机<b>绑定(交换机名,通道名)</b>,再消费消息<br>
4.订阅模型<b>direct</b><br>上面的模型,交换机里面消息能被所有消费者消费,但是有时候是需要指定消费者消费哪条消息,这时候需要用到路由key<br>在广播模型基础上,<b>生产者在发消息的时候还要指定路由key,消费者在绑定交换机和临时队列的时候也要指定路由key</b><br>
5.动态路由模型Topic<br>上面的订阅模型在消费绑定队列和交换机的时候要指定确定的路由key,如果需要消费很多种消息就要指定很多个路由key,写起来很麻烦,这时候可以用通<b>配符来配置路由key</b><br>消费者在绑定队列和交换机的时候通过*(一个) #(多个)通配路由key<br>
应用场景
异步处理业务<br>
应用解耦:在两个需要通讯的系统之间加一层消息队列,就算其中一个系统挂了短时间内也不会影响另一个系统<br>
流量削峰:在系统中加入消息队列射程最大消息数量,超过这个消息数量之后流量也不会再打进来<br>
Maven和Git
maven常用命令
mvn compile 编译
mvn test 测试
mvn clean 清理
mvn package 打包
mvn deploy 发布项目
git常用命令
git add 添加代码到<b>暂存区</b><br>
git commit 添加到<b>本地仓库</b><br>
git push 添加到<b>远程仓库</b>
git clone 从远程仓库复制<br>
git init 初始化<br>
0 条评论
下一页