面试
1. java
1. 基础
1. 基础
1. 特性
简单,面向对象,可移植,解释执行(先编译为class文件,然后jvm解释执行)
2. 数据类型
1. 基本数据类型
1. 整数型(byte,short,int,long)
2. 浮点数型(float,double)
3. 字符型(char 2)
4. 布尔型(boolean 1)
2. 引用数据类型
类和接口、String、数组
2. 面向对象
1. 定义
面向过程注重事情的每个步骤及顺序,面向对象注重参与事情的对象有哪些,以及他们各自需要做什么
2. 三大特性
1. 封装
将类的某些信息隐藏在内部(private),对外通过方法调用,不用关心具体实现。减少耦合
2. 继承
从已有的类中派生出新的类,能继承并扩充已有类的属性和方法(父类private不能继承)
3. 多态
父类或接口定义的引用变量可以指向不同子类的实例对象,实现对引用变量的同一个方法调用,执行不同的逻辑。让代码更好的扩展和维护。
3. 特殊类
1. 抽象类(abstract)
“是不是”的概念,1.不能创建对象,只能被继承;2.可以定义抽象方法。
2. 接口(interface)
“有不有”的概念,1. 方法都是抽象的,变量都是final修饰的常量。2. 不能创建对象,只能被类实现或者被接口继承。3. 1.8后新增default方法,不是抽象方法,不用实现。
3. 内部类
-
- 作用
1. 内部类可以更方便访问外部类成员(否则只能通过外部类对象访问),2. 每个内部类都能独立的继承一个接口的实现,使多继承变得更加完善.
- 作用
-
- 注意
1. 内部类和外部类可相互访问private属性。2. 非静态的内部类不能定义静态方法和变量。
- 注意
-
- 调用内部类变量或方法
1. 间接调用(通过外部类方法调用)。2. 直接调用(定义内部类对象调用)Outer.Inter inter=new Outer.new Inter();
- 调用内部类变量或方法
-
- 匿名内部类
new对象时,直接接创建类new X{public void f(){…}}
- 匿名内部类
4. 变量类型
1. 常量(final 修饰,无默认值,存在常量池)
2. 类变量/静态变量(static修饰,有默认值,存在方法区)
3. 成员变量/实例变量(方法外,有默认值,存在堆中)
4. 局部变量(方法内,无默认值,存在栈中)
5. 修饰符
1. 作用域
private(同类)、protected(同包和子类)、default(同包)、public(所有)
2. static
static修饰的变量内存只有一份,static修饰的变量和方法可以直接通过类名访问。static修饰的代码块只会执行一次
3. final
final修饰的变量成为常量,final修饰的方法不能被覆盖,final修饰的类不能被继承
4. super
可以调用父类的方法和属性
3. 高阶
1. 泛型
1. 含义
类定义时不设置属性或方法具体类型,实例化的时候再指定具体类型。优点:代码重用、保护类型的安全以及提高性能。
2. 泛型通配符
1. class A{}:可以定义任意类型
2. class A{}:只能定义B或B的子类
3. class A{}:只能定义B或B的父类
2. 反射
通过反编译,实现通过对象获取类的方法和变量信息。(可以动态获取类的信息, 运行期类型的判断,动态加载类,提高代码灵活度,框架中比较常见)
3. 动态代理
作用:类似Spring的aop,可以动态的,不修改源代码的情况下为某个类增加功能,如在一个方法的前后添加一下功能。
4. 异常
Throwable
1. Error(错误):程序无法处理
1. 栈/内存溢出
2. 虚拟机运行错误
2. Exception(异常):程序可以处理
1. RuntimeException及其子类:如下标越界
2. 非RuntimeException异常:可查异常,需要try catch 或throws。如文件上传
5. IO
1. 字节流:以字节为单位,可处理任意类型数据
2. 字符流:以字符为单位,一次性可读多个字节,处理字符类型数据。
2. 集合
1. collection(单列集合)
1. list(可重复,有序:元素存取顺序一样)
1. 查快:ArrayList
1. 底层数组,查快,增删慢,线程不安全,效率高
2. Vector:线程安全,结构跟ArrayList类似。内部实现直接使用synchronized 关键字对 每个方法加锁。性能很慢。现在淘汰(synchronizedList跟Vector类似,区别是使用synchronized 关键字对 每个方法内部加锁)
3. CopyOnWriteArrayList:线程安全,在写的时候加锁(ReentrantLock锁),读的时候不加锁,写操作是在副本进行,所以写入也不会阻塞读取操作,大大提升了读的速度。缺点:写操作是在副本上进行的,数组的复制比较耗时,且有一定的延迟。
2. 删快:LinkedList
底层双向链表(所以可以作为栈和队列),查慢,增删快,线程不安全,效率高
2. set(唯一,无序:元素存取顺序不同)
1. 未排序:HashSet
1. 底层哈希表,哈希表依赖hashcode()和equals()保证唯一性。
2. 有一个子类LinkedHashSet,底层为链表和哈希表,依赖链表保证有序
2. 排好序:TreeSet
底层红黑树(Compareable保证唯一)
2. map(双列集合,无序)
1. 未排序:HashMap
1. 线程不安全,底层哈希表+红黑树(1.8之前为哈希表),效率高,各操作接近O(1)(补充:1.8之前哈希冲突后通过单向链表保存,查找时间n,1.8将单向链表换成了红黑树(链表长度超过8。1.8后链表插入从头插法改为了尾插法,因为头插法在多线程中可能导致死循环),查找时间为logn——n为链表长度)
2. 线程安全的HashTable,底层hash表,通过Synchronized保证安全,现已淘汰(被concurrentHashMap替代,因为HashTable锁住整个map,效率低。而concurrentHashMap锁住一部分map)
3. 线程安全的concurrentHashMap ,底层哈希表+红黑树,通过synchronized加锁保证线程安全(1.8之前底层是segment数组和哈希表,通过segment加锁保证线程安全)
2. 根据key排好序:TreeMap
底层红黑树,各操作O(logn)
3. 线程
1. 开启线程三种方式
1. 继承Thread类,重写run方法。(代码:Thread t=new MyThread()😉
2. 实现Runnable接口,在类中实现run()方法(代码:Thread t=new Thread(new MyThreadt())😉
3. 实现Callable接口,实现call方法,再结合FutureTask 创建线程(可获取线程结束后的返回值)(代码:Thread t = new Thread(new FutureTask<>(new MyCallable));)
4. 比较:接口实现优势:1.避免java中的单继承的限制,2. 使用接口的方式能放入线程池。(Callable较Runnable而言,线程可以有返回值)
2. 线程相关方法
1. Thread.sleep(long millis) :线程睡眠,线程转到阻塞状态
2. Obj.wait() :线程等待,线程转到等待状态(释放对象锁)
3. Obj.notify(): 线程唤醒,唤醒等待中的线程(注:它不是立即唤醒,notify()是在synchronized语句块内,在synchronized(){}语句块执行结束后当前线程才会释放对象锁,唤醒其他线程)
4. Thread.yield() :线程让步,让相同优先级的线程之间能适当的轮转执行
5. join(): 线程加入,在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
6. interrupt():线程中断,向其他线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出异常。
7. ThreadLocal:线程本地存储机制,它可以将数据缓存到某个线程内部。(数据是以key为ThreadLocal对像,值为数据缓存到ThreadLocalMap中的,ThreadLoacalMap是每个线程都有一个)注意:因为线程池中的线程不会销毁,所以ThreadLocal对应的值也不会被回收会导致内存泄漏,所以建议在使用完后remove掉。
8. 注:线程优先级范围1-10,默认为5。main()本身也是一个线程,当main结束后,子线程仍会继续执行到结束。每个程序至少有两个线程,main和垃圾回收线程(每个程序启动都会启动一个jvm)
3. 线程池
1. 定义
可以容纳多个线程的容器,其中线程可以反复使用,避免反复创建线程的开销。
2. 创建线程池步骤
1. 创建线程池:ExecutorService service = newFixedThreadPool(5);//或者 ExecutorService service = Executors.newFixedThreadPool(5);
2. 加入线程:service.submit(new MyCallable());//或者service.submit(new MyRunnable ());
3. 关闭线程池:service.shutdown();
3. 线程池的七大参数
1. 常驻核心线程数(corePoolSize):线程中长驻的核心线程
2. 最大线程数(maximumPoolSize):大于等于1
3. 多余的空闲线程存活时间(keepAliveTime):线程数超过常驻核心线程数时,多余线程的空闲时间达到keepAliveTime时就会销毁
4. unit:keepAliveTime的单位
5. 任务队列(workQueue):被提交但是尚未被执行的任务。
6. 线程工厂(threadFactory):用于创建线程
7. 拒绝策略(handler):当任务队列满了并且工作线程大于等于最大线程数时,就会触发拒绝策略(4种:1. 异常策略(AbortPolicy):直接抛出异常。2. 丢弃策略DiscardPolicy:新来任务被提交后直接丢弃。3. 淘汰最久任务策略DiscardOldestPolicy:丢弃存活时间最长的任务。4. 执行策略(CallerRunsPolicy):让提交任务的线程自身去执行该任务)。
4. 锁(保证线程安全)
1. Synchronized和ReentranLock
1. Synchronized是一个关键词(jvm层面的锁)可以修饰方法和代码块(在方法前加static synchronized 可以锁住某个类的所有对象),Jvm自动释放锁。ReentranLock是一个类(API层面的锁),只能修饰代码块。手动释放锁
2. Synchronized锁的是对象,锁信息保存在对象头中的Markword中(记录四个锁状态),ReentrantLock锁的是线程,锁的信息保存在AQS 的state标识,以及ownerThread字段。
3. ReentrantLock 相对 synchronized 多了三种功能
1. 1.等待可中断(正在等待的线程可以选择放弃等待,改为处理其他事情。)
2. 2.可实现公平锁(默认非公平)
1. 公平锁:在获取锁时,会先检查AQS同步队列是否有线程在排队,有就进行排队(AQS先进先出的双向队列)
2. 非公平锁:获取锁时不会检查是否有线程在排队,而是直接竞争锁(如果没竞争到锁,后面就跟公平锁一样也会去排队,当锁释放只会唤醒AQS队列的首个线程,非公平只体现在加锁阶段,不体现在唤醒阶段)
3. 3.可绑定多个条件,实现有选择的唤醒等待(synchronized结合notifyAll()会唤醒所有等待的线程。Reentrantlock中一个锁对象可以创建多个对象监视器,线程对象可以注册在指定的对象监视器中,从而可以有选择性的进行线程通知)
4. Synchronized和ReentranLock都是可重入锁
1. 就是在一个线程中允许你反复获得同一把锁,若多次加锁记需要多次解锁才能释放(实现:每个锁关联一个请求计数器和一个获得该锁的线程,加一次锁计数器加1)
2. 为什么需要可重入锁:最大程度避免死锁(对象中加锁的A方法调用加锁的B方法,就会导致死锁,可重入锁允许一个线程反复或的一把锁,就不会导致死锁)
2. Synchronized相关
1. 为什么说Synchronized是一个重量级锁
Synchronized 底层实现依赖于操作系统的互斥锁。而操作系统实现线程之间的切换需要从用户态转换到核心态,转换耗时,成本非常高。这种依赖于操作系统互斥锁的称为重量级锁。
2. 锁机制如何升级
1. java创建的新对象,默认不打开锁,超过四秒(默认值)就打开偏向锁,如果有少数线程竞争资源,就打开轻量级锁,如果竞争很多或者等待时间太长,太多线程自旋竞争锁会消耗cpu资源,所以就会开启重量级锁。
2. 偏向锁:在对象头中记录当前获得该锁的线程id,下次来获取就能直接获取了。(因为大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁)
3. 轻量级锁:每个竞争线程通过自旋不停看被锁住资源是否释放。
4. 重量级锁:由操作系统来判断锁住资源是否释放,释放再通知其他线程。
3. 双重检查锁DCL(Double Check Lock)
1. 作用:尽可能减少加锁的范围(锁如果加在第一重,初始化除了创建对象可能还涉及其他赋值操作,所以加锁范围太大。在初始化中创建 对象为什么还要二重检查?因为第一重检查未加锁,所以可能导致多个线程都判断对象为空,从而进入初始化方法,依次创建多个对象)
2. 问题1:为什么要在对象变量前加volatile:1. 保证修饰变量的可见性(修饰的变量修改后会马上更新到主存)。2. 防止指令重排(指令重排序是编译器处于性能考虑,源码顺序和程序顺序可能不一样。)
3. 问题2:为什么出现指令重排:由于初始化不是原子操作,底层是三步(1.分配内存空间。2. 执行构造方法,初始化对象。3. 把这个对象指向空间。)所以可能导致A线程执行了1,3。b线程判断此时对象为空,然后又执行初始化操作。
3. ReentrantLock相关
使用:先创建一个ReentranLock对象,然后通过lock()和unLock()方法加锁和释放锁。还有tryLock()尝试加锁,可以用于自旋。
4. 面试题
1. jdk、jre和jvm
1. jdk:java开发工具包,包含java编译器,jre,java常用类库
2. jre:Java运行环境,包括jvm和jvm所需类库
3. jvm:java虚拟机,用于运行字节码文件(会将字节码文件解释为机器指令,不同操作系统机器指令不同,所以不同操作系统的jvm也不同,但都运行字节码文件)
2. ==、hashCode()和equals()
1. 默认每个对象都可以调用hashCode(),相当于每个对象的指纹信息,相同的哈希值不一定为一个对象,但不同hash值一定不是同一个对象。一些集合类先判断哈希值是否相同,如果相同再判断equals判断。(类中可以重写hashCode方法和equals方法。重写可以根据类中属性进行比较)
2. ==:如果是基本类型,则比较的是值,引用类型比较的是引用地址。equals具体比较看方法重写的是什么(String内部重写的就是全部字符是否相等)
3. 重写和重载
1. 重写是子类重写覆盖父类的方法(注意:重写方法的 修饰符范围不能小于父类,且private方法不能重写)
2. 重载是同一个类中方法名相同,参数不同
4. 深拷贝和浅拷贝
1. 浅拷贝:只复制一个对象的引用
2. 深拷贝:把对象完全复制一份
5. 为什么数组具有快速查找的能力
数组是连续的内存地址,且元素都是同一类型,占用空间大小一样,所以通过下标就可以计算出元素的位置。
6. JUC
为了更好的支持高并发引入的一个包,了解其中的ConcurrentHashMap
7. String、StringBuffer和StringBuilder区别
1. String:底层时final修饰的byte数组(所以不可被继承)。若频繁修改字符串时,会产生很多无用的中间对象,效率很低。
2. StringBuilder :底层也是一个byte数组,但不是常量,当容量不足时会进行扩容。线程不安全。
3. StringBuffer:在StringBuilder基础上考虑了线程安全。通过synchronized为每个方法加锁保证线程安全。效率较低
8. new对象初始化顺序
父类静态代码块,子类静态代码块,父类构造方法,子类构造方法
9. int和Integer有什么区别
1. Integer:Integer是int提供的封装类;Integer对象需要实例化,默认值为null。
2. int:int是基本数据类型,直接存储数值,默认值是0;
10. Integer和int数值比较
1. Integer和int比较:Integer是int的封装类,int与Integer比较时,Integer会自动拆箱,无论怎么比,int与Integer都相等
2. Integer和Integer比较:通过equals比较,但对于数值在-128与127之间的Integer对象,会自动存在内存中。所以会直接从内存取,不会创建新的对象,所以也可以通过==比较。
11. 构造方法有哪些特性
名字与类名相同,没有返回值,但不能用 void 声明构造函数,创建对象时自动执行
12. run()和start()
run方法就是一个普通的方法,而start方法会创建一个新线程去执行run()的代码。
13. 浮点数
1. IEEE754标准
1. float占4字节,32位(1个符号位,8个指数位,23个尾数位)指数对应表示大小范围:8位指数大小范围是[-127,128]),所以表示数值范围是-2128到2128。尾数对应精度:23位尾数,能表示最大十进制为2的23次方为7位数,所以完整精确表示的为6位。(省略位为1,所以不算进去)
2. double占8字节,64位(1个符号位,11个指数位,52个尾数位)表示10进制精度位15位。表示数值范围为-21024到21024
2. 十进制数值存储过程(以float为例)
1. 先把10进制转换为2进制,然后规范化(科学计数法)。对于符号位:正数存0,负数存1。对于指数位:因为指数位8位范围是[-127,128],转二进制存计算机时,为了避免负数,加上一个固定偏移量127,然后转二进制存入8位指数位。对于尾数:由于规范化后首位都为1,所以省略首尾,将小数点后的位数放入尾数部分,不足补0。
2. 例子:10.75转为二进制为1010.11,规范化为1.01011*10^3。 正数符号位为0。尾数为省略首位后为01011。指数为3+127=130,换算为二进制为10000010。所以最终10.75存在计算机就为:0 10000010 01011000000000000000000
3. 注意事项
1. 比较两个浮点不要用==,而是做差是否在一定范围(一般差小于10的-6次方)。2. 尽量使用double而不是float(因为float精度太低 )。3.金融场景一定要使用BigDecimal(无精度损失)
4. 扩展
1. int占4字节,32位,除去符号位还剩31位。范围为[-231,231-1]。(因为31可以表示231个正负数。分别为[0,231-1]和[-231+1,-0]。因为只需要一个0,而且-0的原码加上符号位,恰好等于-0的补码,所以负数要多一个,最后范围为[-231,2^31-1])
2. 正数:原码=反码=补码。负数:反码为原码符号位不变,其他位相反;补码为反码加1。(计算机都存的补码)
14. 各种树
1. 二叉树:缺点:树的高度不稳定,可能变成链表,影响效率。
2. 平衡二叉树:左右子树高度差不超过1
3. 平衡二叉查找树:平衡二叉树基础上保证,中序遍历为递增。(平衡算法有:红黑树,AVL树)
4. 红黑树:增删差时间复杂度为logn(子树高度差可能大于1,不是严格平衡二叉树)。特征:1.红节点的孩子是黑节点,2. 叶子节点不存储数据空节点,3. 叶节点和根节点为黑色。4.从任一节点出发到任意叶子节点的路径,黑色节点的数量是相等的。(所以去掉红色节点后长度不超过log(n+1),又因为两个红色节点间一定有黑色节点,所以总高度不超过2log(n+1))。
5. AVL树:相对红黑树高度控制更严格,查找效率小于logn。但增加删除的调整都相对复杂(所以一般用红黑树)
6. B树:一个节点有两个或多个孩子节点,且一个节包含多个元素(可设置),节点间和节点内部元素都是排好序的(可设置)。
7. B+树:相对于B树,叶子节点包含所有元素,且叶子节点间有指针。
8. 引入:1. 为了保证节点均匀分布,就有了平衡二叉树。2. 为了减少树的高度和一次IO读取更多的数据引入B树。3. B+树在B树的基础上,在查询的稳定性 和排序方面进行了优化,因为B+树所有数据都会保存到叶子节点,且叶子节点有序。
15. ArrayList的扩容
使用无参构造方法时,初始大小是0,当有数据插入时,扩展到10。每当容量到达最大量就会自动扩容原来容量的1.5倍(会把老数组元素重新拷贝一份到新数组,代价较高,所以知道初始容量,可以初始化时指定一个初始容量)。
16. hashMap
1. put添加过程
1. 1.根据Key通过哈希算法与与运算得出数组下标
2. 2.如果数组下标位置元素为空,则将key和value封装为对象并放入该位置(JDK1.8之前是Entry对象,JDK1.8中是Node对象)
3. 3.如果数组下标位置元素不为空
1. 1.8之前:先判断是否需要扩容,如果要扩容就进行扩容,如果不用扩容就生成Entry对象,并使用头插法添加到当前位置的链表中
2. 1.8之后:先判断当前位置是红黑树Node,还是链表Node。
1. 如果是红黑树Node,则将key和value封装为一个红黑树节点并添加到红黑树中去(在这个过程中会判断红黑树中是否存在当前key,如果存在则更新value)
2. 如果是链表Node,则将key和value封装为一个链表Node并通过尾插法插入到链表的最后位置去,(因为是尾插法,如果存在当前key,如果存在则更新value)。最后判断节点如果大于等于8,那么则会将该链表转成红黑树。
3. 插入节点后,最后判断是否需要进行扩容,如果需要就扩容,如果不需要就结束PUT方法
2. 扩容机制(默认大小16)
1. 1.8之前:1.先生成新数组。2.遍历老数组中的每个位置上的链表上的每个元素。根据key,并结合新数组长度,计算出每个元素在新数组中的下标,然后将元素添加到新数组中去。3.最后将新数组赋值给HasshMap对象的table属性
2. 1.8之后:跟1.8前不同的就是判断节点时如果是红黑树:则先遍历红黑树,然后计算每个元素在新数组中的下标位置,然后统计每个下标位置的元素个数,个数超过了8,则生成一个新的红黑树,放到对应位置,如果个数没有超过8,那么则生成一个链表,放到对应位置
3. 加载因子
默认0.75(hashmap默认大小16,乘以加载因子为12,所以达到12就进行扩容。)加载因子越大,空间利用率越高,但冲突机会增加。
17. java8新特性
1. lambda表达式
1. 定义:语法更简单,本质就是创建一个接口实现类的具体对象。(对应接口只能有一个抽象方法,可以在接口加一个注解,限制其只能有一个抽象方法)
2. 例子:X x = ()->System.out.print(“1”);//()对应的就为接口中无参方法,箭头后就为实现的内容。
3. 作用:简化代码,适用于代码不复用的场景
2. 函数式接口
可以在接口加一个注解,对应接口只能有一个抽象方法,与lambda连用
3. 方法引用
1. lambda深层引用,如果在lambda表达式中,函数式接口的参数和返回值 和 方法体实现中的方法的的参数和返回值一样就可以使用
2. 三种使用情况:
1. 对象::实例方法名(非静态方法)例子:X x = y::方法名; x.方法名(参数)(y为方法体中的方法对应的对象)
2. 类::静态方法名
3. 类::实例方法名(还要满足条件:两个参数,第一个参数为方法调用者,第二个参数为方法的实际参数)
4. 接口新增default方法
不是抽象方法,不用重写
5. 修改一些底层实现
如hashmap,底层新增红黑树结构
6. 新增一些api(日期相关)
2. JavaWeb
1. Spring
1. Spring AOP
1. 面向切面编程,说的通俗易懂就是可以动态的,不修改源代码的情况下为程序添加功能,如在一个方法的前后添加一下功能。具体实现:在beans.xml中定义一个切面(包含新功能的类)和一个切点(表示在哪个类的哪个方法添加切面)
2. AOP底层
就是可以动态的,不修改源代码的情况下为程序添加功能。底层实现是通过代理对象,代理对象继承普通对象,代理对象中有一个target属性(它就等于前面依赖注入后的对象),在代理对象中,会重写切点方法(在重写的方法里面加入切面逻辑以及调用target对象的切点方法)
3. Spring事务底层原理
Spring事务底层也是基于AOP。如果类中加了@Transactional,在创建该类bean的 初始化后 这个过程,就会生成代理对象。通过代理对象执行某个方法时,先在代理对象中判断改方法是否有@Transactional,有就1. 先通过事务管理器新建一个数据库连接,2. 然后关闭数据库自动提交,3. 再通过target执行普通对象的方法。4. 最后数据库提交或者回滚。
4. 事务传播机制(事务嵌套)
1. 默认是将内层事务合并到外层一起提交,有异常一起回滚。
2. 其他事务传播:1. @Transactional(propagation=Propagation.NEVER):表示如果已经存在事务的话,会抛出异常。2. propagation=mandatory:外层没有事务,抛出异常。3. propagation=support:内层事务依赖外层,外层是事务才生效。
3. 注意:只能通过代理对像调用事务方法才会才会生效(如果在一个类中一个普通方法调用一个有事务注解的方法,是不会生效的,因为普通方法调用相当于是普通对象调用的事务方法,解决办法,增加一个属性,自己注入自己,然后在普通方法中调用事务方法)。
2. Spring IOC
1. Spring IOC
1. 控制反转(依赖注入),一种面向对象编程的思想,通过引入IOC容器,利用依赖注入的方式,实现对象之间的解耦。(底层通过反射实现。联想齿轮)
2. IOC容器:管理bean对象,负责创建对象和建立对象间的依赖。(通过读取配置文件来完成对象的实例化和装配)
2. Bean之间的关系
1. 继承。2. 依赖(如果某个bean依赖其他bean,创建该bean时会先创建依赖的那个bean)。3. 引用(通过property对bean起别名)
3. 创建bean
1. Spring如何创建bean对象
1. 通过构造方法创建一个普通对象。2. 对普通对象进行依赖注入。3. 进行初始化(包括初始化前,初始化,初始化后)。4. 将完整的bean对象放入map单例池中
2. 初始化前过程(通过@PostConstruct注解,也称后置处理器)
作用于bean对象创建过程中的初始化前。原理就是:通过反射判断创建的对象中是否有带有这个注解的方法,有的话就执行(通过这种方式,程序员可以对对象里的属性赋值等操作)
3. 初始化过程
如果创建的对象实现了InitializingBean这个接口,就会在该类中实现一个初始化方法(程序员可以做一些初始化工作),在bean初始化的时候就会判断该对象是否实现了这个接口,如果实现了就执行该初始化方法
4. 初始化后过程
初始化后可以进行AOP,会产生代理对象,如果有代理对象,就把代理对象放单例池中。
5. 推断构造方法是什么
在创建bean对象时,前面会先创建普通对象,如果类中有多个构造方法,spring会选择无参的构造方法,如果没有就会报错,也可以加@Autowired来告诉spring选择哪个构造方法。
6. 依赖注入先bytype再byName是什么
创建bean时,如果类属性中有@Autowired(或者构造方法需要传某个对象),就会给它注入值,先去map单例池中找是否存在,先通过byType找,如果只有一个直接赋值,如果有多个再byName找(如果bytype找到多个,且byName没找到则报错)。如果map单例池中没有就直接创建一个bean再赋值。
7. 普通对象和bean对象区别
Bean对象就是在普通对象的基础上进行依赖注入,放入map单例池过后的对象
4. 配置bean
1. @Configuration
@Configuration用于定义配置类,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被用于构建bean对象。@Configuration注解的类本身也是一个bean对象。
2. @Component
可以用于注册所有bean((@Controller和@Service和@Repository,分别用于组测controller,service,dao层))
3. 为什么有了@Compent,还需要@Bean呢?
如果想将第三方的类变成组件,你又没有没有源代码,也就没办法使用@Component进行自动配置,这种时候使用@Bean就比较合适了。
5. 注入bean
1. @Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上
2. @Autowired:默认按类型装配(这个注解是属业spring的)有三种方法注入:1. 属性注入,2. 构造方法注入,3. set方法注入
3. @Resource:默认按照名称进行装配(这个注解属于J2EE的)
6. 循环依赖
1. 定义
创建bean a时,需要b,创建b时需要a
2. 具体解决方案:三级缓存
1. 一级缓存:就是单例池,存放完整的单例bean
2. 二级缓存:1.用于存放还没有经过完整bean生命周期步骤的单例bean。2.保证不会重复创建多个不完整的bean
3. 三级缓存:存放的lambda表达式(其中包含对应的普通对象和是否需要aop的一些信息)
3. 创建过程
1. 开始创建A,先把A名称放入creatingSet中,表示A正在创建。2. 创建A普通对象后,将lambda表达式存到三级缓存中(lambda表达式包含A的普通对及相关信息,如是否需要AOP)3. 对A中属性B进行填充时,先到单例池找是否有B,这时没有,就开始创建B。4. 创建B普通对象后开始填充A,现在单例池中找,没找到。5. 然后判断是否在creatingSet中,找到则出现了循环依赖。6. 然后到二级缓存中找是否存在A,此时没找到。7. 然后通过三级缓存获取A的lambda,根据是否需要AOP,创建A代理对象或者普通对象。8. 最后将创建的A对象放入二级缓存。9. 继续后续操作完成B的创建。10. 继续完成A的后续创建。
4. 导致循环依赖失效原因及解决
-
- 情况1:构造方法导致循环依赖失效
1. 原因:a构造方法需要b,b构造方法需要a,这样连普通对象都生成不了
2. 解决:在构造方法前加@lazy,通过这种方式不会产生循环依赖(原理是先直接给该类属性生成一个代理对象,并未真正生成对应的bean,而是在具体调用该属性的时候再去生成真正对应的单例bean,此时当前类已经生成了单例bean,所以不会出现循环依赖。)
- 情况1:构造方法导致循环依赖失效
-
- 情况2:@Async 会导致循环依赖失效
1. 原因:因为如果类本身有aop的话,在循环依赖时会提前生成代理对象,而提前生成的代理对象只会处理aop, 在创建bean对象最后步骤里会判断二级缓存是否已经存在该代理对象,如果存在,就不会创建新的代理对象,但是@Async并没有处理,所以又会创建一个处理@Async的代理对象,这样就会存在两个不同的代理对象,所以会抛出异常。
2. 解决方法:可以通过在会导致循环依赖的类属性前加@lazy,通过这种方式不会产生循环依赖(原理是先直接给该类属性生成一个代理对象,并未真正生成对应的bean,而是在具体调用该属性的时候再去生成真正对应的单例bean,此时当前类已经生成了单例bean,所以不会出现循环依赖。)
- 情况2:@Async 会导致循环依赖失效
7. 单例bean和单例模式区别
1. 单例bean:可以一个类对应不同名称的多个bean
2. 单例模式:一个类对应的bean只能有一个
2. Springboot
1. 介绍
1. 大部分配置都通过类实现,简化Spring环境搭建和开发(集成其他spring系列的框架,让他们之间协同工作更简单,配置更方便)
2. 优点:1.简洁,配置方便(无代码生成、无需编写XML,全部都集成在框架中)2.内嵌web服务器(不用把项目打包成war,发布到如tomcat服务器上)3.自动配置Spring以及第三方功能(如开发web程序,只需要导入web包就能自动配置开发web对应的依赖,且自动协调它们之间版本关系)4.更好的监控,检查(如运维人员可以很方便的监控项目的状态)
3. 缺点:1. 迭代快。2.封装深(不容易懂底层)
4. 核心文件
启动类(application.java),配置文件(application.properties),maven配置文件(pom.xml)
5. SpringBoot 之 MVC 支持(常用注解)
1. @Controller 处理 http 请求。2. @RequestMapping 配置 url 映射。3. @RestController直接输出到页面(等同于@Controller +@ResponseBody)。4. @PathVariable 获取 url 参数。5. @RequestParam 获取请求参数(post 或 get 都可以)。6. @Autowired自动装配
6. SpringBoot之切面AOP
相比spring,不用再beans.xml中配置,可以通过注解全部在切面类中完成(切点在切面类中定义)。
2. 面试题
1. Springboot底层如何判断选择tomcat 还是jett?
Spring 是先创建Spring容器,再启动tomcat或者jett,具体选择哪个是根据项目中有哪个相关的依赖。Spring默认为tomcat是因为pom.xml中spring-boot-starter-web中默认有tomcat的依赖。
2. SpringBoot自动配置原理
SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration, 每个自动配置类会按照条件进行生效(条件装配),默认都会绑定配置文件指定的值。在xxxxProperties里面获取对应值,生效的配置类就会给容器中装配很多组件,只要容器中有这些组件,相当于这些功能就有了
3. 条件装配底层原理?
1. 原理:Spring是已经编译成class字节码文件的,1. 先通过ASM工具获取字节码文件中@ConditionalOnClass后的字符串“a.class”,2. 再判断这个类是否被加载,3. 最后决定是否装载。
2. 条件装配:@ConditionalOnClass({a.class}):存在a这个类才执行该方法
3. 扩展:Springboot启动时扫描哪些需要创建bean也是通过ASM来判断扫描路径下的类是否包含@component或者@bean。而不是把扫描路径下所有类字节码文件都加载到jvm中,通过反射来判断是否存在@component或者@bean(这样浪费内存空间)
4. bean后置处理器?
通过bean后置处理器可以修改bean里面的属性。(如springboot默认端口信息是在TomcatServletWebServerFactory这个bean中,如过配置文件中自定义了端口,就会通过bean后置处理器来进行修改)
5. 访问网址响应流程
1. 先跟据网址访问到tomcat服务器。2. 然后找到项目对应的DispatcherServlet(它的属性中有Spring容器,里面包含了所有的bean)3. 然后DispatcherServlet找到对应的Controllerbean,遍历其中的方法找到对应的方法。4. 执行对应方法并返回。
6. 项目启动
1. 创建一个SpringApplication对象 (加载一些事件监听器接口的实现类)。2. 根据SpringApplication对象 调用run()方法(1. 创建上下文对象,创建上下文对象同时会注册spring的核心组件类。2 .启动Spring容器和内置的Servlet容器)
3. SpringMvc
1. 配置
配置文件web.xml中配置DispatcherServlet及映射路径,以及初始化springmvc配置文件路径spring-mvc.xml。在spring-mvc.xml中配置视图解析器以及扫描包的路径。
2. 项目启动
1.Tomcat启动。2.解析web.xml ,其中listener创建spring容器(父容器)servletContext。3.DispatcherServlet实例化,DispatcherServlet对象.init()创建spring容器(子容器)。4.接收请求。扩展:一个DispatcherServlet对应一个Spring子容器,子容器可以共用父容器中的bean。一般项目就一个DispatcherServlet
3. 访问流程(接收请求及响应)
网址:localhost:8080/testWeb/app/test。1. 根据ip加端口访问到tomcat,再根据项目名找到对应的项目,然后根据app目录找到对应的DispatcherServlet(它的属性中有Spring容器,里面包含了所有的bean)2. 然后DispatcherServlet找到Controllerbean,遍历其中的方法找到是否有test方法。3. 执行test方法并返回值到至视图解析器(spring-mvc.xml),视图解析器返回对应的jsp到前台。
4. 跳转
1. 重定向(客户端跳转,地址栏变化):return “redirect:+地址”;
2. 内部转发(服务器跳转,地址栏不变,可携带参数和request范围值):return “forword:+地址”;
4. Mybatis
1. 实现
使用xml配置sql映射器(springboot跟这个类似)
2. Mybatis关系映射
就是查询时表之间的关联查询,一对一关系实现(比如学生实体类中包含的地址也是一个实体类,查询的时候返回值address需要封装所以采用关联查询,在返回值resultMap中 ,通过association来封装address(它会在address.xml根据id再查询一次地址信息),一对多则是collection)
3. 动态Sql
动态sql就是根据查询条件不同动态的拼接sql语句(如,if、when等等)
4. 分页查找
1. 逻辑分页(查出全部数据到内存,再取分页数据,比较占内存,一般不用,mybatis支持逻辑分页,通过传RowBounds参数实现)
2. 物理分页(通过limit实现,常用)
5. Mybatis缓存
1. 一级缓存:MyBatis 默认启用一级缓存,同一个用户调用了相同的select 语句,则直接会从缓存中返回结果(默认select使用缓存,insert,update,delete 是不使用缓存的)。(可以配置cache,来调整缓存大小,刷新周期,以及缓存策略等)
2. 二级缓存: 开发者可以自己配置二级缓存,二级缓存是全局的,是对所有用户select使用缓存;
6. 使用注解配置SQL映射器
就是在mapper中,每个查询方法上加上@xxx,优点就是更简洁,缺点就是不够灵活,企业开发一般还是用xmL配置。
5. JSP
JSP(Java Server Pages)技术就是一种将java信息转化成html信息的技术, 它有九大内置对象:response,exception等(实现页面中的一些特定功能,比如数据传送、页面重定向、自动刷新、异常处理、操作Cookie等。9大对象包含了四大作用域的对象(pageContext当前页面有效,request一次请求有效,session一次会话中有效,application整个应用有效)。)
6. Servlet
生成动态Web内容。( 客户端请求->服务器转发至servlet-> Servlet 生成动态Web内容并将其传给服务器->服务器将响应返回给客户端。)
7. Shiro
是一种安全框架,用主要用于身份验证和授权。自定义realm:登陆的时候,将用户的的权限,角色赋值给用户。然后给url设置访问权限,满足权限才能访问。
3. JVM
1. 定义
1. jvm是程序虚拟机,运行字节码,主要功能是内存管理和垃圾回收。
2. 目前主要使用的JVM为HotSpot,它采用解释器和即时编译器并存的架构
1. 翻译字节码(解释执行):读一行执行一行,响应快,速度慢,类似走路
2. Jit编译器(翻译执行):将热点代码编译成机器码,并进行优化,下次执行就很快,响应慢,速度快,类似等公交车。
3. jvm采用的是基于栈的指令架构
1. 基于栈的指令架构:零地址指令,完成一个功能需要更多指令,性能较低,不与硬件直接打交道,可移植,跨平台。
2. 基于寄存器指令架构:多地址指令,完成一个功能花费更少指令,性能较高,指令集架构依赖硬件,可移植性差,设计实现也较复杂。
2. 结构
####
3. 类加载器
1. 定义
类加载就是将磁盘上的class文件加载到内存中。类加载器记录了加载类的集合,就是哪些类是由它加载的。
2. 类加载过程
1. 加载
在内存中生成一个该类的Class对象(用于创建实例对象)
2. 链接
1. 验证:确保Class文件合法。
2. 准备:为类变量分配内存,设置默认初始值,如0,false…(类变量为static修饰的,这里不包含final static ,因为final static变量在编译时已经分配)
3. 解析:将常量池的符号引用转换为直接引用
3. 初始化
执行类构造器方法 clinit() 的过程,该方法由编译器收集类中所有类变量赋值动作和静态代码块中的语句合并而来
3. 分类
1. 引导类加载器(BootstrapClassLoader):又称启动类加载器,使用c/c++实现,加载Java核心类库的类
2. 自定义类加载器:所有引导类加载器派生出的子类(主要包括:扩展类加载器,系统类加载器,用户自定义类加载器)
3. 用户自定义类加载器
1. 用途
1. 扩展加载源
2. 隔离加载类(如使用不同中间件时,防止加载的类重名冲突)
3. 防止源码篡改(对字节码加密,加载到内存时自定义类加载器解密)
4. 除了引导类加载器,其他加载器都不是必须的,可以使用自定义在需要的时候动态加载。
2. 实现方式
继承ClassLoader或者URLClassLoader()
4. 继承关系
引导类加载器->扩展类加载器->系统类加载器->用户自定义加载器
4. 双亲委派机制
1. 原理
类加载器收到加载请求后会把请求委托给父类加载器去加载,父类加载器不能加载,再由子类加载。
2. 过程
1. 类加载器收到类加载请求。
2. 将类加载请求一直向上委托,直到启动类加载器。
3. 启动类加载器看是否能加载这个类(在rt.jar中找是否有这个类,有就加载然后结束),不能加载就抛出异常,通知子类加载器加载,也就是看扩展类加载器是否能加载这个类(在ext目录下是否有这个类,有就加载结束),不能加载就继续通知子类加载器加载不断重复。
3. 优点
1. 避免类的重复加载
2. 防止核心API被篡改(沙箱安全机制)
4. 运行时数据区
1. 相关
1. jvm内存结构:本地方法栈,虚拟机栈,程序计数器,堆,方法区
2. 一个进程对应一个方法区和堆,一个线程对应一个程序计数器,虚拟机栈,本地方法栈。
3. GC一般在方法区和堆中,OOM不会在程序计数器中
4. OOM故障排除(调优)
1. 尝试扩大堆内存看看结果
2. 用内存快照分析工具分析内存(如Jprofiler,MAT)
通过对程序设置参数(-XX:+HeapDumpOnOutOfMemoryError),当JVM发生OOM时,自动生成DUMP文件。用jprofiler打开,一是可以看哪些对象占了空间,二是可以通过查看各个线程看问题出在哪行
2. 程序计数器
用于存储下一条执行指令的地址,多个线程情况下,当切换回某个线程的时候,就需要通过pc寄存器知道当前线程执行的位置。
3. 方法区
1. 存放已被加载的类信息(构造方法/接口定义)、常量、静态变量。(一个java进程对应一个方法区,1.8以前又叫永久代)
2. 元空间:1.8后方法区被元空间替代,元空间使用本地内存。(不存在垃圾回收,如果加载太多的第三方jar包,可能导致内存溢出)
4. 本地方法栈
1. 作用
管理本地方法调用。
2. 本地方法
带native关键字的方法,主要由c编写,本地方法可以直接调用处理器中的寄存器,分配本地内存等。
5. 虚拟机栈
1. 作用
参与方法调用和返回,保存局部变量和部分结果。扩展:每个线程创建时会创建一个虚拟机栈,虚拟机栈中保存的是多个栈帧(内存区块),一个栈帧对应一个方法,虚拟机栈大小可以固定或者动态变化。(通过-Xss 设置)
2. 栈帧组成
1. 局部变量表
它是一个数子类型的数组(定义的所有类型都能转换为数字),其存储单元为变量槽(Slot),32位以内类型变量占一个槽)若变量作用域失效,变量槽便可以回收重用。局部变量表中直接或间接引用的对象不会被回收,随方法调用结束销毁。局部变量表是性能调优的重要部分。
2. 操作数栈
用于保存计算的中间结果(比如下一条字节码为加操作,通过局部变量表是不知道哪两个数相加。但通过操作数栈弹出两个栈顶数就可以。)
3. 动态链接
-
- 作用
每个栈帧都保存了 一个 可以指向当前方法所在类的 运行时常量池, 作用就是: 当前方法中如果需要调用其他方法的时候, 通过运行时常量池,就可以将符号引用转换为直接引用,然后就能直接调用对应方法。
- 作用
-
- 常量池
1. 常量池:存放final常量,字符串和基本数据类型的值,符号引用(方法的名称和描述符,字段的名称和描述符)。它是字节码文件中的一部分 ,本质就是符号地址和真实地址的对照表。
2. 运行时常量池:当类的字节码被加载到内存中后,它的常量池信息就会集中放入到一块内存,这块内存就称为运行时常量池,并且把里面的符号地址变为真实地址(如#2会被转化为内存中的地址)。位置:1.6之前在方法区,1.7在堆,1.8后在元空间
- 常量池
-
- 两类方法调用
1. 静态链接:目标方法在编译期可知(对应方法早期绑定非虚方法)
2. 动态链接:目标方法在运行时才可知(对应方法晚期绑定和虚方法)比如多态中,animal类可能时狗猫,所以编译时不知道调用狗的eat()还是猫的eat();所以为晚期绑定。
3. 不同方法调用指令不同,java7出现动态调用指令invokedynamic。它是为了实现 动态类型语言支持而做的一种改进。(静态类型语言是判断变量自身的类型信息;动态类型语言是判断变量值的类型信息)
4. 虚方法表
调用虚方法时,在运行过程中根据调用者的动态类型来决定具体的目标方法(多态),效率就很低,所以引入虚方法表。每个类中都有一个虚方法表,存储各方法实际入口
- 两类方法调用
4. 方法返回地址
用于存放pc寄存器的值 ,在该方法结束后返回到被调用的位置。如果正常退出就到调用位置的下一条语句,异常退出根据异常表确定(比如try catch捕获了就会接着执行,未捕获就会向上抛 )
5. 一些附加信息(了解)
栈帧中还允许携带与Java虚拟机实现相关的一些附加信息。例如对程序调试提供支持的信息。
6. 堆
1. 介绍
1. 一个java进程对应一个堆内存,堆内存大小在创建时确定(可设置)堆可以在物理上不连续,但必须在逻辑上连续。2. 在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。
2. 结构
新生代:伊甸园区和幸存区(from 和 to)+老年代(伊甸园和from,to比例8:1:1,新生代老年代比例1:2)
3. 堆设置参数
1. -Xms:设置堆初始内存大小(默认电脑内存大小1/64)
2. -Xmx:设置堆的最大内存大小(默认电脑内存大小1/4)
3. -Xmn:设置新生代大小(可设置初始值和最大值)
4. -XX:NewRatio=2:设置新生代和老年代占比为1:2(默认就是2)
5. -XX:ServivorRatio=8:设置伊甸园区和幸存区比例为8:1:1(默认就是8)
6. -XXHandlePromotionFailure:设置空间分配担保(1.7之前有效,1.7之后固定为true)
4. TLAB
1. 定义
堆区中的新生代区划分出一部分空间,为每个线程分配一个私有缓存区。这个缓存区被称为TLAB(Thread Local Allocation Buffer)。
2. 优点
因为共享区的数据为了安全会使用加锁等机制,从而影响效率,所以产生TLAB,当线程数据在私有缓存区放不下才放到共享数据区。
5. 逃逸分析
1. 定义
逃逸主要是指new的对象是否在方法外使用,因为栈中是栈帧,每个栈帧对应一个方法,所以如果在方法外使用的话,一旦栈帧弹出栈就会出现问题(java7之后,默认开启了逃逸分析,java7之前需要设置参数开启。)注意:只有在server模式下,才可以开启逃逸分析。
2. 开启后编译器可对代码优化
-
- 栈上分配对象
除了堆中分配对象外,在栈上也可以分配对象,不过有条件,经过逃逸分析后发现一个对象如果没有发生逃逸,就可以在栈上分配,如果发生逃逸就不能栈上分配。(栈帧弹出栈,变量就自动被回收,就不用GC。)
- 栈上分配对象
-
- 同步省略(锁消除)
JIT编译器可以借助逃逸分析来判断一个对象是否只被一个线程访问,如果是,JIT在编译这个同步块的时候,会取消这部分代码的同步,这样能提高并发性和性能
- 同步省略(锁消除)
-
- 分离对象(标量替换)
在JIT阶段,如果经过逃逸分析,发现一个对象不会被外界访问的话,那么经过JIT优化,就会把这个对象拆解成若干个其中包含的若干个成员变量来代替。这个过程就是标量替换。(无法分解成更小数据的数据,如java的基本数据类型)
- 分离对象(标量替换)
5. 垃圾回收
1. 新对象申请内存过程
1. 先判断伊甸园是否能放下,2. 不能就YGC然后判断伊甸园是否能放,3. 还不能就判断是否Old区能否放下, 4. 还不能就FGC然后判断是否能方向,不能就OOM。(能就分配内存)
2. YGC
1. 新生代范围的gc。伊甸园区空间不足时触发,频率比较高(常用复制算法)
2. YGC前(空间分配担保):步骤:1. 老年代最大连续可用空间是否大于新生代所有对象的总空间,是的话YGC,2. 不是则判断老年代最大连续可用空间是否大于之前晋升的平均大小,是的话YGC,否则FGC。(jdk1.7前:在2步骤前,如果参数HandlePromoionFailure为true才进行2步骤,为false就直接FGC)
3. FGC
FGC:全堆范围的gc,老年代空间不足或者对空间使用达到80%触发(可调整)或者调用System.gc() (常用分代收集算法,新生代和老年代采取不同收集算法)
4. 常用GC算法
1. 复制算法
1. 每次ygc,将伊甸园区活下来的对象和from区寿命未超阈值的对象复制到to区,然后交换from和to。(每次from区对象复制到to对象寿命+1,超阈值15的放old区。如果to区放不下也放old区)
2. 优点:没有碎片空间,缺点浪费空间,因为始终有一个幸存区为空。适用对象存活度较低的场景
2. 标记清除法
1. 扫描两次,第一次标记要清除的对象,第二次清除标记的对象
2. 优点不需要额外空间,缺点产生内存碎片
3. 标记压缩法(标记整理算法)
1. 三次扫描,在标记清除法的基础上,第三次是向一端移动对象,消除内存碎片
2. 优点无内存碎片,缺点三次扫描效率低
4. 标记清除压缩
与标记压缩法不同在于多进行几次标记清除,产生较多的碎片后再进行压缩
5. 垃圾回收器
1. 新生代
1. Serial: 单线程收集器,采用复制算法,优点:对垃圾回收来说简单高效。缺点:会造成STW(就是垃圾回收时用户线程停止)
2. ParNew: 多线程收集器,相对Serial是多线程去执行垃圾回收。(只是多线程进行垃圾回收,也会造成STW)
3. Parallel Scavenge:与ParNew不同的就是它达到一个可控制的吞吐量(CPU运行用户程序时间除以CPU运行总时间)
2. 老年代
1. Serial Old
Serial收集器的老年代版本,不同是采用标记压缩算法 。
2. Parallel Old
Parallel Scavenge 的老年代版本,不同是采用标记压缩算法 。
3. CMS
1. 并发收集器,垃圾回收线程和用户线程同时执行(在并发标记阶段不会造成STW),采用标记清除算法。
2. 优缺:优点:低停顿。缺点:1. 无法清除浮动垃圾(主要在并发清除阶段,因为并行,所以在回收过程,用户程序还会产生垃圾)。2. 因为采用标记清除法,所以会产生内存碎片。3. 对cpu敏感,CMS默认启动的回收线程数是(CPU数量+3)/ 4,如果cpu数量越少,处理用户线程的cpu资源就越少
3. 过程
1. 初始标记:标记 GCRoots 能直接关联到的对象,速度很快(为了防止程序继续产生GC Root对象,所以需要STW)
2. 并发标记:基于初始标记的GC Root对象,进行可达性分析,标记存活对象,它在整个回收过程中耗时最长。(不会造成STW)
3. 重新标记:并发标记期间,因用户程序的运行产生一些新的垃圾,很少所以快(为了防止继续产生垃圾所以需要STW)。
4. 并发清除:并发的清除没有标记的垃圾对象。(不会造成STW)
3. 新一代收集器G1
1. 采用标记压缩算法,不区分新生代和老年代,直接把堆空间划分固定大小的region,在后台维护一个set集合指向这些region,根据筛选决定回收哪些region的垃圾(回收运行的时间、垃圾堆积程度以及region的优先级)。
2. 优缺:优点:不会产生内存碎片,能精确控制垃圾回收时间。缺点:维护指向每个region区域的指针,占用额外内存。
3. 过程
1. 初始标记:标记 GCRoots 能直接关联到的对象,速度很快(为了防止程序继续产生GC Root对象,所以需要STW)
2. 并发标记:基于初始标记的GC Root对象,进行可达性分析,找到存活对象,它在整个回收过程中耗时最长。(不会造成STW)
3. 最终标记:并发标记期间,因用户程序的运行产生一些新的垃圾,很少所以快(为了防止继续产生垃圾所以需要STW)。
4. 筛选回收:根据回收运行的时间、垃圾堆积程度以及region的优先级来回收对应region。(会造成STW)
4. 相关问题
1. 新生代为什么用复制算法:因为复制只需要一次扫描,效率高。新生代存活对象少,所以复制所需的额外空间小。
2. G1和CMS对比:1. G1采用标记压缩不会产生内存碎片。2. G1具有筛选回收,所以对回收时间可预测(也可自己设置)3. G1在筛选回收时候不是并行所以不会产生浮动垃圾,CMS是并发清除会产生浮动垃圾。4. G1内存占用更多(因为后台需要维护指向每个区域的Set集合)
3. 判断对象是否可回收:通过可达性分析算法实现。从当前存活的对象(GC Root对象)向下搜索,形成的路径为引用链。对访问过的对象打上标记,没有标记的对象等待被回收。
6. 面试题
1. 内存溢出举例?
1. JVM栈溢出:局部数组过大,递归调用层数太多,大量循环
2. JVM堆溢出:创建对象太多
2. 为什么CMS会将serial old作为后备垃圾回收器?
因为CMS采用标记清除算法,会产生内存碎片。serial old采用标记压缩算法,能避免产生内存碎片
4. mysql
1. 数据存储引擎
1. InnoDB
支持事务,行级锁,外键,使用聚集索引
2. MyIsam
支持全文索引(FULLTEXT),使用非聚集索引
3. memory
基于内存,速度很快,断电数据丢失
2. 事务
1. 定义
要么全部执行成功,要么全部执行失败
2. mysql操作事务
1. mysql默认开启了事务自动提交,每条语句都是一个事务(autocommit =1)
2. 手动执行事务步骤
1. 1.关闭自动提交:set autocommit = 0;
2. 2.开启事务:start TRANSACTION (下面语句都在一个事务中);
3. 3.执行的命令;
4. 4.提交:commit(持久化(成功))
5. 5.回滚:ROLLBACK(回到原来的样子(失败))
6. 6.最后开启自动提交:set autocommit =1(了解:SAVEPOINT 可以设置事务保存点,可回滚到保存点)
3. 数据一致性
1. CAP理论
1. C:强一致性(系统时刻都保持一致性)
2. A:可用性(每个操作在一定时间内返回结果)
3. P:分区容错性(系统存在网络分区的情况下,仍能接受请求)
4. CAP意义:CAP是分布式系统需要考虑的三个问题,只能同时满足两个
5. BASE理论:是CAP理论中AP的延申,强调可用性。它的核心是基本可用和最终一致性
2. 事务特性:ACID原则
1. 原子性:要么都成功,要么都失败
2. 一致性:事务前后的数据完整性要保持一致(A转B50,最后A少50,B多50)
3. 隔离性:事务产生并发时候,互不干扰
4. 持久性:事务一旦提交就不可逆转,被持久化到数据库中
4. 并发事务问题(解决对应4中隔离级别)
1. 脏写
1. 定义:一个事务写覆盖另一个事务的写。(例如,同时存款时会导致少存)
2. 解决:通过事务隔离级别:读未提交read uncommitted(修改数据会加写锁,一个事务写数据未提交或者回滚前其他事务不能写,解决脏写)
2. 脏读
1. 定义:读到其他事务修改但未提交的数据。
2. 解决:通事务隔离级别:读已提交read committed(修改数据会加写锁,读数据加读锁(只在读的时候加),读写锁不能同时存在,一个事务写数据未提交前其他事务不能读写,解决脏读)
3. 不可重复读
1. 定义:在一个事务内多次读取的结果不同(多次读的间隙其他事务更新了数据)
2. 解决:通过事务隔离级别:可重复读repeatable read(InnoDB默认)(修改数据会加写锁,读数据加读锁。读数据时加的读锁在事务结束前不会释放,可以防止其他事务写数据。解决不可重复读(mysql InnoDB不是通过锁,而是通过MVCC一个事务里只会获取一次read view副本,来保证一个事务多次读取数据一致))
4. 幻读
1. 定义:在一个事务内多次读取的数据行数不同(其他事务插入或删除了数据)
2. 解决:通过事务隔离级别:串行化serializable(事务串行,能够解决所有因为事务并发带来的问题(解决幻读还可以通过加间隙锁来解决。间隙锁通过对查询的范围加锁来避免幻读))
5. 说明
隔离级别越高,事务安全性越高,但事务并发性能越低。开发中,我们要保证数据库的性能,所以建议事务在程序中控制。
3. 锁
1. 介绍
对数据加锁是为了解决事务的隔离性问题,数据库里的锁是基于索引实现的。(命中索引节点对应 行锁,未命中索引节点就是锁住整颗b+树,也就是表锁)
2. 分类
1. 属性分类
1. 共享锁(读锁)
多个事务共享读锁。都可以访问数据,但不能修改。(解决脏读,重复读问题)
2. 排他锁(写锁)
只有一个事务能够获得排他锁,其他事务都不能获取该行的锁。InnoDB会对update\delete\insert语句自动添加排他锁。(解决脏写,脏读问题)
2. 按粒度分类
1. 表锁
粒度大,加锁资源开销小,易冲突
2. 行锁
1. 粒度小,加锁资源开销大,不容易冲突
2. 记录锁:记录锁的范围只是表中的某一条记录
3. 间隙锁:锁住的是表记录的某一个区间(解决幻读,是可重入锁)
4. 临键锁:记录锁和间隙锁的组合,临键锁会把查询出来的记录及对应范围(包括两边相邻的下一个区间)锁住。
3. 按锁的状态分类
1. 意向共享锁:加共享锁之前,需要获得这个表的意向共享锁。
2. 意向排它锁:加排他锁之前,需要获得这个表的意向排他锁。
3. 为什么需要:a事务对表加锁前,需要检查每个节点是否已经加锁,导致性能很低。所以当一个事务对某表加锁后就设置一个意向锁,通过意向锁就能快速判断改表是否加锁。
4. 自增锁
通常是针对MySQL当中的自增字段。如果有事务回滚这种情况,数据会回滚,但是自增序列不会回滚。
5. 全局锁
全局锁:加锁之后整个数据库实例都处于只读状态。所有的数据变更操作都会被挂起。一般用于全库备份的时候。
4. MVCC
1. 介绍
数据库多数是查询操作,由于加读锁实现的事务隔离会降级数据库性能。MVCC 是InnoDB中多版本控制,解决加读锁导致的性能问题(MVCC通过保存数据的历史版本,根据比较数据的版本号来决定数据的是否显示,不需要加读锁的情况就能达到事务的隔离效果)
2. 事务版本号
事务开启前会获得一个自增的事务id(通过事务id判断事务执行的先后顺序)
3. 表的隐藏列
1. DB_TRX_ID:记录操作该表事务的ID
2. DB_ROLL_PTR:指向undo log中该表上一版本的指针
3. DB_ROW_ID:隐藏ID。(mysql会为每张表建一个主键索引,如果没主键,会找表中的唯一键(就是表中无重复数据的字段)。如果没有唯一键,mysql会自动生成一个隐藏主键)
4. undo log
1. 作用:表修改前会把数据先拷贝到undo log中,用于回滚和MVCC多版本控制
2. 当前读:读取数据库最新的数据,需要加锁
3. 快照读:快照读是从undo log中读取的历史版本数据,不用加锁,在普通select语句中采取的是快照读,自己加锁的select语句中是当前读(增删改操作是最新数据,不是undolog中的历史版本)
5. read view
1. 介绍
每个事务开启后会得到一个read_view,主要保存的是当前活跃事务的id号(未提交的,也就是需要与当前事务隔离的事务id)
2. 四个重要属性
1. trx_ids
当前活跃的事务版本号合集
2. low_limit_id
当前系统最大事务版本号+1
3. up_limit_id
当前活跃事务中最小版本号
4. creator_trx_id
创建该read view的事务版本号
6. Innodb实现MVCC原理
1. 流程
1.先获得事务版本号。2.再获得read view。3.接着查询数据,将查询的数据与read view 进行匹配,如果不符合再从undo log中获取历史版本数据进行匹配,最后返回数据(没有匹配数据返回null)
2. read view 匹配条件
1. 数据的事务ID <当前活跃事务中最小版本号时,说明该数据是在当前事务启之前就已经存在了的,所以可以显示(数据的事务ID:数据隐藏列操作该表的事务ID(DB_TRX_ID))
2. 数据的事务ID>=当前系统最大事务版本号+1时,说明该数据是在当前read view 创建之后才产生的,所以数据不显示
3. 当前活跃事务中最小版本号 <=数据的事务ID<当前系统最大事务版本号+1时:则将数据的事务ID与活跃事务集合trx_ids里匹配,若在就不显示,若不在trx_ids中就可以显示。(trx_ids中不包含当前事务id)
7. 不同隔离级别下MVCC工作方式
1. 读未提交级别的事务不会获取read view 副本,所以存在脏读
2. 读已提交级别的事务每次查询都会获得一个readview副本,所以存在不可重复读的问题
3. 可重复读级别的一个事务里只获取一个read view副本
8. MVCC是否能解决幻读
快照读的情况下可以避免幻读问题(一个事务只会获取一次readView副本)。在当前读的情况下存在幻读问题,可以通过间隙锁来解决幻读问题的。
5. 索引
1. 定义
MySQL索引的建立对于MySQL的高效运行是最重要的,索引可以大大提高MySQL的查询速度。
2. 原则
1. 索引不是越多越好(空间,内存)
2. 不要对经常变动的数据加索引
3. 小数据量的表不需要加索引
4. 索引一般加在常用来查询的字段上
3. 分类
1. 逻辑分类
1. 普通索引(KEY/INDEX)
普通索引是最基本的索引,它没有任何限制,值可以为空,仅加速查询。
2. 唯一索引(UNIQUE KEY)
唯一索引与普通索引类似,不同的就是:索引列的值不同(可以有一个空值,空值跟其他值也不同)。
3. 主键索引(PRIMARY KEY)
主键索引是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。
4. 联合索引
组合索引指在多个字段上创建的索引,只有在查询条件中包含了创建索引时的第一个字段,索引才会被使用(遵循最左前缀原则)
5. 全文索引(FULLTEXT)
全文索引主要用来查找文本中的关键字,而不是直接与索引中的值相比较(特定的数据库引擎才有,如MyISAM)
6. 普通索引和唯一索引选择:优先选择普通索引,普通索引可以配合changebuffer,在更新时速度较快(唯一索引不能更新到changebuffer是因为它值必须唯一,所以需要从内存读数据判断是否有冲突,没冲突再插入)
2. 按存储形式分类
1. 聚集索引:数据页和索引页放一起(InnoDB)。注意:除了主键索引外,其他索引的叶子节点没有存实际值,是因为这样每建一个索引都复制一份数据非常占空间。还有就是更新某值的时候所有索引对应的数据都要修改值。所以实际存的为索引的值和对应的主键,当找到某索引时,再通过其主键去主键索引找对应的全部值(这就称为:回表)
2. 非聚集索引:数据页和索引页分开存放,叶子节点存的是数据的地址信息(MyIsam)
3. 按数据结构分类
1. Hash类型
速度快,但是不支持范围查询,所以不常用
2. b+树
1. 结构:一张表对应一个B+树,b+树一般三层,可存放千万行的数据。具体为:叶子节点为数据页,其他节点为索引页。数据页中有页目录和对应数据,数据按索引顺序存放,每个二层索引页中存放的是若干页地址,一个页地址对应一个数据页中页目录的起始地址。一层索引页中存放的若干页地址,一个页地址对应二层索引页中一个索引页中的第一个页地址。
2. 说明:innodb索引默认B+树(b+树是Btree的一种)。b+树默认每页大小为16kb(不然一行一行从磁盘读写太费时间,所以读写都是叶为单位)
3. 数据存储计算:假如索引为int类型占4字节,指针占6字节,两层可以指向的数据页就是(161024/10 )^2=2683044页。每页16kB,如果一条记录占1kb,那就能存268304416=42,928,704(四千多万条数据)
4. 索引失效
1. 查不到造成
最左前缀原则:联合索引必须遵循最左前缀原则,是因为比如联合索引字段为a,b,c。它是在a排好序的基础上再排b,所以如果查询条件没有a的话,是不能通过b来走索引的
2. 索引查询更慢造成
范围查询可能走索引也可能不走索引。(因为如果查询范围数据量大,而且索引中不包含需要查询的字段。会导致大量的回表操作,速度还不如全表扫描。)(但是如果查询的字段在索引中,这个时候就可以走索引了,因为不用回表操作了。这个称为覆盖索引)
5. 索引下推
MySQL 5.6 提出,目的是减少回表次数(是联合索引的情况下),具体做法是:可以在非聚簇索引遍历过程中,对索引中包含的字段先做判断,过滤掉不符合条件的记录,再进行回表,从而减少回表次数。(如联合索引为name 和age两个字段, 5.6之前根据最左匹配原则,是在联合索引中找到符合name的主键,然后回表,server层再对符合age的进行过滤。5.6过后是回表前就先对age进行过滤)
6. 数据库优化
1. sql优化
1. sql慢查询的定位
在mysql中开启对应的“慢查询日志”功能,查询慢的sql语句会被日志记录下来(默认10秒认为慢sql,可以设置)
2. sql语句性能分析
在SQL语句前加上一个explain关键字,就可以得到SQL的执行计划,里面记录了各种信息(如是否使用索引,扫描了多少记录等)
3. 索引优化细则
1. 尽可能使用覆盖索引:就是我们查询的列数据通过索引就可以获得数据,不用回表(只有主键索引是聚集索引,其他索引没有全部数据,只有对应索引数据和主键,所以如果索引数据不包含需要查询列的数据,需要根据主键再通过主键索引查询。)
2. 最左匹配原则:模糊查询(如like)时,只有从左边匹配时才能用到索引(最左前缀原则)。(如何判断索引命中了多少个:通过sql性能分析explain中 key_len 的长度,和对应索引字段长度比较)
3. 避免隐式转换:例子:1.索引类型为varcchar,查询时匹配条件给的数字。2.查询时把函数套在字段上,没有套在匹配条件上(这样会把数据库对应字段全部转换再和匹配条件对比)
4. 只获取必要的列,尽量不使用select :即影响查询效率,也占不必要内存
5. 减少数据加锁时间和范围:1. 锁一条记录使用行锁不使用表锁。2. 查询语句放事务外,可以减少事务的时间从而减少事务中修改语句加锁时间。
6. 尽量保证join、oder by、group by的字段使用索引:1.join 的字段使用了索引,就会使用高效的Index Nested-Loop Join 算法。2.sort、group by 字段使用了索引就可以利用b+树已经排好序的特性
7. join用小结果集驱动大结果集。join是先查出两张表,通过一个双层循环来遍历外层表 与内层表匹配的过程,如果关联的表有索引,假如小表500条,大表1000条。左表驱动匹配次数就是500索引高度,反之就是1000*索引高度
2. 表设计优化
1. 索引设计
1. 适合建索引:对经常要查询的列、需要经常排序的列和要求值唯一的列建立索引(索引b+树排好序)
2. 不适合建索引:大字段的列,离散性不高的字段(如只有男女),频繁修改的字段(索引放内存,大字段占内存。区分度不高建索引无意义。频繁修改的字段也会频繁修改索引,影响效率)
3. 多使用联合索引:因为组合索引匹配出来的数据更少
2. 表设计
1. 字段冗余:同个字段出现在多张表,减少联表的操作。(如join 查询,匹配的数据量是呈笛卡尔积的上升的)
2. 表冗余:可以多建表来保存一些统计数据,提高效率。
3. 数据冷热分离:对于数据量很大的表,根据表中数据的查询量把数据拆分成多张表。
3. 数据库架构优化
1. 硬件方面
硬件升级
2. 资源池化
1. 解释:应用和数据库频繁建立连接非常消耗性能,可以通过连接池技术来优化
2. 连接池创建思路:1.创建n个初始连接。2.请求连接时,连接池有空闲连接则返回一个。3.如果没有空闲连接且未达连接池最大值,就新建一个返回。4.如果没有空闲且达最大连接。再看任务队列,如果未满则提交一个任务放到队列里面,在有限的超时时间内等待执行。5.如果任务队列慢,则拒绝请求
3. 分流(读写分离)
1. 解释:读写通过不同数据库完成(mysql通过binlog来同步事务型SQL来实现)
2. 问题:主从复制导致查询的延迟问题。解决:1.写入数据库的同时也写入一份数据到缓存,应用优先从缓存查询数据 。2. 实时性强的业务直接从主库读。
4. 分片(分库分表)
1. 解释:数据量很大导致索引都很大时,通过分片来解决
1. 垂直拆分:不同业务的数据放不同服务器
2. 水平拆分:根据拆分规则来把一张表数据分到多个服务器(如: 对id进行hash,再对其取余来判断放那个服务器)
2. 带来的问题:
1. 如何定位数据在哪个表:解决:垂直拆分根据不同业务查找,水平拆分通过定义拆分规则主键找,其他字段要建一个其他字段和ID的映射表。
2. Join查询问题:解决:1.数据冗余,需要链表查询的数据冗余到其他表。2. 代码筛选,先把多个表数据查出来再筛选需要的
3. 数据分页问题。解决:1.通过中间件。2.冗余一份全量数据到Elasticsearch中,通过Elasticsearch 查询分页
4. 无法使用数据库的自增长id。解决:1.使用redis自增,但依赖Redis。2.使用uuid,不依赖任何组件,但它有32 个 16 进制数字组成,比较占空间,不利于索引。
5. 无法直接统计数据量count。解决:通过新建一张表统计或者放到缓存中
6. 分布式事务问题。解决:Spring的JTA、阿里开源的seata
5. 使用nosql
1. redis:缓存热点数据(存在问题见redis篇)
2. MongoDB:大量文档型数据的查询
3. Elasticsearch:全文数据的搜索
7. 核心日志(WAL)
1. binlog
binlog 是server层的日志,用于恢复和同步数据,但不支持崩溃恢复。(三种记录形式:记录改变的行,记录sql语句,或者混合模式)写入策略为追加。
2. redolog
redo log是引擎层(innodb)的日志,物理日志,记录的是在数据页上做了什么修改,所以支持崩溃恢复。写入策略是循环写
3. undolog
undolog属于引擎层(innodb)的日志,记录数据的历史版本,用于事务失败回滚和MVCC版本控制
4. 日志生成顺序与数据恢复
1. 一条语句执行流程
1. 查询语句:1.通过连接器处理客户端连接和授权认证等 2.mysql8.0前先会去缓存区找。没有就先进行词法分析,语法分析,优化sql语句。3. 执行器调用引擎接口,返回执行结果(从磁盘读取)。
2. 更新语句:相对查询语句,1. 在读到数据后记录undolog日志.2. 修改内存数据,然后记录redolog(prepare状态),然后记录binlog,3. 最后提交数据,同时记录redolog(commit状态)
2. redolog为什么需要两阶段提交
1. redo log日志在事务提交后会持久化到磁盘,如果先写redolog再写binlog,如果一条语句在redolog后崩溃,此时本机有数据,但由于未写binlog,所以从节点会少一条数据,会造成主从不一致。
2. 如果先写binlog再写redolog,如果一条语句写binlog之后崩溃了,事务未提交本机无数据,且因为本机未写入redolog,所以本机数据不能回复。但从服务器可能根据同步的binlog日志已经进行数据修改,从而造成主从不一致。
3. 所以引出两阶段提交:在写binlog前先写一次redolog(prepare状态,此时事务未提交),这样如果是在binlog之后崩溃,从服务器有数据,本机也可以根据redolog重做数据,保证主从数据一致性。
3. 数据库崩溃恢复:
1. redolog和binlog都无记录,未修改不操作
2. redolog为prepare状态,binlog无记录,通过undolog回滚
3. redolog为prepare状态,binlog有记录。根据redo log对数据进行重做(相当于提交事务)。(这里不回滚是因为binlog日志可能已经同步到从服务器了,所以回滚可能导致主从数据不一致)
4. redolog为commit状态,binlog有记录。正常完成的事务,不需要恢复。
8. mysql三大范式
1. 第一范式:保证每一列不可再分
2. 第二范式:在一范式基础上,每张表只描述一件事情
3. 第三范式:在一二范式基础上,需要确保数据表中的每一列数据都和主键直接相关,而不是间接相关
4. 规范性和性能的问题:因为遵循范式会让表的数量增加,从而影响性能,所以一般需要综合考虑。(关联查询的表,不得超过三张表)
2. 数据查询语言DQL(对表的查询操作(select)- 位置已排序)
1. 去重查询distinct
例子:select distinct sal from emp;
2. 连接查询join(笛卡尔积)
1. 内连接
select * from a inner join b on a.s=b.s;//只显示符合条件的内容( inner join 可以省略用逗号代替,然后on换成where)
2. 外连接
1. 左连接查询(left outer join … on ):查询结果参照左表,左边全显示,右表符合条件显示,不符合条件用null填充。例子:select * from sushe s left outer join sushe2 s2 on s.name=s2.name;
2. 右连接查询(right outer join … on)
3. 原理
1. Simple Nested-Loop Join(简单的嵌套循环连接):双层for 循环,内外层数据一一比较。(nm次)
2. Index Nested-Loop Join(索引嵌套循环连接):根据内层表的索引去找,减少了循环匹配次数(n索引高度 次)
3. Block Nested-Loop Join(缓存块嵌套循环连接):一次性缓存外层表的多条数据到join buffer中,再进行比较,减少IO 次数
4. mysql中join先使用索引嵌套循环连接,不行再使用缓存块嵌套循环连接
3. 条件查询where
一些关键字:in();between…and…;is null 等等(注意:字符和数字的类型转换:如果=两边类型不同,会把字符转为数字。非数字字符都会转为0,数字字符转换为对应数字。)
4. 模糊查询like
‘_’ 表示任意一个字符,'%'表示任意多个字符。例子:SELECT * FROM emp WHERE sname LIKE ‘s_0%’;
5. 分组查询group by
having对查询结果进行过滤。例子:SELECT deptno,COUNT(*) num,SUM(sal) SUM FROM emp GROUP BY deptno HAVING SUM(sal)>9000 ;//查询每个部门的编号,人数以及每个部门的工资和(且工资和必须大于9000)。
6. 条件过滤Having
过滤分组后的信息,条件和where类似,只是位置不同
7. 排序查询order by:
1. 介绍
降序desc(默认s升序asc)例子:SELECT * FROM emp ORDER BY sal DESC,empno;-- sal按照降序排序,sal相同empno按照降序排序
2. 先把查询的的结果方sortbuffer中,然后根据字段排序后将数据返回给客户端。(如果sortbuffer空间不足可以分割数据到多个临时文件,采用归并排序合并再返回客户端,磁盘操作较内存慢很多,所以也可以先获取必要字段(排序字段和id)到sortbuffer中,排序后再根据id查出其他字段,最后返回客户端,具体步骤如下)
3. 原理
1. 1. 把查询出的数据先放到sort buffer中(只放必要字段,如排序字段和id)。
2. 2.根据排序好的字段根据id获取需要的其他数据。
3. 3.返回给客户端(注意:sort buffer在内存中,如果数据超过sortbuffer会把数据拆分到多个临时文件中排序再合并返回给客户端,排序算法为归并排序)
8. 分页查询limit
limit 0,5;//0表示起始行,5表示共读取多少行
9. 查询结果拼接
union
10. 常用函数
1. IFNULL(a,b):若a为null 返回b,否则返回a
2. 聚合函数:count();avg(列名);sum(列名);max(列名);min(列名)//分别为计算指定列的不为null行数;平均值;和;最大值;最小值;例子:SELECT AVG(column_name) FROM table_name
3. MD5加密,AES加密(因为MD5加密不可逆,所以只适合不逆转的加密,比如密码。像资料需要回显的可以使用AES加密)
4. 查询姓张 且名字不同 人的个数:COUNT(DISTINCT a.t_name)
5. 要考虑连接时为null的情况,选择左连接或右连接。
6. 能同时得到就用连接,不能同时得到就用嵌套(当多个表连接时,效率不如嵌套)
7. 使用in时判断是否在集合里时,先把集合里重复的去掉提高效率。
8. 条件判断语句:select case when 条件 then … else… end
9. 逻辑(行转列):先分组后,语句MAX(case WHEN a.c_id=‘01’ THEN a.s_score ELSE NULL END)语文 含义:分组后同学的成绩一条条判断,这条成绩可能为语文数学或英语,如第一条判断,如果不为语文就为null,所以最后每个同学每科会有多个成绩(一个真正成绩,还有多个null),所以前面取max。(还可以通过把每列查出来,然后通过连接拼起来)
10. 表中的中文列不能使用 a.‘语文’,而应该使用a. 语文
(tab上面按键)
11. avg(a):把a一个个加进去,求和然后除以加进去的个数。所以可以求及格率avg(case when a>60 then 1 else 0 end),解释:大于60为1,把大于60个数相加,再除以加进去的个数,就得到了及格率
12. 取别名最好加一个’',否则与关键字冲突会报错
13. in还可以这么用in(2,3):表示是否在2,3中
14. 查询年year(date)curdate()curtime()
15. datediff([参数],日期1,日期2)返回日期1-日期2的天数(参数省略的情况下,参数可以为month:返回间隔的月,注:低版本参数不能使用)
16. 窗口函数(获得排序值)
1. row_number:相同值排序也不重复90 ,89,89,88 对应1,2,3,4;
2. rank():跳跃式排序(值相同排序相同)90 ,89,89,88 对应1,2,2,4
3. dese_rank():连续式排序(值相同排序相同)90 ,89,89,88 对应1,2,2,3
4. 例子:例子:rank() over([partition by 列名] order by 列名)(partition by 是根据什么分组,组内再进行order by,partition by可以省略就是所有数据为一组)
5. redis
1. 介绍
1. redis属于nosql,基于内存(速度快),采用单线程(避免多线程上下文切换的开销),Redis是基于单Reactor模型来设计的事件处理模型,使用IO多路复用技术来监听来自客户端的大量连接。
2. 注意:redis实例中执行命令模块是单线程,但redis实例中还包含其他模块,模块是多线程的
2. 数据类型
1. 五种基本数据类型
1. String字符串(可用于计数器,缓存等)
2. Hash(本质跟String类似,key-value形式,hash多用于对象存储,String多用于字符串存储(因为String存储序列化后的值,对象这些获取还需要反序列化,效率没有hash高))
3. List列表(底层是链表。可以当作栈,队列,阻塞队列来使用)
4. Set集合(值不重复,可以用于且交集,如共同好友等)
5. ZSet有序集合(相对Set多一个值,ZSet会根据该值排序,可用于排行榜之类的)
6. ZSet底层
1. ZSet底层是 压缩列表+跳表。一开始采用压缩列表,当元素个数超过128或者大小大于64字节(可配置),使用跳表。
2. 压缩列表:跟数组类似,只是压缩列表每个元素空间大小不是固定的,通过在每个元素前记录它的长度来实现查找,从而节省内存空间。(由于数组为每个元素分配空间固定,redis可以存储不同数据类型就会导致一些元素的空间并没有占满,浪费内存。所以引出了压缩列表)
3. 跳表
1. 定义:对有序链表,添加层数索引,使其具有二分查找的功能,各项操作时间复杂度O(logn)
2. 时间复杂度推导:对于具有n 个元素的链表,共logn +1层(进位取整)。因为上一层相对下一层,隔一个取一个元素,大约为下一层的一半,所以总的来说类似二叉树的高度。
3. 1. 删除元素:删除每层该元素。2. 插入元素:对于插入元素建几层索引层数,采取抛硬币的形式。(经过多少次才抛到反面就建多少层索引)
2. 三种特殊数类型
1. Geospatial(可以添加地理位置(经纬度),可以计算两地间的距离,或者查找某地周围元素,它底层是ZSet,可以使用ZSet相关命令操作Geospatial)
2. Hyperloglog(可以求并集,可以用于统计网站访问量(同一个用户算一次访问),占用固定12kb内存,就可以统计2^64用户量,有0.81%错误率。如果不允许出错,可以使用set或自定义数据类型)
3. Bitmap(位存储,只有0,1两个状态,用于统计两个状态的信息,如活跃,不活跃。如使用bitmap记录员工一年打卡记录,可以很快算出打卡天数)
3. 事务
1. 单条命令保持原子性,redis事务是一组命令的集合,不保证原子性(其中异常命令如1除0抛出异常,其他命令正常执行)。命令本身有错事务执行失败。
2. Redis事务中的监控Watch(乐观锁)
1. 悲观锁:每次修改数据先上锁
2. 乐观锁:先修改数据(还未提交),更新数据时判断是否有人改过该数据,改过就不执行。Redis中watch就是乐观锁。(先监控一个数据,在开启事务,加入命令,执行时判断监控数据是否被修改,修改就会导致执行事务失败)
4. 持久化
1. 含义
redis是内存数据库,如果不将内存中的数据保存到磁盘,一旦出现如断电情况,数据就会丢失
2. aof
1. 定义:将所有执行过的写命令记录下来,恢复的时候再全部执行一遍(aof默认不开启)
2. 触发机制
1. 配置文件中可以设置
1. appendfsync always # 每次修改都会 sync。数据完整,但消耗性能
2. appendfsync everysec # 每秒执行一次 sync,可能会丢失这1s的数据!
3. appendfsync no # 不执行 sync,这个时候操作系统自己同步数据,速度最快!
2. 执行flushall命令
3. 退出redis时
3. 优缺点
1. 优点:如果每次命令都同步,数据完整性会很好
2. 缺点:效率比rdb慢
3. rdb
1. 定义:在指定间隔内将内存中的数据集快照写入磁盘,恢复时直接读到内存中()
2. 触发机制
1. 配置文件中的Sava规则满足时(如save 900 1表示900秒内,有至少1个内容修改就进行持久化操作。)
2. 执行flushall命令
3. 退出redis时
3. 优缺点
1. 优点:效率较高,适合大规模数据恢复
2. 缺点:因为是一定间隔时间进行复制,所以数据可能不完整
5. 发布订阅
含义:是一种消息通信模式,通过一个字典实现,字典键就是一个个频道,字典值就是一个链表(保存所有订阅了该频道的客户端)。比如在某个频道发布一个消息,频道所对应链表的所以客户端都能收到消息。
6. 主从复制
1. 含义
一个主节点,多个从节点,数据复制只能从主节点到从节点,主节点master写为主,从节点slave读为主。可用于数据的备份和恢复,负载均衡(读写分离)。
2. 相关说明
1. 复制原理:每次从机重新连接主机都会进行一次全量复制,正常情况都是进行增量复制(只复制主机中新增加的数据)
2. 如果主机宕机了,可以手动使用slaveof no one让自己变成主机(手动太麻烦,后面引入哨兵模式)
7. 哨兵模式
1. 原理
主机宕机后,自动从从机中选择主机(哨兵是独立的进程,用于监控每个reids服务器是否正常运行。如果发现主机宕机后,就会选择一个新的主机,然后通过发布订阅模式通知其他的从机让他们切换主机)
2. 多哨兵模式
哨兵也可能宕机,所以一般也有多个哨兵,当一个哨兵认为主机服务器不可用时,成为主观下线。当发现主机不可用的哨兵到达一定值时,就会进行failover故障转移操作,通过投票选择一个新的主机。
3. 优缺点
1. 优点:主从复制的优点他都有,自动切换主机更加健壮。
2. 缺点:在线扩容。多哨兵配置麻烦。
8. 缓存穿透
1. 定义
在缓存中查不到,数据库也查不到,大量请求都在持久层数据库时,就造成了缓存穿透。
2. 解决方案
1. 布隆过滤器:一种数据结构,对所有可能查询的参数以hash形象存储,查询前先过滤不符合的查询请求。
2. 缓存空对象:持久层数据库查不到时,将返回的空对象也缓存起来,并设置一个过期时间,后面查询这个值时,就会在缓存中获取了,减少持久层数据库的压力(数据一致性有影响)
9. 缓存击穿
1. 定义
在某个热点key过期的瞬间,大量该值的请求在缓存中查不到,都会涌入数据库。
2. 解决方案
1. 热点词不过期
2. 加互斥锁:使用分布式锁,对每个key同时只有一个线程去查询,其他该key的查询等待。
10. 缓存雪崩
1. 定义
在某个时间段,缓存集中过期,或者reids宕机。
2. 解决方案
1. redis集群
2. 加互斥锁:使用分布式锁,对每个key同时只有一个线程去查询,其他该key的查询等待
3. 数据预热:正式部署前,将可能大量访问的数据先访问一边,让它加载到缓存中去。
11. 数据一致性(持久层和缓存数据同步)
1. 定义
如正常情况:A先修改完数据库–>A修改缓存–>B修改数据–>B修改缓存。(可能发生:A先修改完数据库–>B修改数据–>B修改缓存–>A修改缓存)
2. 解决
1. 加锁(影响效率)
2. 同步删除:修改数据库后删除缓存(问题:1. 并发场景(一写一读就可能会有问题)。2. 缓存删除失败的时候)
3. 延迟双删:删除缓存,修改数据库,延迟一会再删除缓存(问题:1.延迟需要时间,高并发场景性能较低。2. 在读写分离情况下,由于数据同步需要时间,从库可能读到脏数据)
4. 利用MQ异步重试机制:保证缓存删除成功,实现最终一致性即可。(问题:耦合性比较高,每个修改数据地方都需要发消息到MQ。解决:通过主从复制机制,mysql从机监听binlog日志,发生修改时再发消息到MQ)
12. MQ
1. MQ中间件:消息队列(先进先出数据结构)。
2. 作用:可以异步处理(先把需要处理的消息放到MQ中,然后读出来处理)
3. 场景:
1. 1. 秒杀大量请求时,可以放到MQ中,一点点的处理,处理完再通知。
2. 2. 提高响应速度场景。比如群发邮件。
6. 操作系统
1. 概述
简介:操作系统是管理硬件和软件的一种应用程序,作为软硬件的中间层,让用户无需关注硬件的实现,主要功能是对进程、内存、设备、文件进行管理,以及为用户提供访问硬件的接口。
2. 程序运行
1. 程序运行过程
1. 预编译: 主要处理源代码文件中的以“#”开头的预编译指令。(如:处理“#include”预编译指令,就是将文件内容替换到它的位置)
2. 编译:对预编译后的代码进行词法分析、语法分析、语义分析,然后经过优化,生成相应的汇编代码文件,再将汇编代码翻译为机器指令。
3. 链接:将不同源文件产生的目标文件进行链接,从而形成一个可以执行的程序。
1. 静态链接:直接将需要的执行代码拷贝到调用处(优:不需要依赖库,缺:体积大)
2. 动态链接:不直接拷贝可执行代码,操作系统将需要的动态库加载到内存中。然后程序在运行到指定的代码时,去共享已加载动态库中的可执行代码。
2. 外中断和内中断
1. 外中断: CPU 执行指令以外的事件引起,如:I/O 完成中断
2. 内中断: CPU 执行指令的内部事件引起,如:地址越界
3. CPU内核态和用户态
1. 内核态:CPU 可以访问任意的数据,包括外接设备,且CPU 不会被抢占(处于特权级0)。
2. 用户态:CPU 只能访问指定的内存,不可以访问外接设备。CPU可能被抢占。
3. 状态切换步骤:1. 用户态程序将必要数据值放在寄存器中。2. 用户态程序执行陷阱指令,CPU切换到内核态,并执行请求的服务。3. 系统调用完成后, 操作系统会重置CPU为用户态并返回系统调用的结果。
3. 进程和线程
1. 进程组成
1. 程序段:就是程序的代码
2. 数据段:就是程序运行时产生的数据(比如全局变量、局部变量等)。
3. PCB:进程相关息(如进程标识符PID,进程当前状态,进程优先级)。
2. 进程三个状态及转换
1. 阻塞
阻塞—>就绪:处于阻塞状态的进程,其等待的事件已经发生(如等待的输入/输出完成,资源得到满足时),进程便从阻塞状态转变为就绪状态。
2. 就绪
就绪 —> 执行:进程调度程序按某种策略,选中一个就绪进程分配处理机后,该进程便由就绪状态变为运行状态;
3. 运行
1. 执行 —> 就绪:因时间片用完或在采用抢先式优先级调度算法的系统中,有更高优先级的进程要运行时,该进程便由执行状态转变为就绪状态。
2. 执行 —> 阻塞:正在执行的进程因发生某等待事件而无法执行(进程申请资源得不到满足时),则进程由执行状态变为阻塞状态。
3. 进程间通信IPC
1. 无名管道PIPE:存在内核中,只能用于父子进程或者兄弟进程之间的进程的通信,它是半双工的(同一时间只能读或写)
2. 命名管道FIFO:存在磁盘(占磁盘大小为0,对应内核上的一个缓存),可以在无关的进程之间交换数据。也是半双工
3. 消息队列:存放在内核中的链接表,全双工,消息队列独立于发送与接收进程,读取消息不一定要以先进先出的次序,也可以按消息的类型或优先级读取。
4. 共享内存:多个进程共享一个给定的存储区(最快的进程间通信)
5. 信号量:信号量是一个计数器。用于实现进程间的互斥与同步。
4. 进程和线程区别
1. 1. 进程是资源分配的基本单位,而线程则是任务调度和执行的基本单位(算轻量级进程)。2. 同个进程下的线程共享进程的地址空间与资源。3. 线程间切换开销小,进程间切换开销大
2. 为什么需要线程:进程在同一时间只能干一件事情,如果进程阻塞,整个进程就会被挂起,即使进程中有些工作不依赖与等待的资源,仍然不会执行。所以引入线程提高并发性能。
3. 举例:打开qq,浏览器就是打开了两个进程,在qq里中一边发消息,一边逛空间就是在进程中开了多个线程处理任务
5. 协程
1. 定义:协程可以理解为用户态线程,一个线程可以创建多个协程,操作系统调度线程,线程调度协程(切换协程时也需要保护执行现场,还有用户程序不能操作内核空间,所以给协程分配的是用户栈)
2. 为什么需要协程:多线程情况下,由于线程间切换时需要切换对应的内核栈,导致切换代价高。所以引出协程。
6. 并发和并行
1. 并行是指多个事件在同一时刻发生。
2. 并发是指多个事件在同一时间间隔发生。
7. 特殊的几种进程
1. 守护进程:在后台运行的特殊进程。它周期性地执行某种任务。(如web服务器进程http等)
2. 孤儿进程:一个父进程退出,而它子进程还在运行,那么这些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
3. 僵尸进程
1. 含义:如果子进程先退出,父进程还没退出,那么子进程必须等到父进程捕获到了它的退出状态才真正结束,这个时候子进程就称为僵尸进程。(可以通过signal通知内核回收)
2. 如何避免僵尸进程:
1. 1. 在 fork() 子进程之后我们都要及时在父进程中使用 waitpid 系统调用,等子进程结束后,父进程回收子进程 PCB 的资源(waitpid()会导致父进程阻塞)。
2. 2. 当子进程退出的时候,内核都会给父进程一个SIGCHLD 信号,所以可以建立一个捕获 SIGCHLD 信号的信号处理函数,在函数体中调用 waitpid ,就可以清理退出的子进程以达到防止僵尸进程的目的。
4. 死锁
1. 定义
多个进程因争夺资源而造成的一种僵局(例子:一个线程持有资源a,它要获取资源b。另一个线程持有资源b,他要获取资源a)
2. 产生死锁的4个必要条件
1. 互斥条件:资源在某一段时间内,仅为某一进程所占用。
2. 请求和保持条件:当进程因请求资源导致阻塞时,对持有的资源保持不放。
3. 不剥夺条件:进程已获得的资源不能剥夺,只在使用完时才释放。
4. 环路等待条件:必然存在进程和资源的环形链,如A进程正在等待B进程占用的资源,而B又在等待A占用的资源.
3. 解决死锁方法
1. 破除四个必要条件,比如撤销进程或剥夺资源至某个进程解除死锁。
2. 预防死锁(破除4个必要条件之一)
3. 避免死锁.(防止系统进入不安全状态.)
4. 检测死锁. (简化资源分配图使进程成为孤立点即无死锁)
5. 解除死锁.(撤销进程或剥夺资源至某个进程解除死锁)
4. 同步、异步、阻塞和非阻塞
A调用B的同一个过程,对调用者A来说就是阻塞和非阻塞,对被调用者B来说就是同步和异步。如异步:A调用B,不用等待B,继续执行。对A来说就是非阻塞,对B就是异步。A调用B,需要等待B,才继续执行。对A来说就是阻塞,对B就是同步。
5. 线程同步
1. 互斥锁:只能一个线程拥有互斥锁,其他线程只有等待
2. 条件变量:条件变量被用来阻塞一个线程,当条件不满足时,线程会解开互斥锁,进入睡眠状态,条件满足时再唤醒。(互斥锁效率低,所以引入条件变量,与互斥锁一起使用)
3. 读写锁:可以多个读,只允许一个写,写优先于读。
4. 信号量(pv):信号量可以同步多个资源以达到线程同步。(比如营业厅5个窗口,10个顾客进入,当窗口被占满就只能等待,当窗口小于5就可以服务新的顾客,这里窗口就相当于信号量)p操作信号量减,v操作信号量加。
5. 内存管理
1. 名词解释
1. 物理地址:物理设备上真正的地址。
2. 逻辑地址:是指计算机用户看到的地址,操作系统可以将不连续的物理地址隐射成连续的逻辑地址。
3. 虚拟内存:把磁盘空间作为虚拟内存使用。不用把程序的全部加载到内存,可以把暂时不需要的放到虚拟内存中,在需要时进行数据交换
4. 虚拟地址空间:虚拟地址空间是映射到物理内存和虚拟内存上
2. 内存管理方式
1. 分页存储管理
1. 把进程分为大小固定的页,内存分为同样大小的块。优点:内存空间利用率高。(最后一页装不满也会有内存碎片)
2. 页表:存储页号和内存块号的映射(页号从0开始,所以省略)。
3. 地址转换:逻辑地址除以每页大小可以得到页号和页内偏移,根据页表可以找到页号对应的块号,块号加页内偏移就为物理内存地址。例子:如一页为1k,1023逻辑地址转物理地址步骤:1.1023除以1k 的0余1023,再通过页表查0对应的内存块号,最后内存块号乘以1024+1023就为物理地址。
4. 多级页表作用:1. 可用于减少页表占用的连续空间。2. 可以节约内存(相同内存多级可以隐射更多的内存地址,可以不用把页表全部加载到内存)
2. 分段存储管理
1. 把进程按模块分为大小不固定段,逻辑地址为二维(段号和段内偏移组成) 。优点:方便按照逻辑模块实现信息的共享和保护。缺点:因为段内地址必须连续,所以容易产生内存碎片
2. 段表:由存储段号,起始地址,和段长组成。
3. 地址转换:根据段表找到段号对应的起始地址,起始地址加上段内偏移就为对应物理内存地址(段内偏移超过段长就会越界中断)。例子:如将逻辑地址(1,103)转物理地址步骤:先根据1找到段表中的段号,然后判断103是否小于段长,小于物理地址就为起始地址+103。大于就越界中断
3. 段页式存储管理
将进程按逻辑模块分段,再将各段分页。
4. 请求分页
请求分页是在分页的基础上实现。区别在于是否将作业的全部地址空间同时装入主存。请求分页存储管理只需把当前需要的页面装入内存。所以请求分页存储管理可以提供虚存。(请求分段同理)
3. 地址映射及变换
1. 全相联映射:主存任一一块都可以放到cache的任一一行中.
2. 直接映射:将主存按cache大小分成若干区,区内的块与cache行一一对应
3. 组相联映射:将cache分组,主存分区,每个区包含的块数等于cache组数。组内全相联映射,组间直接映射。
4. 页面置换算法
1. 最佳置换算法OPT:每次选择淘汰的页面都是在未来最长时间不在访问的页面。(拥有最好的性能,实际无法达到,可用于评价其他算法的优劣)
2. 先进先出置换算法FIFO
3. 最少使用置换算法LFU
4. 最近最久未使用置换算法LRU
5. 时钟置换算法CLOCK(每个页面设置一个访问位,访问:访问位设为1,需要淘汰时循环扫描访问位:1变为0,0淘汰)
5. 抖动(颠簸现象):页面在内存和外存间,短时间频繁调度称为抖动。主要原因是分配给进程的内存不够。
6. 网络编程
1. IO模型
1. BIO:阻塞IO,如accept和recv都是阻塞的,要有连接或者数据才会继续执行。
2. NIO:非阻塞IO,如accept和recv都是非阻塞的,没有连接或数据直接返回-1。
3. AIO:异步IO,跟非阻塞IO不同的是,当有连接或数据时,会通过回调函数通知。(linux没用AIO是因为如果大量IO时,太多回调函数会造成触发不过来。AIO使用少量IO情况下,比如磁盘,文件可以采用AIO)
2. IO多路复用
1. 产生
网络服务器可以与大量客户端建立tcp连接,如果每个连接都用一个线程,CPU上下文切换消耗大,所以引出单线程IO多路复用的方式(单线程如何在处理A客户时,接收其他客户请求,答:通过DMA实现,DMA控制器是独立CPU专门处理IO的)。
2. 原始IO多路复用
while(true)中遍历fd,如果某个fd有数据就处理。这样就能处理每个网络连接。但如果需要程序来轮询判断fd是否有数据程序效率很低,所以可以考虑使用内核来轮询判断。(文件描述符fd:linux一切都是文件,每一个网络连接在内核中都是以文件描述符fd的形式存在。文件描述符存储的是一个数字)
3. 三种IO多路复用
1. select
1. select函数过程:1. 将文件描述符集合拷贝到内核。2. 通过内核态监听哪些fd有数据(有数据对应bitmap置位)3.把文件描述符集合拷贝到用户态,对有数据的进行处理。
2. select五个参数:文件描述符最大值+1,读文件描述符集合,写文件描述符集合,异常文件描述符集合,超时时间。(读写文件描述符集合中为一个bitmap,共1024位,根据已建立连接的文件描述符fd决定哪位为1,其余为0。第一个参数的作用在于内核遍历bitmap的范围)
3. select函数流程:先接收连接,得到已建立连接的文件描述符数组集合fds,同时得到文件描述符最大值max,然后while循环(先通过fds初始化读写文件描述符集合set,然后执行select(max+1,rset,wset,null,null)函数,最后处理有数据的fd )
4. 优点:通过内核态监听哪个fd有数据,速度快
5. 缺点:1. bitmap有1024的限制,fd大于1024就不能用select了。2. bitmap不能重用,下次while循环需要重新初始化。3. 执行select函数会用用户态到内核态的数据拷贝开销。4. select返回时,并不知道哪个fd有数据,需要通过遍历bitmap得知,遍历时间复杂度o(n)。
2. poll
1. 特点:与select不同在于没有用bitmap,而是把fd封装成一个结构体pollfd,在结构体中包含fd,events(表示读还是写),revents(标志位,表示是否有数据),poll函数传输结构体数组。
2. 优点:自定义数组,没有fd大小限制。使用结构体所以能解决select中bitmap不可重用的问题。
3. 缺点:依然存在select中3.4问题(数据拷贝和轮询判断)。
3. epoll
1. 优点
1. 1. 不用把fd从用户态拷贝到内核态,而是通过内存映射(mmap)来实现与内核消息传递。(mmap:可以将普通文件映射到内存中,对内存修改直接映射到文件本身。传统读文件时,需要先把数据从磁盘拷贝到内核态,内核态再拷贝到用户态,非常影响性能,)
2. 2. epoll不采用轮询的方式判断是否有数据,而采用事件通知的形式(有数据就通过回调函数通知)
2. 缺点
当事件触发比较频繁时,回调函数也会被频繁触发
3. 三个重要方法
1. epoll_create 在内核空间中创建一个红黑树根节点。
2. epoll_ctl 每个连接建立时(accept),就把fd加入根节点。(添加删除节点)
3. epoll_wait 有数据的fd会通知内核,然后会把它放到就绪链表中,最后返回有数据fd的个数。
4. 两种触发方式
1. 水平触发:有数据但没有处理的fd,会在下一次调用epoll_wait的时候再次放到就绪链表中(缺点就是如果一直不处理就重复返回,耗费性能)
2. 边缘触发:只放一次,不处理就不会通知了
4. 场景选择
1. select场景:适用连接数量少或者活跃的连接数量多的情况。
2. epoll场景:适用连接数量多,但活跃的连接数量少的情况。
5. 三种方式如果传入超时时间为0就是非阻塞。否则就是阻塞
3. reactor
1. reactor模型:通俗讲就是将对io处理转换为对事件的处理,通过reactor来进行管理。它对epoll进行封装,然后根据epoll_wait返回的fd判断可读还是可写,然后进行相应处理。(它由非阻塞IO和IO多路复用组成)
2. 单reactor模型:网络处理(比如判断哪些fd可读)跟具体的业务逻辑处理(处理可读fd)都在一个线程中(缺点就是不适用高并发情况)。例子:redis采用的就是单reactor模型,因为redis是内存数据库,操作其中数据非常快,所以可以采用这种方式。
3. 单reactor模型+任务队列+线程池:与单reactor相比,它把具体的业务逻辑处理交给其他线程去处理。(缺点很大流量情况下不适用,就需要采用多reactor模型)例子:skynet采用单reactor模型+任务队列+线程池
4. 多reactor模型:一个reactor专门负责接收网络连接,然后把连接分配给多个reactor。每个reactor都是一个线程或进程。例子:nginx采用多reactor模型,每个reactor是一个进程。(通过master接收网络连接,然后fork出多个子进程worker,子进程都监听一个端口,共享一块内存,跟据锁机制来判断那个worker处理连接)
5. 多reactor+消息队列+线程池:对于大流量且业务密集的情况下可以采用这种模式。
7. linux常用命令
1. java 操作相关
1. 后台启动jar:nohup java -jar 文件名 & (nohup:不关服务器就一直运行,&:后台运行。输入日志可以在文件名后加:>日志名(不写生成到当前目录))
2. 根据Jar包查询进程号:ps -ef|grep jar包名
3. 杀掉进程:kill 进程号
4. 查看jvm内存命令:jstat -gc 进程号
2. 打开文件
1. vi:打开文件,如果没有文件,会创建一个,如果没有保存退出,则不会创建(输入:i。搜索:esc后/(n下一个,N上一个)。不保存退出:esc后:q!。保存退出:esc后:wq!)
2. cat 文件名:一次性显示文件内容。
3. more 文件名:一页页显示文件内容(空格翻页,q退出)
4. tail -f 文件名:显示文件最后几行,文件内容刷新就实时增加。
3. 操作文件和文件夹
1. 创建文件:vi 文件名(打开文件,如果没有文件,会创建一个,保存退出才会创建)
2. 创建文件夹:mkdir 文件夹
3. 删除文件:rm 文件名(参数-f:不用确认,参数-r:删文件夹 )
4. 复制文件:cp 文件名 目标文件名(也可改名,加上参数-r可以复制目录)
5. 移动文件或文件夹:mv 文件名 目标文件名(可以改名)
6. 打包文件:tar zcvf 生成的文件名 打包哪些文件(在打包还有zip和unzip命令也可以)
7. 解压文件:tar zxvf 文件名 (压缩到指定文件夹后面加: -C 文件夹名)
8. 搜索文件:find 目录名 -name 搜索文件名 -print (目录名就是在那个目录下搜索,省略就是当前目录下)
9. 查看当前目录:pwd
10. 列出当前目录文件:ls (参数-l:查看详细信息。参数-t:按时间排序)
11. 搜索文件中的内容:grep 搜索内容 文件名
12. 统计文件行数、字数和大小:wc 文件名
4. 系统相关
1. 终止命令:ctrl+c
2. 清屏:clear
3. 查看端口占用情况:lsof -i:端口
4. 正则表达式:?匹配一个字符,*匹配任意个字符
5. 查看本机ip地址:ip addr
6. 显示磁盘使用情况:df(参数-h:方便阅读)
7. 判断网络是否连通:ping ip地址或域名(可以加参数指定包的个数:-c 包的个数)
7. 计算机网络
面试题
1. 分层
1. OSI七层参考模型
1. 物理层:通过光缆,电缆进行01信号传输
2. 数据链路层:单独的0/1信号没有意义,通过以太网协议对0/1信号分组,一组电信号称为一个数据包,表头包含发送者和接收者的mac地址,可以通过广播在同一子网发送接受数据。
3. 网络层:通过引入ip地址,可以子网掩码区分不同计算机是否属于同一个子网络。同一子网络通过广播方式发送,非同一网络就通过路由方式发送。(相关协议:IP、ARP(地址解析协议,根据IP地址获取mac地址)、NAT(网络地址转换协议,将在本地网络中使用的私有地址,在连接互联网的同时转换成为公共 IP 地址的技术)、ICMP协议(ping发送的包就是ICMP应答报文,用于测试网络是否畅通))
4. 传输层:解决端口到端口的通信;(相关协议: TCP、UDP)
5. 会话层:管理不同进程之间的通信,如不同软件数据分发给不同软件。
6. 表示层:提供数据处理功能(如压缩加密等)
7. 应用层:为应用进程提供服务(如解析数据)(相关协议:HTTP(网络通信协议)、DNS(根据域名找ip)、DHCP(分配IP地址和相关的网络参数。)、SSH(安全通信协议)、FTP(文件传输协议)、SMTP和IMAP(SMTP 协议只负责邮件的发送,POP3/IMAP负责邮件接收)
2. 五层模型
相对七层,将会话层和表示层融入到应用层(如https涉及加密等,这个需要在表示层的权限,所以将会话层和表示层合并到应用层,这样程序员就能实现相应功能)
3. TCP/IP四层模型(tcp/ip协议)
相对五层,把物理层和数据链路层合并为网络接口层
2. 上网过程
1. 前提:通过DHCP协议获取四个参数(本机IP地址、子网掩码、网关的IP地址、DNS的IP地址)
2. 访问一个网页的全过程
1. 客户端输入网址——获得目标IP(缓存没有就通过DNS解析)——三次握手来建立TCP连接——发送与接收数据(通过http访问网页)——最后通过四次挥手来断开TCP连接
2. HTTP访问:应用层向域名发送一个HTTP请求报文——传输层通过TCP协议将HTTP请求报文按序号分割成多个报文段——网络层通过IP协议判断是否为同一子网,根据是否同一子网然后通过ARP协议获取目标mac地址(非同一子网先传到对应网关),然后通过网络接口层进行数据传输——目标机收到数据后:传输层通过TCP协议重装报文段——应用层通过HTTP协议对请求内容进行处理(回传也类似)
3. 应用层相关
1. b/s和c/s架构
1. c/s架构
客户端/服务器架构,缺点就是相对不好维护
2. b/s架构
浏览器/服务器架构,缺点就是安全性相对不好控制
2. http
1. http版本
1. HTTP/1.0
浏览器与服务器只保持短暂的TCP连接,请求处理完成后立即断开连接,速度慢(TCP连接的建立需要三次握手)。
2. HTTP/1.1
1. 1. 连接复用,就是TCP连接默认不关闭,可以被多个请求复用。2. 引入了管道机制(半双工),即在同一个TCP连接里面,客户端可以同时发送多个请求,但是服务端还是顺序执行的。
2. TPC连接关闭情况:1. 主动通知关闭时。2. 浏览器关闭时。3. 浏览器通过TCP保活功能检测服务端长时间无响应时。4.服务器判断客户端长时间未操作时 。
3. 扩展:TCP保活功能(TCP Keep-Alive):每隔一段时间发送一个消息,判断对方是否还存在,属于传输层的概念,主要用来检测并关闭异常连接。(心跳检测:应用层实现的TCP保活功能,应用层下更灵活)
3. HTTP/2 :采用了多路复用,相当于全双工,就是在一个TCP连接里,客户端和浏览器都可以同时发送多个请求或响应,而且不用按照顺序执行(最后组装)。
2. http和https区别
1. http
超文本传输协议,默认80端口,http协议是无状态的(每个请求独立,互不影响)
2. https
1. 在http基础上引入一个加密层,数据会加密传输。默认443端口。
2. CA证书作用:恶意拦截请求,伪装身份(通过ca证书就可以解决,就可以判断公钥是否合法)
3. http和https工作流程
1. http工作流程
客户端输入网址——DNS解析域名为ip——建立TCP连接——客户端发送请求——服务器响应——断开TCP连接
2. https工作流程
1. 步骤跟http不同的就是再请求响应阶段
2. https(SSL协议):采用非对称加密。
1. 具体过程:客户端请求公钥——服务器返回公钥——客户端使用公钥将明文加密为密文,再发送密文——服务器通过私钥解密
2. 缺点:非对称加密速度慢,效率低(特别当内容很多的时候)。
3. https(TSL1.0协议)采用混合加密(非对称加密+对称加密)
1. 具体过程:客户端请求公钥——服务器返回公钥——客户端用公钥对随机Key加密,发送加密后的随机Key——服务器通过私钥解密得到随机Key——客户端结合随机Key和明文对称加密,然后发送——服务器结合随机Key对称解密。
2. 因为RandKey很小,所以不会很慢,内容很大,采用对称加密很快,平衡了两种加密方法优缺点。
4. http报文
1. http请求报文组成(响应报文类似)
1. 请求行
请求行由 请求方式(GET、POST、DELETE等)、URL和HTTP协议版本3个字段组成,它们用空格分隔。例如,GET /index.html HTTP/1.1。
2. 请求头部
请求头部由键值对组成。用于通知服务器有关于客户端请求的信息(如:User-Agent:客户端浏览器类型。Accept:客户端可识别的内容类型列表。Host:请求的主机名)
3. 空行
表示以下不再有请求头。(发送回车符和换行符)
4. 请求数据
在POST方法中使用(GET方法中不使用,与请求数据相关的最常使用的请求头是Content-Type和Content-Length。)
2. http常见状态码
1. 1开头(信息类)
100,接受的请求正在处理,信息类状态码
2. 2开头(成功类)
1. 2xx(成功)表示成功处理了请求的状态码
2. 200(成功)服务器已成功处理了请求。
3. 3开头(重定向,需要进一步操作)
1. 301,永久性重定向,表示资源已被分配了新的 URL
2. 302,临时性重定向,表示资源临时被分配了新的 URL
3. 303,表示资源存在另一个用GET方法获取资源的URL
4. 304,(未修改)自从上次请求后,请求网页未修改过。服务器返回此响应时,不会返回网页内容,浏览器从缓存中获取内容
4. 4开头(客户端错误)
1. 400:请求的语法错误
2. 401:请求需要身份认证(比如访问需要登录的页面)
3. 403(禁止)服务器拒绝请求
4. 404(未找到)服务器找不到请求资源
5. 5开头(服务器错误)
1. 500,(服务器内部错误)服务器遇到错误,无法完成请求
2. 503,表示服务器处于停机维护或超负载,无法处理请求
3. post和get区别
1. 定义:get 和 post请求是http协议中的两种请求方式。get一般般用来获取信息的。post一般是用来更新信息。
2. 参数:get参数在地址栏,安全性低,长度限制2048字符。 post传递参数放在请求报文的请求数据中,安全性高,参数长度没限制(参数类型:get只接受ASCII字符,而post没有限制。)
3. 是否被缓存:get请求可以被缓存, post请求不会被缓存(所以get请求刷新浏览器或者回退没有影响,post请求则会重新请求一遍)
4. 产生数据包:get产生一个tcp数据包,post两个tcp数据包(一个包用于检查服务器端是否正常,正常再发送携带数据的包)
4. Cookie和Session
1. 为什么需要:由于HTTP协议无状态,例如对于一些场景下,并不知道是哪个用户在执行操作,这时就需要session或者Cookie
2. Session:保存在服务端的,相对安全,Session存储内容太多会影响服务器性能。Session访问后会刷新生命周期
3. Cookie:保存在客户端的,相对不安全,单个Cookie数据量不超过4k,Cookie生命周期是累计的(到时间就过期)
5. 报文传输过程
http报文 分割成多个 报文段,传输层通过tcp组装成一个个tcp报文(包含端口等信息),网络层加上ip后就变成一个个数据包。
6. URI 和 URL 的区别是什么?
1. URI统一资源标志符,可以唯一标识一个资源
2. URL统一资源定位符(更具体的URI),它不仅唯一标识资源,而且还提供了定位该资源的信息。
7. 当我们ping的时候发送的是什么包
ping发送的是ICMP 应答报文,用于测试网络是否畅通
4. 传输层相关
1. TCP和UDP区别
1. TCP: 面向连接,可靠,效率慢,所需资源多。(如:文件传输等)
2. UDP:面向无连接,不可靠,效率高,所需资源少(如:qq语音,直播等)
2. TCP 协议如何保证可靠传输
1. 滑动窗口:如果每个数据发送后都需要等待确认才发下一个太慢了,所以引出滑动窗口,它把要发送的数据分为4各部分,分为已发送并确认,已发送未确认,即将发送,等待发送 。发送窗口由已经发送未确认和即将发送组成(发送窗口在三次握手时接收端提供,会根据拥塞改变),当收到ACK确认后,窗口收缩,当窗口大于容纳数据时就会进行窗口扩张。
2. 流量控制:协调端到端的数据传输接收能力(通过滑动窗口实现)
3. 拥塞控制
1. 防止网络负载过大(当拥塞窗口小于慢开始阈值的时候,拥塞窗口指数增加(慢开始算法),超过阈值后线性增加(拥塞避免算法),直到出现超时后,把慢开始阈值设置为出现拥塞时拥塞窗口的一半,再重复慢开始的操作)
2. 快重传和快恢复:当收到3个对同一个报文段的重复确认,就代表数据丢失,慢开始阈值降为当前窗口一半,窗口不重新慢开始,而是直接从阈值处线性增加。
4. 超时重传(ARQ 协议):
1. 停止等待 ARQ 协议:每发完一个分组就等待,在规定时间内没收到确认就重发。(信道利用率低,等待时间长)
2. 连续 ARQ 协议:发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,不需要等待对方确认。接收方收到窗口所有数据后再确认(信道利用率高,如发送了1-5的数据包,第3条丢失,这时接收方回复ack(5+1),数据窗口2。发送方收到ACK报文后,就知道接收端收到后两个,重传前三个)
5. 校验和: TCP 数据包中有校验和字段,接收端可以根据它来确认数据包是否有损坏,损坏则丢弃
6. 有序编号:每个tcp数据包都有编号,接收端可以把数据有序的组装。
3. 三次握手
1. 过程
1. 第一次握手:客户端向服务器发送SYN报文,并指明初始化序列号(此时处于 SYN_Send 状态)
2. 第二次握手:服务器收到 SYN 报文之后,会以自己的 SYN 报文作为应答(建立并确认服务器到客户端的通信),并指明自己的初始化序列号 ,同时会把客户端的序列号 + 1 作为 ACK 的值,表示自己已经收到了客户端的 SYN(此时服务器处于 SYN_RCVD 的状态)
3. 第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的序列号 + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 established 状态。
4. 最后:服务器收到 ACK 报文之后,也处于 established 状态,此时,双方以建立起了链接
2. 三次握手作用
1. 确认双方的接受发送能力是否正常。2、指定自己的初始化序列号,为后面的可靠传送做准备。(初始化序列号是动态生成的,如果固定容易被攻击)
3. 什么是半连接队列
服务器第一次收到客户端的 SYN报文后,服务器会把此种没有完全建立的连接放在半连接队列。已经完成三次握手会放在全连接队列中。(如果队列满了就有可能会出现丢包现象。)
4. 三次握手可以携带数据吗
前两次不可以(攻击者可以第一次握手时携带大量数据,重复发送大量syn报文,导致服务器出现问题),第三次可以(第三次客户端以已建立连接)
4. 四次挥手
1. 过程
1. 第一次挥手:客户端发送一个FIN报文,报文中会指定一个序列号。此时客户端处于FIN_WAIT1状态。
2. 第二次挥手:服务端收到FIN报文后,会发送ACK报文(且把客户端的序列号值+1作为ACK报文的序列号值)表明已经收到客户端的报文了,此时服务端处于CLOSE_WAIT状态。
3. 第三次挥手:如果服务端也想断开连接了,发给FIN报文,且指定一个序列号。此时服务端处于LAST_ACK的状态。
4. 第四次挥手:客户端收到FIN报文后,一样发送一个ACK报文作为应答(且把服务端的序列号值+1作为自己ACK报文的序列号值),此时客户端处于TIME_WAIT状态(持续2MSL)。需要过一阵子以确保服务端收到自己的ACK报文之后才会进入CLOSED状态
5. 最后:服务端收到ACK报文之后,就处于关闭连接CLOSED状态。
2. 为什么需要四次
第二次挥手是告诉客户端已经收到关闭请求,你不要再发送数据了,但此时服务器可能还没完全接收数据,所以需要接收完后再通过第三次握手发送关闭请求。
3. 为什么需要timeWait
最后一个 ack报文 ,有可能丢失,这时服务器会重新发fin报文, 如果这时主动方处于 关闭状态 ,就会响应 rst(异常的关闭连接) 而不是 ack。
5. tcp粘包
1. tcp粘包定义
我们发送的数据是一个个数据包,但tcp是以流的方式传输,所以接收端可能同时接受到多个数据包,造成粘包的现象。
2. 如何解决tcp粘包
1. 使用标准的应用层协议(如http,https)来封装传输的数据包,然后接收端根据协议的规则来解析。
2. 数据包尾部添加特殊字符,缺点是每个字符都要判断是否为特殊字符,效率低。
3. 数据包头部添加数据头,数据头中包含了数据长度。(如果数据类型复杂,怎样获得数据长度?通过把数据序列化,到接收端再反序列化即可(如通过Json序列化和反序列化))
3. UDP会产生粘包吗
UDP 是基于数据报的传输协议,一次发送一个报文,不会有粘包问题。(数据包太长,需要分片,那也是应用层的事情)
6. 怎样判断接收方接收到多少数据(重传)
如发送方发送了1-100的数据包,接收端接收到数据后回复ack(100+1)以及数据窗口为50。发送方收到ACK报文后,就知道接收端收到后两个,重传前三个
8. 设计模式
1. 含义
是前辈们对代码开发经验的总结,是解决特定问题的一系列方案(针对面向对象)。
2. 分类(共23种)
1. 创建型模式
1. 定义
描述怎么创建对象,让创建和使用分离,让代码更好的维护和扩展。
2. 包含
5种:单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。
2. 结构型模式
1. 定义
从程序的结构上实现松耦合,使得类或者对象可以按某种布局组成更大的结构,解决复杂的问题
2. 包含
7种:适配器模式,桥接模式,装饰模式,组合模式、外观模式,享元模式,代理模式
3. 行为型模式
1. 定义
让类或对象之间更好的相互协作
2. 包含
11种:模板方法模式,命令模式,迭代器模式,观察者模式,中介者模式,备忘录模式,解释器模式,状态模式,策略模式,职责链模式,访问者模式。
3. 面向对象七大原则(oop)
1. 开闭原则:对扩展开放,对修改关闭
2. 单一职责原则:如引起类改变只有一个原因
3. 接口隔离原则:要为各个类建立它们需要的专用接口
4. 里氏替换原则:确保父类性质在子类中仍然成立
5. 依赖倒置原则:要面向接口编程,不要面向实现编程。
6. 合成复用原则:先考虑通过接口来实现(有没有),其次才考虑使用继承关系来实现(是不是)
7. 迪米特法则:只与你的直接朋友交谈,不跟“陌生人”说话。(朋友指的是成员变量或方法输入输出的对象)
4. 创建型模式例子
1. 单例模式
1. 定义
通过私有构造函数,确保一个类只有一个实例存在。
2. 表现形式
1. 饿汉式单例
类加载时就创建对象,如果不使用就造成资源浪费。(线程安全)
########
2. 懒汉式单例
1. 使用时再创建对象(线程不安全,如多个线程同时创建对象)
########
2. DCL懒汉式(双重检测锁模式的懒汉式单例)
3. 静态内部类单例(将实例放在静态内部类中,比较少用)
#######
3. 反射可以破解单例(反射不能破解枚举单例,底层会进行判断)
2. 工厂模式
1. 定义
实现了创建者和调用者分离。让代码更好的维护和扩展
2. 分类
1. 简单工厂模式
1. 工厂类通过不同参数创建不同对象(这些对象对应的类都继承同一父类)
2. 举例:创建一个工厂类,里面可以创建各种类型的车,根据参数返回需要创建的车。(缺点就是没有满足开闭原则,比如新增一类车时,需要在车工厂里新增代码。解决方法就是通过工厂方法模式)
2. 工厂方法模式
1. 相对于简单工厂,为了满足开闭原则。
2. 例子:先创建一个抽象工厂类,再给每个类型的车都创建一个工厂类(实现了了抽象工厂类),然后就可以通过抽象工厂类创建一个具体类别的工厂,再通过具体工厂创建对应类别的车,新增一个车品牌的时候之间创建一个新的工厂即可。但是工厂方法模式会新增很多类,在编程和结构复杂度上太复杂,所以一般还是优先选择简单工厂模式。
3. 抽象工厂模式
1. 创建一个工厂的工厂。适用于涉及产品族的时候。缺点:不满足开闭原则,就是增加功能会修改代码。所以一般用于功能固定的产品族。
2. 例子:比如,小米和华为时两个产品族,都可以创建手机或者电脑。先创建手机和路由器的工厂(小米和华为都实现它们)。然后创建一个工厂的工厂A(里面包含创建手机或者电脑的工厂),小米和华为都实现A得到小米工厂和华为工厂,然后就可以通过工厂的工厂创建一个小米或华为工厂,再通过创建的工厂创建对应的品牌的手机或者电脑
5. 结构型模式
1. 适配器模式
1. 通过引入适配器将原本接口不兼容的类能一起工作。一般采用对象适配器,不采用类适配器(对象适配器可以通过多态实现多种类型的适配)
2. 例子:如电脑上只有usb接口,插不上网线接口,通过引入一个转接器(适配器)将电脑接上网线。
2. 桥接模式
1. 适用于多维度的场景,将每个维度分离,使它们都可以独立的变化,再通过桥接把不同维度组装成起来。
2. 例子:新增一个品牌时,电脑和品牌的组合就会多很多,所以把品牌和电脑分离。通过桥接的方法把它们再组合起来(在电脑中通过一个品牌的成员变量,创建电脑时,传入具体品牌)
6. 行为型模式
策略模式
1. 定义一组算法,将每个算法都封装起来, 它们都实现同一个策略抽象类,同过这个策略可以自由切换不同的算法。
2. 例子:比如洗衣机有快洗,大物洗,这就是两个算法,通过定义一个洗衣算法的接口,快洗,大物洗实现这个洗衣算法接口。用户在洗衣的时候,只需要选择一个算法传入洗衣这个方法中即可。
更多推荐
计算机复习
发布评论