Spring,SpringMVC,SpringBoot,SpringCloud
- Spring是核心,提供了基础功能;(控制反转IOC、面向切面AOP)
- SpringMVC是基于Spring的一个MVC框架;是Spring的一个模块,针对网站应用程序或者服务开发(URL路由、session、模板引擎、静态web资源等)
- SpringBoot 是简化了Spring配置的快速开发整合包
- SpringColid 是构建在SpringBoot之上的服务治理框架
Spring框架中Bean的生命周期
- 通常通过配置文件定义Bean
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd”>
<bean id=”HelloWorld” class=”com.pqf.beans.HelloWorld”>
<property name=”msg”>
<value>HelloWorld</value>
</property>
</bean>
</beans>
- 初始化bean,常在配置文件中指定init-method来初始化
- 调用
- 销毁,使用destory-method属性指定销毁方法
HashMap和TreeMap
都是非线程安全的
HashMap: 要求添加的键明确定义了hashCode()和equals(),可以重写,可调优初始化容量和负载因子
TreeMap: 基于红黑树,没有调优选项,默认升序排列,可重写compare方法自定义比较器对关键字进行排序
如果你需要得到一个有序的结果时就应该使用TreeMap(因为HashMap中元素的排列顺序是不固定的)。除此之外,由于HashMap有更好的性能,所以大多不需要排序的时候我们会使用HashMap
public class MapTest {
public static void main(String[] args) {
//初始化自定义比较器
MyComparator comparator = new MyComparator();
//初始化一个map集合
Map<String,String> map = new TreeMap<String,String>(comparator);
//存入数据
map.put("a", "a");
map.put("b", "b");
map.put("f", "f");
map.put("d", "d");
map.put("c", "c");
map.put("g", "g");
//遍历输出
Iterator iterator = map.keySet().iterator();
while(iterator.hasNext()){
String key = (String)iterator.next();
System.out.println(map.get(key));
}
}
static class MyComparator implements Comparator{
@Override
public int compare(Object o1, Object o2) {
// TODO Auto-generated method stub
String param1 = (String)o1;
String param2 = (String)o2;
return -param1.compareTo(param2);
}
}
}
分库分表主键处理
- 置数据库其实ID和步长。
实现简单。但是服务节点固定,步长固定。以后增加服务节点影响大。 - 雪花算法
单例模式
- 基础饱汉式(懒加载),多在单线程下使用
public class Singleton1 {
private static Singleton1 singleton = null;
private Singleton1() {
}
public static Singleton1 getInstance() {
if (singleton == null) {
singleton = new Singleton1();
}
return singleton;
}
}
- 饿汉式,类加载后一直不使用会造成资源浪费,多线程性能好
public class Singleton2 {
private static final Singleton2 singleton = new Singleton2();
private Singleton2() {
}
public static Singleton2 getInstance() {
return singleton;
}
}
Redis实现分布式锁
Redisson 单点模式例子:
// 1.构造redisson实现分布式锁必要的Config
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:5379").setPassword("123456").setDatabase(0);
// 2.构造RedissonClient
RedissonClient redissonClient = Redisson.create(config);
// 3.获取锁对象实例(无法保证是按线程的顺序获取到)
RLock rLock = redissonClient.getLock(lockKey);
try {
/**
* 4.尝试获取锁
* waitTimeout 尝试获取锁的最大等待时间,超过这个值,则认为获取锁失败
* leaseTime 锁的持有时间,超过这个时间锁会自动失效(值应设置为大于业务处理的时间,确保在锁有效期内业务能处理完)
*/
boolean res = rLock.tryLock((long)waitTimeout, (long)leaseTime, TimeUnit.SECONDS);
if (res) {
//成功获得锁,在这里处理业务
}
} catch (Exception e) {
throw new RuntimeException("aquire lock fail");
}finally{
//无论如何, 最后都要解锁
rLock.unlock();
}
hashCode()和equals()
equals(): 用来判断两个对象是否相等
hashCode(): 获得散列码,确定该对象在哈希表中的索引位置
如果equals()相等,那么他们的hashCode()值一定相同。反过来不成立
什么是缓存雪崩?怎么解决
缓存雪崩: 通常会使用缓存用于缓冲对数据库的冲击,如果缓存宕机,所有请求将直接打在数据库,造成数据库宕机,从而导致整个系统宕机
解决: 对缓存做高可用(集群),防止缓存宕机。使用断路器,如果缓存宕机,限制部分流量进入DB。保证部分可用,其余请求返回断路器的默认值
事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
缓存穿透
缓存穿透: 缓存查询一个没有的key,同时数据库也没有,发生这种大量查询方式会导致DB宕机
解决: 访问一个不存在的key,然后再去查数据库,如果还是没有,就在缓存中放一个占位符,下次访问时,检查这个占位符,不去查询数据库
大量请求查询一个刚刚失效的key,导致DB压力倍增,也可能造成宕机,但实际查询的数据是相同的。可以在这些请求代码上加双重检查锁。
缓存并发竞争
解释: 多个客户端写一个key,如果顺序错了,数据就不对了,但是顺序无法控制
解决方案: 使用分布式锁,同一时刻只有抢到锁的客户端才可以写入,同时在写入的时候比较当前数据的时间戳和缓存中数据的时间戳
缓存和数据库双写不一致
连续写数据库和缓存,但是操作期间出现并发,数据不一致。
通常使用先更新数据库,再删除缓存
幂等
添加请求的表单里,在打开添加表单页面的时候,就生成一个AddId标识,这个AddId跟着表单一起提交到后台接口。
后台接口根据这个AddId,服务端就可以进行缓存标记并进行过滤,缓存值可以是AddId作为缓存key,返回内容作为缓存Value,这样即使添加按钮被多次点下也可以识别出来。
在保存成功并且清空表单之后,才变更这个AddId标识,从而实现新数据的表单提交
HashMap
基于哈希表的 Map 接口的实现。并允许使用 null 值和 null 键。
HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。
HashMap底层是通过链表来解决hash冲突的。
一般对哈希表的散列很自然地会想到用hash值对length取模(即除法散列法),Hashtable中也是这样实现的,这种方法基本能保证元素在哈希表中散列的比较均匀,但取模会用到除法运算,效率很低,HashMap中则通过h&(length-1)的方法来代替取模,同样实现了均匀的散列,但效率要高很多
在jdk1.8中,在多线程环境下,会发生数据覆盖的情况
ArrayList和LinkedList
当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会有更好的性能;当操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了
构造一个拥有100万数据的ArrayList和等价的LinkedList
最简便的ForEach循环并没有很好的性能表现,综合性能不如普通的迭代器,而是用for循环通过随机访问遍历列表时,ArrayList表项很好,但是LinkedList的表现却无法让人接受,甚至没有办法等待程序的结束。这是因为对LinkedList进行随机访问时,总会进行一次列表的遍历操作。性能非常差,应避免使用
LinkedList
- 优点:
- 不需要扩容和预留空间,空间效率高
- 增删效率高
- 缺点:
- 随机访问时间效率低
- 改查效率低
Java序列化与反序列化
序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程
- 对象序列化可以实现分布式对象
- java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据
- 序列化可以将内存中的类写入文件或数据库中
- 对象、文件、数据,有许多不同的格式,很难统一传输和保存
实现序列化: 实现Serializable接口
实现序列化: 输入输出流
消息队列
特性: 一个具有普适性的消息队列组件,不需要考虑上层业务模型,只需要做好消息的分发。
为什么需要消息队列: 当系统中出现生产和消费的速度或者稳定性等因素不一致的时候就需要消息队列,作为抽象层,弥合双方的差异。
消息队列的好处:
- 提高系统响应速度
- 提高系统稳定性
- 异步化、解耦、消除峰值
为什么在分布式中使用消息队列:
- 消息队列中的数据需要在多个系统间共享数据才能发挥价值。所以需要提供分布式通信机制、协同机制
Dubbo 分布式服务框架
核心:
- 远程通讯:透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需要简单配置,没有任何API侵入
- 集群容错:软负载均衡及容错机制,降低成本,减少单点
- 自动发现:服务自动注册与发现,不需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或者删除服务提供者
List、Set、Map 之间的区别是什么
List列表: 以线性方式存储,可以存放重复对象
- ArrayList:长度可变的数组,插入删除元素速度慢
- LinkedList:采用链表数据结构,插入和删除速度快,但是访问速度慢
Set集合: 对象不安特定的方式排序,没有重复对象
- HashSet:按照hash算法来存取集合中的对象,存取速度比较快。
- TreeSet:实现了SortedSet接口,能够对集合中的对象进行排序
Map映射: 时一种把键对象和值对象映射的集合
- HashMap:基于散列表实现,插入和查询的开销是固定的
- LinkedHashMap:类似于HashMap,但是迭代遍历它时,取得的元素顺序是其插入顺序
- TreeMap:基于红黑树实现。查看时,会被排序。
ZooKeeper
四种类型的znode
- PERSISTENT-持久化目录节点
- PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
- EPHEMERAL-临时目录节点
- EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
用途
- 命名服务
- 配置管理
- 集群管理
是否有机器退出和加入、选举master。每次选取编号最小的机器作为master - 分布式锁
- 队列管理
Mybatis
#{}和${}的区别是什么?
#{}是预编译处理,防止sql注入。$ {}是字符串替换,直接替换参数,不能有效防止注入。
Xml映射文件中,常见标签
select,insert,updae,delete,resultMap、parameterMap、sql、include、selectKey
trim,where,set,foreach,if,choose,when,otherwise,bind
Dao接口的工作原理
映射文件中的namespace的值是Dao的全类名
Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。
为什么说Mybatis是半自动ORM映射工具
Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取
Java线程之间通信方式
- synchronized 同步
本质上就是“共享内存”式的通信。多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行 - while轮询的方式
- wait/notify机制
volatile 与 synchronized 的比较
①volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法
②volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。synchronized不仅保证可见性,而且还保证原子性
类加载流程
如果该类还未被加载到内存中,则JVM会通过加载、链接、初始化三个步骤对该类进行类加载。
- 加载
将类的class文件读入内存,并为之创建一个java.lang.Class对象 - 链接
将二进制数据合并到 JRE中。链接需要通过验证、准备、解析三个阶段 - 初始化
虚拟机主要对类变量进行初始化:- 一个类要创建实例需要先加载并初始化该类
- 一个子类要初始化需要先初始化父类
- 一个类初始化,先执行静态类变量和静态代码块,按顺序只执行一次
BeanFactory和FactoryBean的区别
区别:BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的
BeanFactory
在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。
现在一般使用ApplicationnContext,其不但包含了BeanFactory的作用,同时还进行更多的扩展。
FactoryBean
Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑
。
位运算
符号 | 描述 | 运算规则 |
---|---|---|
& | 与 | 两个位都为1时,结果才为1 |
或 | 两个位都为0时,结果才为0 | |
^ | 异或 | 两个位相同为0,相异为1 |
~ | 取反 | 0变1,1变0 |
<< | 左移 | 各二进位全部左移若干位,高位丢弃,低位补0 |
>> | 右移 | 各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移) |
判断奇偶: 最未位是0还是1来决定,为0就是偶数,为1就是奇数;if ((a & 1) == 0)
Java引用类型
强引用、软引用、弱引用、虚引用。
####################
Java锁的种类
公平锁/非公平锁
可重入锁
独享锁/共享锁
互斥锁/读写锁
乐观锁/悲观锁
分段锁
偏向锁/轻量级锁/重量级锁
自旋锁
Spring的IOC
控制反转:即上层控制下层,而不是下层控制着上层。我们用依赖注入(Dependency Injection)这种方式来实现控制反转。
所谓依赖注入,就是把底层类作为参数传入上层类,实现上层类对下层类的“控制”
通过反射去创造实例
AOP
面向切面,实现原理是动态代理
动态代理:他是将代理模式变成动态的,利用的是反射和动态编译原理
反射
反射就是在运行的时候才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法
Class clazz=Class.forName(className);
System.out.println("获取共有和私有的所有字段,但不能获取父类字段");
Field[] fields= clazz.getDeclaredFields();
System.out.println("获取指定字段");
Field field=clazz.getDeclaredField("name");
System.out.println("获取所有方法,包括私有方法" +
"所有声明的方法,且获取当前类方法");
methods= clazz.getDeclaredMethods();
动态代理
静态代理:
//代表真实角色
public class AaFactory implements ManToolsFactory {
@Override
public void saleManTools(String size) {
System.out.println("按需求定制了一个size为"+size+"的女model");
}
}
//抽象角色
public interface ManToolsFactory {
void saleManTools(String size);
}
/*代理角色*/
public class Mark implements ManToolsFactory {
public ManToolsFactory factory;
public Mark(ManToolsFactory factory) {
this.factory = factory;
}
@Override
public void saleManTools(String size) {
// 调用前后,可以额外实现其他业务需求。
factory.saleManTools(size);
}
}
//访问
public static void main(String[] args) {
AaFactory factory = new AaFactory();
Mark mark = new Mark(factory);
mark.saleManTools("D");
}
通过引入代理对象的方式来间接访问目标对象
真实对象和代理对象都要实现同一个抽象接口
代理对象可以额外做多个事情,通过传入真实角色,来间接访问
动态代理
public class AaFactory implements ManToolsFactory {
@Override
public void saleManTools(String size) {
System.out.println("按需求定制了一个size为"+size+"的女model");
}
}
--------------------------------------------------------------------------------------
public class BbFactory implements WomanToolsFactory {
@Override
public void saleWomanTools(float length) {
System.out.println("按需求定制了一个高度为"+length+"的男model");
}
}
----------------------------------------------------------------------------------------
public class MarkCompany implements InvocationHandler {
/*持有的真实对象*/
private Object factory;
public Object getFactory() {
return factory;
}
public void setFactory(Object factory) {
this.factory = factory;
}
/*通过proxy获得动态代理对象*/
public Object getProxyInstance(){
/**
*三个参数,
*第一个是被代理类的类构造器,
*第二个指的是被代理类的接口,也就是Subject接口,
*第三个是实现这个代理的类
*
**/
return Proxy.newProxyInstance(factory.getClass().getClassLoader(),factory.getClass().getInterfaces(),this);
}
/*通过动态代理对象方法进行增强*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
doSthBefore();
Object result = method.invoke(factory, args);
doSthAfter();
return result;
}
/*后置处理器*/
private void doSthAfter() {
System.out.println("精美包装,快递一条龙服务");
}
/*前置处理器*/
private void doSthBefore() {
System.out.println("根据需求,进行市场调研和产品分析");
}
}
----------------------------------------------------------------------------
public interface ManToolsFactory {
void saleManTools(String size);
}
-----------------------------------------------------------------------------
public interface WomanToolsFactory {
void saleWomanTools(float length);
}
----------------------------------------------------------------------------------
public static void main(String[] args) {
/*动态代理*/
ManToolsFactory manToolsFactory = new AaFactory();
MarkCompany company = new MarkCompany();
company.setFactory(manToolsFactory);
ManToolsFactory employee = (ManToolsFactory) company.getProxyInstance();
employee.saleManTools("E");
WomanToolsFactory womanToolsFactory = new BbFactory();
company.setFactory(womanToolsFactory);
WomanToolsFactory employee1 = (WomanToolsFactory) company.getProxyInstance();
employee1.saleWomanTools(1.9f);
}
Redis的过期键删除策略
- 定时删除: 设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。
- 惰性删除: 每次从键空间中获取键时,检查该键是否过期,如果过期,就删除该键,如果没有过期,就返回该键。
- 定期删除: 每隔一段时间,程序对数据库进行一次检查,删除里面的过期键
List如何一边遍历,一边删除
- 使用Iterator的remove()方法
List<String> platformList = new ArrayList<>(); platformList.add("AAA"); platformList.add("BBB"); platformList.add("CCC"); Iterator<String> iterator = platformList.iterator(); while (iterator.hasNext()) { String platform = iterator.next(); if (platform.equals("AAA")) { iterator.remove(); } } System.out.println(platformList);
- for循环
List<String> platformList = new ArrayList<>(); platformList.add("AAA"); platformList.add("BBB"); platformList.add("CCC"); for (int i = 0; i < platformList.size(); i++) { String item = platformList.get(i); if (item.equals("AAA")) { platformList.remove(i); i = i - 1; } } System.out.println(platformList);
- stream流
List<String> platformList = new ArrayList<>(); platformList.add("AAA"); platformList.add("BBB"); platformList.add("CCC"); List<String> collect = platformList.stream().filter(iteam -> !iteam.equals("AAA")).collect(Collectors.toList()); System.out.println(collect);
面向对象设计的六大原则
单一职责原则——SRP
- 一个类应该只负责一件事情
开闭原则——OCP
- 软件中的对象(类,模块,函数等)应该对于扩展是开放的,但是对于修改是关闭的
里式替换原则——LSP
- 子类可以去扩展父类的功能,但是不能改变父类原有的功能
依赖倒置原则——DIP
- 高层模块不应该依赖底层模块,两者都应该依赖其抽象类 抽象类不应该依赖实现类 实现类应该依赖抽象
接口隔离原则——ISP
- 客户端不应该依赖他不需要的接口
迪米特原则——LOD
- 一个对象应该对其他对象保持最小的了解。
Nginx
Nginx是一个 轻量级/高性能的反向代理Web服务器。跨平台、配置简单、方向代理、高并发连接
正向代理 就是一个人发送一个请求直接就到达了目标的服务器。
反方代理 就是请求统一被Nginx接收,nginx反向代理服务器接收到之后,按照一定的规则分发给了后端的业务处理服务器进行处理了。
server { # 第一个Server区块开始,表示一个独立的虚拟主机站点
listen 80; # 提供服务的端口,默认80
server_name localhost; # 提供服务的域名主机名
location / { # 第一个location区块开始
root html; # 站点的根目录,相当于Nginx的安装目录
index index.html index.htm; # 默认的首页文件,多个用空格分开
} # 第一个location区块结果
}
应用场景
- 网页静态服务器
- 虚拟主机
- 反向代理,负载均衡
- 搭建API接口网关
动静分离
server {
listen 80;
server_name www.lijie.com;
location / {
### 指定上游服务器负载均衡服务器
proxy_pass http://backServer;
###nginx与上游服务器(真实访问的服务器)超时时间 后端服务器连接的超时时间_发起握手等候响应超时时间
proxy_connect_timeout 1s;
###nginx发送给上游服务器(真实访问的服务器)超时时间
proxy_send_timeout 1s;
### nginx接受上游服务器(真实访问的服务器)超时时间
proxy_read_timeout 1s;
index index.html index.htm;
}
}
Java GC机制
- 方法区: 存储整个 class文件的信息
- 堆( Heap): 每个应用都唯一对应一个JVM实例,而每一个JVM实例唯一对应一个堆。堆主要包括关键字 new的对象实例、 this指针,或者数组都放在堆中,并由应用所有的线程共享。堆由JVM的自动内存管理机制所管理,名为垃圾回收—— GC(garbage collection)
- 栈( Stack): 操作系统内核为某个进程或者线程建立的存储区域,它保存着一个线程中的方法的调用状态
- 寄存器: 存放一条指令的地址,每一个线程都有一个PC寄存器
- 本地方法栈: 用来调用其他语言的本地方法
synchronized 对象锁和类锁
synchronized 加到 static 方法前面是给class 加锁,即类锁;
而synchronized 加到非静态方法前面是给对象上锁
类锁对该类的所有对象都能起作用,而对象锁不能
Java深拷贝和浅拷贝
浅拷贝: 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。也就是说,拷贝对象和被拷贝对象是两个不同对象,但是值相同。他们属性中对其他对象的引用是指的是同一个对象
Student2 student2 = (Student2) student1.clone();
深拷贝: 深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。
Java的clone是浅拷贝,要想实现深拷贝,需要自己重谢clone方法
Error 和 Exception 区别
Error 类型的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等。仅靠应用程序本身无法恢复;
Exception 类的错误是可以在应用程序中进行捕获并处理的
JVM 是如何处理异常的
在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给 JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给 JVM 的过程称为抛出异常
JVM 会顺着调用栈去查找看是否有可以处理异常的代码,如果有,则调用异常处理代码
如果 JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器(默认处理器为 JVM 的一部分),默认异常处理器打印出异常信息并终止应用程序。
异常方法创建异常对象-----------》jvm拿到异常对象------------》寻找异常处理代码块
throw 和 throws 的区别是什么
- throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常,受查异常和非受查异常都可以被抛出。
- throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。一个方法用 throws 标识了可能抛出的异常列表,调用该方法的方法中必须包含可处理异常的代码,否则也要在方法签名中用 throws 关键字声明相应的异常。
final、finally、finalize 有什么区别
- final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
- finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
- finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,Java 中允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作
MySQL索引的设计原则
- 选择唯一性索引
- 为经常需要排序、分组和联合操作的字段建立索引
- 为常作为查询条件的字段建立索引
- 限制索引的数目
- 尽量使用数据量少的索引
- 数据量小的表最好不要使用索引
MySQL中myisam与innodb的区别
MyISAM:
- 不支持事务,但是每次查询都是原子的;
- 支持表级锁,即每次操作对整个表加锁;
- 存储表的总行数;
- 一个MYISAM表有三个文件:索引文件、表结构文件、数据文件;
- 采用非聚集索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致,但是辅索引不用保证唯一性。
InnoDb:
- 支持ACID的事务,支持事务的四种隔离级别;
- 支持行级锁及外键约束:因此可以支持写并发;
- 不存储总行数;
- 一个InnoDb引擎存储在一个文件空间(共享表空间,表大小不受操作系统控制,一个表可能分布在多个文件里),也有可能为多个(设置为独立表空,表大小受操作系统文件大小限制,一般为2G),受操作系统文件大小的限制;
- 主键索引采用聚集索引(索引的数据域存储数据文件本身),辅索引的数据域存储主键的值;因此从辅索引查找数据,需要先通过辅索引找到主键值,再访问辅索引;最好使用自增主键,防止插入数据时,为维持B+树结构,文件的大调整。
MySQL行锁和表锁
MyISAM不支持行锁,而InnoDB支持行锁和表锁
MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。
上共享锁(读锁)的写法:lock in share mode,例如:
select math from zje where math>60 lock in share mode;
上排它锁(写锁)的写法:for update,例如:
select math from zje where math >60 for update;
行锁
- 行锁必须有索引才能实现,否则会自动锁全表,那么就不是行锁了。
- 两个事务不能锁同一个索引。
- insert,delete,update在事务中都会自动默认加上排它锁
MySQL索引
- Normal 普通索引
- Unique 唯一索引:索引列的值必须唯一,但允许有空值。
- Full Text 全文索引
- SPATIAL 空间索引:有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON
PRIMARY KEY(主键索引)
ALTER TABLE
table_name
ADD PRIMARY KEY (column
)
UNIQUE(唯一索引)
ALTER TABLE
table_name
ADD UNIQUE (column
)
INDEX(普通索引)
ALTER TABLE
table_name
ADD INDEX index_name (column
)
FULLTEXT(全文索引)
ALTER TABLE
table_name
ADD FULLTEXT (column
)
索引和sql语句的优化
- 前导模糊查询不能使用索引
- Union、in、or可以命中索引,建议使用in
- 负条件查询不能使用索引,可以优化为in查询
- 联合索引最左前缀原则,又叫最左侧查询,如果在(a,b,c)三个字段上建立联合索引,那么它能够加快a|(a,b)|(a,b,c)三组的查询速度。
- 建立联合查询时,区分度最高的字段在最左边
- 如果建立了(a,b)联合索引,就不必再单独建立a索引
- 范围列可以用到索引,但是范围列后面的列无法用到索引。索引最多用于一个范围列
- 在字段上计算不能命中索引。
- 强制类型转换会全表扫描
- 更新十分频繁、数据区分度不高的字段上不宜建立索引。
- 建立索引的列不能为null,使用not null约束及默认值
- 如果明确知道查询结果只要一条,limit 1能够提高效率
- 如果排序字段没有用到索引,就尽量少排序
查看sql是否使用索引
explain sql语句
MySQL日志文件
- redo log:重做日志。记录已成功提交事务的修改信息
- undo log:回滚日志。记录数据被修改前的信息
mysql锁技术以及MVCC基础
- 共享锁(shared lock),又叫做"读锁":多个读请求可以共享一把锁读数据,不会造成阻塞。
- 排他锁(exclusive lock),又叫做"写锁":排斥其他所有获取锁的请求,一直阻塞,直到写入完成释放锁。
- MVCC :多版本并发控制。通过在每行记录的后面保存两个隐藏的列来实现的。这两个列, 一个保存了行的创建时间,一个保存了行的过期时间,当然存储的并不是实际的时间值,而是系统版本号。
MySQL事务特点
原子性:使用 undo log ,从而达到回滚
持久性:使用 redo log,从而达到故障后恢复
隔离性:使用锁以及MVCC,运用的优化思想有读写分离,读读并行,读写并行
一致性:通过回滚,以及恢复,和在并发环境下的隔离做到一致性。
Mysql 隔离级别
- read uncommitted : 读取尚未提交的数据 :就是脏读
- read committed:读取已经提交的数据 :可以解决脏读
- repeatable read:可重复读:可以解决脏读 和 不可重复读 —mysql默认的
- serializable:串行化:可以解决 脏读 不可重复读 和 虚读—相当于锁表
创建对象的五种方法
-
new
new User();
-
使用Class类的newInstance方法
User emp2 = (User)
Class.forName(“com.demo.User”).newInstance(); -
使用Constructor类的newInstance方法
Constructor constructor = User.class.getConstructor();
User emp3 = constructor.newInstance(); -
使用clone方法
-
使用反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream(“data.obj”));
User emp5 = (User) in.readObject();
==和equals有什么区别
==比较对象地址
equals比较对象内容
如果一个对象没有自己的equals,那么使用的是Object的equals方法,效果和==一样
mybatis的xml接收复杂参数
- item:集合中元素迭代时的别名,该参数为必选。
- index:在list和数组中,index是元素的序号,在map中,index是元素的key,该参数可选
- open:foreach代码的开始符号,一般是(和close=")"合用。常用在in(),values()时。该参数可选
- separator:元素之间的分隔符,例如在in()的时候,separator=","会自动在元素中间用“,“隔开,避免手动输入逗号导致sql错误,如in(1,2,)这样。该参数可选。
- close: foreach代码的关闭符号,一般是)和open="("合用。常用在in(),values()时。该参数可选。
- collection: 要做foreach的对象,作为入参时
public List<User> selectByIds(List<Integer> list);
-------------------------------------------------------------------------
<select id="selectByIds" resultType="java.util.ArrayList">
select * from t_user where id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
RabbitMQ
生产者
- 连接工厂:配置连接的host、port、username、password、vhost
rabbitmq默认虚拟机名称为“/”,虚拟机相当于一个独立的mq服务器
在rabbitmq服务器的admin页的Virtual Hosts可以创建新的vhost - 通过连接工厂得到连接对象connection
- 通过连接对象得到通道channel,每个连接可创建多个通道
- 通过通道声明队列。(五个参数:队列名称、是否持久化、是否该连接独占、不使用时是否自动删除、扩展参数)
- 通过通道发布消息。(四个参数:交换机名称、路由、消息属性、消息体)。根据路由去匹配队列名称
消费者
- 连接工厂创建连接
- 得到通道
- 声明队列
- DefaultConsumer consumer = new DefaultConsumer(channel)消费消息
- channel.basicConsume监听消息(三个参数:队列名称、是否自动回复、接收到消息调用的方法consumer)
交换机类型: 指定路由类型
- direct:默认。点对点发布订阅。需要路由完全匹配
- Topic:主题交换机。#匹配多个单词,*匹配一个单词
- faonout:广播模式。发送到该交换机的所有队列
- headers
消息的确认方式有2种
channel.basicConsume监听消息的第二个参数
- autoAck=false: RabbitMQ会等待消费者显示回复确认消息后才从内存(或者磁盘)中移出消息
- autoAck=true: RabbitMQ会自动把发送出去的消息置为确认,然后从内存(或者磁盘)中删除,而不管消费者是否真正的消费了这些消息
手动确认消息
basicAck(long deliveryTag, boolean multiple)
deliveryTag:标识信道中投递的消息,确定时告诉mq时哪条消息被确认了
- DefaultConsumer的handleDelivery方法的envelope.getDeliveryTag()可以获得
multiple=true: 消息id<=deliveryTag的消息,都会被确认
myltiple=false: 消息id=deliveryTag的消息,都会被确认