面试宝典
2023-03-16 13:46:53 0 举报
AI智能生成
个人总结的一份java面试宝典
作者其他创作
大纲/内容
第四章:网页设计与开发
HTML
超文本编辑语言
布局
表格布局
table
thead
tbody
tfoot
div布局
盒子模型?
真实宽高 width height
内边距 padding
边框 border
外边距 margin
浮动 float
左浮动
left
右浮动
right
定位 position
普通文档流定位 (默认)
相对定位
relative
相对于自己原来的位置定位,不释放原来的 占用的空间
绝对定位
absolute
相对于第1个已定定位父元素,如果没有,往上找,直到找到body,会释放原来占用的空间
固定定位
fixed
表格
colspan
跨列
rowspan
跨行
CSS
全称:层次样式表 CSS 语法由三部分构成:选择器、属性和值: body {color: blue}
选择器有哪些?
id选择器
#id
类选择器 class
.a, .b
元素选择器,表现选择器
p h1
属性选择器
[attribute=value]
伪类选择器
:checked :active
层级选择器
:nth-child(n)
:nth-of-type(n)
css引入的方式
内嵌式
<span style="color:red"></span>
内联式
head style
外部引入
<link rel="stylesheet" type="text/css" href="theme.css" />
@import "style.css"
JavaScript
JS中undifined和not defined的区别
未声明变量,会抛出 not defined
typeof 未声明的变量 却是返回undefined
怎么判断一个Object是否是数组(array)?
1.使用Object.prototype.toString来判断
Object.prototype.toString.call(xxx)=='[object Array]'
2.使用原型链来判断
xxx._proto_==Array.prototype
3.利用JQ
$.isArray(xxx)
JS的数据类型
string
number
boolean
undefined
Array
Object
JS的内置对象
字符串(String)对象
Array 对象
Date 对象
Math 对象
Number 对象
RegExp 对象
=、==、===的区别
=
赋值符
==
比较若类型不同,先尝试转换类型,再作值比较,最后返回值比较结果
===
只有在相同类型下,才会比较其值
任务定时器、一次性定时器怎么定义,如何清除?
一次性定时器
setTimeout
任务定时器
setInterval
清除
clearTimeout(obj)和clearInterval(obj)。
JS如何判断数据类型以及强制转化为整数和浮点数
判断某个值是什么类型?
typeof
数值的转换
string-->整数
parseInt()
string-->浮动
parseDouble()
JS得到DOM节点对象的方式
getElementById
getElementsByTagName
getElementsByClassName
JS正则表达式(邮箱、手机号、身份证号码)
身份证号码
/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/
邮箱
/^([a-zA-Z]|[0-9])(\w|\-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/
手机号
/^1[3456789]\d{9}$/
JQuery
JQuery的$()是什么意思
用于将任何对象包裹成为JQ对象,然后可以调用JQ方法
包裹选择器则可以返回一个包含所有匹配的DOM元素数组的JQ对象
$(document).ready()是什么函数?有什么用?
用于文档进入ready状态时执行代码
当DOM完全加载,JQ允许你执行代码
适用于所有浏览器
onload()的方法是在页面加载完成后才发生,这包括DOM元素和其他页面元素(例如图片)的加载。而$(document).ready()所要执行的代码是在DOM元素被加载完成的情况下执行,所以,使用document.ready()方法的执行速度比onload()的方法要快
JQuery发送ajax
$.ajax
url : 请求路径
data: 前端给后端数据
datatype: json,text 服务器 预期返回给我们的数据类型
success: 请求响应成功 200 执行 function(){}
type: 发送的请求post get 请求
error: function 失败的时候
JQ对象和DOM对象相互转化
JQ->DOM
$(document).get(0)
$(document)[0]
DOM->JQ
$(DOM)
使用CDN加载jq库的优势
节省服务器带宽、下载速度快
分支主题
已经从CDN下载类相同的jq版本,就不会再下载一次,即用户访 问过使用任何这些CDN的jQuery框架的任何站点,它将被缓存
选择器
id选择器
类选择器
标签选择器
层次选择器
属性选择器
表单选择器
第五章:JavaWEB开发
Http长连接和短连接
Http1.0
默认短连接
每进行一次http操作,就建立一次连接,完成就断开连接
Http1.1
默认长连接
数据传输完成TCP连接不断开,等待下一次传输
Http1.0和Http1.1的区别
1.可扩展性
http1.1消息中增加了版本号
2.缓存
http1.1有缓存
3.带宽优化
http1.0有浪费带宽的现象,如断点下载的时候,如果第一次下载断开了,第二次必须重新从头下载
http1.1允许请求资源的某一部分
4.长连接
http1.1长连接
5.消息传递
6.Host头域
7.错误提示
http1.0定义16个响应状态码,不够具体
http1.1定义24个响应状态码
http常见状态码
200
客户端请求成功
301
请求的URL已移走
302
重定向
400
客户端请求有语法错误
401
请求未经授权
403
服务器拒绝服务
404
请求资源不存在
500
服务器发送不可预期错误
503
服务器当前不可服务,过一段时间可以恢复
Cookie和Session的区别
1.cookie是服务器发送浏览器信息,浏览器在本地存储cookie。session是服务器的一块信息,session中存储特定用户会话产生的信息
2.无论客户端怎么设置,session都能正常工作,但是如果禁用cookie则cookie无法使用
3.cookie只能 存储String对象,而session可以存储任意java类型
单点登录,若cookie被禁用?
单点登录原理
后端生成一个sessionID,设置cookie,之后所有请求带上该cookie,然后从cookie里获取sessionID,从而查询用户信息
关键点是这个session ID,而不是cookie
使用HTTP请求头来传输
BS与CS
CS是Client/Server的缩写
BS是Brower/Server的缩写
区别
1.硬件环境不同
CS一般建立在专用网络,小范围网络环境
BS用于广域网,适应范围更广
2.安全要求不同
CS一般面向固定用户群,安全性更强
BS安全性相对差些,可能面向不可知用户
3.程序架构不同
CS程序可以更注重流程
BS对安全和访问速率更要求
4.软件重用不同
CS不可避免对整体性考虑
构件相对独立,便于重用
5.系统维护不同
CS与操作系统相关,升级较难
BS在浏览器之上,开发成本低
6.处理问题不同
CS处理固定用户群
BS处理更广的用户群
7.用户接口不同
CS建立在操作系统,表现方式有限
BS建立在浏览器上,表现方式丰富
8.信息流不同
CS中央集权的机械式处理
BS信息流向变化,像交易中心
AJAX(Asynchronous JavaScript And XML)
全称:异步JavaScript和Xml
核心
JS对象的XmlHttpRquest
优点
提高用户体验
提高应用程序性能
进行局部刷新
同步和异步的区别
同步时阻塞的,异步非阻塞的
同步当一个线程在使用,其他线程就必须一直等待锁
异步当前线程使用不影响其他线程,各行其事
跨域
协议、域名、端口都相同才是同域,否则就是跨域
如何解决跨域问题?
ajax的过程
乱码问题
会话跟踪技术
page
当前页上下文
request
本次请求
session
当前会话
application
当前应用程序
获取页面参数值的方式
getParameter()
返回请求的特定参数值
getParameterNames()
返回所有可用属性名的枚举
getParameterValues()
返回包含参数所有值的数组
Session的主要方法
getSession()创建session
setAtrribute()设置值
getAtrribute()获得值
invalidate()销毁session
设置session存活时间
web.xml
<session-config>
<session-timeout>15</session-timeout>
</session-config>
<session-timeout>15</session-timeout>
</session-config>
tomcat目录下conf/web.xml
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<session-timeout>30</session-timeout>
</session-config>
setMaxInactiveInterval()
优先级的问题,从高到低
方法>web.xml>外部web.xml
session的过期时间计算是从当前session的最后一次请求开始的
session的默认失效时间
Tomcat为30分钟
Get和Post的区别
1.get的请求参数会在地址栏显示出来,post请求参数在请求头里
2.get携带的数据不多,一般不超过2k,post理论上没有限制,但是不建议携带过多数据
3.get安全性较低但是执行效率快,post相反
4.get是向服务器索取数据,post是向服务器传送数据
JAVAWEB三大组件
Servlet
创建serlvlet的三种方式
实现Servlet接口
继承HttpServlet
使用Servlet注解
继承GenericServlet
Servlet生命周期
1.创建
创建一个Servlet实例
2.初始化
初始化数据,可以在we.xml配置
3.服务
请求和响应处理的方法
4.销毁
结束服务
2 注册
web.xml注册
servlet-->servlet-name,servlet-class servletmapping-->servletname,url-pattern
注解形式
@WebServlet
3 url的匹配形式有哪些?
1 精确匹配
1个servlet对应一个 url
2 后缀匹配
.do , .action, .html
3 全匹配
/*
/
/* 和 / 的区别 /* 会拦截 .jsp
Filter
filter用于拦截用户请求,在服务器作出响应前,可以在拦截后修改request和response,这样实现很多开发者想得到的功能。
filter:可以理解一个一种特殊Servlet,主要用于对用户请求进行预处理
Filter的生命周期
构造器:创建Filter实例时调用,Filter实例服务器一旦启动就会被创建
init():实例创建后马上被调用,用来对Filter做一些初始化的操作
doFilter():Filter的主要方法,用来完成过滤器主要功能的方法,每次访问目标资源时都会调用。
destroy():服务器停止时调用,用来释放资源。
方法
init
doFilter
chain.doFilter(request, response)
destroy
常见用途
处理全站中文乱码问题
实现自动登录
过滤敏感词汇
压缩网页
选择性让浏览器缓存
Listener
第一维度
被监听的对象
ServletRequest
HttpSession
ServletContext
第二维度
监听域对象的创建与销毁
application监听器
ServletContextListener
contextInitialized
在application创建时就调用
contextDestroyed
当application销毁时调用
session监听器
HttpSessionListener
sessionCreated
当打开一个浏览器时,就会触发这个方法
sessionDestroyed
当调用session.invalidate();或超时时调用
request监听器
ServletRequestListener
requestInitialized
request初始化
requestDestroyed
request销毁
监听域对象的属性变化
application属性监听器
ServletContextAttributeListener
attributeAdded
当调用application.setAttribute()时调用
attributeRemoved
当调用applcaition.removeAttribute()时调用
attributeReplaced
/当调用两次application.setAttribute()赋予相同属性时调用
session属性监听器
HttpSessionAttributeListener
attributeAdded
当调用session.setAttribute()时调用
attributeRemoved
当调用session.removeAttribute()时调用
attributeReplaced
当调用两次session.setAttribute()赋予相同属性时调用
request属性监听器
ServletRequestAttributeListener
attributeAdded
增加属性
attributeRemoved
属性删除
attributeReplaced
属性替换(第二次设置同一属性)
监听器常用的用途
统计在线人数,利用HttpSessionLisener
加载初始化信息:利用ServletContextListener
统计网站访问量
实现访问监控
框架web.xml中的监听器
ContextLoaderListener
JSP内置对象
Request
本次请求
Response
PageContext
当前页上下文
Session
当前会话
Application
当前应用程序
Out
Config
Page
转发和重定向的区别
1.转发是服务器端的行为,重定向是客户端的行为
2.转发在当前项目转发,而重定向可以在任何网站
3.转发发送一次请求,重定向发送两次请求,产生状态码302
4.转发url地址栏不变,而重定向会变
5.转发携带request对象的数据不会丢失,重定向会丢失
第六章:主流Java框架技术
Mybatis
什么是Mybatis
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射
优点
把SQL独立进XML,维护便利
封装底层JDBC,转化成java Bean对象,简化重复工作
能够完成复杂的查询
MyBatis则简化了传统JDBC方法,解决了:
①频繁链接和释放数据库的浪费、
②SQL语句与代码的高耦合、
③参数传递麻烦、
④结果解析麻烦。
执行流程
使用
UserDao userMapper = sqlSession.getMapper(UserDao.class);
1.Resource类加载核心配置文件
调用getResourceAsStream()
2.SqlSessionFactoryBuilder调用build()创建SqlSessionFactory
创建一个XMLConfigBuilder解析对象后返回configuration对象
解析过程中
Mapper接口及其映射文件是在加载mybatis-config配置文件的时候存储进去的
MyBatis都会将xml映射文件和Mapper接口进行关联
返回的就是一个实现了SqlSessionFactory接口的DefaultSqlSessionFactory对象
3.SqlSessionFactory调用openSession()创建SqlSession
生成Transaction对象和执行核心执行器Executor
最终创建了实现SqlSession接口的DefaultSqlSession对象
4.jdk动态代理生成mapper接口的代理对象MapperProxy
5.通过MapperProxy调用Maper中相应的方法
ORM
对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术
三个对象
SqlSession:获取数据库连接,一个request请求期间
SqlSessionFactory:创建SqlSession对象,应用的全局作用域
OpenSession() 创建SqlSession 实例,默认是 false ,关闭事物提交。
SqlSessionFactoryBuilder:创建SqlSessionFactory,局部变量
用过即丢
核心配置文件
properties属性
setting设置
自动映射
autoMappingBehavior
懒加载
lazyLoadingEnabled
aggressiveLazyLoading
二级缓存
cacheEnabled
日志
logImpl
typeAliases别名
alises type
<typeAlias alias="Author" type="domain.blog.Author"/>
package
<package name="domain.blog"/>
@Alias
@Alias("author")
plugin插件
分页插件
PageHelper原理
PageHelper方法使用了静态的ThreadLocal参数,分页参数和线程是绑定的
执行查询的时候通过拦截器在sql语句中添加分页参数,之后实现分页查询,查询结束后在 finally 语句中清除ThreadLocal中的查询参数
ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量
桥接模式
enviroment环境配置
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
databaseIdProvider数据库厂商标识
mappers映射器
resource
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
url
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
package
<package name="org.mybatis.builder"/>
class
<mapper class="org.mybatis.builder.AuthorMapper"/>
一对一、一对多、多对一、多对多
多对一就是一对一,多对多就是一对多
public class Shoporder {
private Integer sid;
private String sname;
private Double price;
private Integer num;
private Integer uid;
// 一对一
private User user;
}
public class User {
private Integer uid;
private String uname;
// 一对多
ArrayList<Shoporder> shoporders;
}
正常
一对一
<resultMap id="rm" type="shoporder">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<result property="price" column="price"/>
<result property="num" column="num"/>
<result property="uid" column="uid"/>
<association property="user" javaType="user">
<id property="uid" column="uid"/>
<result property="uname" column="uname"/>
</association>
</resultMap>
<select id="getAllById" resultMap="rm">
select * from shoporder s,user u where s.uid=u.uid and sid=#{sid}
</select>
一对多
<resultMap id="rm" type="user">
<id property="uid" column="uid"/>
<result property="uname" column="uname"/>
<collection property="shoporders" ofType="shoporder">
<id property="sid" column="sid"></id>
<result property="sname" column="sname"/>
<result property="price" column="price"/>
<result property="num" column="num"/>
<result property="uid" column="uid"/>
</collection>
</resultMap>
延迟加载:例如进行一对多查询的时候,只查询出一方,当程序中需要多方的数据时,mybatis再发出sql语句进行查询,这样子延迟加载就可以的减少数据库压力
select:指定延迟加载需要执行的statement的id(是根据user_id查询用户信息的statement)
column:订单信息中关联用户信息查询的列,是user_id
一对一延迟加载
<resultMap id="rm" type="shoporder">
<id property="sid" column="sid"/>
<result property="uid" column="uid"/>
<!-- 使用缓存-->
<association property="user" javaType="user" select="dao.UserDao.findByUserId" column="uid"/>
</resultMap>
<select id="getAllById" resultMap="rm">
select sname
from shoporder
where sid = #{sid}
</select>
<select id="findByUserId" resultType="pojo.User">
select * from user where uid=#{id}
</select>
一对多延迟加载
<resultMap id="rm" type="user">
<id property="uid" column="uid"/>
<result property="uname" column="uname"/>
<collection property="shoporders" ofType="shoporder" select="dao.ShoporderDao.getAllById" column="uid"/>
</resultMap>
<select id="findByUserId" resultMap="rm">
select * from user where uid=#{id}
</select>
<select id="getAllById" resultType="shoporder">
select * from shoporder where uid=#{uid}
</select>
映射文件
cache
resultMap
resultType
区别
resultType简单类型,resultMap复杂类型
resultType自动映射,resultMap自定义映射
insert
update
delete
select
传入参数
1.一个参数直接写
2.两个参数,arg0,arg1/parm1.parm2
3.两个参数,@Param()
4.两个参数,Map
5.两个参数,实体类
#{}和${}区别
1.#{}会预编译,${}是直接填充
2.#{}更安全,${}有SQL注入的危险
注解
@Select
@Update
@Delete
@Insert
sql
include
动态SQL
if
where
choose\when\otherwise
set
trim
foreach
一级缓存和二级缓存
进入一级缓存的查询流程前,先在CachingExecutor 进行二级缓存的查询
cache 标签有多个属性
eviction: 缓存回收策略
LRU - 最近最少回收,移除最长时间不被使用的对象(默认)
底层使用LinkedHashMap实现
FIFO - 先进先出,按照缓存进入的顺序来移除它们
底层使用队列来实现
SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象
WEAK - 弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象
二级缓存需要序列化对象
由于二级缓存是应用级缓存,可以跨线线程使用。当不同线程获取到同一个对象时,做不同操作时,就会产生不同的结果相互影响。获取到的对象进行反序列化,获得的对象ID不同,但是对象的值相同,这样不同的操作就不会相互干扰。
二级缓存失效的条件
第一次SqlSession 未提交
探究多表操作对二级缓存的影响
更新对二级缓存影响
使用二级缓存的注意点
缓存是以namespace为单位的,不同namespace下的操作互不影响。
insert,update,delete操作会清空所在namespace下的全部缓存。
通常使用MyBatis Generator生成的代码中,都是各个表独立的,每个表都有自己的namespace。
多表操作一定不要使用二级缓存,因为多表操作进行更新操作,一定会产生脏数据。
二级缓存需要手动开启
核心配置文件settings设置
映射文件加<cache/>标签
一级缓存是session级别的
是本地缓存,默认开启
四个失效条件
不同的session
同session,查询条件不同
同session,查询过程中进行了增删改
调用clearCache手动清除缓存
Spring
什么是Spring
Java企业级应用开源开发JavaEE框架
分支主题
Spring核心容器
Beans、Core、Context、Expression
core和beans模块提供了整个框架最基础的部分,包括了IOC和DI。
Context建立在Core和Beans模块提供的基础之上:他提供了框架式访问对象的方式
core、beans、context构成了Spring的骨架
Expression:提供了一种强大的用于在运行时操作对象的表达式语言
IOC
什么是IOC
把创建对象的权力转给spring容器,业务层与数据层高度耦合,减少耦合性
工厂模式
xml解析得到类名,通过反射创建对象返回
“控制反转”,不是什么技术,而是一种设计思想
创建bean对象
默认寻找无参构造器创建
依赖注入:注入bean和属性
实现IOC的两种方式
BeanFactory
Spring内部使用,开发一般不用
加载配置文件不创建对象,使用的时候创建
ApplicationContext
BeanFactory的子接口,面向开发人员使用
加载配置文件时就创建对象
IOC和DI的关系
其实它们是同一个概念的不同角度描述
IOC 是通过DI 来实现的,DI 是IOC 实现的手段
DI依赖注入
注入bean
普通Bean和工厂Bean
普通bean
定义什么类型返回什么类型
一般定义的都是普通bean
工厂bean
定义和返回的类型可以不一样
实现FactoryBean接口,实现方法Object,getObjectType,isSingleton
注入外部bean,用ref
注入内部bean,写在properties里
注入属性
set注入
普通字段注入
<bean id="student" class="com.xmx.pojo.Student">
<property name="sid" value="100"/>
<property name="sname" value="张三"/>
<property name="age" value="100"/>
</bean>
引用类型注入(类)
<bean id="teacher" class="com.xmx.pojo.Teacher">
<property name="tname" value="金平"/>
<property name="book" ref="book"/>
</bean>
自动注入(自动装配)
byName 按照名称注入
<bean id="xx" class="..." autowire="byName">
<property> </property>
</bean>
byType 按照类型注入
<bean id="xx" class="..." autowire="byType">
<property> </property>
</bean>
数组类型属性注入
<property name="array">
<array>
<value>arr1</value>
<value>arr2</value>
</array>
</property>
list类型属性注入
<property name="list">
<list>
<value>list1</value>
<value>list2</value>
</list>
</property>
集合注入的提取
1.引入命名空间util
2.定义<util:list id="a"></util:list>
3.使用<properties ref="a">
map类型属性注入
<property name="map">
<map>
<entry key="map-key1" value="map-value1"></entry>
<entry key="map-key2" value="map-value2"></entry>
</map>
</property>
<map>
<entry key="map-key1" value="map-value1"></entry>
<entry key="map-key2" value="map-value2"></entry>
</map>
</property>
set类型属性注入
<property name="set">
<set>
<value>set1</value>
<value>set2</value>
</set>
</property>
使用<value></value>注入空字符串值
使用<null/>注入null值
构造方法注入(以有参构造函数注入)
<bean id="student" class="com.xmx.pojo.Student">
<constructor-arg index="0" value="1"/>
<constructor-arg index="1" value="张三"/>
</bean>
p 命名空间 注入
xml头加约束,p命名空间
<bean id="book" class="com.xmx.pojo.Book" p:bid="111" p:bname="平凡的世界">
</bean>
底层用到set方式注入
bean的作用域
singleton(默认)
当Bean的作用域为singleton的时候,Spring容器中只会存在一个共享的Bean实例
加载xml配置文件创建
prototype
每次对该Bean请求的时候,Spring IoC都会创建一个新的作用域。
调用getBean时创建
request
Request作用域针对的是每次的Http请求,Spring容器会根据相关的Bean的定义来创建一个全新的Bean实例
session
针对http session起作用,Spring容器会根据该Bean的定义来创建一个全新的Bean的实例
global session
类似标准的http session作用域,不过仅仅在基于portlet的web应用当中才有意义
bean的生命周期
1.通过无参构造器创建bean对象
2.为bean对象属性设置值或其他bean引用
调用set方法
3.把bean实例传给后置处理器的方法postProcessBeforeInitialization
创建后置处理器
实现BeanPostProcessor接口,实现方法
4.调用bean初始化的方法
需要配置初始化方法
init-method="自定义初始化 方法名"
5.bean对象获取到
6.把bean实例传给后置处理器的方法postProcessAfterInitialization
7.容器关闭,调用bean销毁方法
需要配置销毁方法
destory-method="自定义销毁方法名"
手动销毁bean
((ClassPathXmlApplicationContext)context).close()
xml加载properties文件
1.引入context命名空间头
2.使用标签引入
<context:property-placeholder location="url"/>
3.读取
${key}
使用注解(类、方法、属性上)
1.引入aop依赖
2.引入context命名空间头
3.开启注解扫描
<context:component-scan base-package="url"/>
自定义扫描过滤器
注解类型
原生注解
创建bean对象
@Component
实现Bean组件的定义, 不知道 用什么注解
@Reposity
用于标注Dao类
@Service
用于标注业务类
@Controller
用于标注控制器类
@Scope
标注bean的作用范围
注入属性
@Value
@Autowired\@Qualifer
不需要加set方法,封装好了
@Resource
区别
1 Autowired是 Spring的注解,Resources是Java的注解
2 Autowired首先按照类型匹配,Resources首先按照名称匹配
3 Autowired根据名称匹配,要配合@Qualifier注解,Resources配合自己的name属性
新注解(完全注解开发)
@Configuration
指定当前类是 一个Spring 配置类, 当创建容器时,从该类上 加载注解(相当于beans的配置文件)
@ComponentScan(base-Packages="")
指定Spring初始化 扫描的包 (相当于下面的扫描语句)<context:component-scan base-package="com.lpc"/>
@Bean
把返回的对象放在配置文件beans中
@PropertySource
加载 .properties 文件
@Import
导入其他配置类
Spring事务
事务
事务一般加在服务Service层
原子性
隔离性
一致性
持久性
编程式事务
PlatformTransactionManager
TransactionDefinition(推荐)
与声明式事务的区别
声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,而编程式事务则需要在代码里进行增加事务代码逻辑
声明式事务
底层是AOP
对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务
可以设置属性
propagation:事务传播机制
REQUIRED(默认值)==required
方法本身有事务,使用该事务,否则创建新的事务
REQUIRES_NEWS==required_new
无论方法本身有无事务,都创建新的事物
MANDATORY==mandatory
使用本身事务,若无抛异常
SUPPORTS==supports
使用本身事务,若无则以无事务执行
NOT_SUPPORTED==not_supported
无论本身有无事务,都以无事务执行
NEVER==never
以无事务执行,有事务抛异常
NESTED==nested
本身有事务则在嵌套事务执行,否则如required操作
事务隔级别
读未提交
读已提交
可重复读
mysql默认
可串行化
事务超时
timeout,一定时间内需要提交,否则回滚,默认-1,不超时
是否只读
readOlny
默认false
设置true,只能做查询操作
rollbackFor
设置哪些异常可以进行回滚
norollbackFor
设置哪些异常不进行回滚
注解配置
步骤
定义数据源
定义事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
导入tx的命名空间头
开启注解
<tx:annotation-driven />
在service类或方法添加事务注解@Transactional
使用try-catch包裹代码,如果异常则会回滚
完全注解形式
@Configuration
相当于spring容器
@EnableTransactionaManagement
替换 <tx:annotation-driven />
@Bean
注入bean
可替换数据源注入和事务管理器注入
XML配置
步骤
导入tx的命名空间头
2 定义事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
3 设置事务属性
<!-- 配置事务传播特性 -->
<tx:advice id="txAdvice">
<tx:attributes>
<!-- <tx:method name="get*" propagation="SUPPORTS" />-->
<!-- <tx:method name="add*" propagation="REQUIRED" />-->
<!-- <tx:method name="del*" propagation="REQUIRED" />-->
<!-- <tx:method name="update*" propagation="REQUIRED" />-->
<!-- propagation事务的隔离级别-->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
4 定义事务切面
<!-- 定义切面 -->
<aop:config>
<aop:pointcut id="serviceMethod" expression="execution(* lpc.service..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod"/>
</aop:config>
JDBCTemplate
概念:spring框架对JDBC进行封装
1.引入依赖
mysql、druid、spring-jdbc、spring-tx、spring-orm
2.配置数据库连接池DataSource
3.配置JdbcTemplate,注入DataSource
4.开启注解扫描
5.类上注入JdbcTemplate
6.使用JdbcTemplate
AOP
基本概念
面向切面编程,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面
不改变原代码,在原有方法代码上添加新功能
设计原则:开闭原则
底层原理
动态代理
有接口
JDK动态代理
特点:
JDK动态代理只能对实现了接口的类生成代理,而不能针对类
Proxy(代理类)
InvocationHandler(接口)
无j接口
CGlib
1 引入jar包
特点:CGLib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)
JDK动态代理和CGlib的有什么区别?
1 JDK动态JDK直接支持,CGLib第三方jar包支持
2 JDK动态是必须要有接口才能代理,2 CGlib代理类是目标类的子类,可以针对于普通类做代理。但是不能是final的类。
3 JDK1.6之前,CGLib底层采用ASM字节码生成框架,效率比JDK动态代理要高
JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,
JDK1.8的时候,JDK代理效率高于CGLib代理
JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,
JDK1.8的时候,JDK代理效率高于CGLib代理
切面、切入点、连接点、目标对象、增强对象、代理对象、织入
切面
一个模块化的横切逻辑
连接点
某个切面具体执行点(哪些方法可以被增强,这些方法就叫连接点)
切入点
连接点的代码逻辑(正则匹配连接方法,实际真正被增强的方法)
目标对象
被切的、被增强的对象
增强对象
在执行点上执行的代码逻辑
代理对象
AOP自动创建,执行增强的方法
织入
切面的过程
AspectJ+Spring
不是Spring框架部分,是AOP框架
导入依赖
Spring依赖
aop和aspects依赖
AspectJ依赖
cglib、aopalliance、aspectj.weaver依赖
切入点表达式
execution(public * *(..))
匹配所有目标类的public方法,第一个*代表返回类型,第二个*代表方法名,而..代表任意入参的方法;
execution(* com.baobaotao.Waiter.*(..))
第一个*代表返回任意类型 *代表Waiter类口中的所有方法;
execution(* com.baobaotao.*.*(..))
匹配com.baobaotao包下所有类的所有方法;
execution(* com.baobaotao..*.*(..))
匹配com.baobaotao包、子孙包下所有类的所有方法
execution(* com..*Dao.find*(..))
com的任何包下类名后缀为Dao的类,方法名必须以find为前缀
配置方式
注解
1.开启注解扫描
<context:component-scan base-package="url"/>
2.创建增强类和被增强类
3.增强类上加@Aspect生成代理对象
开启aop生成代理对象
<aop:aspectj-autoproxy/>
4.配置不同的增强类型
@Before(value="切入点表达式")
前置增强
@AfterReturning
后置增强
@AfterThrowing
异常增强
@After
最终增强
@Around
环绕增强
切入点表达式相同,对切入点提取
@Pointcut(value="切入点表达式")定义在方法上
其他增强注解引入该方法名()
如:@Before(value="a()")
@Order(1)越小优先级越高
决定增强类的优先级
完全注解开发
@EnableAspectJAutoProxy(proxyTargetClass=true)
开启aop生成代理对象,替换<aop:aspectj-autoproxy/>
xml配置
前置增强
aop:before
后置增强
aop:after-returning
异常增强
aop:after-throwing
最终增强
aop:after
环绕增强
aop:around
面试题
Bean的生命周期
实例化一个Bean
也就是我们常说的new
对实例化的Bean进行配置
也就是IOC注入
Bean初始化
Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法
Bean的调用
使用BeanWrapper
使用BeanFactory
使用ApplicationContext
Bean销毁
使用配置文件中的 destory-method 属性
实现 org.springframwork.bean.factory.DisposebleBean接口
SpringMvc
SpringMVC
MVC模式
是什么
springMVC是spring框架的WEB模块
视图(View)-对应组件:JSP或者HTML文件
控制器(Controller)-对应组件:Servlet
模型(Model)-对应组件:JavaBean
优缺点
MVC优点
MVC三个模块相互独立,松耦合架构
多视图共享一个模型,大大提高代码的可重用性
控制器提高了应用程序的灵活性和可配置性
MVC缺点
有利于软件工程化管理
增加了系统结构和实现的复杂性,不适合小型规模的项目
视图层与模型之间需要控制器做中间的连接控制,所以效率较低
运行流程
前端控制器DispatcherServlet
继承FrameworkServlet
继承HttpServletBean
继承HttpServlet
有doGet和doPost方法
里面有processRequest
调用doService
调用doDispatch
调用doDispatch
getHeader()
返回HandlerMapping
根据当前地址找到能处理请求的类
Handler
getHeaderAdapter()
根据处理类找到能处理该类的适配器
HandlerAdapter
适配器执行目标方法
执行后返回一个ModelAndView
根据ModelAndView信息转发到指定页面,并拿到数据
分支主题
XML文件方式
运行流程
前端控制器接收到所有请求
前端控制器匹配@RequestMapping,从而找到对应的执行类
前端控制器找到目标类和方法,并返回方法返回值
返回值是一个地址,再由视图解析器解析
前端控制器帮我们转发到指定地址
1 导入依赖
核心容器
bean
context
core
expression
支持注解
aop
日志
log4j
web模块
web
webmvc
2. 编写 Springmvc的核心文件.
<context:component-scan base-package="com.xmx"/>
扫描所有组件
配置视图解析器
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/"/>
<property name="suffix" value=".html"/>
</bean>
3.配置 web.xml
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<!-- 加载Spring MVC配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
拦截所有请求
/
不拦截jsp,拦截其他
/*
拦截jsp及所有
默认读取
WEB-INF/前端控制器名-servlet.xml(servlet-name)
前端控制器
拦截所有请求,智能派发
是一个servlet
DispatcherServlet
4.编写 Controller
public class HelloController extends AbstractController {
protected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse resp) throws Exception {
System.out.println("进了");
return null;
}
}
控制器
方法上注解
@RequestMapping
value
地址
可以模糊匹配
?
匹配任意一个字符
*
匹配任意多个字符
**
匹配多层路径
精确匹配优先于模糊匹配
method
请求方式
RequestMethod.GET
params
param
必须包含该参数
!param
必须不包含该参数
param!=value
不带该参数或该参数值必须不是value
param=value
该参数值必须为value
headers
规定请求头
consumes
规定请求头里的Context-Type的类型
produces
告诉浏览器返回的内容,给响应头加Context-Type
Springmvc也能在session域对象中出参,request域也存一份
@SessionAttribute(value="",types={String.class})
方法参数
传入参数
Request 请求对象方式,getParameter
传入参数和 参数一致,直接写参数名
POJO对象的形式
参数名不一致是: @RequestParam
value
参数名
required=true
参数必须有,false反之
defaultValue
设置默认值
路径变量 @PathVariable获取路径中的参数接收
@PathVariable
占位符
支持REST风格
资源表现层状态转化,以简洁的url地址发请求
软件架构思想
请求方式区分
GET
查
POST
增
PUT
改
DELETE
删
@RequestHeader
获取请求头key值
@CookieValue
获取某个cookie的key值
可以写原生API
HttpServletRequest
HttpServletResponse
HttpSession
方法体内
数据输出(出参)
不管哪一种都是BindingAwareModelMap在工作
使用Model对象
addAttribute()设置参数
使用Map 对象
map.put()
使用ModelMap对象
addAttribute()设置参数
Springmvc会出参数据存放到request域对象中
方法返回值
使用ModelAndView对象
addObject()
出参
setViewName()
视图和数据合并方式
注解方式
1 导入依赖
2 配置 web.xml
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<!-- 加载Spring MVC配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
3 编写 Controller
@RequestMapping("/login")
public String hello() {
return "login";
}
4 编写 Springmvc的核心文件.
<mvc:annotation-driven/>
<context:component-scan base-package="com.xmx.*" />
<mvc:default-servlet-handler />静态页面,如html,css,js,images可以访问:
SpringMVC静态资源引入
<mvc:default-servlet-handler />
<mvc:default-servlet-handler default-servlet-name="所使用的Web服务器默认使用的Servlet名称" />
<mvc:resources />
<mvc:resources mapping="/static/**" location="/static/" />
SpringMVC 拦截器
xml形式
日志拦截器
<mvc:interceptors>
<!-- 日志拦截器 -->
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/static/**" />
<bean class="拦截器java代码路径" />
</mvc:interceptor>
</mvc:interceptors>
拦截请求
<mvc:interceptors>
<!-- 非法登录拦截器 -->
<mvc:interceptor>
<!--拦截器匹配哪些请求-->
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/login.html"/>
<mvc:exclude-mapping path="/css/*"/>
<mvc:exclude-mapping path="/img/*"/>
<mvc:exclude-mapping path="/user/login"/>
<bean class="com.xmx.config.MyInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
java类配置
要实现拦截器,要继承 HandlerInterceptor
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
正常情况下,对于preHandle就是在在处理函数之前先执行,然后再执行处理函数,接着执行postHandle,最后再执行afterCompletion。afterCompletion无论是否出错是肯定要执行的,而postHandle则不是,不一定会执行。之后看源代码就知道他们的执行情况。
AsyncHandlerInterceptor接口则增添了afterConcurrentHandlingStarted方法,对于此还未研究,先不讨论。
HandlerInterceptorAdapter则默认实现了上述的接口,所以当我们仅仅要实现某个方法时,只需继承HandlerInterceptorAdapter,然后覆盖相应的方法。
异常处理
/**
* 捕获异常信息,跳转到error页面
* @param e
* @param req
* @return
*/
@ExceptionHandler(value={Exception.class})
public String handlerException(Exception e, HttpServletRequest req){
req.setAttribute("e", e);
return "error";
}
JSR303校验
SSM整合
1 导入需要的 jar包,依赖。
springmvc/spring
javax.servlet-api
spring-webmvc
spring-tx
spring AOP
aopalliance
aspectjweaver
mysql连接驱动
mysql-connector-java
连接池
c3p0
commons-dbcp2
mybatis-spring
spring-jdbc
mybatis
mybatis-spring
lombok
log4j
junit
json
jackson-core
jackson-databind
jackson-annotations
pageHelper
build打包
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
2 导入 各框架的核心配置文件
mybatis-config.xml
application-context.xml
springmvc-servlet.xml
database.properties
log4j.properties
4 各配置文件的细节
mybatis-config.xml
配置设置,别名和插件
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
<typeAliases>
<package name="com.xmx.pojo"/>
</typeAliases>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="reasonable" value="true"/>
</plugin>
</plugins>
application-context.xml
<context:component-scan base-package="com.xmx.*"/>
<!-- 读取数据库配置文件 -->
<context:property-placeholder location="classpath:database.properties"/>
<!-- 获取数据源(使用dbcp连接池) -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close" scope="singleton">
<property name="driverClassName" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${user}" />
<property name="password" value="${password}" />
</bean>
<!-- 事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置mybitas SqlSessionFactoryBean-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.xmx.dao" />
</bean>
springmvc-servlet.xml
<context:component-scan base-package="com.xmx.controller"/>
<!--springmvc注解-->
<mvc:annotation-driven/>
<!--springmvc放行静态资源-->
<mvc:resources mapping="/static/**" location="/static/"/>
<!--静态页面,如html,css,js,images可以访问-->
<mvc:default-servlet-handler/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/"/>
<property name="suffix" value=".html"/>
</bean>
database.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/others?useUnicode=true&characterEncoding=utf8&useSSL=false&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=UTC&allowPublicKeyRetrieval=true
user=root
password=123456
log4j.properties
log4j.rootLogger=DEBUG,CONSOLE,file
#log4j.rootLogger=ERROR,ROLLING_FILE
log4j.logger.cn.cvs.dao=debug
log4j.logger.com.ibatis=debug
log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=debug
log4j.logger.com.ibatis.common.jdbc.ScriptRunner=debug
log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=debug
log4j.logger.java.sql.Connection=debug
log4j.logger.java.sql.Statement=debug
log4j.logger.java.sql.PreparedStatement=debug
log4j.logger.java.sql.ResultSet=debug
log4j.logger.org.tuckey.web.filters.urlrewrite.UrlRewriteFilter=debug
# Console Appender 日志在控制输出配置
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.Threshold=error
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=[%p] %d %c - %m%n
# DailyRolling File 每天产生一个日志文件,文件名格式:log2009-09-11
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.DatePattern=yyyy-MM-dd
log4j.appender.file.File=log.log
log4j.appender.file.Append=true
log4j.appender.file.Threshold=error
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-M-d HH:mm:ss}%x[%5p](%F:%L) %m%n
log4j.logger.com.opensymphony.xwork2=error
3 Spring 和 Springmvc 配置进web.xml文件
Spring部分
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application-context.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Springmvc部分
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
SpringBoot
什么是SpringBoot
SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架
能快速创建出生产级别的Spring应用
开箱即用
自动装配
内嵌式容器简化Web项目
Tomcat
Jetty
约定大于配置
Starter启动器,简化依赖问题
整合spring生态圈的一站式框架
第一个SpringBoot项目
1.导入父依赖
2.导入spring-boot-starter-web依赖
3.创建Springboot启动类
@SpringBootApplication
4.写业务代码
pojo\dao\service\controller
5.运行项目
SpringBoot的核心注解
@SpringBootConfiguration
组合了 @Configuration 注解,实现配置文件的功能
@EnableAutoConfiguration
打开自动配置的功能,也可以关闭某个自动配置的选项
@ComponentScan
Spring组件扫描
使用springboot
继承spring-boot-starter-parent项目
导入spring-boot-dependencies项目依赖
Starters启动器
包含了一系列可以集成到应用里面的依赖包
Spring Boot 自动配置原理
@EnableAutoConfiguration
从里面的@Import注解里的类AutoConfigurationImportSelector
找到它类里的SpringFactoriesLoader类,有loadFactoryNames方法
这个方法会加载类路径及所有jar包下META-INF/spring.factories配置中映射的自动配置的类
SpringBoot 日志原理
日志门面(接口)
JCL
SLF4J
jboss-logging
日志(实现)
log4j
util.logging
log4j2
logback
日志选用
SpringBoot底层是Spring框架,
Spring框架默认使用的是 JCL(Commons Logging )
SpringBoot 选用的是SLF4j和 logback.
SLF4j的使用(slf4j+ logBack)
配置文件
logging.level.root=WARN
logging.level.com.lpc=trace
logging.file.path=mylog
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} == %contextName == [%thread] %-5level %logger{36} - %msg%n
日志的级别
trace
debug
info
warn
error
fatal
连接池
连接池的作用就是为了提高性能
默认连接池HikariCP
性能方面 hikariCP>druid>tomcat-jdbc>dbcp>c3p0
druid功能最为全面,sql拦截等功能,统计数据较为全面,具有良好的扩展性。
综合性能,扩展性等方面,可考虑使用druid或者hikariCP连接池。
连接池常用配置
dataSourceClassName
驱动名
jdbcUrl
URL指定的驱动程序参数
username
获取连接时使用的默认身份验证用户名
password
获取连接时使用的默认身份验证密码
maxWait
等待来自池的连接的最大毫秒数
idleTimeout
控制允许连接在池中闲置的最长时间
maxActive
连接池支持的最大连接数
maxIdle
连接池中最大空闲连接数
minIdle
连接池中最小空闲连接数
initialSize
初始化连接数目
defaultAutoCommit
所创建的连接的自动提交(auto-commit)状态
Redis
定义
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI [C语言](编写、支持网络、可基于内存亦可持久化的日志型、Key-Value[数据库],并提供多种语言的API。称之为结构化数据库
作用
1.内存存储\持久化,内存中是断电即使,持久化很重要(rdb\aof)
2.效率高,用于高速缓存
3.发布订阅系统
4.地图信息分析
5.计时器\计数器
类型
五大基本数据类型
String
List
Set
Hash
Zset
三大特殊数据类型
geospatial
Hyperloglog
Bitmaps
Redis持久化
RDB(Redis Data Base)
什么是RDB?
在指定时间间隔里将内存中的数据集快照写入磁盘,恢复时,将快照文件直接读到内存。
redis默认是RDB,一般不需要修改这个配置。
RDB保存文件是dump.rdb
自动生成dump.rdb文件,触发机制
1.满足配置里save规则,自动触发
save 3600 1
2.执行了flushall,自动触发
3.退出redis,自动触发
恢复RDB文件
1.只需要把rdb文件放到redis的启动目录即可。
2.查看需要存在的位置:config get 目录(该目录存在drump.rdb)
在主从复制中,rdb是备用的,在从机上。
优点:如果对数据完整性要求不高,适合大规模的数据恢复。
缺点:需要一定的时间间隔进程操作,如果redis意外宕机,最后一次修改就没了。fork进程的时候,会占用一定内容空间。
AOF(Append Only File)
默认不开启的。
将我们的所有命令都记录下来,history,恢复的时候把这个文件全部再执行一遍。
AOF保存的是appendonly.aof,默认无限添加。
开启:appendonly yes
重启redis服务即可生效
如果aof文件有错位,这时候redis启动不起来,我们需要修复这个aof,redis给我们提供了redis-check-aof修复工具。
命令:redis-check-aof --fix appendonly.aof
优点:每一次修改都同步,文件完整性更加好,每秒同步一次,可能会丢失1s数据。
缺点:相对于数据文件来说,aof远远大于rdb,修复速度也比rdb慢,aof运行效率比rbd慢。
Redis配置文件
单位:大小写不敏感
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
包含别的配置文件
# include /path/to/local.conf
# include /path/to/other.conf
网络
bind 192.168.1.100 10.0.0.1 # listens on two specific IPv4 addresses
# bind 127.0.0.1 ::1 # listens on loopback IPv4 and IPv6
# bind * -::* # like the default, all available interfaces
安全模式
protected-mode no
端口
port 6379
通用GENERAL
以守护进程的方式,默认是no,我们需要自己开启yes
daemonize yes
配置文件的pid文件,以后台方式运行
pidfile /var/run/redis_6379.pid
日志级别
# verbose (many rarely useful info, but not a mess like the debug level)
# debug (a lot of information, useful for development/testing)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
日志文件名
logfile ""
默认16个数据库
databases 16
是否显示logo
always-show-logo no
SNAPSHOTTING快照
SNAPSHOTTING快照:持久化,在规定时间内,执行了多少操作,会被持久化到文件.rda .aof
在3600s内,如果至少有1个key进行了修改,我们进行持久化操作
# save 3600 1
在300s内,如果至少有100个key进行了修改,我们进行持久化操作
# save 300 100
在60s内,如果至少有10000个key进行了修改,我们进行持久化操作
# save 60 10000
持久化出错是否继续工作
stop-writes-on-bgsave-error no
是否压缩rdb文件,需要消耗一些cpu资源
rdbcompression yes
保持rdb文件时,进行错误校验
rdbchecksum yes
rdb文件保持的目录
dir ./
REPLICATION复制
SECURITY安全
设置密码
requirepass foobared xxx
CLIENTS 客户端限制
能连接的最大客户端数量
maxclients 10000
MEMORY MANAGEMENT内存
设置最大的内存容量
maxmemory <bytes>
内存达到上限的处理策略
maxmemory-policy noeviction
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random:随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
APPEND ONLY MODEAOF的配置
APPEND ONLY MODE AOF的配置:默认不开启aof持久化,默认rdb,一般rdb就够用了。
开启aof
appendonly no
持久化的文件的名字
appendfilename "appendonly.aof"
appendfsync everysec 每秒执行一次,可能会丢失这1s的数据
# appendfsync always 每次修改都同步,消耗性能
# appendfsync no 不同步,操作系统自己同步数据,速度最快
常用命令
Collection
ping
测试连接
select index
选择数据库
quit
关闭客户端
auth
服务端验证
Server
flushdb
清空当前数据库
flushall
清空所有数据库
shutdown
关闭服务器
Keys
del key
删除key
exists key
查询是否存在key,存在返回1,不存在返回0
expire key 10
设置key生效时间(秒)
keys *
查询所有的key
presist key 移除key的过期时间
randownkey
返回随机的key
rename key
重命名key
String
append key value
字符串追加
decr key
自减
decr key 2
减少步长
incr key
自增
incrby key 2
增加步长
get key
获得key的值
getrange key start end
获取key的范围值
getset key value
设置key值并获取的是key前一个值
set key value
设置一个key-value
setnx key value
key不存在,设置key,存在不设置
mset k1 v1 k2 v2...
设置多个key-value
mget k1 k2...
获取多个key的value
msetnx k1 v1 k2 v2...
对应给定的keys到他们相应的values上。只要有一个key已经存在,MSETNX一个操作都不会执行
strlen
返回value的字符串长度
List
头部设置list值,命令:lpush list xxx
尾部设置list值,命令:rpush list xxx
通过区间获取值,命令:lrange list 0 -1
头部移除list值,命令:lpop list x(个数)
尾部移除list值,命令:rpop list x(个数)
获取单个值,命令:lindex list x(下标)
查看长度,命令:llen list
移除指定的值,命令:lrem list x(个数) 值,lrem list 2 b
截取,list会被改变,命令:ltrim list start end(从0开始)
组合命令,从旧list尾部移除,到新list头部添加,命令:rpoplpush 当前list 新的list
可以更新list的值,前提是list存在元素,命令:lset list 0(下标) xxx
判断一个list是否存在,命令:exists list
linsert list before/after ,list值 插入值,before头部,after尾部
Set
设置set值,命令:sadd set xxx
查看set值,命令:smembers set
判断是否存在指定元素,命令:sismember set xxx
查看set的长度,命令:scard set
移除set的值,命令:srem set xxx
随机选取n个值,不写x默认一个,命令:srandmember set (x)
随机移除元素,命令:spop set x(个数)
将一个set的指定的值(kkk)移到新的set,命令:smove set set2 kkk
差集:sdiff set set2
交集:sinter set set2
并集:sunion set set2
Hash
设置hashmap值,命令:hset hash key value
获得hashmap值,命令:hget hash key
批量设置,命令:hmset hash k1 v1 k2 v2
批量获取,命令:hmget hash k1 k2
获取所有的数据,命令:hgetall hash
删除指定的key,对应的value值也没了,命令:hdel hash key
获取hash的长度,命令:hlen hash
判断hash中指定字段是否存在,命令:hexists hash key
获取所有的key名,命令:hkeys hash
增加,命令:hincrby hash key value
减少,命令:hdecrby hash key value
存在不设置,不存在设置,命令:hsetnx hash key value
Sorted Set
设置zset的值,命令:zadd zset 1(score) one
遍历zset的值,命令:zrange zset 0 -1
从小到大排序,命令:zrangebyscore zset -inf(负无穷) +inf(正无穷) withscores
范围:-inf(负无穷) +inf(正无穷),可以随便设置(-inf-2500)
移除元素,命令:命令:zrem zset key
查看zset的长度,命令:zcard zset
从大到小排序,命令:zrevrange zset 0 -1
获取区间的数量,命令:zcount zset 1 2
事务
Watch(乐观锁)
发布订阅
Redis主从复制
面试题
缓存雪崩
通常,我们会使用缓存用于缓冲对 DB 的冲击,如果缓存宕机,所有请求将直接打在 DB,造成 DB 宕机——从而导致整个系统宕机。
如何解决呢?
对缓存做高可用,防止缓存宕机
使用断路器,如果缓存宕机,为了防止系统全部宕机,限制部分流量进入 DB,保证部分可用,其余的请求返回断路器的默认值
解释 1:缓存查询一个没有的 key,同时数据库也没有,如果黑客大量的使用这种方式,那么就会导致 DB 宕机
解决方案:我们可以使用一个默认值来防止,例如,当访问一个不存在的 key,然后再去访问数据库,还是没有,那么就在缓存里放一个占位符,下次来的时候,检查这个占位符,如果发生时占位符,就不去数据库查询了,防止 DB 宕机。
解释 2:大量请求查询一个刚刚失效的 key,导致 DB 压力倍增,可能导致宕机,但实际上,查询的都是相同的数据
解决方案:可以在这些请求代码加上双重检查锁。但是那个阶段的请求会变慢。不过总比 DB 宕机好。
缓存并发竞争
解释:多个客户端写一个 key,如果顺序错了,数据就不对了。但是顺序我们无法控制。
解决方案:使用分布式锁,例如 zk,同时加入数据的时间戳。同一时刻,只有抢到锁的客户端才能写入,同时,写入时,比较当前数据的时间戳和缓存中数据的时间戳。
分布式锁常见的三种实现方式
数据库乐观锁;
基于Redis的分布式锁;
互斥性
在任意时刻,只有一个客户端能持有锁。
不能死锁
客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁
只要大部分的Redis节点正常运行,客户端就可以加锁和解锁
容错性
set key value px milliseconds nx
通过Lua脚本实现解锁
基于ZooKeeper的分布式锁。
缓存和数据库双写不一致
解释:连续写数据库和缓存,但是操作期间,出现并发了,数据不一致了。
先更新数据库,再更新缓存。
这么做的问题是:当有 2 个请求同时更新数据,那么如果不使用分布式锁,将无法控制最后缓存的值到底是多少。也就是并发写的时候有问题
先删缓存,再更新数据库。
这么做的问题:如果在删除缓存后,有客户端读数据,将可能读到旧数据,并有可能设置到缓存中,导致缓存中的数据一直是老数据。
使用“双删”,即删更删,最后一步的删除作为异步操作,就是防止有客户端读取的时候设置了旧值。
使用队列,当这个 key 不存在时,将其放入队列,串行执行,必须等到更新数据库完毕才能读取数据。
使用队列,当这个 key 不存在时,将其放入队列,串行执行,必须等到更新数据库完毕才能读取数据。
先更新数据库,再删除缓存。
这个实际是常用的方案,但是有很多人不知道,这里介绍一下,这个叫 Cache Aside Pattern,老外发明的。如果先更新数据库,再删除缓存,那么就会出现更新数据库之前有瞬间数据不是很及时。同时,如果在更新之前,缓存刚好失效了,读客户端有可能读到旧值,然后在写客户端删除结束后再次设置了旧值,非常巧合的情况。
有 2 个前提条件:缓存在写之前的时候失效,同时,在写客户度删除操作结束后,放置旧数据 —— 也就是读比写慢。设置有的写操作还会锁表。
所以,这个很难出现,但是如果出现了怎么办?使用双删!!!记录更新期间有没有客户端读数据库,如果有,在更新完数据库之后,执行延迟删除。
还有一种可能,如果执行更新数据库,准备执行删除缓存时,服务挂了,执行删除失败怎么办???
这就坑了!!!不过可以通过订阅数据库的 binlog 来删除。
还有一种可能,如果执行更新数据库,准备执行删除缓存时,服务挂了,执行删除失败怎么办???
这就坑了!!!不过可以通过订阅数据库的 binlog 来删除。
Redis 的过期策略
定期删除策略
Redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,默认每 100ms 进行一次过期扫描
随机抽取 20 个 key
删除这 20 个key中过期的key
如果过期的 key 比例超过 1/4,就重复步骤 1,继续删除。
Redis 是单线程,全部扫描岂不是卡死了。而且为了防止每次扫描过期的 key 比例都超过 1/4,导致不停循环卡死线程,Redis 为每次扫描添加了上限时间,默认是 25ms
从库的过期策略
从库不会进行过期扫描,从库对过期的处理是被动的。主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令来删除过期的 key
懒惰删除策略
删除指令 del 会直接释放对象的内存,大部分情况下,这个指令非常快,没有明显延迟。不过如果删除的 key 是一个非常大的对象,比如一个包含了千万元素的 hash,又或者在使用 FLUSHDB 和 FLUSHALL 删除包含大量键的数据库时,那么删除操作就会导致单线程卡顿。
unlink 指令,它能对删除操作进行懒处理,丢给后台线程来异步回收内存
Redis采用的是定期删除 + 懒惰删除策略。
Redis 内存满了怎么办?
1、通过配置文件配置
配置最大占用
2、通过命令修改
config set maxmemory 100mb
Redis的内存淘汰
noeviction(默认策略):对于写请求不再提供服务,直接返回错误(DEL请求和部分特殊请求除外)
allkeys-lru:从所有key中使用LRU算法进行淘汰
volatile-lru:从设置了过期时间的key中使用LRU算法进行淘汰
allkeys-random:从所有key中随机淘汰数据
volatile-random:从设置了过期时间的key中随机淘汰
volatile-ttl:在设置了过期时间的key中,根据key的过期时间进行淘汰,越早过期的越优先被淘汰
当使用volatile-lru、volatile-random、volatile-ttl这三种策略时,如果没有key可以被淘汰,则和noeviction一样返回错误
设置淘汰策略
获取当前内存淘汰策略
config get maxmemory-policy
通过配置文件设置淘汰策略
maxmemory-policy allkeys-lru
通过命令修改淘汰策略
config set maxmemory-policy allkeys-lru
LRU算法
即最近最少使用,是一种缓存置换算法
如果一个数据在最近一段时间没有被用到,那么将来被使用到的可能性也很小,所以就可以被淘汰掉
Redis使用的是近似LRU算法,它跟常规的LRU算法还不太一样。近似LRU算法通过随机采样法淘汰数据,每次随机出5(默认)个key,从里面淘汰掉最近最少使用的key
SpringCloud
什么是微服务
微服务架构风格是一种将单个应用程序作为一套小型服务开发的方法,每种应用程序都在自己的进程中运行,并与轻量级机制(通常是HTTP资源API)进行通信。
Java架构的演变
单体架构
传统构架是部署在一个tomcat上的,Tomcat 默认配置的最大请求数是 150,也就是说同时支持 150 个并发,当某个应用拥有 250 个以上并发的时候,应考虑应用服务器的集群。因此当用户达到一定数量的时候就要考虑到集群。
集群架构
通过nginx代理,假设每台服务器能支持150的并发,上面的图中能最大支持300并发。但是配置集群最大的问题就是session共享问题,tomcat的节点越多,它们之间的关系就越复杂。当tomcat集群中节点数量增加,服务能力先增加后下降。所以集群中节点数量不能太多,一般也就5个左右。
垂直结构
根据业务进行拆分,添加服务层
SOA架构
SOA:Service Oriented Architecture面向服务的架构。也就是把工程拆分成服务层、表现层两个工程。服务层中包含业务逻辑,只需要对外提供服务即可。表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现。
微服务架构
把系统按照模块拆分成多个子系统。
优点:
1、把模块拆分,使用接口通信,降低模块之间的耦合度。
2、把项目拆分成若干个子项目,不同的团队负责不同的子项目。
3、增加功能时只需要再增加一个子项目,调用其他系统的接口就可以。
4、可以灵活的进行分布式部署。
缺点:
1、系统之间交互需要使用远程通信,接口开发增加工作量。
2、各个模块有一些通用的业务逻辑无法共用。
CAP理论
Consistency
一致性
Availability
可用性
Partition tolerance
分区容错性
CAP理论指的是一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项
CP without A
如果不要求A(可用),相当于每个请求都需要在服务器之间保持强一致,而P(分区)会导致同步时间无限延长(也就是等待数据同步完才能正常访问服务),一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。设计成CP的系统其实不少,最典型的就是分布式数据库,如Redis、HBase等。对于这些分布式数据库来说,数据的一致性是最基本的要求,因为如果连这个标准都达不到,那么直接采用关系型数据库就好,没必要再浪费资源来部署分布式数据库。
AP without C
如果要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。典型的应用就如某米的抢购手机场景,可能前几秒你浏览商品的时候页面提示是有库存的,当你选择完商品准备下单的时候,系统提示你下单失败,商品已售完。这其实就是先在 A(可用性)方面保证系统可以正常的服务,然后在数据的一致性方面做了些牺牲,虽然多少会影响一些用户体验,但也不至于造成用户购物流程的严重阻塞。
CA without P
如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但放弃P的同时也就意味着放弃了系统的扩展性,也就是分布式节点受限,没办法部署子节点,这是违背分布式系统设计的初衷的。传统的关系型数据库RDBMS:Oracle、MySQL就是CA。
什么是SpringCloud
Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、智能路由、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
Eureka
注册中心
提供服务注册和发现
Eureka Server
@EnableEurekaServer
Eureka Server也会注册自己(因此Eureka Server理论上也是一个Eureka Client)
同时每个Eureka Server会定期(默认60s)执行失效服务的检测,当检测到超过一定时间(默认90s)没有发送心跳的客户端,则会注销该客户端的服务节点。
自我保护
心跳失败比例在15分钟内,低于85%,则开始怀疑自己了,不剔除客户端了
开始调查自己的原因
为了防止这种误杀,Eureka提供了自我保护机制:Eureka在15分钟内收到服务端心跳数小于Eureka本应该收到的总心跳数 * 自我保护阈值(默认0.85)就会触发
两个eureka可以互相注册
服务提供者
Eureka Client
@EnableDiscoveryClient
每个Eureka Client会周期性(默认30s)的向Eureka Server发送心跳,已验证该客户端仍然与服务器连接。
为什么注册服务这么慢
作为一个实例,还会涉及到一个默认的持续时间为30秒的注册表(通过客户端的Service URL)的周期性心跳
直到实例、服务端和客户端在本地缓存中都具有相同的元数据(因此它可能会花费3个心跳周期),客户端才可发现服务
设置eureka.instance.leaseRenewalIntervalInSeconds
来改变周期时间;将其设置为小于30秒,加快客户端连接到其他服务端的过程
在生产中,最好是坚持使用默认,因为在服务器内部有一些计算,它们会对租赁续订期做出一些假设
如何解决Eureka Server不踢出已关停的节点的问题
server端
# 设为false,关闭自我保护主要
eureka.server.enable-self-preservation
# 清理间隔(单位毫秒,默认是60*1000)
eureka.server.eviction-interval-timer-in-ms
client端
# 开启健康检查(需要spring-boot-starter-actuator依赖)
eureka.client.healthcheck.enabled = true
# 租期更新时间间隔(默认30秒)
eureka.instance.lease-renewal-interval-in-seconds =10
# 租期到期时间(默认90秒)
eureka.instance.lease-expiration-duration-in-seconds =30
优化
1. 服务多的来保护,服务少的不保护。
2.Eureka Server在启动时会创建一个定时任务,每隔一段时间(默认60秒),从当前服务清单中把超时没有续约(默认90秒)的服务剔除。我们可以把定时任务间隔的时间设置得短一点,做到快速下线。防止拉取到不可用的服务
Eureka Server为了避免同时读写内存数据结构造成的并发冲突问题,采用了3级缓存机制来进一步提升服务请求的响应速度
3.默认情况下,client每隔30秒就会向服务端发送一次心跳。这个时间也可以适当调小一点。
4.服务端剔除客户端的时间间隔
默认情况下,server在90s之内没有收到client心跳,将我踢出掉。为了让服务快速响应,可以适当地把这个时间改的小一点。
5.手动做到负载均衡
api-client从eureka-server拉取注册表信息是按照defaultZone配置的顺序依次拉取的,当eureka1不可用的时候再从eureka2中获取/注册。但是如果eureka1一直不挂。所有的微服务都会先从eureka1中获取信息,导致eureka1压力过大。在实际生产中,每个微服务可以随机配置不同的defaultZone顺序
6.客户端拉取注册表更及时
api-client会定时到eureka-server拉取注册表。默认情况下每30秒拉取一次。可以根据实际情况设置拉取时间间隔。
服务测算
Eureka日均承受几十万次访问量
流程
注册
续约
下线
剔除
集群同步
Ribbon
利用负载均衡的策略和RestTemplate调用远程服务,而且不用知道远程服务的ip和端口,只需要知道远程服务名就可以了
作用
调用负载均衡器根据相应的规则选择某一个服务进行调研
将逻辑url,也就是服务名替换ip加端口号的形式
流程
在RestTemplate发出请求之前利用拦截器进行拦截,在拦截器中创建负载均衡器(第一次调用的时候),负载均衡器从EurekaClient端获取到已经拉取到本地的服务列表,然后根据配置的负载均衡策略选取一个服务进行调用,调用的时候会将服务名替换成ip和端口号的形式进行调用
使用
在启动服务的时候,通过LoadBalancerAutoConfiguration这个配置类,会创建带有LoadBalanceClient的拦截器,然后将这个拦截器注入到带有@LoadBalance注解的RestTemplate。这样我们在调用RestTemplate发送请求的时候首先会被拦截器拦截。
1.给@LoadBalance修饰的RestTemlate设置拦截器。
2.当RestTemlate发送请求的时候,先创建request对象,会将拦截器设置到request对象中,
3.当执行restTemplate的execute方法的时候,会调用request的execute方法,调用拦截器里面的拦截方法。
4.在拦截器中会从逻辑url中取出服务名,服务名为后面创建子容器备用,然后调用负载均衡客户端获取服务。
5.到了负载均衡客户端中,如果是ribbon首次执行这个服务的请求,会先去创建一个子容器,从子容器中取出负载均衡器,负载均衡器会去获取Eureka Client里面的服务列表,以及创建一个定时任务定时刷新服务列表,然后负载均衡器会从服务列表中利用Rule对象选出一个服务,这个Rule算法默认是轮训算法。
6.拿到服务对象后,将url改成ip加端口的形式进行服务调用,返回结果。
2.当RestTemlate发送请求的时候,先创建request对象,会将拦截器设置到request对象中,
3.当执行restTemplate的execute方法的时候,会调用request的execute方法,调用拦截器里面的拦截方法。
4.在拦截器中会从逻辑url中取出服务名,服务名为后面创建子容器备用,然后调用负载均衡客户端获取服务。
5.到了负载均衡客户端中,如果是ribbon首次执行这个服务的请求,会先去创建一个子容器,从子容器中取出负载均衡器,负载均衡器会去获取Eureka Client里面的服务列表,以及创建一个定时任务定时刷新服务列表,然后负载均衡器会从服务列表中利用Rule对象选出一个服务,这个Rule算法默认是轮训算法。
6.拿到服务对象后,将url改成ip加端口的形式进行服务调用,返回结果。
原理
1.根据服务名称从ribbon子容器中从获取负载均衡器,如果是第一次调用就会创建这个服务对象的子容器,起到配置隔离的作用,这也是为什么ribbon第一次调用服务的时候很容易出现超时的问题,这是因为需要创建子容器。
2.负载均衡器根据负载均衡算法,选中一个服务进行调用。
3.构建RibbonServer,记录服务花费时间,服务并发数,为负载均衡算法选取服务提供数据参考
4.执行请求。
1.当调用request执行的时候,就会调用拦截器执行
2.拦截器里面会根据url获取要调用服务的服务名称并传给负载均衡客户端loadbalanceClient执行请求
3.loadbalanceClient里面做了三件事,第一件就是当第一次调用的时候会创建这个服务对应的子容器,在子容器里面获取到负载均衡器。第二件事就是利用负载均衡器来从服务列表中获取一个服务来调用。负载均衡器会根据rule这个接口的实现类来从选取服务,服务列表是从DynamicServerListLoadBalancer这里获取到
4.调用服务。
2.拦截器里面会根据url获取要调用服务的服务名称并传给负载均衡客户端loadbalanceClient执行请求
3.loadbalanceClient里面做了三件事,第一件就是当第一次调用的时候会创建这个服务对应的子容器,在子容器里面获取到负载均衡器。第二件事就是利用负载均衡器来从服务列表中获取一个服务来调用。负载均衡器会根据rule这个接口的实现类来从选取服务,服务列表是从DynamicServerListLoadBalancer这里获取到
4.调用服务。
负载均衡算法
随机 (Random)
轮询 (RoundRobin)
一致性哈希 (ConsistentHash)
哈希 (Hash)
加权(Weighted)
Nacos
Nacos是阿里巴巴开源的一款支持服务注册与发现,配置管理以及微服务管理的组件
用来取代以前常用的注册中心(zookeeper , eureka等等),以及配置中心(spring cloud config等等)。Nacos是集成了注册中心和配置中心的功能,做到了二合一。
GateWay
单体应用拆分成多个服务后,对外需要一个统一入口,解耦客户端与内部服务
核心功能是路由转发,熔断、限流、认证、日志监控
不要有耗时操作在网关上处理
可以和服务注册中心完美的整合
Gateway是基于WebFlux(提供响应式编程支持)的
WebFlux
非阻塞式
函数式编程端点
功能
动态路由:能够匹配任何请求属性;
可以对路由指定 Predicate(断言)和 Filter(过滤器)
集成Hystrix的断路器功能
请求限流功能
支持路径重写
Route路由
路由是网关的基本构件。它由ID、目标URI、谓词集合和过滤器集合定义。如果聚合谓词为真,则匹配路由
Predicate断言
参照Java8的新特性Predicate。这允许开发人员匹配HTTP请求中的任何内容,比如头或参数
Filter过滤器
可以在发送下游请求之前或之后修改请求和响应
我们为什么选择Gateway
因为Zuul已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖。而且很多功能Zuul都没有;用起来也非常的简单便捷
Spring Cloud Gateway 工作原理
客户端向 Spring Cloud Gateway 发出请求
然后在 Gateway Handler Mapping 中找到与请求相匹配的路由
发送到 Gateway Web Handler
Handler 再通过指 定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑
Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等
在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用
Feign
Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。Feign的英文表意为“假装,伪装,变形”, 可以理解为将HTTP报文请求方式伪装为简单的java接口调用方式。
封装了Http调用流程,更适合面向接口化的变成习惯
工作原理
基于面向接口的动态代理方式生成实现类
根据Contract协议规则,解析接口类的注解信息,解析成内部表现
基于 RequestBean,动态生成Request
使用Encoder 将Bean转换成 Http报文正文(消息解析和转码逻辑)
拦截器负责对请求和返回进行装饰处理
日志记录
基于重试器发送HTTP请求
发送Http请求
Hystrix
(豪猪,,因其背上长满棘刺,从而拥有了自我保护的能力)
雪崩效应
分布式系统环境下,服务间类似依赖非常常见,一个业务调用通常依赖多个基础服务。如下图,对于同步调用,当库存服务不可用时,商品服务请求线程被阻塞,当有大批量请求调用库存服务时,最终可能导致整个商品服务资源耗尽,无法继续对外提供服务。并且这种不可用可能沿请求调用链向上传递,这种现象被称为雪崩效应
雪崩效应常见场景
硬件故障:如服务器宕机,机房断电,光纤被挖断等。
流量激增:如异常流量,重试加大流量等。
缓存穿透:一般发生在应用重启,所有缓存失效时,以及短时间内大量缓存失效时。大量的缓存不命中,使请求直击后端服务,造成服务提供者超负荷运行,引起服务不可用。
程序BUG:如程序逻辑导致内存泄漏,JVM长时间FullGC等。
同步等待:服务间采用同步调用模式,同步等待造成的资源耗尽。
雪崩效应应对策略
硬件故障:多机房容灾、异地多活等。
流量激增:服务自动扩容、流量控制(限流、关闭重试)等。
缓存穿透:缓存预加载、缓存异步加载等。
程序BUG:修改程序bug、及时释放资源等。
同步等待:资源隔离、MQ解耦、不可用服务调用快速失败等。资源隔离通常指不同服务调用采用不同的线程池;不可用服务调用快速失败一般通过熔断器模式结合超时机制实现。
面试题
项目中单点登录的实现原理
共享Session
基于Redis的Session共享方案
将Session存储于Redis上,然后将整个系统的全局Cookie Domain设置于顶级域名上,这样SessionID就能在各个子系统间共享
基于OpenId的单点登录
基于Cookie的OpenId存储方案
MybatisPlus
Swagger
RabbitMQ
面试题
消息队列中,如何保证消息的顺序性
解决方案
拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点;或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。
为什么需要消息队列?使用消息队列有什么好处
当系统中出现“生产“和“消费“的速度或稳定性等因素不一致的时候,就需要消息队列
业务系统触发短信发送申请,但短信发送模块速度跟不上,需要将来不及处理的消息暂存一下,缓冲压力。就可以把短信发送申请丢到消息队列,直接返回用户成功,短信发送模块再可以慢慢去消息队列中取消息进行处理
好处
提高系统响应速度
提高系统稳定性
业务无关,一个具有普适性质的消息队列组件不需要考虑上层的业务模型,只做好消息的分发就可以了,上层业务的不同模块反而需要依赖消息队列所定义的规范进行通信
消息队列
概念
消息队列(Message Queue)是一种应用间的通信方式
消息队列是一种应用间的异步协作机制
消息发布者只管把消息发布到 MQ 中而不用管谁来取,消息使用者只管从 MQ 中取消息而不管是谁发布的
使用场景
需要提升系统服务的性能,这时可以将一些不需要立即生效的操作拆分出来异步执行,比如发放红包、发短信通知等
用于业务解耦
最终一致性、广播、错峰流控
异步
异步的好处
吞吐量提升
无需等待订阅者处理完成,响应更快速
故障隔离
服务没有直接调用,不存在级联失败问题
调用间没有阻塞
不会造成无效的资源占用
耦合度极低
每个服务都可以灵活插拔,可替换
流量削峰
不管发布事件的流量波动多大,都由Broker接收,订阅者可以按照自己的速度去处理事件
异步的坏处
架构复杂了,业务没有明显的流程线,不好管理
需要依赖于Broker的可靠、安全、性能
RabbitMQ中的几个概念
channel:操作MQ的工具
exchange:路由消息到队列中
queue:缓存消息
virtual host:虚拟主机,是对queue、exchange等资源的逻辑分组
常见的消息模型
简单队列模型
只有三个部分
1个publisher:消息发布者,将消息发送到队列queue
1个queue:消息队列,负责接受并缓存消息
1个consumer:订阅队列,处理队列中的消息
WorkQueue 工作模型(任务模型)
一个队列多个消费者共同处理消息处理
设置上限
prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
发布Publish 订阅Subscribe
工作模型加入了exchange(交换机)
exchange负责消息路由,而不是存储,路由失败则消息丢失
发布订阅Fanout 广播 Exchange
Fanout Exchange 会将接收到的消息广播到每一个跟其绑定的queue
发布订阅-DirectExchange 路由
Direct Exchange 会将接收到的消息根据规则路由到指定的Queue,因此称为路由模式(routes)
每一个Queue都与Exchange设置一个BindingKey
Exchange将消息路由到BindingKey与消息RoutingKey一致的队列
一个队列可以绑定 多个 RoutingKey
发布订阅TopicExchange 话题
TopicExchange与DirectExchange类似,区别在于routingKey必须是多个单词的列表,并且以 . 分割。
Queue与Exchange指定BindingKey时可以使用通配符:
#:代指0个或多个单词
*:代指一个单词
面试题
Spring,SpringMVC,SpringBoot,SpringCloud有什么区别和联系
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。Spring使你能够编写更干净、更可管理、并且更易于测试的代码。
Spring MVC是Spring的一个模块,一个web框架。通过Dispatcher Servlet, ModelAndView 和 View Resolver,开发web应用变得很容易。主要针对的是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等等
Spring配置复杂,繁琐,所以推出了Spring boot,约定优于配置,简化了spring的配置流程。
Spring Cloud构建于Spring Boot之上,是一个关注全局的服务治理框架。
Spring是核心,提供了基础功能;
Spring MVC 是基于Spring的一个 MVC 框架 ;
Spring Boot 是为简化Spring配置的快速开发整合包;
Spring Cloud是构建在Spring Boot之上的服务治理框架。
Spring MVC 是基于Spring的一个 MVC 框架 ;
Spring Boot 是为简化Spring配置的快速开发整合包;
Spring Cloud是构建在Spring Boot之上的服务治理框架。
SpringSecurity
主要功能
认证和授权
认证就是我们常说的登录
授权就是权限鉴别,看看请求是否具备相应的权限
支持基于 URL 的请求授权
application.yml中配置
自动装配原理
有个默认的用户名和密码
默认用户名是user
密码是默认生成
UUID
默认的密码有一个问题就是每次重启项目都会变,这很不方便
spring.security.user.name
spring.security.user.password
密码加密
PasswordEncoder
方法
encode
对明文密码进行加密,返回加密之后的密文
matches
对传来的明文加密,与数据库查询到的密码密文比对,返回布尔值
upgradeEncoding
是否还要进行再次加密,一般不用
加密方案
BCryptPasswordEncoder
配置
继承WebSecurityConfigurerAdapter
configure 方法
设置用户名、密码
设置角色
hasRole
设置权限
hasAuthority
自定义登录页
http.loginPage("/login.html");
自定义403页面
http.exceptionHandling().accessDeniedPage("/noAuthority.html");
用户注销
http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll();
注入Bean
PasswordEncoder
自定义编写实现类UserDetailsService
MyUserDetailsService
设置角色、权限
Sentinel
Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助您保障微服务的稳定性
流量控制
流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据
资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
运行指标,例如 QPS、线程池、系统负载等;
控制的效果,例如直接限流、冷启动、排队等。
Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状
熔断降级
如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积
Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果
熔断降级设计理念
Hystrix 通过线程池的方式
Sentinel 对这个问题采取了两种手段
通过并发线程数进行限制
通过响应时间对资源进行降级
系统负载保护
Sentinel 同时提供系统维度的自适应保护能力。防止雪崩
在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去
第七章:Linux操作系统
目录结构
主要目录
/bin
存放系统命令
普通用户和 root 都可以执行
/dev
设备文件保存位置
/home
普通用户的主目录(也称为家目录)
如用户 liming 的主目录就是 /home/liming
/lib
系统调用的函数库保存位置
/media
挂载目录
系统建议用来挂载媒体设备,如软盘和光盘
/opt
第三方安装的软件保存位置
/usr/local/ 目录也可以用来安装软件
/root
root 的主目录
普通用户主目录在 /home/ 下,root 主目录直接在“/”下
/sbin
保存与系统环境设置相关的命令
只有 root 可以使用这些命令进行系统环境设置,但也有些命令可以允许普通用户查看
/boot
系统启动目录,保存与系统启动相关的文件
/etc
配置文件保存位置
/mnt
挂载目录
建议这个目录用来挂载额外的设备,如 U 盘、移动硬盘和其他操作系统的分区
/srv
服务数据目录
一些系统服务启动之后,可以在这个目录中保存所需要的数据
/tmp
临时目录
建议此目录中不能保存重要数据,最好每次开机都把该目录清空
/usr
此目录用于存储系统软件资源
全称为 Unix Software Resource
Linux 系统中,所有系统默认的软件都存储在 /usr 目录下,/usr 目录类似 Windows 系统中 C:\Windows\ + C:\Program files\ 两个目录的综合体
/var
/var 目录用于存储动态数据,例如缓存、日志文件、软件运行过程中产生的文件等
次要目录
/lost+found
当系统意外崩溃或意外关机时,产生的一些文件碎片会存放在这里
/proc
虚拟文件系统
该目录中的数据并不保存在硬盘上,而是保存到内存中
/sys
虚拟文件系统
和 /proc/ 目录相似,该目录中的数据都保存在内存中,主要保存与内核相关的信息
常用命令
列出文件列表
ls
ls -a
显示隐藏文件和可见文件
ls -l
查看当前目录下所有可见文件的详细属性
创建目录和删除空目录
mkdir
创建目录
rmdir
删除空目录
显示文件后几行内容
tail
打包
tar -xvf
打包
tar -zcvf
打包压缩
查询字符串
grep 关键字 文件
显示当前所在目录
pwd
创建空文件
touch 1.txt
编译器
vim/vi
实时动态查看日志
tail -f 日志文件,输出最后10行的内容,同时监视文件的变化,一旦变化就显示出来
tail -f 1.txt |grep a
tail -f xxx.log | perl -pe 's/(ERROR)/\e[1;31m$1\e[0m/g'
tail -nf test.log
输出最后n行的内容,同时监视文件的变化,一旦变化就显示出来
输出文件最后10行的内容
tail -n 10 filename
从第20行至末尾
tail -n +20 test.log
在数字参数前补个“+”号即表示从文档开始截取
显示最后10个字符
tail -c 10 test.log
实时检测步骤
1.创建日志文件a.log
2.执行tail -f a.log
3.另一个ssh连接执行 echo '999' >> a.log
4.可以看见这边的ssh连接的tail -f a.log监听到了内容的新增
查看指定进程PID
ps -ef|grep xxx
强制终止进程
kill -9 PID
查看所有进程里CMD是xxx的进程信息
ps -aux|grep xxx
关机命令
shutdown
重启命令
reboot
查看网卡信息
ifconfig
PING
ping 127.0.0.1
版本管理工具
SVN集中式版本控制系統
什么是SVN
Apache Subversion 通常被缩写成 SVN,是一个开放源代码的版本控制系统,Subversion 在 2000 年由 CollabNet Inc 开发,现在发展成为 Apache 软件基金会的一个项目,同样是一个丰富的开发者和用户社区的一部分。
概念
repository(源代码库):源代码统一存放的地方
Checkout(提取):当你手上没有源代码的时候,你需要从repository checkout一份
Commit(提交):当你已经修改了代码,你就需要Commit到repository
Update (更新):当你已经Checkout了一份源代码, Update一下你就可以和Repository上的源代码同步,你手上的代码就会有最新的变更
主要功能
目录版本控制
真实的版本历史
自动提交
纳入版本控管的元数据
生命周期
创建版本库
检出
更新
解决冲突
提交更改
SVN服务器 安装
安装教程
https://blog.csdn.net/HeyShHeyou/article/details/87979276
服务器下载地址: http://subversion.apache.org/packages.html
安装成功
svn --version
可以在服务器管理界面,新建仓库
安装服务器 之后,可以新建 用户,新建 组
SVN客户端安装
安装教程
http://subversion.apache.org/packages.html
下载地址:https://tortoisesvn.net/downloads.html
SVN 操作
在服务器上自己创建 一个 仓库。名字 自己起,如 lpcRepositories, 建的 仓库中,可以存放多个 项目 。建文件夹即可。
在客户端检出: svn服务器中的 项目。
客户端 更新 查看 等操作。
对于冲突的解决,对于两个 用户,对于 相同的 一行 文件,做操作,两个人都去提交,会有冲突。
解决冲突。
IDEA中集成SVN
教程
https://www.cnblogs.com/blogchen/articles/9040211.html
git分布式版本控制系统
版本控制分类
本地版本控制
分布式版本控制
集中版本控制
所有的版本数据都保存在服务器上,协同开发者做修改和保存
有问题: 可能服务器故障或者损坏,会丢失数据,丢失历史文件
分布式版本控制
所有版本信息仓库
Git 的工作流程
git init
git clone
工作区
git add
暂存区
git commit
本地仓库
git push
远程仓库
git pull
Git文件的状态
Untracked
未跟踪
通过git add 状态变为Staged
Staged
暂存状态
使用git checkout 则丢弃修改过, 返回到unmodify状态
执行git reset HEAD filename取消暂存, 文件状态为Modified
Unmodify
文件已经入库, 未修改
它被修改, 而变为Modified
Modified
文件已修改, 仅仅是修改
Git的分支
git branch dev 创建一个开发分支
git branch –v 查看现在有哪些分支
git checkout xxx 切换分支
数据结构与算法(算法+数据结构=程序)
数据结构
数组
数组,可以说数组几乎能表示一切的数据结构,在每一门编程语言中,数组都是重要的数据结构
数组的局限性
插入快
查找慢
删除慢
数组一旦创建后,大小就固定了
排序
冒泡排序
选择排序
插入排序
大O表示法
"大O表示法"表示程序的执行时间或占用空间随数据规模的增长趋势。
时间复杂度
空间复杂度
栈
栈(英语:stack)又称为堆栈或堆叠,栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表
利用数组 模拟实现。
Java本身的Stack类型
借用LinkedList来简介实现Stack
队列
队列(queue)是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列
数组模拟队列
链表
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。
单向链表
内部类实现单向链表
双端链表
内部类实现双端链表
树
二叉树
满二叉树
完全二叉树
非完全二叉树
前中后序遍历
哈希表
堆
图
算法
五大特性
有穷性
确定性
可行性
有输入
有输出
设计原则
正确性
健壮性
高效率与低存储量需求
第一章:java基础
1.java基础名词
JDK
java开发工具包,包括jre
JRE
java运行环境,包括jvm
JVM
java虚拟机,运行字节码文件
javase
简单的服务器应用java平台
javaee
复杂的服务器应用java平台
javame
微型手机和其他小型设备java平台
sdk
1998-2006年之间的JDK
j2
1998-2006年之间的java版本
2.JDK下的目录
bin
可执行程序,如编译器、解释器
db
javadb的数据库
include
本地代码头文件
jre
java运行环境
lib
可执行文件的依赖文件
src
jdk类库,源代码文件
3.变量命名规范
由数字、字母、下划线和$符号组成
不以数字开头
命名的方式
匈牙利命名法
小写字母做前缀,如m_xxx
骆驼命名法(Camel)
高低起伏,如run_Fast
Pascal命名法
与骆驼命名法类似,首字母大小,如MyLike
5.java的数据类型
8种基本数据类型
整数型
byte(1字节)
short(2字节)
int(4字节)
long(8字节)
浮点型
float(4字节)
double(8字节)
字符型
char(2字节)
布尔型
boolean(1字节)
引用类型
String、类、数组
4.JDK、JRE、JVM的区别
JDK是java的开发工具集,包括JRE
JRE是 java的运行环境,执行字节码文件,包括JVM
JVM是java的虚拟机,可以加载类
6.包装类
Java5引入的自动装箱、拆箱机制
装箱:把基本数据类型转化为包装类
拆箱:把包装类转化成基本数据类型
包装类
Byte
Short
Integer
Long
Float
Double
Character
Boolean
7.&与&&的区别
&和&&都是运算符两边同为true,才返回true
&
1.按位与
2.逻辑与
&&
短路与
左边表达式为false,直接中断判断
8.面向对象的特征
封装
隐藏属性和实现细节,对外提供最简单的接口
继承
从已有类得到继承信息,创建新类的过程。提供信息的叫父类,得到信息的叫子类。提高代码复用性
多态
允许不同子类对象,对同一信息,作出不同响应。
分为编译时多态和运行时多态。而方法重载实现了编译时多态,方法重写实现了运行时多态
抽象
用一类对象的共同特征来构造类的过程
9.抽象类和接口的区别
抽象类
被abstract修饰符修饰的类就是抽象类
抽象类不能实例化
抽象类中有抽象方法和普通方法
有抽象方法的类一定时抽象类
接口
比抽象类还抽象
接口不能实例化
接口的成员变量都是常量
接口的方法都是公有方法public
10.final关键字的作用
1.修饰类
该类不可以被,继承
2.修饰方法
该方法不可以被,重写
3.修饰变量
该变量为,常量
11.方法重载与重写的区别
重载
一个类中,同名但参数列表不同(参数,类型、个数、顺序不同)
修饰符和返回值可以相同也可以不同
重写
必须有继承关系,子类重写父类的方法
返回值必须相同
重写的方法,访问修饰符范围不得小于被重写方法
不能比被重写方法抛出更多的异常
12.Error和Exception的区别
Error
系统级错误,程序不必处理的异常
Exception
需要捕捉,程序需要处理的异常
13.集合
Collection实现比较的接口
1.实体类实现Comparable接口,实现compareTo()方法
2.外部比较器实现Comparator接口的compare()方法
HashMap和HashTable的区别
HashMap
HashMap是Java1.2引进的Map的一个实现
HashMap是线程不安全的,但效率高于HashTable
HashMap允许null作为key或value
HashTable
HashTable是Java1.1一类,基于Dictionary类
HashTable是线程安全的
但Hashtable不允许null作为key或 value
ArrayList和Vector、LinkedList的区别
ArrayList
有序可重复,都实现了List接口,底层是数组
ArrayList不是线程安全
ArrayList和Vector都有初始容量。当超过容量时,默认ArrayList以原来的0.5倍增长
访问快,修改删除慢
Vector
有序可重复,都实现了List接口,底层是数组
Vector是线程安全的
当超过容量时,默认Vector以原来的1倍增长
LinkedList
底层是双向链表,访问慢,修改删除快
List和Set的区别
List是有序可重复的
Set是不可重复的
Map
无序不可重复的双列集合
Collection和Collections的区别
Collection
是List和Set的父接口
Collections
是集合工具类
ArrayList加入一万条数据,如何提高效率
初始化设置容量
集合
Collection接口
List接口
LinkedList 是List接口实现类
底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素(模拟实现栈和队列)
ArrayList 是List接口实现类
底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素
Vector 是List接口实现类
Stack 是Vector类的实现类
java提供实现栈的类
底层数据结构是数组,查询快,增删慢,线程安全,效率低,可以存储重复元素
元素可以重复,可以通过索引访问元素
Set接口
HashSet
LinkedHashSet
底层数据结构采用链表和哈希表共同实现,有序不重复。线程不安全,效率高。
底层数据结构采用哈希表实现,无序不重复,线程不安全,效率高,可以存储null元素
TreeSet
底层数据结构采用二叉树来实现,有序不重复
元素不可重复,通过迭代器遍历元素,其实大多其底层是包装了一个HashMap去实现的,key作Set的值,而value无用
Map接口
Hashtable
HashMap
LinkedHashMap
WeakHashMap
基于哈希表实现,非线程安全
TreeMap
非线程安全基于红黑树实现
Map用于保存具有映射关系的数据,Map里保存着两组数据:key和value,它们都可以使任何引用类型的数据,但key不能重复
Iterator迭代器
keySet()
HashMap<String,Integer> map=new HashMap<>();
map.put("a",1);
map.put("b",2);
map.put("c",3);
map.put("d",4);
Set set=map.keySet();
Iterator iterator=set.iterator();
while (iterator.hasNext()){
String key= (String) iterator.next();
System.out.print(key+map.get(key)+" ");
}
entrySet()
HashMap<String,Integer> map=new HashMap<>();
map.put("a",1);
map.put("b",2);
map.put("c",3);
map.put("d",4);
Iterator iterator=map.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<String,Integer> entry= (Map.Entry<String, Integer>) iterator.next();
System.out.print(entry.getKey()+entry.getValue()+" ");
}
多线程
什么是线程
线程是操作系统能够运行计算调度的最小单位
所有线程共享一片相同的内存空间
每个线程都拥有自己独立的栈内存
多线程
线程是程序中一个单一的顺序控制流程;而多线程就是在单个程序中同时运行多个线程来完成不同的工作。
进程
线程是进程的子集,一个进程可以有多个线程
不同的进度使用不同的内存空间
java中实现线程方式
继承Thread类
实现Runnable接口
无fan返回值
实现Callable接口
有返回值
线程池
事先创建若干个可执行的线程放入一个池中,需要的时候从池中获取不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销
newSingleThreadExecutor
单线程线程池,只有一个线程工作
newFixedThreadPool
固定大小线程池,使用一次创一个线程直到最大
newCachedThreadPool
可缓存线程池,最大容量大于使用的,会回收,智能分配
newScheduledThreadPool
无限大线程池,支持定时和周期执行任务需求
newSingleThreadExecutor
单线程线程池,支持定时和周期执行任务需求
死锁
两个或两个以上的线程,争夺资源而造成的相互等待的现象
死锁的四个条件
互斥条件
资源一次只被一个线程使用
“一物一人”
请求与保持条件
请求资源被阻塞后,对以获得资源不释放
“你不给我,我不放”
不可剥夺条件
未使用完之前,不允许被剥夺资源
“我不放,你也抢不了我的”
循环等待条件
头尾相接的循环等待资源关系
“这种尴尬的状态”
解决
阻止循环等待,标识和排序线程,线程请求资源按顺序
面试题
start()和run()区别与联系
start()被用于创建新线程
start()内部调用run()方法
run()只会在原来的线程中调用
sleep()和yield()区别
1.sleep给其他线程机会时,不考虑优先级,而yeild会考虑
sleep执行进入阻塞状态,yield执行进入就绪状态
sleep方法会抛出异常,而yield不会
sleep比yield有更好的移植性
sleep()和wait()区别
1.来自不同的类,wait是Object类,sleep是Thread类
2.关于锁的释放,wait会释放锁,sleep不会释放锁
3.使用的范围不同,wait必须在同步代码块中,sleep可以任何地方使用
4.是否需要捕获异常,wait不需要捕获异常,sleep必须捕获异常
让三个线程顺序执行
调用join()方法,T3.join()调用T2,T2.join()调用T1
常用方法
sleep()睡眠
Thread类方法,不考虑优先级,使线程进入阻塞状态,不释放锁,可自动恢复
yield()礼让
考虑优先级,使线程进入就绪状态
wait()等待
Object类方法,线程进入阻塞状态,释放锁,需要notify()/notityAll()唤醒等锁
join()插队
setPriority()
设置线程优先级
notify()
唤醒一个等待的线程,不确定哪个线程,由JVM确定唤醒,但与优先级无关
notifyAll()
唤醒所有等待线程,所有线程竞争获得锁
线程执行流程
分支主题
文件IO
序列化
实现Serializable接口
对象流:将对象的内容进行流化
处理对象流的机制,流化后对象进行读写操作或在网络传输
字节流和字符流
stream结尾的都是字节流
reader和writer结尾的都是字符流
字节流按字节写,字符流按字符写
JDBC
步骤
1.加载驱动
2.创建连接
3.获取操作对象
4.执行SQL语句
5.处理结果集
6.释放资源
Statement和PreparedStatement的区别
1.PreparedStatement代表预编译语句
2.PreparedStatement可以防止SQL注入
3.PreparedStatement会缓存SQL语句,执行效率快
什么是Dao模式
为数据库或其他持久化机制提供抽象接口的对象
不暴露底层持久化方案实现细节的前提下,提供各种数据访问操作
包括Data Accessor数据访问器和Data Object数据对象
事务的特性
1.原子性
要么都成功,要么都失败
2.一致性
修改操作后和操作前系统状态一致
3.隔离性
并发执行事务彼此无法看见中间态
脏读
事务A读取到事务B未提交的数据
不可重复读
事务A读取两次,数据不一样(第二次读事务A读取了事务B已提交修改后的数据和第一次不一致)
幻读
事务A重新执行查询,返回的行中有事务B提交的行
隔离级别
1.读未提交
出现脏读、不可重复读、幻读
2.读已提交
出现不可重复读、幻读
3.可重复读
出现幻读
4.可串行化
4.持久性
事务完成后的持久化操作
杂烩
1.构造器不能被重写,但可以被重载(无参、有参)
2.String类不可以被继承,被final修饰了
3.java传参只有值传递
4.静态变量和实例变量的区别
静态变量
类变量、属于类,可以用类名访问
实例变量
对象成员变量
5.String、StringBuilder、StringBuffer的区别
String
只读字符串,内容不可修改
StringBuilder
Java5引入,线程不安全,可以修改字符串对象,效率高于StringBuffer
StringBuffer
线程安全,可以修改字符串对象
6.throw和throws的区别
throw明确抛出哪个异常
throws声明抛出可能抛出的异常
7.try、catch、finally用法
try指定预防异常程序
catch紧跟try后,捕获异常并处理
finally不管发生什么都会执行
8.运行时异常
ArithmeticException算数异常
ClassCastException类转化异常
IllegalArgumentException非法参数异常
IndexOutOfBoundsException下标超界异常
NullPointerException空指针异常
SecutiyException安全异常
9.final、finally、finalize区别
final
是一个java修饰符
finally
放在try...catch后,不管什么怎么样都运行
finalize
Object类方法,调用在垃圾收集器将对象从内存清除之前做清理工作
equals和hashcode方法的理解
hashCode是用于查找使用的,而equals是用于比较两个对象的是否相等的
hashCode的存在主要是用于查找的快捷性
hashCode是用来在散列存储结构中确定对象的存储地址
两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同
两个对象的hashCode相同,并不一定表示两个对象就相同
“存放在同一个篮子里”
对象的equals方法被重写,那么对象的hashCode也要重写
1.hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有
例如内存中有这样的位置
0 1 2 3 4 5 6 7
而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。
但如果用hashcode那就会使效率提高很多。
我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,9除8的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除 8求余数直接找到存放的位置了。
2.但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),例如9除以8和17除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。
也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。
那么。重写了equals(),为什么还要重写hashCode()呢?
想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊
面试题
hashCode() 和 equals() 之间的关系
equals() 的作用是用来判断两个对象是否相等。
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置
不会创建“类对应的散列表”(我们不会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类)
在这种情况下,该类的“hashCode() 和 equals() ”没有半毛钱关系的
会创建“类对应的散列表”
如果两个对象相等,那么它们的hashCode()值一定相同
这里的相等是指,通过equals()比较两个对象时返回true
如果两个对象hashCode()相等,它们并不一定相等
这是因为虽然p1 和 p2的内容相等,但是它们的hashCode()不等;所以,HashSet在添加p1和p2的时候,认为它们不相等。
原则
1.同一个对象(没有发生过修改)无论何时调用hashCode()得到的返回值必须一样。
2.hashCode()的返回值相等的对象不一定相等,通过hashCode()和equals()必须能唯一确定一个对象。
3.一旦重写了equals()函数(重写equals的时候还要注意要满足自反性、对称性、传递性、一致性),就必须重写hashCode()函数。
Object有几种方法
Java语言是一种单继承结构语言,Java中所有的类都有一个共同的祖先。这个祖先就是Object类
13种方法
Object()
Object类的构造方法
registerNatives()
可以命名任何你想要你的C函数
clone()
用来另存一个当前存在的对象。只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常
getClass()
该方法返回的是此Object对象的类对象/运行时类对象Class
equals()
用来比较两个对象的内容是否相等
hashCode()
返回其所在对象的物理地址(哈希码值)
toString()
返回该对象的字符串表示
wait()
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法
wait(long timeout)
超过指定的时间量
wait(long timeout, int nanos)
其他某个线程中断当前线程,或者已超过某个实际时间量
notify()
唤醒在此对象监视器上等待的单个线程
notifyAll()
唤醒在此对象监视器上等待的所有线程
finalize()
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法
如何决定使用 HashMap 还是 TreeMap
TreeMap<K,V>的Key值是要求实现java.lang.Comparable,所以迭代的时候TreeMap默认是按照Key值升序排序的;TreeMap的实现是基于红黑树结构。适用于按自然顺序或自定义顺序遍历键(key)
HashMap<K,V>的Key值实现散列hashCode(),分布是散列的、均匀的,不支持排序;数据结构主要是桶(数组),链表或红黑树。适用于在Map中插入、删除和定位元素
如果你需要得到一个有序的结果时就应该使用TreeMap(因为HashMap中元素的排列顺序是不固定的)。除此之外,由于HashMap有更好的性能,所以大多不需要排序的时候我们会使用HashMap。
HashMap 和 TreeMap 都是非线程安全
TreeMap中默认是按照升序进行排序的
如何让他降序
实现Comparator接口,重写compare方法
实现comparable接口,重新compareTo方法
HashMap是线程不安全
jdk1.7
扩容造成死循环
在对table进行扩容到newTable后,需要将原来数据转移到newTable中,注意10-12行代码,这里可以看出在转移元素的过程中,使用的是头插法,也就是链表的顺序会翻转,这里也是形成死循环的关键点
jdk1.8
对HashMap进行了优化,在发生hash碰撞,不再采用头插法方式,而是直接插入链表尾部,因此不会出现环形链表的情况,但是在多线程的情况下仍然不安全
在jdk1.7中,在多线程环境下,扩容时会造成环形链或数据丢失。
在jdk1.8中,在多线程环境下,会发生数据覆盖的情况。
在jdk1.8中,在多线程环境下,会发生数据覆盖的情况。
序列化与反序列化
序列化
对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。核心作用是对象状态的保存与重建
反序列化
客户端从文件中或网络上获得序列化后的对象字节流,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象
为什么需要序列化和反序列化
对象序列化可以实现分布式对象。
java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据
序列化可以将内存中的类写入文件或数据库中
对象、文件、数据,有许多不同的格式,很难统一传输和保存
ArrayList和ListkedList怎么选取
当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会有更好的性能;当操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了
HashMap怎样解决hash冲突
链表法
就是将相同hash值的对象组织成一个链表放在hash值对应的槽位
开放地址法
是通过一个探测算法,当某个槽位已经被占据的情况下继续查找下一个可以使用的槽位
进程与线程的区别
进程是资源分配最小单位,线程是程序执行的最小单位
进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段,线程没有独立的地址空间,它使用相同的地址空间共享数据
CPU切换一个线程比切换进程花费小
创建一个线程比进程开销小;
线程之间通信更方便,同一个进程下,线程共享全局变量,静态变量等数据,进程之间的通信需要以通信的方式(IPC)进行
HashMap的实现原理
1.7中采用数组+链表,1.8采用的是数组+链表/红黑树,即在1.7中链表长度超过一定长度后就改成红黑树存储
1.7是采用表头插入法插入链表,1.8采用的是尾部插入法
数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在
HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对
Comparable和Comparator
Comparable可以认为是一个内比较器,实现了Comparable接口的类有一个特点,就是这些类是可以和自己比较的,至于具体和另一个实现了Comparable接口的类如何比较,则依赖compareTo方法的实现
Comparator接口里面有一个compare方法,方法有两个参数T o1和T o2,是泛型的表示方式,分别表示待比较的两个对象,方法返回值和Comparable接口一样是int
第二章:java高级特性
反射
反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
获取Class对象
Class.forName()
类名.class
对象.getClass()
获取Class对象的所有构造函数
//拿到所有公有构造方法
Constructor[] constructors = cz.getConstructors();
//拿到不管访问修饰符的构造方法
Constructor[] dconstructors = cz.getDeclaredConstructors();
用公有的构造函数创建对象
1.Constructor constructor = cz.getConstructor();
2.Student student = (Student) constructor.newInstance(1,"as");
用私有的构造函数创建对象
1.Constructor declaredConstructor = cz.getDeclaredConstructor();
2.declaredConstructor.setAccessible(true);
3.Teacher teacher = (Teacher) declaredConstructor.newInstance();
获取Class对象所有成员变量的名字、类型
Field[] fields = cz.getFields();
拿到所有的字段 不区分 公有和私有
Field[] declaredFields = cz.getDeclaredFields();
Field sname = cz.getDeclaredField("age");// 拿到不区分访问修饰符的字段
获取Class所有的方法
Method[] methods = cz.getDeclaredMethods();
执行Class的方法
1.Method method = cz.getDeclaredMethod("say");
2.method.setAccessible(true);
3.method.invoke(teacher);
动态代理和静态代理得区别
静态代理:事先就知道代理什么
优点
业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点
缺点
一个目标类就需要一个代理类
面向接口,增加一个方法,除了目标类需要实现该方法外,代理类也需要实现该的方法
动态代理:运行的时候才知道代理什么
动态代理有哪些?
JDK动态代理
集体实现类
1.目标对象没具体
private Object target;
2.创建返回代理的方法
public Object getProxyInstance(){
//getClassLoader返回加载类或接口的ClassLoader
//getInterfaces获得这个对象所实现的所有接口
Object obj= Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler(){
// 匿名内部类
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
mq.check();
Object returnvalue=method.invoke(target,args);
return returnvalue;
}
});
// 返回代理对象
return obj;
}
3.调用
PersonDao jdkdtdl = (PersonDao) new MyProxy(ps, mq).getProxyInstance();
CGLib的动态代理
具体实现
创建返回代理的方法
public <T> T createProxy(T t, MyQx obj){
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(t.getClass());
// 设置enhancer的回调对象
enhancer.setCallback(new MyMethodInterceptor(obj));
// 创建代理对象
T proxy= (T)enhancer.create();
return proxy;
}
实现接口MethodInterceptor
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
mq.check();
Object object = methodProxy.invokeSuper(o, objects);
return object;
}
调用
StudentDao ss= cgProxy.createProxy(ps,mq);
优点
与静态代理的在接口声明所有方法相比,能集中处理一个方法,用反射invoke调用
缺点
还是摆脱不了接口代理,其根本是因为java不能多继承
Spring中用了那种动态代理?
两种都用了。
JDK动态代理和CGlib的有什么区别?
1 JDK动态JDK直接支持,CGLib第三方jar包支持
2 JDK动态是必须要有接口才能代理,2 CGlib代理类是目标类的子类,可以针对于普通类做代理。但是不能是final的类。
3 JDK1.6之前,CGLib底层采用ASM字节码生成框架,效率比JDK动态代理要高
JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,
JDK1.8的时候,JDK代理效率高于CGLib代理
java的设计原则,总原则:开闭原则
1.单一职责原则
一个类一个职责
2.里氏替换原则
父类的出现可以被子类替换,子类尽量不要重写和重载父类的方法
子类可以扩展父类的功能,但不能改变父类原有的功能
3.依赖倒置原则
面向接口编程,依赖于抽象而不依赖于具体
4.接口隔离原则
接口中,不存在子类用不到却必须实现的方法
5.迪米特法则
一个类对自己依赖的类,知道的越少越好
6.合成复用原则
首先考虑合成和聚合,而不是继承
23种设计模式
设计模式
普通简单工厂模式
普通简单工厂
工厂类对实现同一接口的类进行实例化(在一个方法内)
多方法简单工厂
工厂类里多个方法分别对实现同一接口的类进行实例化
静态方法简单工厂
工厂类里多个静态方法分别对实现同一接口的类进行实例化,工厂方法直接调用
创建型模式
工厂方法模式
区别于普通简单工厂,把工厂做成接口,多个工厂实现类
抽象工厂模式
抽象工厂像工厂,工厂方法像产品生产线
区别于工厂方法一个抽象产品类而言,它是多个抽象产品类
单例模式
定义
单例类只能有一个实例
实现
构造器私有化
当前类的成员变量
对外创建的公共方法
类型
饿汉模式
类初始化的时候,创建对象
懒汉模式
第一次使用时才创建对象
线程安全的懒汉模式
双重检查
枚举创建
建造者模式
原型模式
以y一个对象为原型,对其进行复制和克隆,产生和原对象类似的 新对象
浅复制
基本数据类型变量重新创建,引用类型不变
深复制
彻底复制,基本数据类型和引用类型都不变
结构型模式
适配器模式
把类的接口转化成客户端期望的接口
类的适配器
对象的适配器
接口的适配器
装饰器模式
代理模式
外观模式
桥接模式
组合模式
享元模式
行为型模式
策略模式
模板方法模式
观察者模式
迭代子模式
责任链模式
命令模式
备忘录模式
状态模式
访问者模式
中介者模式
解释器模式
JVM和GC机制
JVM
定义
运行 Java 字节码文件(.class文件)的虚拟机
包含
栈
JVM栈
本地方法栈
Java栈的区域很小,特点是存取速度很快,
所以在stack中存放基本数据类型的数据,和对象的引用
堆
类的对象放在heap(堆)中,所有的new 出的东西都放在堆中。
方法区
method区 存放所有的 1 类的字节码文件,2 静态变量(static变量),3 方法的模板。
程序计数器
程序计数器用来记录当前正在执行的指令.
JVM加载类
类加载器
1.用户自定义加载器
2.启动类(根boot)加载器
3.扩展类(ext)加载器 jre/lib/ext/
4.应用程序(app)加载器 jre/rt.jar
双亲委派机制
AppClassLoader 向上委托了两次,即“双”,“亲”代表亲人的意思,对双亲的理解
沙箱安全机制
JVM的分类
Sun公司:Hot Spot,Java HotSpot(TM) Client VM (build 25.291-b10, mixed mode, sharing),是目前使用范围最广的Java虚拟机。
BEA公司:JRockit,系列产品是一个全面的Java运行时解决方案组合。
IBM公司:J9VM,是一个高性能的企业级 Java 虚拟机。
内存溢出
原因
内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
代码中存在死循环或循环产生过多重复的对象实体;
使用的第三方软件中的BUG;
启动参数内存值设定的过小;
解决方法
第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)
第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。
第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。
垃圾回收机制
GC机制
1.GC是垃圾回收机制,会自动清理堆中已死亡或长期没使用的对象。也可以调用System.gc()或Runtime.getRuntime().gc()来请求清理。
2.垃圾回收机制出于安全性考虑,可以防止内存泄漏,减少程序员的工作量
3.垃圾回收机制的常用算法
引用计数法
复制算法
根搜索算法
标记清除法
标记压缩整理
分代收集
4.垃圾回收时,Java对象的引用类型
强引用
内存不足也不会回收
软引用
内存足不回收,内存不足回收
弱引用
被GC扫描到就直接回收
虚引用
同没有引用类似,被扫描到后会被回收,用于跟踪对象被回收的活动
必须与引用队列联用
Java8的新特性
面试题
单例模式
饱汉模式
基础的饱汉
先不初始化单例,等第一次使用的时候再初始化,即“懒加载”
好处是更启动速度快、节省资源,一直到实例被第一次访问,才需要初始化单例
小坏处是写起来麻烦,大坏处是线程不安全,if语句存在竞态条件
饱汉 - 变种 1
最粗暴的犯法是用synchronized关键字修饰getInstance()方法,这样能达到绝对的线程安全。
好处是写起来简单,且绝对线程安全
坏处是并发性能极差,事实上完全退化到了串行
性能不敏感的场景建议使用。
饱汉 - 变种 2
变种2是“臭名昭著”的DCL 1.0。
针对变种1中单例初始化后锁仍然无法避开的问题,变种2在变种1的外层又套了一层check,加上synchronized内层的check,即所谓“双重检查锁”(Double Check Lock,简称DCL
DCL仍然是线程不安全的,由于指令重排序,你可能会得到“半个对象”,即”部分初始化“问题
饱汉 - 变种 3
变种3专门针对变种2,可谓DCL 2.0。
instance上增加了volatile关键字
多线程环境下,变种3更适用于性能敏感的场景
饿汉模式
类加载时初始化单例,以后访问时直接返回即可
好处是天生的线程安全(得益于类加载机制),写起来超级简单,使用时没有延迟
坏处是有可能造成资源浪费(如果类加载后就一直不使用单例的话)
Holder模式
核心仍然是静态变量,足够方便和线程安全;通过静态的Holder类持有真正实例,间接实现了懒加载
相对于饿汉模式,Holder模式仅增加了一个静态内部类的成本,与饱汉的变种3效果相当(略优),都是比较受欢迎的实现方式。同样建议考虑
public class Singleton3 {
private static class SingletonHolder {
private static final Singleton3 singleton = new Singleton3();
private SingletonHolder() {
}
}
private Singleton3() {
}
public static Singleton3 getInstance() {
return SingletonHolder.singleton;
}
}
private static class SingletonHolder {
private static final Singleton3 singleton = new Singleton3();
private SingletonHolder() {
}
}
private Singleton3() {
}
public static Singleton3 getInstance() {
return SingletonHolder.singleton;
}
}
枚举模式
用枚举实现单例模式,相当好用,但可读性是不存在的。
如何判断一个对象是否存活
引用计数算法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器加1;当引用失效时,计数器值减1;任何时刻计数器为0的对象就是不能再被引用的
可达性分析算法
可达性分析算法的基本思路是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则证明此对象是不可用的
第三章:关系型数据库
DBMS主要功能
数据定义
数据组织、存储和管理
数据操纵(增删改查)
数据库的事务管理和运行管理
数据库的建立和维护功能
其他功能
视图
虚拟表、存储的查询
create view 视图名 as select 字段名 from 表名;
创建视图的表叫基表
作用
简化用户操作,注意力集中在自己关心的操作上
隐藏字段,对数据有一定的安全性
适当使用视图,便于查询
对重构数据库有一定的逻辑独立性
视图可以对基表进行修改、删除和添加么?
1.如果视图和基表的行是一一对应的,则可以
2.如果视图的行是基表多行计算获得的,则不行
存储过程
定义
包括逻辑判断的sql语句集合
经过预编译,存于数据库中
调用存储过程(有参、无参)名字执行
DELIMITER //
CREATE PROCEDURE myproc(OUT s int)
BEGIN
SELECT COUNT(*) INTO s FROM students;
END
//DELIMITER ;
优点
简化复杂业务逻辑,可重复用
隐藏底层细节
降低网络通信量
可以设置访问权限提高安全性
预编译提高效率
缺点
可移植性差,不能跨多个数据库
服务器压力增加,维护更难
SQL语句性能优化/索引优化
1.使用exists代替in
2.使用表的索引不超过6个
虽然索引提高查询效率,但是会降低insert和update的效率
3.尽量使用数字型字段
提高查询效率,减少储存开销
4.尽量使用varchar/nvarchar代替char/nchar
节省存储空间
char和varcahr的区别
存储字符串'abc'
使用char(10),表示存储的字符将占10个字节(包括7个空字符)
使用varchar2(10),则表示只占3个字节
5.尽量使用>=,不使用>
6.避免使用全表扫描
使用where、group by或建立索引
7.避免使用where使用不等号或or、in、not in
否则索引失效,全表扫描
8.避免使用模糊查询的%xxx%和%xxx(xxx%不会)
否则索引失效,全表扫描
9.避免使用where子句使用参数,字段进行表达式操作或者函数操作
否则索引失效,全表扫描
10.不使用select * 查询所有,而是列出所有字段
11.首先考虑在where和order by涉及的列建立索引
MySQL数据库优化
选取最适用的字段属性
使用连接(join)代替子查询
使用联合查询(union)代替手动创建临时表
使用事务
使用外键
使用索引
优化SQL语句
Mysql(核心)存储引擎
InnoDB
事务型数据库的首选引擎,支持事务安全表(ACID),支持行锁定和外键
InnoDB是默认的MySQL引擎
具有提交、回滚和崩溃恢复能力的事物安全
在SQL查询中,可以自由地将InnoDB类型的表和其他MySQL的表类型混合起来,甚至在同一个查询中也可以混合
InnoDB是为处理巨大数据量的最大性能设计。它的CPU效率可能是任何其他基于磁盘的关系型数据库引擎锁不能匹敌的
InnoDB支持外键完整性约束,存储表中的数据时,每张表的存储都按主键顺序存放,如果没有显示在表定义时指定主键,InnoDB会为每一行生成一个6字节的ROWID,并以此作为主键。
InnoDB将它的表和索引在一个逻辑表空间中
MyISAM
基于ISAM存储引擎,并对其进行扩展
MyISAM拥有较高的插入、查询速度,但不支持事务
大文件(达到63位文件长度)在支持大文件的文件系统和操作系统上被支持
NULL被允许在索引的列中,这个值占每个键的0~1个字节
可以把数据文件和索引文件放在不同目录
当把删除和更新及插入操作混合使用的时候,动态尺寸的行产生更少碎片。这要通过合并相邻被删除的块,以及若下一个块被删除,就扩展到下一块自动完成
每个MyISAM表最大索引数是64,这可以通过重新编译来改变。每个索引最大的列数是16
MEMORY
MEMORY存储引擎将表中的数据存储到内存中,为查询和引用其他表数据提供快速访问
MEMORY表的每个表可以有多达32个索引,每个索引16列,以及500字节的最大键长度
MEMORY不支持BLOB或TEXT列
MEMORY支持AUTO_INCREMENT列和对可包含NULL值的列的索引
当不再需要MEMORY表的内容时,要释放被MEMORY表使用的内存,应该执行DELETE FROM或TRUNCATE TABLE,或者删除整个表(使用DROP TABLE)
Archive
InnoDB和MyISAM的区别(重点)
1. InnoDB 支持事务,MyISAM 不支持事务。这是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;
2. InnoDB 支持外键,而 MyISAM 不支持。对一个包含外键的 InnoDB 表转为 MYISAM 会失败;
3. InnoDB 是聚集索引,MyISAM 是非聚集索引。聚簇索引的文件存放在主键索引的叶子节点上,因此 InnoDB 必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。而 MyISAM 是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
4. InnoDB 不保存表的具体行数,执行 select count(*) from table 时需要全表扫描。而MyISAM 用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
5. InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁。一个更新语句会锁住整张表,导致其他查询和更新都会被阻塞,因此并发访问受限。这也是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;
MyISAM:读写插入为主,比如博客,新闻门户
InnoDB:更新删除频率高,或者数据完整性;并发性高,支持事务和外键,比如自动化办公系统
三大范式
第一范式
字段具有原子性,不可再拆分
第二范式
在第一范式的基础上,数据库每一行可以被唯一的区分,有主键
第三范式
在第二范式的基础上,不产生传递依赖关系,每个字段直接依赖于主键,而是间接依赖
订单表里除了有订单id,还有书籍id,不要在这个订单表里放书籍名称,不然每多一个书籍id,就必须带上书籍名称,造成数据冗余。书籍id直接依赖于订单id,但是书籍名称不直接依赖于订单id。
事务
事务的特性
1.原子性
要么都成功,要么都失败
2.一致性
修改操作后和操作前系统状态一致
3.隔离性
并发执行事务彼此无法看见中间态
脏读
事务A读取到事务B未提交的数据
不可重复读
事务A读取两次,数据不一样(第二次读事务A读取了事务B已提交修改后的数据和第一次不一致)
行级别
关注修改
幻读
事务A重新执行查询,返回的行集合中有事务B提交的行
表级别
关注插入和删除
隔离级别
1.读未提交
出现脏读、不可重复读、幻读
2.读已提交
出现不可重复读、幻读
3.可重复读
出现幻读
4.可串行化
4.持久性
事务完成后的持久化操作
索引
概念
加快查询表中数据,不至于扫描整个表
不能创建在视图上
索引是数据结构
不同存储引擎对索引的实现方式是不同的
MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址
MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址,按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。
这种索引叫做非聚集索引
主索引(数据库为主键自动创建的索引)要求key是唯一的,而辅助索引(用户创建的索引)的key可以重复
InnoDB也使用B+Tree作为索引结构,但具体实现方式却与MyISAM截然不同
InnoDB的数据文件本身就是索引文件,这棵树的叶节点data域保存了完整的数据记录,InnoDB的辅助索引data域存储相应记录主键的值而不是数据记录地址,首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录
这种索引叫做聚集索引
为什么不建议使用过长的字段作为主键,因为所有辅助索引都引用主索引,过长的主索引会令辅助索引变得过大
用非单调的字段作为主键在InnoDB中不是个好主意,因为InnoDB数据文件本身是一棵B+Tree,非单调的主键会造成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用自增字段作为主键则是一个很好的选择
分支主题
索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上
分类
逻辑上
普通索引
唯一索引
主键索引
组合索引
全文索引
拓展
二叉排序树
若左子树不空,则左子树上所有节点的值均小于它的根节点的值
若右子树不空,则右字数上所有节点的值均大于它的根节点的值
它的左、右子树也分别为二叉排序数(递归定义)
极端情况会出现所有节点都位于同一侧,直观上看就是一条直线,那么这种查询的效率就比较低了,因此需要对二叉树左右子树的高度进行平衡化处理,于是就有了平衡二叉树(Balenced Binary Tree)
平衡二叉树
这棵树的各个分支的高度是均匀的,它的左子树和右子树的高度之差绝对值小于1
B树
B树事实上是一种平衡的多叉查找树,也就是说最多可以开m个叉(m>=2),我们称之为m阶b树
每个节点至多可以拥有m棵子树。
根节点,只有至少有2个节点
非根非叶的节点至少有的Ceil(m/2)个子树(Ceil表示向上取整,图中5阶B树,每个节点至少有3个子树,也就是至少有3个叉)
从根到叶子的每一条路径都有相同的长度
从根节点依次比较每个结点,因为每个节点中的关键字和左右子树都是有序的,所以只要比较节点中的关键字,或者沿着指针就能很快地找到指定的关键字,如果查找失败,则会返回叶子节点,即空指针
从根节点P开始,K的位置在P之前,进入左侧指针。
左子树中,依次比较C、F、J、M,发现K在J和M之间。
沿着J和M之间的指针,继续访问子树,并依次进行比较,发现第一个关键字K即为指定查找的值
分支主题
B+树
和B树的区别
有n棵子树的节点含有n个关键字(也有认为是n-1个关键字)。
所有的关键字全部存储在叶子节点上,且叶子节点本身根据关键字自小而大顺序连接。
非叶子节点可以看成索引部分,节点中仅含有其子树(根节点)中的最大(或最小)关键字
所有关键字都存储在叶子节上,且链表中的关键字恰好是有序的。
不可能非叶子节点命中返回。
非叶子节点相当于叶子节点的索引,叶子节点相当于是存储(关键字)数据的数据层。
更适合文件索引系统。
优化的B+树:增加了顺序访问指针
在B+Tree的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的B+Tree。做这个优化的目的是为了提高区间访问的性能,例如图4中如果要查询key为从18到49的所有数据记录,当找到18后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率
分支主题
一般在数据库系统或文件系统中使用
杂烩
分页语句
mysql关键字limit
sqlserver关键字top
oracle关键字rownum
Select语句的执行顺序
执行顺序
from>where>group by>having>select>order by
聚合函数
avg()
count()
max()
min()
sum()
group by()
查询语句连接
外连接
左外连接
以左表为基准,左表全显示,右表匹配就显示,否则显示null
右外连接
以右表为基准,右表全显示,左表匹配就显示,否则显示null
内连接
只显示匹配到的行
全连接
先左外连接,后右外连接
自连接
char和varcahr的区别
存储字符串'abc'
使用char(10),表示存储的字符将占10个字节(包括7个空字符)
使用varchar(10),则表示只占3个字节
面试题
分库分表之后,id 主键如何处理
其实这是分库分表之后你必然要面对的一个问题,就是 id 咋生成?因为要是分成多个表之后,每个表都是从 1 开始累加,那肯定不对啊,需要一个全局唯一的 id 来支持
解决
基于数据库的实现方案
数据库自增 id
这个就是说你的系统里每次得到一个 id,都是往一个库的一个表里插入一条没什么业务含义的数据,然后获取一个数据库自增的一个 id。拿到这个 id 之后再往对应的分库分表里去写入
这个方案的好处就是方便简单,谁都会用;缺点就是单库生成自增 id,要是高并发的话,就会有瓶颈的;如果你硬是要改进一下,那么就专门开一个服务出来,这个服务每次就拿到当前 id 最大值,然后自己递增几个 id,一次性返回一批 id,然后再把当前最大 id 值修改成递增几个 id 之后的一个值;但是无论如何都是基于单个数据库
适合的场景
并发不高,但是数据量太大
设置数据库 sequence 或者表自增字段步长
现在有 8 个服务节点,每个服务节点使用一个 sequence 功能来产生 ID,每个 sequence 的起始 ID 不同,并且依次递增,步长都是 8
适用的场景
在用户防止产生的 ID 重复时,这种方案实现起来比较简单,也能达到性能目标。但是服务节点固定,步长也固定,将来如果还要增加服务节点,就不好搞了
UUID
好处就是本地生成,不要基于数据库来了;不好之处就是,UUID 太长了、占用空间大,作为主键性能太差了;更重要的是,UUID 不具有有序性,会导致 B+ 树索引在写的时候有过多的随机写操作(连续的 ID 可以产生部分顺序写),还有,由于在写的时候不能产生有顺序的 append 操作,而需要进行 insert 操作,将会读取整个 B+ 树节点到内存,在插入这条记录后会将整个节点写回磁盘,这种操作在记录占用空间比较大的情况下,性能下降明显
适合的场景
如果你是要随机生成个什么文件名、编号之类的,你可以用 UUID,但是作为主键是不能用 UUID 的
获取系统当前时间
问题是,并发很高的时候,比如一秒并发几千,会有重复的情况,这个是肯定不合适的。基本就不用考虑了
适合的场景
一般如果用这个方案,是将当前时间跟很多其他的业务字段拼接起来,作为一个 id,如果业务上你觉得可以接受,那么也是可以的。你可以将别的业务字段值跟当前时间拼接起来,组成一个全局唯一的编号
snowflake 算法
是 twitter 开源的分布式 id 生成算法
MySQL查询字段区不区分大小写
不区分
如何解决需要区分英文大小写的场景
utf8_general_ci,表示不区分大小写
utf8_general_cs,表示区分大小写,也可以使用utf8_bin,表示二进制比较,同样也区分大小写
直接修改sql语句,在要查询的字段前面加上binary关键字
疑难点
运行时异常与非运行时异常的区别
运行时异常
运行时异常都是RuntimeException类及其子类异常
这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生
非运行时异常
非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类
对于这种异常,JAVA编译器强制要求我们必需对出现的这些异常进行catch并处理,否则程序就不能编译通过
关系型数据库和非关系型区别
数据存储方式不同
关系型数据天然就是表格式的
非关系型数据通常存储在数据集中,就像文档、键值对或者图结构
扩展方式不同
SQL数据库是纵向扩展
NoSQL数据库是横向扩展
对事务性的支持不同
SQL数据库支持对事务原子性细粒度控制,并且易于回滚事务
NoSQL数据库也可以使用事务操作,但稳定性方面没法和关系型数据库比较
日志的级别
off
fatal
error
warn
info
debug
trace
all
BigDecimal一定不会丢失精度吗?
一般使用BigDecimal来解决商业运算上丢失精度的问题的时候,声明BigDecimal对象的时候一定要使用它构造参数为String的类型的构造器
其他的如BigDecimal b = new BigDecimal(1)这种,还是会发生精度丢失的问题
目前大多数的互联网项目,都是部署在Linux上,也就是说,日志都是在Linux,下面归纳些实际的Linux操作。
1、能通过less命令打开文件,通过Shift+G到达文件底部,再通过?+关键字的方式来根据关键来搜索信息。
2、能通过grep的方式查关键字,具体用法是, grep 关键字 文件名,如果要两次在结果里查找的话,就用grep 关键字1 文件名 | 关键字2 --color。最后--color是高亮关键字。
3、能通过vi来编辑文件。
4、能通过chmod来设置文件的权限。
0 条评论
下一页
为你推荐
查看更多