学习笔记
- java
- 基础类型与String相关
- 基本类型范围
- 基本类型的转换
- byte计算自动转换int
- 基本类型与包装类
- equals与==的区别
- 集合比较与常用集合原理
- 反射机制与真实使用场景
- 动态代理与使用范例
- 异常
- 类加载机制与热加载实现与反编译
- 内存模型与threadLocal与syncronize
- jvm的GC与调优处理
- JUC设计原理
- 文件流处理与网络相关
- Maven
- spring(ssm与springboot)
- spring原理与bean生命周期
- springboot原理与starter编写
- springMVC原理与流程及过滤拦截
- mybatis原理及缓存与分页
- redis原理与zookeeper
- mysql与oracle
- 分库分表以及ES
- mq
- 算法
- Dubbo与springcloud以及Golang等分布式处理
- docker与K8S及Jekins等容器部署相关
- tomcat与nginx,Jboss等一系列服务器
- CAP理论下的一些复杂场景解决方案
- Python
- Golang
- ChatGPT
- 团队经验
java
内容持续更新…
基础类型与String相关
基本类型范围
一个字节占八位,即8bit,byte取值为00000000-11111111,即0-255,由于java中是有符号类型,所以第一位作为符号
那么byte取值就是01111111为最大值127,11111111为最小值-127,此处引入原码,反码,补码的概念,反码用于负数的算数运算,而补码用于正负数跨零的算数运算,计算机都是用补码计算和存储的,因此多出了-128,原理如下:原码反码补码
int是基本数据类型,Integer是int的封装类,是引用类型。int默认值是0,Integer默认值是null,所以Integer能区分出0与null的情况,一旦java看到null,就知道这个引用没有指向某个对象,在任何引用使用前,必须为其指定一个对象,否则会报错
基本数据类型在声明时系统会自动给他分配空间(方法区的常量池),而引用类型声明时只分配了引用空间(线程的java虚拟机栈),必须通过实例化开辟数据空间后才可以赋值(实例化即在java堆中创建对象实例,将引用指向java堆中对象)
虽然定义了boolean这种数据类型,但java虚拟机中操作的所有boolean值,在编译之后都是用了int类型来代替,而boolean数组将会被编译为byte数组
基本类型的转换
byte计算自动转换int
byte b1=3,b2=4,b;b=b1+b2; //错误 运算时会自动转换为int类型 而int类型的值不能赋值给byte 需要强制类型转换、b=3+4;//正确 常量具有常量类型优化机制 可以直接识别为byte(原因:常量运算,先把结果算出来再赋给一个变量)b1+=b2;//正确 +=操作会自动类型转换
byte计算自动转换原因:jvm的32位架构最基础计算单元为int
基本类型与包装类
自动装箱与拆箱,java基本类型与其相应的包装类之间会自动进行类型转换
public class Main{public static void main(String[] args){Integer i1=100;Integer i2=100;Integer i3=200;Integer i4=200;System.out.println(i1==i2);System.out.println(i3==i4);}
}
运行结果:
true
false
原因是Integer的valueOf方法具体实现如下
public static Integer valueOf(int i){if(i>=-128 && i<=IntergerCache.high){return IntegerCache.cache[i+128];elsereturn new Integr(i);
}
在通过valueOf创建Integer对象时,如果在-128~127之间便会返回IntegerCache.cache中已经存在的对象的引用,否则创建一个新的Integer对象
public class Main{public static void main(String[] args){Double i1=100.0;Double i2=100.0;Double i3=200.0;Double i4=200.0;System.out.println(i1==i2);System.out.println(i3==i4);}
}
运行结果:
false
false
原因:在一个范围中的浮点数个数不是有限的,存在精度问题,如果要进行比较,使用BigDecimal并使用BigDecimal提供的对象方法进行计算,构造时使用String作为入参防止丢失精度
equals与==的区别
equals一般比较对象内容是否相等,而==比较两者的栈中保存的对象堆地址值是否相等,即是否指向同一个对象,equals在object中就存在,如不重写的话两者没有区别,重写时一般使用hashcode方法结合,比较相等首先判断hashcode是否相等,相等再进行真实值的比较,大大降低比较时间,最多两次即可判断。
string stringbuffer stringbuilder
string是一个对象,底层为final类型的字符数组,所引用的字符串不可改变,每次+操作都是隐式的在堆上new一个与原字符串相同的stringbuilder对象进行append操作,stringbuffer加了同步锁,线程安全,stringbuilder非线程安全
集合比较与常用集合原理
List,Map,Set
Array是基于索引index的数据结构,因此getByIndex搜索复杂度是O(1),而且可以进行范围查找
ArrayList和LinkedList是List的两种实现,前者可以自动扩容,底层实现是array,自动扩容原理为新建一个两倍长度的数组再将数据复制到新数组中,LinkedList是一个双向链表,因此在添加和删除时会优于ArrayList,数据不经常更新可以使用ArrayList否则使用LinkedList,而如果不是为了范围查找,最常见还是使用map存储,数组还有一种线程安全的实现方式Vector,很少使用
List的队列和栈数据模型:List的队列和栈数据模型
map是以键值对的形式存储数据,主要的实现为hashmap,hashtable,其中hashmap实现为数组加链表的形式,添加数据的原理为每当数据来临,调用hashcode方法计算哈希并找到元素存储位置,若该处无数据则添加,若有数据则调用equals在链表中进行比较,随着hash冲突的增加,链表的比较开销较大,因此在1.8之后设计为HashMap设计原理
hashmap的死循环原因触发于多线程和扩容状态下,因为头插法扩容时原先的a-b-c就会变为c-b-a,扩容也是一个一个放置到新的map中,原map最上面的就先放入最终变成最下面的,这时候如果两个线程一个执行扩容变成c-b-a,另一个在扩容前是a-b-c那么该线程存储的a的next是b,等扩容完去获取b的next就是a,死循环发生,选择头插是认为新插入数据使用可能较大放前面搜索快,改为尾插不会发生死循环,但是并发不能保证
hashtable是同步的,内部方法使用synchronize修饰,效率很低,几乎不使用,syncronizedmap构造方法将this指向mutex,所有方法加synchronized锁住map,只能一个线程访问,如下:
...
private final Map<k,v> m;
final Object mutex;
SynchronizeMap(Map<k,v> m){
this.m=m;
mutex=this;
...
public int size(){
synchronized(mutex){return m.size();}
}
...
}
concurrenthashmap使用分段锁来保证在多线程下的性能。一次锁住一个桶。默认将hash表分为16个桶, 诸如 get put remove等常见操作只锁当前需要用到的桶。能同时有16个写线程执行,并发性能的提升是显而易见的。HashMap在多线程情况下put超过了负载因子0.8总容量就会rehash扩容,此时可能发生死循环,虽然改为尾插不会死循环但多个put也会线程不安全,而hashTable方法都用了Synchronize效率太低,因此ConcurrentHashMap使用分段锁的概念,因为不同的数据集hash不同根本不需要去竞争同一个锁,1.7中数据结构为一个segement数组,默认长度16,然后每个Segement对于一个HashEntry,HashEntry就类似hashmap的结构,当put的时候会hash两次,首先hash确定放入哪个Segement中然后hash确定放入HashEntry哪个位置中,并且在放置的时候因为Segement继承了ReentrantLock所以使用tryLock来判断是否能获取锁,获取到的才会put获取不到就进入Lock的AQS中的CLH队列中等待了,get操作和HashMap类似只不过要两次hash,size操作,因为并发size不准确,提供了两个方案,先不加锁去算最多三次,比较size不变那就返回结果,如果不行那就给每个Segement上锁后再计算返回,这里让我想起了Innodb不像Myisam一样提供数据条数总量的字段的问题,因为有事务的情况所以条数不准确。而到了1.8就放弃了Segement的方式直接使用Node数组+链表+红黑树的结构来实现,保留Segement是为了兼容之前的代码。并发使用Synchronize和CAS来操作,Node是存储的基本单元,继承于HashMap中的Entry,就是一个链表,只能查询不能修改
TreeNode继承自Node,数据结构换成了二叉树,一条链路是一个Bucket,Bucket中可能是链表也可能是红黑树,取决于长度是否大于8。put操作先判断key和value是否为null,然后计算key的hash进入无限循环确保可以插入数据,先判断table是否空如果是初始化table数组表,然后后根据key的hash值取table中节点,不存在则CAS放入,否则判断节点hash是否是MOVED,是则说明在扩容,帮助转移,不是的话synchronize开始对该桶Bucket进行遍历并比较,相等就修改,遍历完都不想等就在末尾生成一个新结点,然后计算长度如果达到阈值就转为红黑树。get函数则是根据key的hash判断在哪个桶,如果首结点符合就返回,如果遇上遇到扩容会调用ForwardingNode的find方法取查找(如果该桶没扩容完可以找到,扩容完会把这个指向原来的引用供查找),如果都不符合就遍历节点,匹配就返回,不匹配就返回null。扩容原理是这样的,首先扩容是n<<1即原来的大小乘2,这样会对桶的结点重新计算存放的hash位置,因为N的最高位是1,哈希码和N的最高位一样都是1那么说明是新位置,否则是原来位置,只有这两种情况,因为以前少一位,如果现在第一位还是0说明就是以前,变为1才说明是到了新的位置,然后每个线程参加扩容都只会分配属于自己的区域。put或者get的时候就会判断节点状态并帮助扩容。
为什么线程安全的map都不允许key为null?
hashtable和concurrenthashmap都不允许key为null,而hashmap却可以,这是出于对并发的考虑,多线程情况下一个线程判断该位置有无元素得到null值时无法确定是没有元素还是其他元素在此时插入了一个null值数据
TreeMap 有序,元素无序要实现comparable
set用于非重复,保证机制还是hashcode加equals方法
Collection是集合类的上级接口包含有关集合操作的静态方法用于实现集合的搜索排序线程安全化操作,不能实例化。
跳表skiplist是一种有序的数据结构,可以作为平衡树的一种替代,因为平衡树比如红黑树之类的实现起来复杂而且要旋转变色在并发场景下锁的性能比较差,跳表是有序链表加稀疏索引的方式,首先底层是有序的链表,比如12345678,第一层索引比如有三个值
1-4-8分别可以指向链表中的148元素,再上一层索引可能只有1-8分别指向第一层索引的18,这样当搜索的时候通过几层索引就可以迅速找到数据在链表中的位置,而不需要一个个找下去,显然索引会随着链表的变化而要变化,因此将每层的中点作为索引不合适
这样每次一个元素修改就要更改索引,所以用随机化,比如设置N个数据的链表第k层有N/2^k个节点
BitMap位图,用每一个位表示某个状态,适用处理海量数据,本质是hash表,原理就是将一个int数据映射到对应的位上,将该位由0变为1,一个int存储要32bit,bitmap只要1bit所以节约了32倍
数据分布不均匀的话中间空值占了很多反而浪费了资源,这就需要用一位存数据,多几位存到下一个数据的跨度信息,合起来作为一个数据的整体,比如10位为一个数据。然后对于字符型的可能就要
使用布隆过滤器(比较难维护,生成之后如果值删除了要去除比较麻烦)。
反射机制与真实使用场景
java反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能调用它的任意一个方法,只要给定类的名字就可以获取它的全部信息,field,constructor,method都可以被获取,提高灵活性但是性能较低,可以通过setAcessible(True)来关闭jdk的安全检查来提高性能
动态代理与使用范例
异常
Exception于error,error是JVM出现的问题,不是代码原因,比如oom,exception有runtimeException和编译时异常,前者有空指针,数组溢出等,运行时才有可能发生,需要try/catch。后者有ParserException,IOExceptio等在编译的时候就要进行处理否则编译无法通过,我们也可以实现Throwable定义自己的异常
类加载机制与热加载实现与反编译
内存模型与threadLocal与syncronize
java的四类引用,强弱软虚
直接创建对象就是强引用,包括new,newInstance,clone和序列化,弱引用weakReference,只要GC就会回收,软引用SoftReference会在内存不足的时候回收,虚引用phantomReference回收机制和弱引用差不多,只要GC就回收,但回收前会被放入ReferenceQueue中,其他引用都是被jvm回收后才被传入ReferenceQueue中,由于这个机制,虚引用大多用于引用被销毁前的处理工作,因此创建虚引用必须带有ReferenceQueue
深拷贝与浅拷贝的区别:深拷贝将对象完全拷贝一份,包括属性和对其他对象的引用,浅拷贝只会拷贝属性,对象的引用复用,所以浅拷贝的两个对象在对引用属性修改时是共享的,深拷贝则完全独立。clone是浅拷贝,深拷贝需要自己实现,即每一层对象都用浅拷贝。
java中不想序列化的字段用transient关键字修饰,不会序列化,反序列化时也不会被持久化和恢复,只能修饰变量,不能用来修饰方法和类。
threadLocal的弱引用产生的内存泄漏隐患,与线程池的结合
syncronize的锁升级机制与AQS算法,monitorenter与monitorexit指令
AQS双向链表等待队列
jvm的GC与调优处理
JVM的内存结构主要源于数据在主存和工作内存的交流方式产生,分别是线程内私有的java虚拟机栈,用于存储局部变量表,方法操作栈等,本地方法栈用于执行native方法,程序计数器用于记录当线程的cpu时间片耗尽时程序执行的位置,执行native时置为空,线程间的共享有堆和方法区两块,堆用于存放几乎所有的对象实例,即新生代和老年代,方法区用于存储常量静态变量和类的信息,即永久代。类的加载和卸载遵循双亲委派机制,加载-验证-准备(设置初始值)-解析-初始化(赋值),该机制保证类不被重复加载,且能保证java核心api不被篡改,bootstrap-ext-application-自定义,以加载器+全类名为唯一标识,GC算法在新生代为标记赋值算法,eden-survivor1-survivor2是minorGC,老年代是标记清楚算法是FullGC,判断是否回收通过引用计数和可达性分析,引用计数简单但不能解决循环引用,可达性分析为从GCRoots向下搜索,搜索走过的路径称作引用链,当一个对象没有任何引用链,说明是不可达的,JVM性能调优通过-Xmx设置堆内存,不宜太小否则都去老年代了。
JUC设计原理
java解决多线程并发访问共享资源的时候提供了两种解决方案
一种是synchronize锁方式,用时间换空间,有无锁的CAS以及乐观锁方式,也有lock包下的锁与synchronize关键字,对共享变量的同时修改采用互斥的方式,synchronize利用monitor的机器指令,锁住的是对象,锁信息置于对象头中,所以没有公平锁的概念,释放资源后产生羊群效应,其中还包括锁优化即偏向锁到轻量级锁到重量级锁的锁升级机制,lock使用的AQS,锁住的是锁本身,用state去控制访问的权限,等待竞争的线程会在CLH队列中,所以可以保证公平锁。但是synchronize可以锁类,lock只能锁方法。
一种是ThreadLocal复制方式,用空间换时间,通过对共享变量的复制,使得每个线程访问并修改自身存储的值,ThreadLocal本身不存储数据,使用线程中threadlocals的一个属性,其类型由threadlocal中threadlocalmap定义,这个还要去看源码。
Condition类:主要用来替代Object中的wait和notify实现线程间的写作,相比Object来说Condition中提供awaitUntil的deadline等待以及awaitUninterrptibly的非中断等待等更有效的方式
场景例子有arrayBlockingQueue的实现:
public class ConTest {final Lock lock=new ReentrantLock();final Condition condition=lock.newCondition();final Lock lockFake=new ReentrantLock();//condition必须和对应lock配合使用如果用lockFake上锁,condition通知会报错public static void main (String args[]){//AbstractQueuedSynchronizer AQS抽象队列同步器//ArrayBlockingQueue 中Condition notEmpty在enqueue添加元素后signal,notFull在dequeue删除元素后signal,在take中count为0则notEmpty.await,在put中count=item.length则notFull.awaitConTest test=new ConTest();Producer p=test.new Producer();Consumer c=test.new Consumer();c.start();p.start();}class Consumer extends Thread{public void run(){try{lock.lock();System.out.println("This Thread is waiting a sign"+ currentThread().getName());condition.await();}catch (InterruptedException e){}finally {System.out.println("getting a sign"+currentThread().getName());lock.unlock();}}}class Producer extends Thread{public void run(){try{lock.lock();System.out.println("This Thread holding on the lock"+ currentThread().getName());condition.signalAll();System.out.println("advertise a sign"+ currentThread().getName());}finally {lock.unlock();}}}}
以Condition实现生产消费者模式:
public class ConTestTwo {private int queueSize = 10;private PriorityQueue<Integer> queue = new PriorityQueue<>(queueSize);final Lock lock = new ReentrantLock();final Condition notFull = lock.newCondition();final Condition notEmpty = lock.newCondition();public static void main(String args[]) throws InterruptedException {ConTestTwo test = new ConTestTwo();ConTestTwo.Producer p = test.new Producer();ConTestTwo.Consumer c = test.new Consumer();c.start();p.start();p.interrupt();c.interrupt();}class Consumer extends Thread {private volatile boolean flag = true;public void run() {try {while (flag) {lock.lock();if (queue.isEmpty()) {try {System.out.println("queue is empty thread is waiting for data");notEmpty.await();} catch (InterruptedException e) {//由于signal不会抛出中断异常,await会被唤醒或者中断,放在这里保证中断时最后执行完一次操作,放外面的try/catch则中断直接就强制结束了flag = false;}}queue.poll();notFull.signal();System.out.println("take one data from queue queue size:" + queue.size());}} finally {lock.unlock();}}}class Producer extends Thread {private volatile boolean flag = true;public void run() {try {while (flag) {lock.lock();if (queue.size() == queueSize)try {System.out.println("queue has full now please wait");notFull.await();} catch (InterruptedException e) {flag = false;}queue.offer(1);notEmpty.signal();System.out.println("put one data into queue queue rest:"+(queueSize-queue.size()));}} finally {lock.unlock();}}}
}
Condition的实现基于AQS,AQS是一个抽象类。里面定义了同步器的基本框架和基本结构功能,只留下状态条件的维护由具体同步器根据场景自行定制,如常见的ReentrantLock,ReentrantReadWriteLock,CountDownLatch,Semaphore等,由三部分组成,state状态,Node组成的CLH队列,ConditionObject条件变量。对于状态由getState和setState和compateAndSetState使用unsafe的CAS,还有isHeldExclusively判断当前线程是否持有资源。CLH是AQS内部维护的FIFO双链表队列,当一个线程竞争资源失败,就会将其封装为一个Node节点通过CAS原子操作插入队列尾部,Node节点连接组成CLH队列,优点在于FIFO保证公平,CAS无锁保证非阻塞,Node中waitStatus来判断线程的状态,nextWaiter设置下一个节点是否shared可共享,流程为线程获取资源失败,封装成Node节点通过addWaiter加入CLH队列尾部并阻塞线程,哨兵节点即第一个节点释放资源时,将head.next指向的线程唤醒,也就是CLH的第二个节点,如果第二个节点获取资源成功,将其设置为哨兵节点,原头部节点出队。acquireQueued方法,这里不需要CAS因为只有一个节点能获取资源。条件变量:其实就是Condition,有await和signal,相比Synchronize的Object的Condition可以有多个条件,ConditionObjec中维护了一个单向条件队列,这里只放置await的节点,也就是不获取资源的,再这里的节点不能在CLH中出现,这里出队的节点会加入到CLH中,当某个线程执行await函数,阻塞当前线程,线程封装成Node节点添加到条件队列末端,其他线程执行signal则将条件队列头部线程节点转移到CLH中参与资源竞争,CAS的方式用enq加入到CLH中,signalAll就是释放所有节点。
AQS采用了模板方法提供独占模板 acquire和release还有共享模版acquireShared和releaseShared,逻辑为执行tryAcquire(子类实现,代表资源获取是否成功),成功返回失败通过addWaiter进入CLH队列,执行acquireQueued自旋阻塞等待获取资源,如果获取成功判断中断状态决定是否执行中断逻辑。释放就是线程释放资源成功,唤醒CLH第二个线程节点。共享模板流程差不多,区别在于addWaiter的时候加入共享节点,获取资源成功后还会判断资源大于0唤醒后续节点,释放区别不大。这就满足semaphore信号量的功能,n个资源可获取,获取完了才等待。CountDownLatch类似于计数器的功能可以让一个或多个线程进入等待状态直至其他线程完成工作,持有countdown方法和await方法,当线程调用countdown的时候计数器将会减一,当线程调用await方法的时候,如果计数器不为0,则该线程进入阻塞状态直到计数器为0时就会直接退出,所以可以在线程执行完后countDown获取资源,然后用await等待直到所有资源被获取完说明都执行完了程序结束。
为什么Condition阻塞队列是单向的,CLH是双向的?CLH很多情况下当前节点操作要判断上一个节点的状态,不用双向链表就就要从头遍历,Conditon的不需要判断前一个节点状态所以单向就行了。
synchronize的锁升级机制:首先有四类锁状态,分别是无锁,偏向锁,轻量级锁和重量级锁。在这里要注意java的对象结构:
对象头:
Mark Word: 标记字,主要用来表示对象的线程锁状态,另外还可以用来配合GC存放该对象的hashCode
Class Pointer: 类对象指针,存放方法区Class对象的地址,虚拟机通过这个指针来确定这个对象是哪个类的实例
Array Length: 数组长度,如果对象是一个Java数组,那么此字段必须有,用于记录数组长度的数据,不是数组对象可以没有,可选字段
对象体:对象体包含对象的实例变量(成员变量)
对齐字节:对齐字节也叫作填充对齐,其作用是用来保证Java对象所占内存字节数为8的倍数
优先级是偏向锁-轻量级锁(先自旋不行再膨胀)-重量级锁。偏向锁是指锁对象的MarkWord不再存储锁记录地址而是存储线程ID,当发生锁重入时判断ID就可以不需要CAS,默认情况新建对象都是偏向锁(Synchronize情景下)轻量级锁指的是当A有偏向锁,A执行完后B又来获取锁此时发生锁膨胀进入轻量级锁,具体指的是线程的栈中会有一个锁记录的对象LockRecord。其中包含对象指针ObjectReference和锁记录地址LockRecord地址00,当这个线程执行到上锁的位置时会将对象头MaekWord和锁记录地址进行CAS交换,成功后对象头存放的就是锁记录地址和状态00,重量级锁指的是A已经有轻量级锁在执行中,B来竞争锁那么会锁膨胀变为重量级锁,也就是Monitor指令情况了。自旋优化指的是当A有锁时B来竞争不会立即进入阻塞而是多次尝试获取锁,如果期间A执行完了B就会获取锁这就是自旋,这样就不需要上下文切换就可以获取锁,如果获取不到B就进入阻塞状态。自旋耗费CPU时间,单核就是浪费只有多核才有用,自旋是自适应的,能成功就会多试几次,不行就少几次,不能控制。Synchronize锁住的代码实际上会加上Monitorenter和Monitorexit指令,会让对象在执行时进入加1退出减1,每一个对象同时刻只能于一个monitor关联,一个monitor同一时刻页只能被一个线程获得,一个对象在获取monitor时如果计数器为0,那么可以获取并将计数器+1,如果重入就会累加,如果不为0就等待。锁消除指的是如果虚拟机编译时发现要求同步的代码不可能出现共享那么就会消除锁,比如逃逸分析发现锁是方法中的局部变量。
流程:轻量级锁只是栈中一个锁对象,不是monitor级别,发生于多线程并发但是加锁时间是错开的,那么synchronize就是轻量级锁,每次执行到同步代码快时都会创建锁记录LockRecord每个线程都会包括一个锁记录的结构,锁记录内部可以存储对象的MarkWord和对象引用Reference,让锁记录中ObjectReference指向对象,并用CAS替换Object对象的MarkWord为LockRecord的指针并MarkWord信息存入锁记录中,这就完成了加锁。如果失败说明有其他线程持有了该Object的轻量级锁,发生了锁竞争那么就升级为重量级锁,如果是自己执行相当于重入,再添加一条LockRecord作为重入计数,且新的LockRecord中对象的MarkWord为null,当线程退出同步代码块时如果取值为null说明有重入,重置锁记录表示计数减一,如果不为null那么使用cas将MarkWord值恢复给Object,成功则解锁成功,失败说明已经锁膨胀进入重量级锁解锁过程。锁膨胀是当A加轻量级锁,此时B已经锁了,那么A的CAS失败,于是A为对象申请Monitor锁让Object的MarkWord指向重量级锁Monitor地址,然后自己进入Monitor的entryList变成BLOCKED阻塞状态,当B退出同步代码块的时候,使用CAS失败那么进入重量级锁解锁过程,按照Monitor地址找到Monitor对象,将Owner设置为null,唤醒EntryList中的A线程。偏向锁出现在于轻量级锁的时候重入也是要CAS的就耗时了,于是用偏向锁将第一次CAS时候将对象的MarkWord头设置为线程ID(没有锁记录这个东西了)之后来只要判断是不是自己ID就行了。
文件流处理与网络相关
xml sax socket
Maven
Maven安装流程比较简单,主要更新setting.xml文件定制仓库路径和下载jar包路径还有镜像。配置进IDEA就可以方便使用了,然后主要命令有如下几个:
---------------实际应用-----------------------
# 1、刷新子模块版本号:
mvn versions:update-child-modules
# 2、重新打包到maven本地库:
mvn clean install -Dmaven.test.skip=true
mvn install
# 3、部署包到远程服务器
mvn clean deploy -Dmaven.test.skip=true#---------------------一般常用命令-----------------------
# 该命令打印出所有的java系统属性和环境变量
mvn help:system 自动在本用户下创建 ~/.m2/repository
# 清理输出目录默认target/
mvn clean
mvn clean compile 清理编译
# maven test,但实际执行的命令有:clean:clean,resource:resources,compiler:compile, resources:testResources, compiler:testCompile,maven在执行test之前,会先自动执行项目主资源处理,主代码编译,测试资源处理,测试代码编译等工作,测试代码编译通过之后默认在target/test-calsses目录下生成二进制文件,紧接着surefile:test 任务运行测试,并输出测试报告,显示一共运行了多少次测试,失败成功等等
mvn clean test 清理测试
mvn clean package 清理打包
mvn clean install 清理将打包好的jar存入 本地仓库 注意是本地仓库
mvn clean deploy 根据pom中的配置信息将项目发布到远程仓库中 echo %MAVEN_HOME%:查看maven安装路径---------------------创建项目-------------------------------
mvn -version/-v 显示版本信息
mvn archetype:generate 创建mvn项目,使用Archetype生成项目骨架
mvn archetype:create -DgroupId=com.oreilly -DartifactId=my-app 创建mvn项目
# 创建Maven的普通java项目:
mvn archetype:create -DgroupId=packageName -DartifactId=projectName
# 创建Maven的Web项目:
mvn archetype:create -DgroupId=packageName -DartifactId=webappName-DarchetypeArtifactId=maven-archetype-webapp ---------------------优化依赖命令-------------------------------
mvn dependency:list 显示所有已经解析的所有依赖
mvn dependency:tree 以目录树的形式展现依赖, 最高层为一层依赖 其次二层依赖 三层依赖....
mvn dependency:analyze 第一部分显示 已经使用但是未显示依赖的的 第二部分显示项目未使用的但是依赖的---------------------第三方jar 发布到远程仓库---------------------
mvn deploy:deploy-file -DgroupId=com -DartifactId=client -Dversion=0.1.0 -Dpackaging=jar -Dfile=d:\client-0.1.0.jar -DrepositoryId=maven-repository-inner -Durl=ftp://xxxxxxx/opt/maven/repository/---------------------第三方jar 安装到本地仓库---------------------
mvn install:install-file -DgroupId=com -DartifactId=client -Dversion=0.1.0 -Dpackaging=jar -Dfile=d:\client-0.1.0.jar -DdownloadSources=true -DdownloadJavadocs=true#你是否因为记不清某个插件有哪些goal而痛苦过,你是否因为想不起某个goal有哪些参数而苦恼,那就试试这个命令吧,它会告诉你一切的.参数: 1. -Dplugin=pluginName 2. -Dgoal(或-Dmojo)=goalName:与-Dplugin一起使用,它会列出某个插件的goal信息,如果嫌不够详细,同样可以加-Ddetail.(注:一个插件goal也被认为是一个 “Mojo”)
mvn help:describe -Dplugin=help -Dmojo=describemvn -e 显示详细错误 信息.
mvn validate 验证工程是否正确,所有需要的资源是否可用。
mvn test-compile 编译项目测试代码。 。
mvn integration-test 在集成测试可以运行的环境中处理和发布包。
mvn verify 运行任何检查,验证包是否有效且达到质量标准。
mvn generate-sources 产生应用需要的任何额外的源代码,如xdoclet。
spring(ssm与springboot)
spring原理与bean生命周期
spring的主要原理是IOC和AOP,ioc是典型的工厂模式和代理模式,applicationContext就是工厂类,所有的bean都在它的map中,我们可以通过bean的生命周期去理解和操作bean的构建,主要在beanpostprocessor和init-method中,bean的生命周期包含了三级缓存来解决循环依赖。而AOP就是代理模式一般用于日志监控等,@autowired和@resource一个是spring提供的一个是java提供的,前者是type查找,与之对应的按name查找的是Qualifier,后者默认name匹配不到就按type,依赖注入的方式有构造器注入,setter注入和注解,bean的生命周期为实例化bean-设置对象属性并依赖注入-处理Aware接口-beanpostprocessor-执行init-method方法-disposableBean接口调用destory方法,bean的作用域有singleton单例,prototype原型每次都请求创建,request每个网络请求创建一个,session每个session创建一个
spring中有两个相同id的bean的处理方式:在同一个xml中是不能存在相同id的bean的否则启动就会报错,因为spring启动会校验id的唯一性,在spring对xml解析为BeanDefinition的时候。但是多个spring配置文件那就会默认覆盖,加载过就不会在加载了,现在一般使用@Configuration声明配置类,然后用@Bean实现bean的声明取代xml形式,此时声明了相同id的bean只会注册第一个声明的实例
spring如何解决循环依赖:循环依赖是指一个或多个对象之间直接或间接的依赖关系形成了循环调用,如图:
解决的方式类似于JVM,JVM中类加载机制有加载,验证,准备,解析,初始化这么几个阶段,而且是在第一次用到类的时候才加载且只加载一次,循环依赖的情况下首先会加载A类然后验证然后准备,在准备阶段需要B类的时候加载B类,等B类到准备阶段的时候由于A类已经被加载,B就可以获取A类初始化完成,之后A类也可以初始化了,这是属性依赖,类加载发生的时间不是强制的,初始化会在new或者反射的时候才会触发,还有一种依赖是构造器依赖,这是无解的,会导致java虚拟机调用栈溢出,因为构造器之间构成了死循环,spring对属性依赖使用了三级缓存的方式,第一级缓存用于保存初始化完成的bean实例,第二级缓存用于保存实例化完成但没注入初始化的半成品bean实例,第三级缓存保存bean的创建工厂,便于后面获取bean对象,bean的加载流程为:执行获取单例A的操作依次从一二三级缓存中查找,找不到则将A加入到singletonCurrentlyInCreation的set中,标记A为创建中,然后反射获取A实例,此时属性都为null,创建玩之后将创建工厂放入singletonFactories也就是三级缓存中,然后为A属性赋值,发现需要B,于是B也走一遍上述流程直到对B属性进行赋值,发现在三级缓存中有A的构造工厂,然后判断A是否需要AOP,从而通过getObject获取earlyBeanReference返回原始bean或者代理对象bean,将这个bean放入二级缓存并删除三级缓存的A工厂(之后别人需要A就可以在二级缓存获取),然后进行B的初始化,完成后B会直接放入一级缓存并且删除B的二三级缓存以及singletonCurrentlyInCreation的set,然后A的属性赋值就可以继续了,A初始化完成后删除二三级缓存和set,此时循环依赖被解决。这里可以发现其实循环依赖解决只要两级缓存就够了,类似JVM只有实例和class信息就解决了,为什么spring要三层呢?原因在于AOP,如果只有两级缓存,那么二级缓存放入的就是普通对象,虽然也可以放入aop对象,但这样就打破了bean的生命周期,原本是在beanpostprocessor后置处理生成aop对象的,两级缓存则要在初始化之前就生成aop了,违反了spring原则。除了三级缓存外,还可以通过@Lazy注解来解决,一般bean都是在启动的时候加载,用了该注解就只会在调用到的时候才加载,也可以解决循坏依赖。
像beanpostprocessor去做功能增强只在bean的初始化流程中一次性的,而aop代理对象的invoke增强是每次调用都有的,它的判断为扫描所有@aspectj的bean然后判断有无@before,@afterReturning等注解的增强方法,通过pointcut路径扫描是否包含当前bean,如果有则生成代理对象,还有就是事务方法,事务也会生成代理对象
bean的生命周期,通过构造方法实例化一个对象,得到后判断有无@autowired属性,有就找出来赋值注入,依赖注入后判断有无实现Aware接口,实现了就表明当前对象要实现这些接口中定义的setBeanName,setBeanFactory等方法,spring就会调用这些方法并传入相应参数,aware回调完成后判断该对象有无@PostConstruct注解的方法,存在则调用,然后判断有无实现initializingBean,如果有就表明该对象要实现afterPropertiesSet方法进行初始化,最后判断是否要AOP。
至于构造方法的判断,只有一个构造方法就选该方法,多个构造方法默认使用无参构造方法,没有无参方法则报错,如果某个构造方法有@autowired,说明使用这个构造方法,这也是spring的构造器注入方式
spring中扩展点及其应用:spring启动时会先解析bean定义为BeanDefinition,在实例化bean,首先在解析阶段,可以用BeanFactoryPostProcessor或者@Import来注册bean的定义,bean生命周期阶段可以用beanPostProcessor来进行前后置化干预,Aware接口如BeanNameAware,BeanFactoryAware等在bean实例化过程中调用的方法,initializingBean在初始化时调用等于init-method,这些接口可以用在整合其他的组件与服务上,帮助架构和系统的搭建集成。
@value类似于@autowired,但是一般是读取配置文件的信息,@value(“something”)会将something赋值给属性,如果不是string或者无法转化为string会报错。@value(“${something}”)会吧something当作key从配置文件中读取,没找到则把双引号中值当作字符串注入,@value(“#{something}”)会被当作spring表达式解析,把something当作beanName从容器中寻找对应bean注入,没有则报错,@value要在spring管理下的类中才能使用,且不能对final和static的属性生效。
spring事务失效的原因:首先spring本身不支持事务,是利用了数据库的事务,所以mysql用myisam就会失效,它不支持事务,还有就是修饰非public方法会失效,因为computeTransactionAttribute类中会判断非public就返回null不支持事务,还有就是final和static修饰的方法失效,因为spring事务使用了动态代理,final无法重写方法,static无法动态代理,还有就是同一个类中非事务方法调用事务方法也会失效,因为在同一个类中调用方法会用this,就用不到代理对象,就不存在事务了,还有未被spring容器管理没有@component也会失效,多线程调用也会失效,因为同一个事物指的是同一个数据库链接,在不同线程中数据库链接也不同,所以是不同的事务。还有自己吞了try/catch,或者配置了不正确的事务特性,有七类特性,一个是存在就加入,不存在就新建,是默认的Required,一个是存在就加入,不存在就非事务运行,一个是存在就加入,不存在抛异常,一个是新建事务,之前有事务就把之前的挂起,一个是非事务运行,存在事务就挂起,一个是非事务运行,之前存在就抛出异常。还有失效的场景就是手动抛出别的异常,默认是runtimeException如果像触发其他的回滚就要在@Transactional(RollbackFor=Exception.class)但是这个配置仅限于Throwable异常与其子类,spring事务在开始时通过aop生成一个connection连接,之后sql命令都通过该连接操作,然后才能得到回滚。
springboot原理与starter编写
springboot自动配置原理在于@springbootApplication中的EnableAutoConfiguration中使用了springFactoryLoader的loaderFactory方法读取了resource/META-INF/spring.factories文件中配置的需要自动注入的bean的路径来进行自动配置,所以我们staters编写的时候需要自动注入的bean也要将路径也在这个文件中,controllerAdvice配合exceptionHandler可以处理异常配置,commandLineRunner可以在启动的时候做一些功能处理
springMVC原理与流程及过滤拦截
springMVC流程在web.xml中设置servlet为dispatchServlet然后收到请求调用handleMapping根据配置找到具体执行的handle以及相应的handleInceptor返回,dispatchServlet再调用handlerAdapter适配找到具体的controller处理,处理完后返回ModelAndView,handlerAdapter将ModelAndView返回dispatchServlet,servlet提交给ViewResolver解析后返回具体的View,即JSP,常用的注解有@RequestMapping,@RequestBody等,其中put/get/post/delete对应增查改删,put幂等,post表单提交非幂等,get获取,当然也可以提交,只需将参数放在url的?后面
mybatis原理及缓存与分页
mybatis原理为sessionFactory读取xml配置文件中的mapper,entity和driver等配置进入Configuration中,然后底层还是jdbc的形式去调用。通过simpleExecutor去执行sql,一级缓存二级缓存很鸡肋,一级缓存是session级别的,将具体sql和sql的id绑定,查询缓存中的数据,如果有任何更新清除所有缓存,在分布式情况下多session易出现数据问题,二级缓存是mapper级别的,如果多个表联查也无法缓存,使用一级二级缓存熟练度不高极易数据出错。mybatis分页有PageHelper,本身支持三种方式,一个是limit关键字,一个是用rowbounds将所有数据查到内存中后在内存中分页,还有是使用Interceptor拦截器动态构建分页sql,像pageHelper也是基于拦截器的,在查询前先通过count查出所有条数,或者一些数据加密脱敏之类的也可以用拦截器做。
mybatis中${}会替换为?,使用prepareStatement,而#会直接替换,不要使用,不能防止sql注入
mybatis-plus对mybatis的优化原理:
redis原理与zookeeper
redis的持久化机制由RDB快照和AOF写入日志,redis快的原因在于单线程不需要上下文切换,内存中操作,渐进式rehash以及io多路复用,数据类型由string,hash,list,set,zset等,过期策略为定时删除加惰性删除,定期选一批数据判断,到期的就删除掉,不能全部扫描太耗性能。惰性删除就是每次查询先判断是否过期,过期了就删掉。内存淘汰策略为lru按最近使用次数,random随机删除,ttl选择最近要到期的,lfu时间加上使用频率,然后都有volatile和all两类,即在有过期时间的和全部的key中,redis使用单线程的原因在于cpu不是性能瓶颈,内存的大小和网络的io才是,10w/s的查询速度,在6.0之后变为多线程因为有些公司业务需要。redis的优化,首先key不要设置太大,还有就是RDB和AOF不要开启,如果数据重要的话就在slave中开启APF每秒同步一次,redis的nosql是一个巨大的map,渐进式rehash的原理是首先在查找中一般由两类解法,各类平衡树或者哈希表,哈希最快但是不能范围查询,平衡树有些可以范围查询,一般的map和字典都是哈希表查询,用key算hash然后用链解决hash冲突,在加载因子到一定值后扩容,redis的map太大了,如果一次扩容必然阻塞,因此有了渐进式rehash,需要扩容时,并不一次完成操作,而是在之后的每次redis操作中按顺序更新一小部分数据,直至完成扩容,是一种分治法的思想。
分布式锁:JUC提供的锁机制只能保证同一个JVM中的并发,微服务情况下多个JVM就需要一个中间人,分布式锁就是用来保证在同一时刻只有一个线程能执行操作
主要的特性:也是锁的特性,互斥,不会死锁,加解锁的线程是同一个,可重入,容错性(集群红锁)
常见的三种实现方式:数据库锁,redis分布式锁,zookeeper分布式锁
数据库锁比较简单,用版本号作为乐观锁即可实现
redis分布式锁:设计首先为了防止死锁,要在try中使用,finally中释放,上锁的逻辑是使用redisTemplate的setIfAbsent互斥上锁并且同时原子的设置过期时间,防止极端情况下锁无法释放,锁的超时时间设置为业务时间的三倍差不多。同时为了防止A上锁执行超时,然后自动释放,B上锁成功,A执行完毕释放了B的锁,然后C上锁的情况,解锁只能是上锁线程来执行,所以UUID作为value,解锁的时候比较和释放是两步操作,所以用lua脚本实现原子性操作。到此为止在单机情况下的普通redis分布式锁就实现了,但是还有很多问题,比如A超时执行的情况下,B还是可以获取到锁的,那么同时会有多个线程在执行,而且不能实现可重入,如果A的锁方法调用了X方法,X方法也需要锁,那么就无法实现了,超时的问题使用看门狗机制,为redis设置一个守护线程,执行定时任务去redis服务端做一个检查,如果查询发现锁还在,为它续签,也就是更新延长锁的过期时间,当然万一就是服务挂了这样一直续签是不能忍受的,所以可能还需要一个监控服务,类似于心跳机制去连接微服务还是否可连接,n次无法通信后可能就由监控服务去释放锁。对于可重入性不能直接在方法中传递参数,侵入性太强,所以value改为用一个静态全局UUID作为APP_ID,加上thread-id作为value,这样在确定了哪一个微服务中哪一个线程在执行,同一个线程可重入。但如果启动一个子线程去执行这样就不行了,这时候要使用InheritableThreadLocal,它的原理是在Thread类中启动子线程的时候会在init()方法中把InheritableThreadLocal的map复制过去,这样就可以获取父类的threa-id了,但要注意不能和线程池一起用,因为线程池线程复用,不会调用init(),无法复制。如果是微服务之间互相调用,可能又要使用微服务调用的traceid来处理,所以value要具体问题具体分析。重入也需要设置超时时间,要防止小于最初时间,而且微服务之间rpc传递的话也不能存在内存中,所以要用一个key存在redis中,最简单的方式就是放大,每次都往上加过期时间。还有就是锁重入要进行计数,否则任意一次锁的释放将把整个锁释放掉,这就计数也需要一个key,之后释放锁要先-1,到0才能释放锁。同时这两个key都是要设置过期时间的,不然异常情况锁就不可用了,他们都是附属于锁的,因此可以使用hash数据结构,key是锁,hashkey是锁的属性,hahsvalue是属性值,超时时间就只要设置key就行了。至此,单机下的redis分布式锁基本完成了,redission都实现了,所以推荐用redission。多机的redis有两类一个是哨兵模式,也就是主从模式,还有一种是集群模式。前者复制只能是RDB或者AOF,那么会有延迟的情况在,我们使用从redis做备份,当主redis宕机之后,从redis顶上接着服务,问题点:当A服务从主redis获取锁之后,主redis掉线了,但是因为还没有把A获得所得状态同步到从redis上去,这时候从redis升级为主redis,B服务又去申请了同一个分布式锁,因为并没有同步到A已经获得这把锁的状态,所以此时就有两个服务获得同一把锁,这样就会出问题了。所以集群模式的红锁方式解决了这个问题,当某个服务分别向整个集群中每个实例去获取锁,如果从大于等于n/2+1个实例获取锁成功,则获取分布式所就成功。也就是大于一半的节点响应就可以获取锁,但可能消耗时间会比较久,在于对集群建立通信。
mysql与oracle
数据库DQL语句指查询语句select,DML语句指操纵语句:就是更新,DDL定义语句就是结构的,建表,索引视图这些,这是隐式commit的不能回滚,DCL语句控制权限,GRANT,rollback,commit这些。mysql执行过程:首先是连接器,然后是分析器,然后优化器,然后执行器,这都是server层的,然后执行器调用引擎innodb进行数据的查询
mysql中MyISAM和InnoDB的区别是什么:InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务。InnoDB是聚集索引,使用B+Tree作为索引结构,数据文件是和(主键)索引绑在一起的(表数据文件本身就是按B+Tree组织的一个索引结构),必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。MyISAM是非聚集索引,也是使用B+Tree作为索引结构,索引和数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。InnoDB不保存表的具体行数,执行select count(1) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数。Innodb没有这个变量的原因在于事务,同一时刻不同事务的行数是不同的!
count是返回该列的不包含null的条数总数,所以count(1)不一定等于count(某个字段)。
mysql在RR级别下使用select for update当前读自动开启next-key-locks,意向锁指的是当一个事务想要锁表的时候,会先查询该表有无意向锁,若有表明有其他事务锁了表,就要等待释放,若无则加上意向锁之后再加表锁,这样就不用去查询表中每一条数据是否上锁了,相当于一个标记,意向锁是server提供的,用户无法操作意向锁。
mysql执行过程:首先是连接器,然后是分析器,然后优化器,然后执行器,这都是server层的,然后执行器调用引擎innodb进行数据的查询
连接器:是半双工模式,就是同一时刻只能客户端向服务端发送请求或反过来,不能同时进行,首先验证用户名和密码是否正确,然后验证权限,权限表一共四个,user,db,tables_priv和columns_priv,验证过程为先判断user是否是全局用户,通过就结束了,否则再判断db,是否由库权限,再判断tables表权限,再列权限的顺序。在这之后会查询缓存,缓存以key和value的形式存储,key为具体sql,value是结果集,如果无法命中缓存,会走到分析器,命中就直接返回,缓存在8.0后删除了,因为失效非常频繁,在写入较多的情况下,缓存的新增和失效非常频繁命中率极低。
分析器:主要作用对sql进行语义分析,解析语法和判断是否能执行,比如表和列是否存在等
优化器:进入到优化器阶段说明sql是符合语义且可以运行的,此处主要是sql的优化,explain就是模拟优化器的模式指定执行计划返回,因此不会执行,不过如果有子查询,子查询会被执行。否则主查询的计划生成不了了。这里的优化在于比如有个联合索引abc,sql查询where b=x and a=x看起来不满足最左前缀原则,不会走索引,优化后变为where a=x and b=x,在此阶段是自动按照执行计划进行预处理选择一个最佳执行计划
执行器:执行器阶段会调用存储引擎的API,常用的innodb,会根据语句是DQL还是DML不同的执行,DML会写undolog存储事务id和回滚指针,然后在bufferpool中查询是否目标页存在,存在的话DQL就返回,DML如果普通索引就写入changebuffer中,有唯一索引因为要比较唯一冲突因此会去磁盘,如果不在bufferpool中就去磁盘读取该页(16kb)所有数据进bufferpool,如果索引使用频率达到一定程度,会触发自适应hash的产生,以hash的形式查询是最快的,但是只能生效于const类型的查询,即一条数据的情况,执行完成写入redolog中,事务提交,将redolog置为prepare然后写入binlog中,写完后将redolog置为commit,这样二阶段提交是为了保证redolog和binlog数据一致
为什么要二阶段提交:因为每次操作都更新到磁盘很慢,于是引入了两个日志redolog重做日志和binlog归档日志,这种技术是WAL,write ahead logging ,redolog是物理格式的日志,记录了修改的信息,是有大小限制的,写满了就要刷盘之后再写入,有两个点writePos和checkpoint一个记录当前写到哪里了,一个记录刷盘刷到哪里了,这两个相遇就说明写满了。有了redolog,异常宕机重启后数据也不会丢失,但是redolog只能保证数据存在,不能保证事务正确性,所以引入了binlog,是一个二进制的逻辑日志,可以简单认为是执行过的sql,用于主从同步,是由server提供的,而redolog只有innodb有,binlog是追加写入的,一个文件写到一定大小后会切换到下一个,无限。再来看二阶段提交,为了保证两个日志的数据一致,如果只写了redolog,重启后redolog恢复了数据,binlog没有,库之间去同步就会出现从库数据少了,如果只写了binlog,重启后redolog没执行,那么库之间同步出现主库数据少了。这两者的刷盘频率可以设置,都设为1则每次都会提交最安全,设置为0,那么redolog会放入redologbuffer,binlog会放在binlogcache中,速度快但在内存中会丢失不安全
慢sql的优化:首先开启慢sql查询日志,找到执行慢的sql,然后使用explain查看执行计划,limit 1可以防止全表扫描,如果有唯一索引就没必要加了,本来就是const不会全表扫描,explain中的select_type有simple,表明是普通查询没有子查询,primary是复杂查询的最外层查询。Type字段有System,这是很少见的,表明只有一条数据,然后是const是唯一查询,ref表明使用了索引,range表明是范围扫描,index表明用了索引全表扫描,不需要排序,All就是完全的全表扫描最慢,Possible_key表明可能用到的索引并不是一定用到,rows列表明估算的要读取的行数,很重要,越少越好,extra列using index表明用了索引,using where表明用了条件查询而且查询列没用到索引,using fliesort用了外部排序,没用索引,一般是All查询
举例一句sql的执行:sql中每一步产生的结果以临时表提供给下一阶段使用,顺序为from-join/on-where-groupby-having-select-orderby-limit
select distinct s.id from T t join S s on t.id=s.id where t.name=“Yrion” group by t.mobile having count(1)>2 order by s.create_time limit 5
第一步就是选择出from关键词后面跟的表,这也是sql执行的第一步:表示要从数据库中执行哪张表。join是表示要关联的表,on是连接的条件。通过from和join on选择出需要执行的数据库表T和S,产生笛卡尔积,生成T和S合并的临时中间表Temp1。on:确定表的绑定关系,通过on产生临时中间表Temp2.where表示筛选,根据where后面的条件进行过滤,按照指定的字段的值(如果有and连接符会进行联合筛选)从临时中间表Temp2中筛选需要的数据,注意如果在此阶段找不到数据,会直接返回客户端,不会往下进行.这个过程会生成一个临时中间表Temp3。注意在where中不可以使用聚合函数,聚合函数主要是(min\max\count\sum等函数)group by是进行分组,对where条件过滤后的临时表Temp3按照固定的字段进行分组,产生临时中间表Temp4,这个过程只是数据的顺序发生改变,而数据总量不会变化,表中的数据以组的形式存在对临时中间表Temp4进行聚合,这里可以为count等计数,然后产生中间表Temp5,在此阶段可以使用select中的别名在temp4临时表中找出条数大于2的数据,如果小于2直接被舍弃掉,然后生成临时中间表temp5对分组聚合完的表挑选出需要查询的数据,如果为*会解析为所有数据,此时会产生中间表Temp6在此阶段就是对temp5临时聚合表中S表中的id进行筛选产生Temp6,此时temp6就只包含有s表的id列数据,并且name=“Yrion”,通过mobile分组数量大于2的数据distinct对所有的数据进行去重,此时如果有min、max函数会执行字段函数计算,然后产生临时表Temp7此阶段对temp5中的数据进行去重,引擎API会调用去重函数进行数据的过滤,最终只保留id第一次出现的那条数据,然后产生临时中间表temp7会根据Temp7进行顺序排列或者逆序排列,然后插入临时中间表Temp8,这个过程比较耗费资源limit对中间表Temp8进行分页,产生临时中间表Temp9,返回给客户端,所以limit我们优化的时候用子查询传入id将上次最后一个id作为条件放入where中能加速的原因就是temp8变小了,limit的时候不用扫描过前面的无用数据。因为查询的时候不加id那么首先是随机查询出符合条件的二级索引然后再进行回表查询找到数据,如果查第三万到三万零五条实际是查了三万零五条然后忽略前三万条这就是越往后limit越慢的原因,而你用子查询限定主键之后只查询了五条,这个执行过程也可以用select index_name,count(1) from information_schema.INNODB_BUFFER_PAGE where index_name in(xxx) and table_name like ‘xxtable’ 的方式去查看两种查询实际的bufferpool数据页大小来证实。实际上这个过程也并不是绝对这样的,中间mysql会有部分的优化以达到最佳的优化效果。
索引下推:ICP,5.6之后的新特性,不使用的时候存储引擎通过索引检索到数据返回给服务器,服务器判断是否符合条件,使用后如果索引中的列有判断条件,服务器会将条件传递给存储引擎,引擎来判断索引是否符合条件,只有符合条件的数据才会返回给服务器,简而言之不符合条件的数据不用再回表查询了。
MySQL服务层负责SQL语法解析、生成执行计划等,并调用存储引擎层去执行数据的存储和检索。
索引下推的下推其实就是指将部分上层(服务层)负责的事情,交给了下层(引擎层)去处理
举个例子:创建联合索引(name, age)并查询 select *from tuser where name like ‘张%’ and age=10;
根据索引最左匹配原则,这个语句在搜索索引树的时候,只能用 张,找到的第一个满足条件的记录id为1
如果没有使用ICP:
存储引擎根据通过联合索引找到name like ‘张%’ 的主键id(1、4),逐一进行回表扫描,去聚簇索引找到完整的行记录,server层再对数据根据age=10进行筛选。
我们看一下示意图:
可以看到需要回表两次,把我们联合索引的另一个字段age浪费了
如果使用了ICP:
存储引擎根据(name,age)联合索引找到,由于联合索引中包含列,所以存储引擎直接在联合索引里按照age=10过滤。按照过滤后的数据再一一进行回表扫描。
我们看一下示意图:
可以看到只回表了一次。
除此之外我们还可以看一下执行计划,看到Extra一列里 Using index condition,这就是用到了索引下推,并不是Using index。因为并不能确定利用索引条件下推查询出的数据就是符合要求的数据,还需要通过其他的查询条件来判断。
索引下推适用条件:需要整表扫描的情况。比如:range, ref, eq_ref。InnDB引擎只适用于二级索引,因为InnDB的聚簇索引会将整行数据读到InnDB的缓冲区,这样一来索引条件下推的主要目的减少IO次数就失去了意义。因为数据已经在内存中了,不再需要去读取了。引用子查询的条件不能下推。
索引下推默认是开启的,索引下推优化技术其实就是充分利用了索引中的数据,尽量在查询出整行数据之前过滤掉无效的数据。由于需要存储引擎将索引中的数据与条件进行判断,所以这个技术是基于存储引擎的,只有特定引擎可以使用(InnoDB和Myisam)。并且判断条件需要是在存储引擎这个层面可以进行的操作才可以,比如调用存储过程的条件就不可以,因为存储引擎没有调用存储过程的能力
mvcc
分库分表以及ES
一般的搜索是在文章中搜索单词,而搜索引擎需要通过单词能找到文章,这种反向的搜索就是倒排索引,实现原理为将某个单词作为key,将它出现过的文章id集合作为value存下来,下次再搜索这个单词的时候就可以返回所有文章的id了,这是最初版,但是单词
太多了,所以为单词再添加一层字典树,字典树只存单词的前缀,将单词有序的排列,然后字典树通过字典树可以快速的找到单词所在的大概位置,即都是这个前缀的单词,然后在部分单词中找到准确的那一个
mq
削峰填谷
一个大任务的多线程处理
扩容消费者,broker生产者扩容,处理积压
丢弃mq,使用持久化在谷时恢复执行
事务消息顺序消息延迟消息死信队列
分布式事务2pc,3pc提交协议,本质还是加一个全局者,然后全局者和每个事务参与者确认状态最终确认事务的状态,2pc将事务的提交分为两个阶段,协调者先将事务发送给各参与者并等待处理结果,各参与者操作完将undo和redo保存,方便回滚和最后确认,
协调者收到所有事务做完的结果,再通知各参与者事务可提交,缺点在于同步阻塞一个事务要等所有完成才行,还有的问题就是单点问题,一阶段有人挂了还好说,二阶段有人挂了数据就不一致了,协调者挂了那所有人都锁住了,3pc是对2pc的一种优化,在
一阶段和二阶段之间增加了一个准备阶段,保证在之前的二阶段各参与者状态都一致,同时在参与者和协调者之间引入了超时机制,若未收到协调者的commit请求会自行commit不会阻塞,解决了单点故障,但还是没有完全解决数据一致性问题,这里还有一个ack
机制,在mq中保证消息一定发送和消费的机制,用日志保证存储中的消息丢失可恢复,还有mysql主从复制防止写丢失的半同步复制也有用到ack,主库写入后通知从库写binlog任一从库返回ack才算主库记录写入完成
算法
数组的查找一般普通查找和二分法,二分法每次取中间值判断大小,然后再剩下的一半里一直重复直到找到,二分法需要有序,数组的排序算法,有冒泡排序,比较相邻两个数据,将大的那个换到后面,然后再继续往下,一次完成之后最后的元素就是最大的,然后再来
选择排序,和冒泡类似,从第一个开始和后面每个元素比,两个中更小的那个和后面的比,之后最后最小的和最后的换位置,然后再来(就换一次,冒泡换n次),快速排序,就是用一个基准值,将所有大于它的放到右边,小于它的放到左边,然后左右两边的数组再
按这种方式一次又一次排序最终就有序了,方式为以第一个值作为基准设置i=0,j=length-1,然后从末尾往前依次比较j–,有比基准小的值,和基准互换,然后i++和基准进行比较,如果有大的就和基准互换然后j–,就这样直到i==j然后一次排完了,这时候
基准两边的两段数组再重复上述操作,堆排序:堆是一个完全二叉树,利用完全二叉树的结构来维护一维数组,根据特点可以分为大顶堆小顶堆,前者每个结点的值都大于左右孩子节点,后者都小于,根据堆的定义,在一维数组的描述中就是如果父结点下标是i
那么左孩子结点下标是2i+1,右孩子结点是下标2i+2,这种特性很重要,可以快速访问到重要的元素,所以优先级队列使用了这种方式,即堆顶是k个元素中最大或最小,建堆从最后一个非叶子节点开始,计算为length/2-1,和他的子结点比较换个大的过来,然后
找上一个父结点比较,换大的上去就这样从左往右到最后完成构建,然后像查找数组中第k大的元素可以用一个优先级队列,容量为k,比较器设置为a-b>0这样会把最小的放在上面,把前k个数add进去,然后数组剩下的值来和堆顶q.peak比较,小于k的不要,大于
k则q.poll丢弃顶堆,将大的数据加入q.add始终保持顶堆为目前的第k大,最后返回q.peak堆顶就行了
链表的查找一般有双指针法,一快一慢或者一前一后将两次循环变为一次,动态规划例子如下比如有面值1,3,5的硬币怎么用最少的硬币凑够11元,首先f(n)是拿n元的最小硬币数,则前一次f(n-h[i])其中h[i]等于1,3,5所以f(n)=min(f(n-h[i])+1)然后找到
底层边界问题f(1)=1,f(2)=2,f(3)=1,f(4)=2,f(5)=1,然后自底向上算出最小硬币数
这个题目如果改为有多少种拿硬币的方式,f(n)为拿n个硬币的所有方式f(n)=sum(f(n-h[i])+1)就是f(n)=f(n-1)+1+f(n-3)+1+f(n-5)+1,然后底层边界问题的定义之后进行递归计算
滑动窗口,迪杰斯特拉算法
Dubbo与springcloud以及Golang等分布式处理
docker与K8S及Jekins等容器部署相关
k8s缩扩容有两种方式,一种是修改deploy.yml的replices下的数字去手动控制容器数量,另一种是使用自动缩扩容技术HPA,HPA控制器根据cpu和内存以及并发数,包的传输大小等检测状态每隔30s运行一次来判断是否需要缩扩容
tomcat与nginx,Jboss等一系列服务器
CAP理论下的一些复杂场景解决方案
Python
Golang
ChatGPT
ChatGPT无魔法免费体验
国内Chat列表导航
awesome -prompts
ChatGPT相关原理与基础知识
团队经验
团队的管理首先就是知道自己的资源,然后据此需求分析和计划排期,之后要留有冗余预防突发情况,以及对任务排优先级做好最低期望,还有向上反馈需要的资源和困难之处,阶段性复盘提炼总结,对下也要以结果为导向,任务的优先级和问题的及时反馈
资源的需要等,都是一套模式,有预期的情况下1v1定每个人的计划,不定期不定量的任务则平衡分配然后根据产出来排绩效