Java复习
2022-07-19 17:16:50 0 举报
AI智能生成
Java复习知识点
作者其他创作
大纲/内容
JavaSE
面向对象
三大特性
封装
概述
利用抽象数据类型将数据和基于数据的操作封装到一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据的内部,尽可能的隐藏内部的细节,只保留一些对外的接口与外部建立联系
优点
减少耦合,可以独立的开发、测试、优化、使用、理解和修改
减轻维护的负担,可以更容易被理解,在调试过程中可以不影响其他模块
有效地调节性能: 可以通过剖析确定哪些模块影响了系统的性能
提高软件的可重用性
降低了构建大型系统的风险,即便整个系统不可用,这些单独的模块也是可用的
继承
概述
继承实现了IS-A关系,例如Cat和Animal就是一种IS-A关系,子类可以继承父类的非私有的方法和属性
继承应当遵循里氏替换原则,子类对象必须能够替换掉所有父类对象
多态
概述
编译时多态
方法的重载
运行时多态
程序定义的对象引用所指向的具体类型在运行期间才确定
条件
继承
覆盖(重写)
向上转型
知识点
数据类型
包装类型
Boolean
Byte
Char
Short
Integer
Float
Double
Long
缓存池
除了new一个新的对象外,其余使用的方法会优先看是否有缓存
缓冲池
基本类型的缓冲池
boolean values true and false
all byte values
short values between -128 and 127
int values between -128 and 127
char in the range \u0000 to \u007F
特性
在使用这些基本类型对应的包装类型时,就可以直接使用缓冲池中的对象
如果在缓冲池之外,若之前为创建好,则无缓存,需要新创建
基本类型
boolean/1
byte/8
char/16
short/16
int/32
float/32
long/64
double/64
自动装箱/拆箱
装箱
Integer x = 2
拆箱
int y = x
String
特性
String被声明为final,所以不可被继承
内部使用char数组存储数据,数组被声明为final
不可变的好处
可以缓存hash值
String的hash值经常被使用,字符串不可变的特性导致对应的hash值也不可变,因此只需要一次计算
String pool的需要
如果一个String对象已经创建过了,则可以直接使用
安全性
String作为参数,不可变就可以保证参数不可变
线程安全
String不可变天生具备线程安全,可以在多个线程中安全的使用
可变字符串
StringBuffer
线程安全,内部使用Synchronized同步
速度较慢
StringBuilder
线程不安全,但是速度较快
存放位置
常量保存在本地内存的元空间,但字符串常量池、静态变量仍在堆中
抽象类与接口
抽象类
抽象类和抽象方法都使用abstract关键字进行生命
接口
接口是抽象类的延伸,可以看做是一个完全抽象的类,不可以有任何方法体
接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。
字段默认都是static和final
字段默认都是static和final
从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了
区别
抽象类不能被实例化,需要继承抽象类才能实例化其子类
从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系
从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类
接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。
super/this
super
访问父类的构造函数super()
必须在第一行,不可和this同时出现
可使用super关键字调用父类的方法和属性(非私有)
this
访问本身的构造方法
必须在第一行,不可和super同时出现
可使用this关键字调用自身的方法和属性
重写和重载
重写(Override)
存在于继承体系中,之子类实现了一个与父类在方法声明上完全相同的方法
满足里氏替换原则
子类方法访问权限必须大于等于父类方法
子类方法的返回类型必须是父类方法返回类型或者其子类
使用 @Override 注解,可以让编译器帮忙检查是否满足上面的两个限制条件
重载(Overload)
存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同
应该注意的是,返回值不同,其它都相同不算是重载
克隆(clone)
Object类下的protected的方法
不重写不可使用
必须实现cloneable接口
浅拷贝
拷贝对象和原始对象的引用类型引用同一个对象
共同的地址,修改一个,两个都变
深拷贝
拷贝对象和原始对象的引用类型引用不同对象
替代方案
使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象
复制原有的构造函数,构建
关键字
static
静态变量
又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它;静态变量在内存中只存在一份
静态方法
静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法(abstract)
只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字
静态语句块
静态语句块在类初始化时运行一次
静态内部类
非静态内部类依赖于外部类的实例,而静态内部类不需要
静态内部类不能访问外部类的非静态的变量和方法
静态导包
在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低
初始化循序
父类(静态变量、静态语句块)
子类(静态变量、静态语句块)
父类(实例变量、普通语句块)
父类(构造函数)
子类(实例变量、普通语句块)
子类(构造函数)
反射
概述
每个类都有一个class对象,包含了与类有关的信息,编译一个新类时,就会产生一个新的class文件
类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 Class.forName("com.mysql.jdbc.Driver") 这种方式来控制类的加载,该方法会返回一个 Class 对象
Class 和 java.lang.reflect 一起对反射提供了支持
Field:可以使用get/set方法读取和修改Field对象关联的字段
Method : 可以使用 invoke() 方法调用与 Method 对象关联的方法
Constructor : 可以用 Constructor 创建新的对象
反射就是把java类中的各种成分映射成一个个的Java对象
异常
受检异常
需要用try-catch语句捕获处理,可以从异常中恢复
非受检异常
程序运行时错误,且无法恢复
泛型
注解
并发编程
多线程
为什么需要多线程
众所周知,CPU、内存、I/O 设备的速度是有极大差异的,为了合理利用 CPU 的高性能,平衡这三者的速度差异,计算机体系结构、操作系统、编译程序都做出了贡献,主要体现为
CPU 增加了缓存,以均衡与内存的速度差异;// 导致 可见性问题
操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异;// 导致 原子性问题
编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。// 导致 有序性问题
并发三要素
可见性: CPU缓存引起
高速缓存中的值改变并没有直接存入到内存中
原子性: 分时复用引起
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
i++
将变量 i 从内存读取到 CPU寄存器
在CPU寄存器中执行 i + 1 操作
将最后的结果i写入内存(缓存机制导致可能写入的是 CPU 缓存而不是内存)
由于CPU分时复用(线程切换)的存在,线程1执行了第一条指令后,就切换到线程2执行,假如线程2执行了这三条指令后,再切换会线程1执行后续两条指令,将造成最后写到内存中的i值是2而不是3
有序性: 重排序引起
在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。重排序分三种类型:
编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序
指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序
子主题
内存系统的重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行
解决并发问题
三维度
原子性
可见性
Java提供了volatile关键字来保证可见性
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值
而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性
有序性
Happens-Before 规则来保证有序性的
JMM本质上可以理解为,Java 内存模型规范了 JVM 如何提供按需禁用缓存和编译优化的方法。具体来说,这些方法包括
volatile、synchronized 和 final 三个关键字
Happens-Before 规则
线程基础
线程状态
新建(new)
可运行(Runnable)
阻塞(Blocking)
无限期等待(waiting)
死亡(Terminate)
状态转换
线程使用方式
实现Runnable接口
实现Callable接口
继承Thread类
实现接口比继承Thread类优势
Java不支持多重继承,继承了Thread之后就无法在继承其他类
类可能只要求可执行就行,继承整个Thread类开销过大
线程机制
Executor
管理多个异步任务的执行,而无需程序员显式的管理线程的生命周期
CachedThreadPool: 一个任务创建一个线程
FixedThreadPool: 所有任务只能使用固定大小的线程
SingleThreadExecutor: 相当于大小为 1 的 FixedThreadPool。
Daemon
守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分
main() 属于非守护线程
使用 setDaemon() 方法将一个线程设置为守护线程
sleep()
Thread.sleep(millisec) 方法会休眠当前正在执行的线程,millisec 单位为毫秒
sleep() 可能会抛出 InterruptedException,因为异常不能跨线程传播回 main() 中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理
不释放锁
yield()
对静态方法 Thread.yield() 的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行
交给优先级高(同级)的
不释放锁
线程中断
InterruptedException
通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞
Executor 的中断操作
调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法
线程协作
join() Thread方法
在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束
释放锁
Object方法
wait()
使用 wait() 挂起期间,线程会释放锁
notify()
concurrent
await()
await() 可以指定等待的条件
signal()
唤醒等待的线程
signalAll()
并发编程导图
所有的锁
乐观锁和悲观锁
乐观锁
概述
在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)
乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的
乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升
悲观锁
synchronized和Lock的实现类
悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
自旋锁和适应性自旋锁
无锁、偏向锁、轻量级锁、重量级锁
公平锁和非公平锁
公平锁
会带来更大的开销、降低吞吐量
队列,先进先出
非公平锁
插队,失败入队列
可重入锁和非可重入锁
独享锁(排它锁)和共享锁
独占锁/独享锁/排他锁
同一时间只能有一个线程占有它
Synchronized
共享锁
同一时间可以有多个线程同时占有
ReadWriteLock
Semaphore
他们可以设置共享的线程数量
目录
知识点
Synchronized关键字
注意点
一把锁只能同时被一个线程获取,没有获得锁的线程只能等待
每个实例都对应有自己的一把锁(this),不同实例之间互不影响;例外:锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象公用同一把锁
synchronized修饰的方法,无论方法正常执行完毕还是抛出异常,都会释放锁
对象锁
包括方法锁(默认锁对象为this,当前实例对象)和同步代码块锁(自己指定锁对象)
原理分析
实例
分析
Monitorenter和Monitorexit指令,会让对象在执行,使其锁计数器加1或者减1。每一个对象在同一时间只与一个monitor(锁)相关联,而一个monitor在同一时间只能被一个线程获得,一个对象在尝试获得与这个对象相关联的Monitor锁的所有权的时候,monitorenter指令会发生如下3中情况之一
monitor计数器为0,意味着目前还没有被获得,那这个线程就会立刻获得然后把锁计数器+1,一旦+1,别的线程再想获取,就需要等待
如果这个monitor已经拿到了这个锁的所有权,又重入了这把锁,那锁计数器就会累加,变成2,并且随着重入的次数,会一直累加
这把锁已经被别的线程获取了,等待锁释放
monitorexit指令:释放对于monitor的所有权,释放过程很简单,就是讲monitor的计数器减1,如果减完以后,计数器不是0,则代表刚才是重入进来的,当前线程还继续持有这把锁的所有权,如果计数器变成0,则代表当前线程不再拥有该monitor的所有权,即释放锁
原理
可重入原理:加锁次数计数器
可重入:(来源于维基百科)若一个程序或子程序可以“在任意时刻被中断然后操作系统调度执行另外一段代码,这段代码又调用了该子程序不会出错”,则称其为可重入(reentrant或re-entrant)的。即当该子程序正在运行时,执行线程可以再次进入并执行它,仍然获得符合设计时预期的结果。与多线程并发执行的线程安全不同,可重入强调对单个线程执行时重新进入同一个子程序仍然是安全的
可重入锁:又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。
Synchronized的重入性
锁的优化
锁粗化(Lock Coarsening)
锁消除(Lock Elimination)
轻量级锁(Lightweight Locking)
偏向锁(Biased Locking
适应性自旋(Adaptive Spinning)
Synchronized与lock
synchronized的缺陷
效率低:锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock可以中断和设置超时
不够灵活:加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活
无法知道是否成功获得锁,相对而言,Lock可以拿到状态,如果成功获取锁,....,如果获取失败
Lock
方法
lock()加锁
unLock()解锁
tryLock()尝试获取锁,返回一个boolean值
tryLock(long,TImeUtil) 尝试获取锁,可以设置超时
synchronized实际上是非公平的,新来的线程有可能立即获得监视器,而在等待区中等候已久的线程可能再次等待,这样有利于提高性能,但是也可能会导致饥饿现象
注意点
锁对象不能为空,因为锁的信息都保存在对象头里
作用域不宜过大,影响程序执行的速度,控制范围过大,编写代码也容易出错
在能选择的情况下,既不要用Lock也不要用synchronized关键字,用java.util.concurrent包中的各种各样的类,如果不用该包下的类,在满足业务的情况下,可以使用synchronized关键,因为代码量少,避免出错
避免死锁
volatile
防止重排序
可见性
可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到
原因是每个线程拥有自己的一个高速缓存区——线程工作内存
Volatile可以将修改后的值马上刷新进内存
原理
内存屏障,又称内存栅栏,是一个 CPU 指令
在程序运行时,为了提高执行性能,编译器和处理器会对指令进行重排序,JMM 为了保证在不同的编译器和 CPU 上有相同的结果,通过插入特定类型的内存屏障来禁止+ 特定类型的编译器重排序和处理器重排序,插入一条内存屏障会告诉编译器和 CPU:不管什么指令都不能和这条 Memory Barrier 指令重排序
final
数据
特点
对于基本类型,final 使数值不变
对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的
声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量
方法
方法不可以被子类重写
可以被重载
可以被重载
类
声明类不允许被继承
private是隐式的final
JUC
CAS
概念
Compare-And-Swap
对比和交换,是一条原子指令
只可以保障一个共享变量的原子操作
作用
是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值
存在的问题
ABA 问题
版本号
内存值V、预期值A、要修改的新值B
UnSafe类
位于sun.misc包下,执行低级别,不安全操作的方法
直接访问系统内存资源、自主管理内存资源等
功能
方法
public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3
public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);
使用CAS
AtomicInteger
原理
Voliate保证可见性、CAS保证原子性
方法
public final int get():获取当前的值
public final int getAndSet(int newValue):获取当前的值,并设置新的值
public final int getAndIncrement():获取当前的值,并自增
public final int getAndDecrement():获取当前的值,并自减
public final int getAndAdd(int delta):获取当前的值,并加上预期的值
ReentranLock
ConcurrentHashMap
与HashTable比较
HashTable使用了synchronized关键字对put方法,synchronized锁的是整个对象,锁住了整个hash表
原理
1.7
ConcurrentHashMap在对象中保存了一个Segment数组,将整个hash表分成多段,每个Segment元素,类似于一个HashTable,只需要通过算法定位到元素属于哪个Segment对相应的分段加锁即可
Segment继承ReentranLock进行加锁
segment内部是由数组+链表组成的
1.8
数组+链表+红黑树
加锁方式更改为synchronized和CAS
ReentranLock和Synchronized对比
对比图
什么是可重入
可以重复进入
公平锁
队列,先进先出
会带来更大的开销、降低吞吐量
非公平锁
插队,失败入队列
Synchronized悲观锁、非公平锁、可重入锁
ReentranLock悲观锁、非公平锁(可公平)、可重入锁
集合
Collection
List
ArrayList
顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null元素,底层通过数组实现
初始容量10,加载因子0.5
int newCapacity = (oldCapacity * 3)/2 + 1;
ArrayList的容量为10,一次扩容后是容量为16
扩容
新建一个1.5倍容量的数组,然后使用Arrays.copyOf复制过去
LinkedList
顺序容器/队列(Queue)
底层双向链表,决定了所有跟下标相关的操作都是线性时间,而在首尾位置只需要常数时间
子主题
如果需要同步,可以采用Collections.synchronizedList()对其进行包装
Queue(接口)
Vector
Stack
不推荐使用stack,推荐使用ArrayDeque
次选LinkedList
初始容量10,负载因子1
Set
SortedSet
TreeSet
HashSet
LinkedHashSet
默认内存10,加载因子0.75
Queue
Deque
ArrayDeque
Map
SortedMap
TreeMap
HashMap
LinkedHashMap
事实上LinkedHashMap是HashMap的直接子类,二者唯一的区别是LinkedHashMap在HashMap的基础上,采用双向链表(doubly-linked list)的形式将所有entry连接起来,这样是为保证元素的迭代顺序跟插入顺序相同
无序容器,容器可能会对所有元素重新哈希
冲突
1.7
冲突链表
1.8
开放地址方式
冲突链表方式
hash方法得当,put和get方法常数级
初始容量和负载系数可以影响HashMap的性能
当entry的数量超过capacity*load_factor时,容器将自动扩容并重新哈希。对于插入元素较多的场景,将初始容量设大可以减少重新哈希的次数
方法详解
1.7
get()
getEntry
通过hash得到对应bucket的下标,然后遍历冲突链表,key.equal()比较
put()
先将指定的value查询一遍,如果有,直接返回
如果没有找到,则会通过addEntry(int hash, K key, V value, int bucketIndex)方法插入新的entry,插入方式为头插法
remove()
删除key对应的value
removeEntryForKey(Object key)会首先找到key值对应的entry,然后删除entry的相关引用
1.8
get()
计算 key 的 hash 值,根据 hash 值找到对应数组下标: hash & (length-1)
判断数组该位置处的元素是否刚好就是我们要找的,如果不是,走第三步
判断该元素类型是否是 TreeNode,如果是,用红黑树的方法取数据,如果不是,走第四步
遍历链表,直到找到相等(==或equals)的 key
put()
调用putVal()
如果第一次put值的时候,会触发resize
resize扩容2倍
找到对应的数组下标,如果为空,new一个节点放入
如果已经有值
相等直接返回
不相等判断是红黑树节点还是链表调用对应的插值方法
链表插入到最后面(1.7是最前面)
如果插入之后容量达到8,触发treeifyBin,转换成红黑树
remove()
数组+链表+红黑树
在 Java8 中,当链表中的元素达到了 8 个时,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)
默认容量16,HashMap最大容量2^30;加载因子0.75
EnumMap
HashTable
Dictionary
JDK1.8 新特性
stream
模式
并行模式
List <Person> people = list.getStream.parallel().collect(Collectors.toList());
顺序模式
List <Person> people = list.getStream.collect(Collectors.toList());
API
创建
通过已有的集合创建、
Stream stream = list.stream();
通过流的方法,为制定的元素构成流
Stream stream = Stream.of("hollis","hollischuang");
中间操作
filter
filter方法用于通过设置的条件过滤元素
List<String> strings = Arrays.asList("a", "b", "", "d");
Stream<String> stream = strings.stream();
stream.filter(string->!string.isEmpty()).forEach(System.out::println);
Stream<String> stream = strings.stream();
stream.filter(string->!string.isEmpty()).forEach(System.out::println);
用法
map
map方法用于映射每个元素对应的结果
List<Integer> strings = Arrays.asList(1,3,5,1,2,6,8);
Stream<Integer> stream = strings.stream();
stream.map(i->i+1).forEach(System.out::println);
Stream<Integer> stream = strings.stream();
stream.map(i->i+1).forEach(System.out::println);
limit/skip
limit返回Stream前面的n个元素;skip是去掉前n个元素
List<Integer> numbers= Arrays.asList(1,3,5,1,2,6,8);
numbers.stream().limit(4).forEach(System.out::println);
sorted
sorted方法对于流进行排序
List<Integer> numbers= Arrays.asList(1,3,5,1,2,6,8);
numbers.stream().sorted().forEach(System.out::println);
numbers.stream().sorted().forEach(System.out::println);
distinct
distinct主要用来去重
List<Integer> numbers= Arrays.asList(1,3,5,1,2,6,8);
numbers.stream().distinct().forEach(System.out::println);
List<LikeDO> likeDOs=new ArrayList<LikeDO>();
List<Long> likeTidList = likeDOs.stream().map(LikeDO::getTid)
.distinct().collect(Collectors.toList());
List<Long> likeTidList = likeDOs.stream().map(LikeDO::getTid)
.distinct().collect(Collectors.toList());
最终操作
概述
Stream的中间操作得到的结果还是一个Stream,最终结果将不会再返回一个流
foreach
迭代流中的每个数据
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
random.ints().limit(10).forEach(System.out::println);
count
统计流中的元素个数
List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Ho llis666", "Hello", "HelloWorld", "Hollis");
System.out.println(strings.stream().count());
int countOfAdult=persons.stream()
.filter(p -> p.getAge() > 18)
.map(person -> new Adult(person))
.count();
.filter(p -> p.getAge() > 18)
.map(person -> new Adult(person))
.count();
collect
可以接受各种做法作为参数,将流中的元素累积成一个汇总结果
List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Ho llis666", "Hello", "HelloWorld", "Hollis");
strings = strings.stream().filter(string -> string.startsWith("Hollis")).collect(Collectors.toList());
System.out.println(strings);
用法
简写构造方法
// Supplier<Student> s = () -> new Student();
Supplier<Student> s = Student::new;
Supplier<Student> s = Student::new;
将权限列表以id为key,以权限对象为值转换成map
Map<Long, UmsPermission> permissionMap = permissionList.stream()
.collect(Collectors.toMap(permission -> permission.getId(), permission -> permission));
方法引用
构造引用
// Supplier<Student> s = () -> new Student();
Supplier<Student> s = Student::new;
对象::实例方法
// set.forEach(t -> System.out.println(t));
set.forEach(System.out::println);
类名:静态方法
// Stream<Double> stream = Stream.generate(() -> Math.random());
Stream<Double> stream = Stream.generate(Math::random);
类名:实例方法
// TreeSet<String> set = new TreeSet<>((s1,s2) -> s1.compareTo(s2));
/* 这里如果使用第一句话,编译器会有提示: Can be replaced with Comparator.naturalOrder,这句话告诉我们
String已经重写了compareTo()方法,在这里写是多此一举,这里为什么这么写,是因为为了体现下面
这句编译器的提示: Lambda can be replaced with method reference。好了,下面的这句就是改写成方法引用之后:
*/
TreeSet<String> set = new TreeSet<>(String::compareTo);
/* 这里如果使用第一句话,编译器会有提示: Can be replaced with Comparator.naturalOrder,这句话告诉我们
String已经重写了compareTo()方法,在这里写是多此一举,这里为什么这么写,是因为为了体现下面
这句编译器的提示: Lambda can be replaced with method reference。好了,下面的这句就是改写成方法引用之后:
*/
TreeSet<String> set = new TreeSet<>(String::compareTo);
Collectors
Collectors.joining(", ")
Collectors.toList()
Collectors.toSet() ,生成set集合
Collectors.toMap(MemberModel::getUid, Function.identity())
Collectors.toMap(ImageModel::getAid, o -> IMAGE_ADDRESS_PREFIX + o.getUrl()
函数式接口
消费型接口(需要一个参数,返回kong)
//消费型
Consumer consumer = new Consumer<>() {
@Override
public void accept(Object o) {
System.out.println("消费型接口");
}
};
Consumer consumer = new Consumer<>() {
@Override
public void accept(Object o) {
System.out.println("消费型接口");
}
};
供给型接口Supplier(不需要参数,返回一个值)
///供给型
Supplier supplier= new Supplier<String>() {
@Override
public String get() {
return "我是返回值";
}
};
Supplier <Integer> supplier = () -> 12;
Supplier <Integer> supplier = "aa"::hashCode;
Supplier supplier= new Supplier<String>() {
@Override
public String get() {
return "我是返回值";
}
};
Supplier <Integer> supplier = () -> 12;
Supplier <Integer> supplier = "aa"::hashCode;
函数型接口Function(需要一个参数,返回一个值)
//函数型
Function function = new Function() {
@Override
public Object apply(Object o) {
return null;
}
};
Function function = o -> null;
Function function = new Function() {
@Override
public Object apply(Object o) {
return null;
}
};
Function function = o -> null;
断言型接口Predicate(需要一个值,返回一个布尔类型的值)
//断言型
Predicate predicate = new Predicate() {
@Override
public boolean test(Object o) {
return false;
}
};
Predicate predicate = o -> false;
Predicate predicate = new Predicate() {
@Override
public boolean test(Object o) {
return false;
}
};
Predicate predicate = o -> false;
lamda
使用
list.forEach(n -> System.out.println(n));
list.forEach(System.out::println); // 使用方法引用
list.forEach(System.out::println); // 使用方法引用
要点
lambda内部可以使用静态、非静态和局部变量,这称为lambda内的变量捕获。
Lambda表达式在Java中又称为闭包或匿名函数,所以如果有同事把它叫闭包的时候,不用惊讶。
Lambda方法在编译器内部被翻译成私有方法,并派发 invokedynamic 字节码指令来进行调用
private static java.lang.Object lambda$0(java.lang.String);
lambda表达式有个限制,那就是只能引用 final 或 final 局部变量,这就是说不能在lambda内部修改定义在域外的变量
框架
Spring
Spring是什么
Spring是一个轻量级的Java框架,有IOC、AOP两大功能
IOC 控制反转
通过获取目标类的无参构造方法进行创建对象的
AOP 面向切面编程
注解
@Aspect
表名这是一个切面类
@Before
@pointcut
声明切那个包下哪个方法
@After
@around
前后都有
后置处理器
InitializingBean
afterPropertiesSet
BeanPostProcessor
postProcessBeforeInitialization
postProcessAfterInitialization
后置处理器的执行顺序
postProcessBeforeInitialization,然后是afterPropertiesSet,然后是init-method,然后是postProcessAfterInitialization
BeanFactoryPostProcessor
BeanDefinitionRegistryPostProcessor
ConfigurationClassPostProcessor
将配置类指定的类解析为BeanDefinition对象,并放入BeanFactory的beanDefinitionMap中
PlaceholderConfigurerSupport
处理占位符
异常
BeanDefinitionStoreException
AbstractBeanFactory
AbstractAutowireCapableBeanFactory
doCreateBean
populateBean()
进行bean的属性填充
如果标注有 @Autowired 注解或 autowire=“byType/byName” 标签,则根据 BY_NAME 或 BY_TYPE,提取相应依赖的 bean,并统一存入到 propertyValues 中
特点
非侵入性
基于Spring开发的应用开发的应用中的对象不依赖与Spring的API
控制反转
依赖注入
依赖的对象不需要手动调用setxx方法设值,通过配置赋值
面向切面编程
组件化
容器
一站式
ApplicationContext和Beanfactory的区别
1.二者都是最核心的接口,ApplicationContext是Beanfactory的一个子类
2.BeanFactory定义了IOC的基本功能,定义、创建、实例化、加载、依赖注入
Applicationcontext作为实现类,拓展了很多功能,像国际化
Applicationcontext作为实现类,拓展了很多功能,像国际化
3.Beanfactory中的bean是延迟加载的,只有等到真的调用bean时才会实例化
applicationContext是一次性加载所有的Bean
applicationContext是一次性加载所有的Bean
4.BeanFactory启动较快,但是当需要Bean的时候,还需要实例化
ApplicationContext一次性加载完全,启动时间较长,但后期使用较快
ApplicationContext一次性加载完全,启动时间较长,但后期使用较快
SpringMVC
三层
Model模型层
View视图层
Controller控制器
请求流程
mybatis
简述
mybatis是一个半ORM的框架,需要依赖配置信息或者注解
Hibernate是完全的ORM框架,是完全自动的,查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取
什么是ORM
ORM(Object/Relational Mapping)即对象关系映射,是一种数据持久化技术。它在对象模型和关系型数据库直接建立起对应关系,并且提供一种机制,通过JavaBean对象去操作数据库表的数据。 MyBatis通过简单的XML或者注解的方式进行配置和原始映射,将实体类和SQL语句之间建立映射关系,是一种半自动(之所以说是半自动,因为我们要自己写SQL)的ORM实现。
Mapper接口的工作原理
在 MyBatis 的初始化过程中,每个一个 XML 映射文件中的<select />、<insert />、<update />、<delete />标签,会被解析成一个 MappedStatement 对象,对应的 id 就是 XML 映射文件配置的 namespace+'.'+statementId,这个 id 跟 Mapper 接口中的方法进行关联
所以不可以定义重载方法
每个 Mapper 接口都会创建一个动态代理对象(JDK 动态代理),代理类会拦截接口的方法,找到对应的 MappedStatement 对象,然后执行数据库相关操作
流程图
MapperProxy 为 Mapper 接口的动态代理对象的代理类
#{}和${}的区别
#{} 是预编译处理,${}是字符串替换
Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
Mybatis在处理${}时,就是把${}替换成变量的值
使用#{}可以有效的防止SQL注入,提高系统安全性
PageHelper
使用
查询前需要PageHelper.startPage(pageNum,pageSize) 设置当前页和每页大小
会首先new一个page对象,将其放到threadLocal中
会新建一个拦截器,是Interceptor的实现类,拦截四参/六参的query方法,若args.length = 4 会加上两个参数,拦截Executor类型
如果允许count,会使用一个count语句查询总条数
如果不需要分页,会直接调用Executor.query查询
会自动给startPage下一个query语句进行分页操作
配置文件详解
pagehelper.helper-dialect=mysql
选择的数据库
pagehelper.reasonable=true
分页是否合理化
pagehelper.params=count=countSql
可以进行count语句的查询
pagehelper.support-methods-arguments=true
以接口的方式传递信息是被允许的
Springcloud
注解
JVM优化
页面置换算法
先进先出(FIFO)
最优置换(optimal)
最长最久未使用(LRU)
生命周期
1.编写Java 文件
2.编译器编译Java源文件生成class文件
3.JVM的类加载器装载.class文件,在堆中生成一个代表这个类的Class对象,作为方法区中这些数据的访问入口,并为变量分配相应的内存
加载
官方描述
JVM的主要目的是将字节码从各个位置(网络、磁盘)转换为二进制流加载到内存中,接着为这个类在JVM的方法区创建一个对应的class对象,这个class对象就是这个类各种数据的访问入口
1.通过类的全限定名来获取定义的二进制字节流
2.将这个字节流所代表的静态存储结构转换成方法区的运行时数据结构
3.在堆中生成一个代表这个类的class对象,作为方法区这些数据的访问入口
验证(连接)
为什么要验证
类加载器是个通用组件,加载别人编译的.class文件,避免异常错误,需要对文件进行验证
主要做什么
文件格式验证
元数据验证
字节码验证
符号引用验证
准备(连接)
为类变量(static)分配内存并设置初始值
初始值是按照数据类型默认赋值,不是代码中被显式赋的值
解析(连接)
虚拟机将常量池中的符号引用转化为直接引用的过程
符号引用
以一组符号描述所引用的目标,可以使任何形式的字面量,只要可以无歧义的定位到目标就好
直接引用
可以指向目标的指针、相对偏移量或者一个可以直接或者间接定位到目标的句柄
主要针对对象
类、接口、字段、类方法、接口方法、方法类型、方法句柄、调用点限定符
初始化
初始化步骤
1、假如这个类还没有被加载和连接,则程序先加载并连接该类
2、假如该类的直接父类还没有被初始化,则先初始化其直接父类
3、假如类中有初始化语句,则系统依次执行这些初始化语句
初始化时机
只有当对类的主动使用的时候才会导致类的初始化
主动使用
当虚拟机启动,先初始化main方法所在的类
new一个类的对象
调用类的静态成员(除了final常量)和静态方法
使用java.lang.reflect包的方法对类进行反射调用
当初始化一个类,如果其父类没有被初始化,则会先初始化它的父类
父类如果没有初始化,会先初始化父类
类变量初始化<clinit>
类的构造器方法
特点
保证父类的<clinit>方法执行完毕才会执行子类<clinit>方法
由于父类的<clinit>先执行,父类的静态代码块也先于子类执行
如果类中没有静态代码块,也没有为变量赋值,可以不生成<clinit>方法
虚拟机会保证在多线程环境下<clinit>方法能够被正确的加锁、同步,如果有多个线程执行<clinit>其他线程都会被阻塞,直至方法执行完毕。同时其他线程也不会执行<clinit>方法了,保证了同一个类加载器,一个类只会被初始化一次
实例变量初始化<init>
new对象,调用构造方法时才会执行
4.执行引擎
流程图
存储地址
笔记链接:https://note.youdao.com/s/PiHlFJrE
堆
一切new出来的对象,包含与之对应的class信息
所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
栈
栈中保存基本数据类型的值及对象及基础数据的引用
其他线程不可以访问
三部分
基本类型变量区
执行环境上下文
操作指令区
方法区(静态区)
所有线程共享
class文件、static变量与方法
双亲委派
设计模式
单例模式
优点
对于频繁使用的对象,可以省去new对象花费的时间
new对象次数减少,对内存的使用频率降低,降低GC的压力
适用对象:系统关键组件和被频繁使用的对象
饿汉式
代码
特点
当用户需要,调用getInstance马上返回一个单例对象
缺点
效率低
没有延迟加载
要点
构造函数是私有的
instance对象必须是static
懒汉式
代码
子主题
缺点
线程争抢cpu过程中,会出现非单例情况
所以要引入synchronized关键字修饰方法
在多线程情况下效率极低
双重加锁机制
代码
优化懒汉式
静态私有内部类
代码
优点
自动延迟加载
singleton类加载时,内部类并不会被初始化,只有当调用getInstance时才会加载SingletonHolder
线程友好
缺点
工厂模式
观察者模式
代理模式
静态代理
动态代理
概念
在运行时动态生成代理类,即代理类的字节码将在运行时生成并载入当前的classloader
优点
不需要写一个形式上完全一样的封装类
不需要为每一个接口都写代理方法
方便维护
可以再运行时指定代理类的执行逻辑,灵活性强
动态代理方式
JDK代理
实现InvocationHandler接口
重写invoke方法
获取代理对象(强制转换类型)
Proxy.newProxyInstance()需要三个参数
classLoader
ClassLoader.getSystemLoader()
代理的对象class数组
new Class[]{IDBQuery.class}
实现InvocationHandler接口的对象
new JDKQueryHandler()
CGlib代理
实现MethodInterceptor接口
重写invoke方法
获取代理对象
新建Enhancer对象
enhancer对象setCallable传入实现了MethodInterceptor的实现类对象
enhancer对象传入new Class[]{IDBQuery.class}对象
使用enhancer.create()再强制类型转换对象
Javaassist
ASM
享元模式
概述
如果在一个系统中存在多个相同的对象,那么只需要共享一份数据的拷贝,不必每一次都创建新的对象
由于我们需要构造和维护可以共享的变量,因为我们需要一个工厂类,用来维护和创建对象
主要作用
复用大对象(重量级对象),以此来节省内存空间和对象创建时间
优势
可以节省重复创建对象的开销,相同对象只会创建一次
创建的对象数减少,对系统内存的需求减少,GC的压力也减少,更健康的内存结构和更快的反应速度
角色
享元工厂
创建具体的享元对象,他保证相同的享元对象可以被系统共享,及内部使用了类似单例模式的算法,当已经存在对象时,直接返回对象,不再创建新对象
抽象享元
定义需要共享的业务接口,享元类被创建出来是为了实现某些特定的业务逻辑,而抽象享元定义了这些逻辑的语义
具体享元类
实现抽象享元类的接口,完成具体逻辑
主函数
使用享元模式的组件,通过享元工厂取得享元对象
流程
大体流程
享元工厂会维护一个对象列表,当任何组件尝试获取享元类时,如果请求的享元类已经被创建,则直接返回已有的享元类对象,如果没有,将会利用享元工厂创建,然后将对象添加到维护列表
流程图
装饰者模式
观察者模式
基础算法
贪心算法
前端
vue
ajax
jquery
前后端分离
远程方法调用
HttpCLient
Rpc远程调用
Redis
Redis
基础
概述
Redis是一个开源的、使用C语言编写的,支持网络交互的、可基于内存也可持久化的key-value数据库
整个数据库都放在内存中进行操作,定期通过异步操作把数据库中的数据flush到硬盘中进行保存
建议
key不要太长尽量不要超过1024字节,不仅会消耗内存也会降低查找的速率
key也不要太短,key太短可读性会降低
在一个项目中key最好使用统一的命名格式,例如user:1000:password
数据存放在内存中,这也是他速度快的原因
Redis 是单进程单线程的, redis 利用队列技术将并发访问变为串行访问, 消除了传统数据库串行控制的开销
数据结构
String
字符串、整数、浮点数、二进制
可对整个字符串或者一部分进行操作,比如执行自增和自减
特点
可修改,称为动态字符串(Simple Dynamic String 简称 SDS)
说是字符串,其实更像ArrayList,内部维护一个字节数组,并且在内部预分配一定的空间,以减少内存的频繁分配
分配机制
字符串的大小小于1MB,每次扩容只会加倍现有的空间
字符串的大小超过1MB,每次扩容1MB的空间
最大长度为512MB
数据结构
struct SDS{
T capacity; //数组容量
T len; //实际长度
byte flages; //标志位,低三位表示类型
byte[] content; //数组内容
}
capacity 和 len两个属性都是泛型,为更合理的使用内存,不同长度的字符串采用不同的数据类型表示,且在创建字符串的时候 len 会和 capacity 一样大,不产生冗余的空间,所以String值可以是字符串、数字(整数、浮点数) 或者 二进制
应用场景
存储key-value键值对
String存的是用户全部信息经过序列化后的字符串
常用命令
set [key] [value] 给指定key设置值(set 可覆盖老的值)
get [key] 获取指定key 的值
del [key] 删除指定key
exists [key] 判断是否存在指定key
mset [key1] [value1] [key2] [value2] ...... 批量存键值对
mget [key1] [key2] ...... 批量取key
expire [key] [time] 给指定key 设置过期时间 单位秒
setex [key] [time] [value] 等价于 set + expire 命令组合
setnx [key] [value] 如果key不存在则set 创建,否则返回0
incr [key] 如果value为整数 可用 incr命令每次自增1
incrby [key] [number] 使用incrby命令对整数值 进行增加 number
List
链表,每个链表的每个节点都包含一个字符串
从链表的两端推入或者弹出元素,根据偏移量来对链表进行修剪(trim),读取单个或者多个元素,根据值来查找或者移除元素
特点
LinkedList,链表结构,list的插入和删除都是常数级别
不单单是一个双向链表
数据量较少时
底层存储结构为一块连续内存,称之为ziplist(压缩列表),特点所有元素紧挨着一起储存,分配的是一块连续的内存
数据量较多时
变成quickelist(快速链表)结构
数据结构
struct ziplist<T>{
int32 zlbytes; //压缩列表占用字节数
int32 zltail_offset; //最后一个元素距离起始位置的偏移量,用于快速定位到最后一个节点
int16 zllength; //元素个数
T[] entries; //元素内容
int8 zlend; //结束位 0xFF
}
int32 zlbytes; //压缩列表占用字节数
int32 zltail_offset; //最后一个元素距离起始位置的偏移量,用于快速定位到最后一个节点
int16 zllength; //元素个数
T[] entries; //元素内容
int8 zlend; //结束位 0xFF
}
entry
struct entry{
int<var> prevlen; //前一个 entry 的长度
int<var> encoding; //元素类型编码
optional byte[] content; //元素内容
}
entry它的 prevlen 字段表示前一个 entry 的字节长度,当压缩列表倒着遍历时,需要通过这
个字段来快速定位到下一个元素的位置
个字段来快速定位到下一个元素的位置
常见场景
消息队列:lpop和rpush(或者反过来,lpush和rpop)能实现队列的功能
朋友圈的点赞列表、评论列表、排行榜:lpush命令和lrange命令能实现最新列表的功能,每次通过lpush命令往列表里插入新的元素,然后通过lrange命令读取最新的元素列表
粉丝列表、关注列表
常见命令
rpush [key] [value1] [value2] ...... 链表右侧插入
rpop [key] 移除右侧列表头元素,并返回该元素
lpop [key] 移除左侧列表头元素,并返回该元素
llen [key] 返回该列表的元素个数
lrem [key] [count] [value] 删除列表中与value相等的元素,count是删除的个数。 count>0 表示从左侧开始查找,删除count个元素,count<0 表示从右侧开始查找,删除count个相同元素,count=0 表示删除全部相同的元素
(PS: index 代表元素下标,index 可以为负数, index= 表示倒数第一个元素,同理 index=-2 表示倒数第二 个元素。)
lindex [key] [index] 获取list指定下标的元素 (需要遍历,时间复杂度为O(n))
lrange [key] [start_index] [end_index] 获取list 区间内的所有元素 (时间复杂度为 O(n))
ltrim [key] [start_index] [end_index] 保留区间内的元素,其他元素删除(时间复杂度为 O(n))
Set
包含字符串的无序收集器,并且包含的字符串都是唯一的
添加、获取、移除单个元素,检查一个元素是否存在于集合中,可以计算交集、并集、差集,从集合里随机获取元素
特点
类似HashSet,键值对无序、唯一,相当于一个特殊的Map,所有的value都是null
当集合的最后一个元素被移除之后,数据结构自动删除,内存回收
应用场景
好友、关注、粉丝、刚兴趣的人
sinter命令可以获得AB两个用户的共同好友
sismember命令可以判断A是否为B的好友
scard命令可以获取好友数量
关注时,smove命令可以将B从A的粉丝集合转移到A的好友集合
首页展示随机,set集合存放所有需要展示的内容,而randomenber命令可以从中随机获取几个
存储活动中奖的用户ID,有去重功能,可以保证用户不会中奖两次
共同好友,共同关注
常用命令
sadd [key] [value] 向指定key的set中添加元素
smembers [key] 获取指定key 集合中的所有元素
sismember [key] [value] 判断集合中是否存在某个value
scard [key] 获取集合的长度
spop [key] 弹出一个元素
srem [key] [value] 删除指定元素
Hash
包含键值对的无序散列表
添加、获取、移除单个键值对;获取所有键值对
特点
HashMap,数组+链表的结构,当发生Hash碰撞时会把元素追加到链表上
Redis中的hash中的value只能时字符串
实例
hset books java "Effective java" (integer) 1
hset books golang "concurrency in go" (integer) 1
hget books java "Effective java"
hset user age 17 (integer) 1
hincrby user age 1 #单个 key 可以进行计数 和 incr 命令基本一致 (integer) 18
hset books golang "concurrency in go" (integer) 1
hget books java "Effective java"
hset user age 17 (integer) 1
hincrby user age 1 #单个 key 可以进行计数 和 incr 命令基本一致 (integer) 18
与String的区别
String存的是用户全部信息经过序列化后的字符串,如果想要修改某个用户字段必须将用户信息字符串全部查询出来,解析成相应的用户信息对象,修改完后在序列化成字符串存入
hash可以只对某个字段修改,从而节约网络流量,不过hash内存占用要大于 String,这是 hash 的缺点
应用场景
购物车:hset [key] [field] [value] 命令, 可以实现以用户Id,商品Id为field,商品数量为value,恰好构成了购物车的3个要素
存储对象:hash类型的(key, field, value)的结构与对象的(对象id, 属性, 值)的结构相似,也可以用来存储对象
结构化的数据(序列化)
常用命令
hset [key] [field] [value] 新建字段信息
hget [key] [field] 获取字段信息
hdel [key] [field] 删除字段
hlen [key] 保存的字段个数
hgetall [key] 获取指定key 字典里的所有字段和值 (字段信息过多,会导致慢查询 慎用:亲身经历 曾经用过这个这个指令导致线上服务故障)
hmset [key] [field1] [value1] [field2] [value2] ...... 批量创建
hincr [key] [field] 对字段值自增
hincrby [key] [field] [number] 对字段值增加number
Zset(SortedSet)
字符串成员由浮点数分值之间的有序映射,元素的排列顺序由分值的大小决定
添加、获取、删除单个元素;根据分值范围(range)或者成员来获取元素
特点
类似于Set,保证内部valu的唯一性,另一方面它可以给每个value赋一个score,代表value的排序权重
内部实现使用的是一种叫做跳跃列表的数据结构
应用场景
排行榜,但是和list不同的是zSet可以实现动态排序
可以用来存储粉丝列表,value值是粉丝的用户ID,score是关注时间,我们可以对粉丝列表关注时间进行排序
存储学生成绩,value值为学生的ID,score是他的考试成绩
常用命令
zadd [key] [score] [value] 向指定key的集合中增加元素
zrange [key] [start_index] [end_index] 获取下标范围内的元素列表,按score 排序输出
zrevrange [key] [start_index] [end_index] 获取范围内的元素列表 ,按score排序 逆序输出
zcard [key] 获取集合列表的元素个数
zrank [key] [value] 获取元素再集合中的排名
zrangebyscore [key] [score1] [score2] 输出score范围内的元素列表
zrem [key] [value] 删除元素
zscore [key] [value] 获取元素的score
使用
配置文件
项目依赖
<!--redis依赖配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
properties配置
redis:
host: localhost # Redis服务器地址
database: 0 # Redis数据库索引(默认为0)
port: 6379 # Redis服务器连接端口
password: # Redis服务器连接密码(默认为空)
jedis:
pool:
max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
max-idle: 8 # 连接池中的最大空闲连接
min-idle: 0 # 连接池中的最小空闲连接
timeout: 3000ms # 连接超时时间(毫秒)
host: localhost # Redis服务器地址
database: 0 # Redis数据库索引(默认为0)
port: 6379 # Redis服务器连接端口
password: # Redis服务器连接密码(默认为空)
jedis:
pool:
max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
max-idle: 8 # 连接池中的最大空闲连接
min-idle: 0 # 连接池中的最小空闲连接
timeout: 3000ms # 连接超时时间(毫秒)
根节点自定义key配置
# 自定义redis key
redis:
key:
prefix:
authCode: "portal:authCode:"
expire:
authCode: 120 # 验证码超期时间
redis:
key:
prefix:
authCode: "portal:authCode:"
expire:
authCode: 120 # 验证码超期时间
常用操作
注入template
在使用@Resource注解注入RedisTemplate的时候需要注意
需要注入RedisTemplate,而且属性名必须为redisTemplate,因为需要按照名称注入
Resource和AutoWried注解的区别
Autowried注解默认(byType)类型进行注入
单个类型
注入成功
多个相同类型
根据名称注入
在Controller层中使用@qualifier(name)来制定需要注入的名称
在实现类中使用@primary注解标明为主要实现
代码
String
先获取valueOperation对象
存储数据
获取数据
设置超期时间
删除事件
自增操作
hash
获取ValueOperation对象
插入数据(hashkey,key,value)
获取数据
获取所有key值
set
获取ValueOperation对象
添加数据
删除key
删除key中的数据
获取key中的所有数据
查看key中是否存在某条数据
区分给定的两个集合
两个给定的集合的交集
获取指定集合中的几个随机元素
List
获取ValueOperation对象
左侧插入(leftPush)
右侧插入(rightPush)
左侧弹出(leftPush)
右侧弹出(rightPush)
截断(trim)
指定位置插入指定值(set(key,index,value))
zSet
获取ValueOperation对象
添加数据
移除数据
增加排序分数
随机元素(可不重复)
返回指定个数的元素
randomMember(s) (key,(count))
随机返回指定个数的不重复元素
distinctRandomMembers(K key, long count);
返回给定key的排名
正向排名
反向
返回指定分数区间里的所有元素
指定分数取区间里的元素数量
Long count(K key, double min, double max);
RedisTemplate
Redis与memcached的比较
优势
1、Memcached 所有的值均是简单的字符串, redis 作为其替代者, 支持更为丰富的数据类
2、Redis 的速度比 Memcached 快很
3、Redis 可以持久化其数据
区别
1、存储方式 Memecache 把数据全部存在内存之中, 断电后会挂掉, 数据不能超过内存大小。 Redis有部份存在硬盘上, 这样能保证数据的持久性
2、数据支持类型 Memcache 对数据类型支持相对简单。 Redis 有复杂的数据类型。
3、使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。 Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话, 会浪费一定的时间去移动和请求。
事务
概述
事务是一个单独的隔离操作,事务中的所有命令都睡被序列化、按顺序的执行,事务在执行的过程中,不会被其他客户端发送来的其他命令请求所打断
基础
Redis事务可以一次执行多个命令,并且带有三个重要保证
批量操作在发送EXEC命令前被放入队列缓存
收到EXEC命令后放入事务执行,事务中的任意命令执行失败,其余的命令依然被执行
在事务执行过程中,其他客户端提交的命令不会插入到哦事务执行命令序列中去
事务从开始到执行的三个阶段
开始事务
命令入队
执行事务
实例
以multi开始一个事务、然后将多个命令入队到事务中,最后由EXEC命令执行所有命令
注意点
单个redis命令是原子性的,但是Redis没有在事务上增加任何维持原子性的机制,所以Redis事务并不是原子性的
批量执行原子性操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会导致后面的指令不执行
相关命令
discard
取消事务、放弃执行事务块中所有命令
Exec
执行所有事务块的命令
multi
标记一个事务块的开始
watch
监视一个或多个key,如果事务执行前这个key被其他的命令所改动,事务将被打断
unwatch
取消watch命令对所有key的监视
ACID原则
原子性
Redis单条命令具有原子性,但事务不具备原子性,不支持回滚
一致性
子主题
隔离性
多个事务并发访问同一数据产生的问题,Redis是单进程单线程的操作
持久性
通过RDB、AOF可以实现
CAS
Watch可以监视key的值是否发生变化
分布式锁
特点
1. 多进程可见: 必须多个jvm都能去访问到该锁资源
2. 互斥: 锁资源必须是互斥
3. 高可用: 锁的稳定性要得到保证
4. 高性能: 加锁本来就会降低系统性能,如何保证
5. 安全性: 锁假如无法释放怎么办
实现
使用ReentrantLock
单体架构可以使用,多台服务器,不同端口会有并发风险
Redisson分布式锁
引入依赖
<!--redisson依赖-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>
注入
@Autowired
Redisson redisson;
Redisson redisson;
加锁
lock.lock()
解锁
finally{
if(lock.isLocked() && lock.isHeldByCurrentThread()){
lock.unlock();
}
}
if(lock.isLocked() && lock.isHeldByCurrentThread()){
lock.unlock();
}
}
持久化
AOF
RDB
高可用
集群
主从复制
哨兵机制
MySQL
存储引擎
myisam和innodb的区别
1.innodb支持事务、myisam不支持事务
2.innodb支持外键、myisam不支持外键
3.innodb行(默认)、表锁,myisam表锁
4.Innodb是聚集索引,使用B+树作为索引结构,数据文件是和(主键)索引绑在一起的(表数据文件就是按B+Tree组织的一个索引结构,必须要有主键,但是辅助索引需要两次查询,第一次查询主键、再通过主键查找数据)
Myisam是非聚集索引,也是使用B+Tree作为索引结构,索引和数据都是分离的,索引保存的是数据文件的指针。主键索引和辅助索引都是独立的
Myisam是非聚集索引,也是使用B+Tree作为索引结构,索引和数据都是分离的,索引保存的是数据文件的指针。主键索引和辅助索引都是独立的
5.在不加where条件时,Innodb由于没有count的变量查询全表行数速度要比Myisam的速度要慢
6.Innodb不支持全文索引,Myisam支持全文索引,5.7之后innodb也支持了
7.Innodb必须有唯一索引(如主键),用户没有指定的话,会生成一个隐藏列row_id充当主键,而myisam可以没有
选择方式
是否要支持事务,如果要请选择innodb,如果不需要可以考虑MyISAM;
如果表中绝大多数都只是读查询,可以考虑MyISAM,如果既有读也有写,请使用InnoDB。
系统奔溃后,MyISAM恢复起来更困难,能否接受;
MySQL5.5版本开始Innodb已经成为Mysql的默认引擎
聚集索引
聚集索引定义了表中数据的物理存储顺序,索引顺序和物理顺序一直,Innodb聚集索引的叶子结点存储行记录
因此Innodb只能有一个聚集索引,是基于B+Tree
规则
如果定义了主键,那么主键作为聚集索引
如果没有定义主键,该表的第一个唯一非空索引被作为狙击索引
如果没有主键也没有合适的唯一索引,会生成一个隐藏的主键列作为聚集索引,是递增的
innodb引擎的4大特性
插入缓冲(insert buffer)
目的
提升插入性能
具体步骤
只对于非聚集索引(非唯一)的插入和更新有效,对于每一次的插入不是写到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,如果在则直接插入;若不在,则先放到Insert Buffer 中,再按照一定的频率进行合并操作,再写回disk。这样通常能将多个插入合并到一个操作中,目的还是为了减少随机IO带来性能损耗
优化
change Buffer
区别
insert buffer只对插入有效
change buffer对修改操作都有效
条件
非聚集索引
非唯一索引
二次写(double write)
用户需要一个页的副本,当写入失效发生时,先通过页的副本来还原该页,再进行重做
自适应哈希索引(ahi)
预读(read ahead)
线性预读(linear read-ahead)
随机预读(randomread-ahead)
InnoDB为什么推荐使用自增ID作为主键
自增ID可以保证每次插入时B+索引是从右边扩展的,可以避免B+树和频繁合并和分裂(对比使用UUID)。如果使用字符串主键和随机主键,会使得数据随机插入,效率比较差
优化的七个步骤
SQL优化
索引优化
表结构优化
事务处理
锁表
事务执行过程中,表会被锁定,其他用户请求只有等到当前事务解除之后,才能继续执行
系统配置的优化
操作系统的连接数
断开连接的资源回收的设置
打开文件的数量的限制
MySQL连接数的限制
内存设置慢查询的设置
硬件的优化
服务器硬件和网络带宽的优化
索引失效
口诀
模型数空运最快
模
模糊查询%开头,索引会失效
型
数据类型错误,索引也会失效
函数
对索引的字段使用内部字段,索引也会失效
应该建立基于函数的索引
空
索引不存储空值,如果不限制not null,数据库会任务索引类存在空值的情况,也不会按照索引进行计算
运
对索引列进行加减乘除等四则运算会导致索引失效
最
最左原则
如果索引列不是最左列,无法使用索引
快
数据库觉得全表扫描更快,就不会使用索引
优化
索引知识点
select * from table where name='XXX' and age= 18;
索引 idx_name_age(name,age)
索引 idx_name_age(name,age)
回表查询
由于select *
命中索引后,数据库还必须回去聚集索引中查找其他数据
命中索引后,数据库还必须回去聚集索引中查找其他数据
索引覆盖
select name,age
name,age在索引中都能找到,不需要回表
name,age在索引中都能找到,不需要回表
最左前缀原则
最左优先,上面的索引相当于 name索引和(name,age)索引
索引下推优化
Index Condition Pushdown
ICP的诞生主要是为了进一步提高B+Tree索引查询的可用性
name like 'XX%' and age= 18
模糊匹配后结果变成无序,所以后面条件无法再使用到索引,
因此需回表提取出name like 'XX'结果集后,再通过普通查询得到age = 18的最终结果
因此需回表提取出name like 'XX'结果集后,再通过普通查询得到age = 18的最终结果
引入ICP后
在索引内部取到name结果之后,就顺便判断了结果中的age是否等于18,对于不等于18的记录直接跳过,
因此在index(name,age)这棵索引树中直接匹配到了结果记录,减少了完整查询记录(一条完整元组)读取的个数,
此时拿着结果集的id去主键索引树中回表查询全部数据,减少了二次查询时间,I/O次数也会减少
因此在index(name,age)这棵索引树中直接匹配到了结果记录,减少了完整查询记录(一条完整元组)读取的个数,
此时拿着结果集的id去主键索引树中回表查询全部数据,减少了二次查询时间,I/O次数也会减少
SELECT
语法
执行顺序
SQL优化
避免不走索引
尽量避免通配符在前的模糊查询,如like '%XX'
尽量避免使用in/not in
连续的可以换成between
子查询可以换 exists
尽量避免使用or
可以用union
尽量避免NULL值
可以设置默认值0
查询条件避免等号左侧做运算、使用函数操作
避免使用<>或!=
避免类型转换,如varchar类型字段,查询条件用123
orderby要和where条件一致
正确使用hint优化语句
如FORCE INDEX
避免走索引
数据量小的
不常用的列
频繁更新的列
差异性小的列
select语句优化
避免select *
不需要的列会增加数据传输时间和网络开销
对于无用的大字段,如 varchar、blob、text,会增加 io 操作
失去MySQL优化器“覆盖索引”策略优化的可能性
多表级联,小表在前
使用表别名
where替代having
调整where条件顺序
MySQL采用从左往右,自上而下的顺序解析where子句。
根据这个原理,应将过滤数据多的条件往前放,最快速度缩小结果集。
根据这个原理,应将过滤数据多的条件往前放,最快速度缩小结果集。
性能分析
Explain
id
select_type
关联类型,决定访问表的方式
SIMPLE
简单查询,没有子查询和union
PRIMARY
如果不是SIMPLE,最外层被标记为PRIMARY
....
table
type
system>const>eq_ref>ref>fulltext>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>ALL
eq_ref
主键或唯一索引,最多返回一条数据
ref
多条数据,普通索引
range
范围查询
ALL
全表扫描
possible_key
可以使用哪些索引
key
实际决定使用哪个索引
key_len
索引字段的可能最大长度,不是表中实际数据使用的长度
ref
表示key展示的索引实际使用的列或者常量
rows
查询数据需要读取的行数,只是一个预估的数值,但是能很直观的看出SQL的优劣了
filtered
表示针对符合查询条件的记录数的百分比估算,用rows和filtered相乘可以计算出关联表的行数
Extra
解析查询的附加额外信息
Using index
使用覆盖索引
Using index condition
使用索引下推
索引下推简单来说就是加上了条件筛选,减少了回表的操作
Using where
where过滤
....
Git
分区
workspace:工作区
staging area:暂存区/缓存区
local repository:版本库或本地仓库
remote repository:远程仓库
常用命令
git init
初始化仓库
git add
添加文件到暂存区
git commit
将暂存区添加到仓库中
git push
上传远程代码合并
git pull
下载远程代码并合并
git rm
将文件从暂存区和工作区删除
git mv
移动或者重命名工作区文件
git merge + 分支名
合并
Elastic Search
0 条评论
下一页