Java学习记录
JavaEE
1.Java语言的特点,其与C++等其他高级语言的区别
2.JVM、JRE、JDK的理解=>什么是字节码?采用字节码的好处是什么?为什么说Java语言编译与解释并存?
JVM、JRE、JDK是三个不同的概念,JVM是运行Java字节码的Java虚拟机,不同的系统具有不同的JVM,这使得同一字节码可以在不同操作系统中运行的到相同的结果,是Java具有跨平台性的主要原因。
JDK是 Java开发工具箱,拥有JRE所拥有的一切,并且能够自己开发创建一个新的Java程序。
JRE是运行时环境,只能运行Java程序。
3.基本语法?
- 字符常量和字符串常量的区别
- continue、break、return(循环、方法的跳出)
- final、finally、finalize
- 静态方法与非静态实例方法的区别?关键字static作用
- 重载与重写
- ==和equals区别,另hashCode与equals关系
1.区别分基本数据类型和引用类型
2.两个相等对象的hashCode必相等,但两个hashCode相等的对象却不一定相等,还需要equals方法来判断具体内容。hashCode就是为了提高equal效率的。 - 自动拆箱与装箱及原理(装箱:valueOf方法,拆箱:xxxValue方法)
- 成员变量与局部变量的区别:语法形式、存储方式、生存时间、默认值
- 对象和引用的概念
- String、StringBuffer、StringBuilder的区别?
String | 父类 | 可变性 | 线程安全 | 性能 | 使用总结 |
---|---|---|---|---|---|
String | String | 不可变 | 线程安全 | 每改变一次生成一个新的对象,指针指向新的String对象 | 少量String数据 |
StringBuilder | AbstractStringBuilder | 可变 | 非线程安全 | 对本身对象进行操作,效率比StringBuffer高10%~15% | 单线程操作字符串缓冲区下操作大量数据 |
StringBuffer | AbstractStringBuilder | 可变 | 线程安全(同步锁) | 对StringBuffer对象本身进行操作 | 多线程操作字符串缓冲区下操作大量数据 |
- 接口和抽象类的异同点
1.接口是规范,是某种动作约束;抽象类是所属关系,强调代码复用。
2.只可以继承一个类,但可以实现多个接口
3.接口中的成员变量只能是 public static final 类型的,且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值
4.深拷贝和浅拷贝?什么是引用拷贝?
5.寄存器、堆、栈、常量池、静态域:
- 寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制.
- 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆或者常量池中。
- 堆:存放所有new出来的对象。
- 静态域:存放静态成员(static定义的)
- 常量池:存放字符串常量和基本类型常量(public static final)。
栈和常量池中的对象可以共享,而堆中的对象不可以共享。栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会消失。堆中的对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定,具有很大的灵活性。
对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。
6.泛型(或者你的项目中哪里用到了泛型)?常用的通配符?(T,E,K V,?)
7.反射、注解、异常
通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
- 类的加载过程:程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾),接着我们使用java命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中,此过程称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例
获取反射的四种方式:1)知道具体类的情况;
2)通过类的全路径获取
3)通过对象实例获取
4)通过类加载器传入类路径获取
注解:只有在被解析之后才会生效,常见的解析方法有两种:
1)编译期直接扫描 :编译器在编译 Java 代码的时候扫描对应的注解并处理,
比如某个方法使用@Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
2)运行期通过反射处理 :像框架中自带的注解(比如 Spring 框架的 @Value 、@Component)都是通过反射来进行处理的。
异常与错误的区别(程序是否可处理)、异常的种类
8.序列化?反序列化?transient关键字?
- 序列化:将数据结构或对象转化为二进制字节流
- 反序列化:序列化的逆过程
- transient:标注此变量不可序列化
- 序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中
9.Java采用的是值传递方式?
10.BigDecimal
11.IO模型
参考
12.IO流的分类?为什么有了字节流还需要字符流?(肯定是和字符编码所占字节数目相关)
- 操作方式分类:
- 操作对象分类javaGuide
Collection
1.Collection接口: 主要用于存放单一元素
- List接口:存储有序、可重复的数据。
- Set接口:存储无序的、不可重复的数据
- Queue接口:按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的。
2.Map接口:存放键值对,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。
Collection | ||||
---|---|---|---|---|
List | ArrayList | LinkedList | Vector | |
Set | HashSet | LinkedHashSet | TreeSet | |
Queue | Queue | ArrayDeque | PriorityQueue | |
Map | HashMap | Hashtable | LinkedHashMap | TreeMap |
ps :Properties也是Map接口,底层继承Hashtable
3.子接口的异同?选择集合/Map依据,主要从线程安全、效率、底层数据结构、初始化及实现线程安全加锁方式多个方面讨论,例
- HashSet/LinkedHashSet/TreeSet异同
1) 都是Set接口的实现类,都不是线程安全的且都能保证唯一性;
2) HashSet、LinkedHashSet 和 TreeSet 的主要区别在于底层数据结构不同。
HashSet 的底层数据结构是哈希表(基于 HashMap 实现)。
LinkedHashSet 的底层数据结构是链表和哈希表,元素的插入和取出顺序满足FIFO。
TreeSet 底层数据结构是红黑树,元素是有序的,排序的方式有自然排序和定制排序。
3) 底层数据结构不同又导致这三者的应用场景不同。
HashSet 用于不需要保证元素插入和取出顺序的场景,
LinkedHashSet 用于保证元素的插入和取出顺序满足 FIFO 的场景,
TreeSet 用于支持对元素自定义排序规则的场景
- Queue和Duque异同
- HashMap和Hashtable异同
- ConcurrentHashMap和Hashtable的区别
- 其他有关扩容机制、为什么HashMap长度是2 的幂次方?
- ConcurrentHashMap
JUC
1.进程、线程及其区别
进程是程序的一次执行过程,是系统运行的基本单位。线程是比进程更小的执行单位,一个进程在其执行过程中可以产生多个线程,但进程中多个线程共享进程的堆和方法区,每个线程还有自己专属的程序计数器、虚拟机栈和本地方法栈。
2.线程的生命周期和状态
3.sleep和wait:最主要的区别在于前者没有释放锁,后者释放了锁
- sleep是Thread的静态方法,wait 是 Object 的方法,任何对象实例都能调用。
- sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在 synchronized 中)。
- 它们都可以被interrupted方法中断。
• 两者都可以暂停线程的执行。
• wait() 通常被用于线程间交互/通信,sleep() 通常被用于暂停执行。
• wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout) 超时后线程会自动苏醒。
4.并发和并行:并发:都在执行不一定是同时;并行:同时执行
5.为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
new 一个 Thread,线程进入了新建状态。调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 但是,直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
6.什么是死锁以及如何避免死锁?线程死锁描述的是这样一种情况:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。造成死锁需要满足4个条件缺一不可:互斥,占有且等待,不可抢占,循环等待。如何写一个死锁?
public class DeadLock {
public static void main(String[] args) {
Object object1 = new Object();
Object object2 = new Object();
Thread thread1 = new Thread(new FirstThread(object1, object2));
Thread thread2 = new Thread(new SecondThread(object1, object2));
thread1.start();
thread2.start();
}
}
class FirstThread implements Runnable {
Object object1;
Object object2;
public FirstThread(Object object1, Object object2) {
this.object1 = object1;
this.object2 = object2;
}
@Override
public void run() {
synchronized (object1) {// 获得锁对象1
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (object2) {// 获得锁对象2
}
}
}
}
class SecondThread implements Runnable {
Object object1;
Object object2;
public SecondThread(Object object1, Object object2) {
this.object1 = object1;
this.object2 = object2;
}
@Override
public void run() {
synchronized (object2) {// 获得锁对象2
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (object1) {// 获得锁对象1
}
}
}
}
7.为什么使用多线程呢?使用多线程可能带来什么问题?比如:内存泄漏、死锁、线程不安全等等。
8.什么是上下文切换?
9.多线程编程步骤
1)创建资源类,在资源类创建属性和操作方法 资源类的操作方法:判断、干活、通知
2)创建多个线程,调用资源类的操作方法
3)防止虚假唤醒问题
10.sychronized和Lockd的区别
- Lock 是一个接口,而 synchronized 是 Java 中的关键字
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
- Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
- 通过Lock可以知道有没有成功获取锁,而 synchronized 却无法办到。
- Lock可以提高多个线程进行读操作的效率。在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于synchronized。
11.synchronized 实现同步的基础:Java 中的每一个对象都可以作为锁。
具体表现为以下 3 种形式。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的 Class 对象。
对于同步方法块,锁是 Synchonized 括号里配置的对象
12.双重校验锁实现对象单例模式(线程安全:会写volatile and synchronized)
13.synchronized底层原理(JVM层面)、JDK1.6之后对其对优化
14.ReentrantLock和synchronized区别:可重入锁,依赖有所不同,前者比后者增加了一些高级功能
15.volatile关键字:除了防止JVM的指令重排,还保证变量的可见性。
- CPU Cache 缓存的是内存数据用于解决 CPU 处理速度和内存不匹配的问题,内存缓存的是硬盘数据用于解决硬盘访问速度过慢的问题。
- Java 内存模型抽象了线程和主内存之间的关系,就比如说线程之间的共享变量必须存储在主内存中。Java 内存模型主要目的是为了屏蔽系统和硬件的差异,避免一套代码在不同的平台下产生的效果不一致。
- 在 JDK1.2 之前,Java 的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。要解决这个问题,就需要把变量声明为 volatile ,这就指示 JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。
16.比较synchronized和volatile
- volatile 关键字是线程同步的轻量级实现,所以 volatile 性能肯定比synchronized关键字要好 。但是 volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块 。
• volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
• volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。
17.ThreadLocal与内存泄漏问题
- 线程中的资源通常是共享的,ThreadLocal使每个线程拥有自己的专属本地变量。
- 创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal变量名的由来。他们可以使用 get() 和 set() 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。
- ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。
- 可以通过调用ThreadLocalMap中的set()、get()、remove() 方法清理掉 key 为 null 的记录,解决内存泄漏。使用完 ThreadLocal方法后最好再手动调用remove()方法
项目中threadlocal的应用:
/**
* 持有用户信息,用于代替session对象.
*/
@Component
public class HostHolder {
private ThreadLocal<User> users = new ThreadLocal<>();
public void setUser(User user) {
users.set(user);
}
public User getUser() {
return users.get();
}
public void clear() {
users.remove();
}
}
18.线程池
newCachedThreadPool、newFixedThreadPool、newSingleThreadExecutor 底层都是ThreadPoolExecutor。
• int corePoolSize线程池的核心线程数
• int maximumPoolSize能容纳的最大线程数
• long keepAliveTime 空闲线程存活时间
• TimeUnit unit 存活的时间单位
• BlockingQueue workQueue存放提交但未执行任务的队列
• ThreadFactory threadFactory 创建线程的工厂类
• RejectedExecutionHandler handler 等待队列满后的拒绝策略,队列、最大线程数都满了之后就开始执行拒绝策略:
1)CallerRunsPolicy:既不会抛弃任务,也不会抛出异常,而是将某些任务退回到调用者,从而降低新任务的流量。只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大
2)AbortPolicy:丢弃任务,并抛出拒绝执行 RejectedExecutionException异常信息。
线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
3)DiscardPolicy:直接丢弃,其他啥都没有
4)DiscardOldestPolicy:当触发拒绝(饱和)策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入
使用线程池的优点?
• 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
• 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
• 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
19.Atomic原子类
20.AQS
21.CAS的底层原理,及悲观锁:synchronized,乐观锁CAS的区别,CAS的应用
22.集合的线程安全问题与解决:lock.newCondition()可以获得await和notify
ArrayList线程不安全问题的解决:Vector,Collections、CopyOnWriteArrayList(重点)
HashSet:CopyOnWriteArraySet
HashMap:CopyOnWriteHashMap
- volatile:动态数组与线程安全,下面从“动态数组”和“线程安全”两个方面进一步对CopyOnWriteArrayList 的原理进行说明。
动态数组”机制
o- 它内部有个“volatile 数组”(array)来保持数据。在“添加/修改/删除”数据时,都会新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给“volatile 数组”, 这就是它叫做 CopyOnWriteArrayList 的原因
o- 由于它在“添加/修改/删除”数据时,都会新建数组,所以涉及到修改数据的操作,CopyOnWriteArrayList 效率很低;但是单单只是进行遍历查找的话,效率比较高。
· “线程安全”机制
o- 通过 volatile 和互斥锁来实现的。
o- 通过“volatile 数组”来保存数据的。一个线程读取 volatile 数组时,总能看到其它线程对该 volatile 变量最后的写入;就这样,通过 volatile 提供了“读取到的数据总是最新的”这个机制的保证。
o- 通过互斥锁来保护数据。在“添加/修改/删除”数据时,会先“获取互斥锁”,再修改完毕之后,先将数据更新到“volatile 数组”中,然后再“释放互斥锁”,就达到了保护数据的目的。
22.Callable与Runnable接口的比较和关系:Runnable接口有实现类FutureTask实现类,FutureTask构造可以传递Callable
1)实现方法是否有返回值(R无C有)
2)是否抛出异常(R无C有)
3)实现方法名称不同,一个是run方法,一个是call方法
4)工具类Executors可以实现R-》C的转换
23.FutureTask原理:在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成。
当主线程将来需要时,就可以通过 Future 对象获得后台作业的计算结果或者执行状态
• 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
• 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞get方法。一旦计算完成,就不能再重新开始或取消计算。get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常
• get 只计算一次,因此get方法放到最后
24.JUC的三大辅助类
CountDownLatch:定义初始值,调用方法建议,实现等待
CountDownLatch类可以设置一个计数器,然后通过countDown方法来进行减1的操作,使用await方法等待计数器不大于0,然后继续执行await方法之后的语句。
· CountDownLatch主要有两个方法,当一个或多个线程调用 await 方法时,这些线程会阻塞
· 其它线程调用countDown方法会将计数器减 1(调用 countDown 方法的线程不会阻塞)
· 当计数器的值变为 0 时,因await方法阻塞的线程会被唤醒,继续执行
CyclicBarrier循环栅栏:设置固定值,创建CyclicBarrier对象,集齐龙珠过程并等待,许愿。
CyclicBarrier看英文单词可以看出大概就是循环阻塞的意思,在使用中 CyclicBarrier的构造方法第一个参数是目标障碍数,每次执行CyclicBarrier一次障碍数会加一,如果达到了目标障碍数,才会执行cyclicBarrier.await()之后的语句。可以将CyclicBarrier理解为加1操作。
Semaphore信号灯:创建Semaphore对象设置许可数量,6个线程模拟6辆车
Semaphore 的构造方法中传入的第一个参数是最大信号量(可以看成最大线程池),每个信号量初始化为一个最多只能分发一个许可证。使用acquire方法获得许可证,release方法释放许可
25.待补充:读写锁(ReadWriteLock)(重要) :一个资源可以被多个读线程访问,或者可以被一个写线程访问,但是不能同时存在读写线程,读写锁互斥的,读读是可以共享,提升性能。
悲观锁:synchronized乐观锁CAS:表锁:
行锁:比表锁更容易发生死锁
读锁:共享锁,发生死锁
写锁:独占锁,发生死锁
锁降级(ReentrantReadWriteLock):将写锁降级为读锁。但注意:读锁不能升级为写锁。
获取读锁-》获取写锁-》释放读锁-》释放写锁
在线程持有读锁的情况下,该线程不能取得写锁(因为获取写锁的时候,如果发现当前的读锁被占用,就马上获取失败,不管读锁是不是被当前线程持有)。
· 在线程持有写锁的情况下,该线程可以继续获取读锁(获取读锁时如果发现写锁被占用,只有写锁没有被当前线程占用的情况才会获取失败)。
原因: 当线程获取读锁的时候,可能有其他线程同时也在持有读锁,因此不能把获取读锁的线程“升级”为写锁;而对于获得写锁的线程,它一定独占了读写锁,因此可以继续让它获取读锁,当它同时获取了写锁和读锁后,还可以先释放写锁继续持有读锁,这样一个写锁就“降级”为了读锁。
SQL
1.DB、DBS、DBMS、DBA
2.什么是元组(行)、码(属性、列)、候选码(某一属性或属性组)、主码、外码、主属性、非主属性。
3.什么是ER图
4.数据库范式:
第一范式:属性不能再被分割;第二范式:在1NF的基础上,消除了非主属性对于码的部分函数依赖;第三范式:在2NF的基础之上,消除了非主属性对于码的传递依赖
函数依赖:x->y;部分函数依赖:(x, y)->z, x->z, y->z;完全函数依赖(与部分函数依赖作对比);传递函数依赖:x->y, y->z => x->z
5.存储过程
6.drop、delete与truncate区别:1)用法 2)不同的数据库语言 3)执行速度不同
7.数据库设计通常分为哪几步? 1)需求分析 2)概念->逻辑->物理结构设计 3)数据库实施 4)数据库的运行和维护
8.何为字符集、常见的字符集有哪些?
9.存储引擎
MyISAM和InnoDB的区别? 其他Archive:用于数据存档,插入和查询;Blackhole引擎:丢弃写操作,读操作会返回空内容;CSV引擎:不支持索引;Memory引擎:hash;Federated引擎:远程表
10.锁机制与InnoDB锁算法
数据库的锁机制,是悲观锁。可通过自定义锁定义乐观锁
11.事务?数据事务的实现原理,和分布式事务的区别?关系型事务的ACID特性?并发事务带来哪些问题?事务的隔离级别有哪些?
事务有ACID四个特性,A原子性,事务不可再分割,是一个整体,要么一起完成,要么一起都不完成;C一致性,有点类似于能量守恒;I隔离性,操作互不影响;D持久性,能够持久保存吧啦的。
实现原理:
- MySQL InnoDB 引擎使用 redo log(重做日志) 保证事务的持久性,使用 undo log(回滚日志) 来保证事务的原子性。
- MySQL InnoDB 引擎通过锁机制、MVCC 等手段来保证事务的隔离性( 默认支持的隔离级别是 REPEATABLE-READ )。保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障
并发事务带来的问题:脏读、不可重复度、幻读
隔离级别
• READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
• READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
• REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
• SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
12.索引及索引作用?索引的底层数据结构
索引是一种用于快速查询和检索数据的数据结构。常见的索引结构有: B 树, B+树和 Hash。 索引的作用就相当于目录。打个比方: 字典目录、图书馆查书目录。
-
为什么不使用Hash其作为索引的数据结构呢?
Hash 冲突问题
Hash 索引不支持顺序和范围查询 -
B-Tree和B+Tree都是指的B+Tree,在 MySQL 中,MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构,但是,两者的实现方式不太一样。
MyISAM 引擎中, 叶子节点的 data 域存放数据记录的地址(.MYD数据和.MYI索引)。在索引检索的时候,首先按照 B+Tree 搜索算法搜索索引,如果指定的 Key 存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这也被称为 “非聚簇索引”。
InnoDB 引擎中,其数据文件本身就是索引文件.idb,(索引文件和数据文件合一)。相比 MyISAM索引文件和数据文件是分离的,其表数据文件本身就是按 B+Tree 组织的一个索引结构。叶子节点 data 域保存了完整的数据记录。这个索引的 key 是数据表的主键,因此 InnoDB 表数据文件本身就是主索引。这被称为 “聚簇索引(或聚集索引)”,而其余的索引都作为辅助索引,辅助索引的 data 域存储相应记录主键的值而不是地址,这也是和 MyISAM 不同的地方。在根据主索引搜索时,直接找到 key 所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。 -
索引类型:主键索引(Primary Key)、二级索引(唯一索引UNIQUE、普通索引INDEX、前缀索引、全文索引FULLTEXT等)
-
聚集索引与非聚集索引优缺点
聚集索引的优点
1 聚集索引的查询速度非常的快,因为整个 B+树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。
聚集索引的缺点
1 依赖于有序的数据 :因为 B+树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或 UUID 这种又长又难比较的数据,插入或查找的速度肯定比较慢。
2 更新代价大 : 如果对索引列的数据被修改时,那么对应的索引也将会被修改,而且聚集索引的叶子节点还存放着数据,修改代价肯定是较大的,所以对于主键索引来说,主键一般都是不可被修改的。
非聚集索引的优点
更新代价比聚集索引要小 。非聚集索引的更新代价就没有聚集索引那么大了,非聚集索引的叶子节点是不存放数据的
非聚集索引的缺点
1 跟聚集索引一样,非聚集索引也依赖于有序的数据
2 可能会二次查询(回表) :这应该是非聚集索引最大的缺点了。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。 但一定会回表吗?
13.MySQL三大日志详解
1).redo log
刷盘时机:InnoDB 存储引擎为 redo log 的刷盘策略提供了 innodb_flush_log_at_trx_commit 参数,它支持三种策略:
• 0 :设置为 0 的时候,表示每次事务提交时不进行刷盘操作
• 1 :设置为 1 的时候,表示每次事务提交时都将进行刷盘操作(默认值)
• 2 :设置为 2 的时候,表示每次事务提交时都只把 redo log buffer 内容写入 page cache
另外,InnoDB 存储引擎有一个后台线程,每隔1 秒,就会把 redo log buffer 中的内容写到文件系统缓存(page cache),然后调用 fsync 刷盘。还有一种情况,当 redo log buffer 占用的空间即将达到 innodb_log_buffer_size 一半的时候,后台线程会主动刷盘。用 redo log 形式记录修改内容,性能会远远超过刷数据页的方式,这也让数据库的并发能力更强。
2).undo log
undo log会产生redo log,作用1:回滚数据,作用2:MVCC,作用
3).binlog
在执行更新语句过程,会记录redo log与binlog两块日志,以基本的事务为单位,redo log在事务执行过程中可以不断写入,而binlog只有在提交事务时才写入,所以redo log与binlog的 写入时机 不一样。
binlog与redolog对比 :
redo log 它是 物理日志 ,记录内容是“在某个数据页上做了什么修改”,属于 InnoDB 存储引擎层产生的。
binlog 是 逻辑日志 ,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于MySQL Server 层。
14.数据库操作行为规范
- 超 100 万行的批量写 (UPDATE,DELETE,INSERT) 操作,要分批多次进行操作。
-由于大批量操作会产生大量日志,传输和恢复所需的时间相对较长,就会造成严重的主从延迟。且大批量的写操作一般都需要执行一定长的时间, 而只有当主库上执行完成后,才会在其他从库上执行,所以会造成主库与从库长时间的延迟情况
避免产生大事务操作
-大批量修改数据,一定是在一个事务中进行的,会造成表中大批量数据进行锁定,从而导致大量的阻塞,特别是长时间的阻塞会占满所有数据库的可用连接,使生产环境中的其他应用无法连接到数据库,从而对 MySQL 的性能产生非常大的影响。- 对于大表使用 pt-online-schema-change 修改表结构
• 避免大表修改产生的主从延迟
• 避免在对表字段进行修改时进行锁表
对大表数据结构的修改一定要谨慎,会造成严重的锁表操作,尤其是生产环境,是不能容忍的。
pt-online-schema-change 它会首先建立一个与原表结构相同的新表,并且在新表上进行表结构的修改,然后再把原表中的数据复制到新表中,并在原表中增加一些触发器。把原表中新增的数据也复制到新表中,在行所有数据复制完成之后,把新表命名成原表,并把原来的表删除掉。把原来一个 DDL 操作,分解成多个小的批次进行。- 禁止为程序使用的账号赋予 super 权限
• 当达到最大连接数限制时,还运行 1 个有 super 权限的用户连接
• super 权限只能留给 DBA 处理问题的账号使用- 对于程序连接数据库账号,遵循权限最小原则
• 程序使用数据库账号只能在一个 DB 下使用,不准跨库
• 程序使用的账号原则上不准有 drop 权限
15.锁(线程=事务):表级锁、页级锁、行锁
select … lock in share mode:对记录加 S 锁,其它事务也可以加S锁,如果加 x 锁则会被阻塞
select … for update、insert、update、delete:对记录加 X 锁,且其它事务不能加任何锁
对于一致性非锁定读(Consistent Nonlocking Reads)的实现,通常做法是加一个版本号或者时间戳字段,在更新数据的同时版本号 + 1 或者更新时间戳。查询时,将当前可见的版本号与对应记录的版本号进行比对,如果记录的版本小于可见版本,则表示该记录可见
在 InnoDB 存储引擎中,多版本控制 (multi versioning)就是对非锁定读的实现。如果读取的行正在执行 DELETE 或 UPDATE 操作,这时读取操作不会去等待行上锁的释放。相反地,InnoDB 存储引擎会去读取行的一个快照数据,对于这种读取历史数据的方式,我们叫它快照读 (snapshot read)
在 Repeatable Read 和 Read Committed 两个隔离级别下,如果是执行普通的 select 语句(不包括 select … lock in share mode ,select … for update)则会使用一致性非锁定读(MVCC)。并且在 Repeatable Read 下 MVCC 实现了可重复读和防止部分幻读+NextKeyLock
(MVCC)(偏底层、问得比较深):
MVCC在Innodb的实现原理:MVCC 的实现依赖于:隐藏字段(DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID)、快照Read View、undo log。在内部实现中,InnoDB 通过数据行的 DB_TRX_ID 和 Read View 来判断数据的可见性,如不可见,则通过数据行的 DB_ROLL_PTR 找到 undo log 中的历史版本。每个事务读到的数据版本可能是不一样的,在同一个事务中,用户只能看到该事务创建 Read View 之前已经提交的修改和该事务本身做的修改。
JDBC
SSM
SSM框架是众多框架一种,spring MVC ,spring和mybatis框架的整合,是标准的MVC模式,将整个系统划分为表现层,controller层,service层,DAO层四层。使用spring MVC负责请求的转发和视图管理,spring实现业务对象管理,mybatis作为数据对象的持久化引擎。
附:SSM框架与SSH框架的对比总结
Spring
容器框架:
框架核心IOC:核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。大白话:创建bean对象,装配他们并管理他们的生命周期。
1.通过IOC创建一个Bean与new一个对象的区别
2.IOC中Bean的生命周期
3.如何注入
4.如何解决循环依赖问题
5.延迟加载?
AOP:Spring AOP模块为基于Spring的应用程序中的对象提供了事务管理服务。通过使用Spring AOP,不用依赖EJB组件,就可以将声明性事务管理集成到应用程序中。
5.AOP源码常用方法
6.Spring中用到的设计模式及设计实现方式待补充
- 代理模式 — 在 AOP 和 remoting 中被用的比较多
- 单例模式 — 在 Spring 配置文件中定义的 Bean 默认为单例模式
- 工厂模式 — BeanFactory 用来创建对象的实例
- 模板方法 — 用来解决代码重复的问题。比如 RestTemplate、JmsTemplate、JdbcTemplate
- 前端控制器 — Spring提供了 DispatcherServlet 来对请求进行分发
- 视图帮助(View Helper) — Spring 提供了一系列的 JSP 标签,高效宏来辅助将分散的代码整合在视图里
- 依赖注入 — 贯穿于 BeanFactory / ApplicationContext 接口的核心理念
7.Spring事务管理
SpringMVC
服务端的三层架构:数据层、业务层、表现层
原理图
1.Spring MVC响应原理,用自己的大白话描述
2.拦截器
- 继承HandlerInterceptor,重写三个方法
- 过滤器和拦截器区别
- 过滤器、监听器及拦截器对比
- 过滤器、拦截器、监听器对应应用
3.RequestMapping
value | method | params | headers |
---|---|---|---|
必须设置,value是请求的匹配映射地址,可以是字符串数组表示能匹配多个请求地址 | 请求方式标识get,post,默认get | 通过请求的请求参数匹配映射,四种格式 | 与params相似,只不过是按照要求的请求头信息匹配 |
4.不同请求方式的区别: |
八种请求类型的介绍
- GET请求
从服务器取回数据。只是取回数据,并不会产生其他影响。
例如用GET请求访问/employee/101/张三,可以取回该员工的详细资料。- POST请求
创建一个实体,也就是一个没有ID的资源。一旦这个请求成功执行了,就会在HTTP请求的响应中返回这个新创建的实体的ID。我们通常用POST请求来上传文件或者表单。
例如用POST请求访问/employee/102/李四,将会创建一个ID为102的新员工。- PUT请求
用来更新一个已有的实体。通过把已经存在的资源的ID和新的实体用PUT请求上传到服务器来更新资源。
例如用PUT请求访问/employee/101/王五,可以更新员工101的信息- DELETE请求
从服务器上删除资源。需要把要删除的资源的ID上传给服务器
例如用DELETE请求访问/employee/101/王五,可以删除员工101的信息- TRACE请求
提供一种方法来测试当一个请求发生的时候,服务器通过网络收到的内容。所以它会返回你发送的内容。- HEAD请求
HEAD请求和GET请求资源类似,但仅仅返回相应的头部,没有具体的响应体。它也不会对服务器造成其他影响- OPTIONS请求
OPTIONS允许客户端请求一个服务所支持的请求方法。它所对应的响应头是Allow,它非常简洁地列出了支持的方法。下面为服务端成功处理了OPTIONS请求后,响应的内容:
Allow: HEAD,GET,PUT,DELETE,OPTIONS- CONNECT请求
主要用来建立一个对资源的网络连接。一旦建立连接后,会响应一个200状态码和一条"Connectioin Established"的消息。
5.获取请求参数的方法
- 通过servlet API:将HttpServletRequest作为控制器方法的形参,此时HttpServletRequest类型的参数表示封装了当前请求的请求报文的对象
@RequestMapping("/testParam")
public String testParam(HttpServletRequest request)
{ String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username:"+username+",password:"+password);
return "success"; }
- 通过控制器:在控制器方法的形参位置,设置和请求参数同名的形参,当浏览器发送请求,匹配到请求映射时,在DispatcherServlet中就会将请求参数赋值给相应的形参
/testParam(username=‘admin’,password=123456)}–>请求地址
@RequestMapping("/testParam")
public String testParam(String username, String password){
System.out.println("username:"+username+",password:"+password);
return "success"; }
- 通过@RequestParam:将请求参数和控制器方法的形参创建映射关系,有value参数名和required两种属性
- 通过@RequestHeader:将请求头信息和控制器方法的形参创建映射关系
- 通过@CookieValue:是将cookie数据和控制器方法的形参创建映射关系
- 通过POJO:可以在控制器方法的形参位置设置一个实体类类型的形参,此时若浏览器传输的请求参数的参数名和实体类中的属性名一致,那么请求参数就会为此属性赋值
<form th:action="@{/testpojo}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
性别:<input type="radio" name="sex" value="男">男<input type="radio" name="sex" value="女">女<br> 年龄:<input type="text" name="age"><br>
邮箱:<input type="text" name="email"><br> <input type="submit">
</form>
@RequestMapping("/testpojo")
public String testPOJO(User user){
System.out.println(user);
return "success"; }
6.向域对象传递共享数据
向request域共享数据
- 使用Servlet API
- 使用ModelAndView
- 使用Model向request
- 使用map向request
- 使用ModelMap向request
index.html
<body>
<h1>This is index.html</h1>
<a th:href="@{/testRequestAPI}">使用RequestAPI向request域中传值</a><br>
<a th:href="@{/modelAndView}">使用modelAndView向request域中传值</a><br>
<a th:href="@{/model}">使用model向request域中传值</a><br>
<a th:href="@{/map}">使用map向request域中传值</a><br>
<a th:href="@{/modelMap}">使用modelMap向request域中传值</a><br>
</body>
success.html
<body>
<h2>success</h2>
<br>
<p th:text="${requestScope}"></p>
</body>
@Controller
public class ScopeController {
@RequestMapping("/testRequestAPI")
public String testRequestAPI(HttpServletRequest request){
request.setAttribute("requestScope","hello,RequestAPI");
return "success";
}
@RequestMapping("/modelAndView")
public ModelAndView testModelAndView(){
ModelAndView mav = new ModelAndView();
mav.setViewName("success");
mav.addObject("requestScope","hello,session");
return mav;
}
@RequestMapping("/model")
public String testModel(Model model){
model.addAttribute("requestScope","hello,model");
return "success";
}
@RequestMapping("/map")
public String testMap(Map<String,Object> map){
map.put("requestScope","hello,map");
return "success";
}
@RequestMapping("/modelMap")
public String testMap(ModelMap modelMap){
modelMap.addAttribute("requestScope","hello,modelMap");
return "success";
}
}
向session域共享数据:(HttpSession session)
向application域共享数据:(HttpSession session),ServletContext application = session.getServletContext();
7.HttpMessageConverter
8.ajax
9.DispatcherServlet源码解析
Mybatis
mybatis是对jdbc的封装,它让数据库底层操作变的透明。mybatis的操作都是围绕一个sqlSessionFactory实例展开的。mybatis通过配置文件关联到各实体类的Mapper文件,Mapper文件中配置了每个类对数据库所需进行的sql语句映射。在每次与数据库交互时,通过sqlSessionFactory拿到一个sqlSession,再执行sql命令.原文链接
1.Mybatis实现,具体编程步骤
2.#{} 和 ${} 的区别是什么?
3.当实体类中的属性名和表中的字段名不一样 ,怎么办
4.在 Mapper 中如何传递多个参数
5.映射原理,Mybatis接口和xml文件?和注解映射方式?(@select,@insert,@update,@delete,@Options声明主键)
6.如何分页?分页插件原理?
7.与JDBC相比,Mybatis优点
SpringBoot
Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。
1.具体配置部署过程
- 使用Spring Initializr启动项目环境
- 创建controller、dao、pojo、service目录
2.SpringBoot核心注解?由哪几个注解组成?提供了哪些核心功能?
3.如何理解SpringBoot中的starter?常见的starter及其提供的功能有哪些?
4.自动装配原理?
5.springboot部署STMP发送邮件=>QQMail
6.统一异常管理
7.通过AOP统一记录日志
others
设计模式及源码
JVM
JVM内存结构包括五大区域:程序计数器、虚拟机栈、本地方法栈、堆区、方法区。其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生、随线程而灭。而Java堆区和方法区内存的分配和回收是动态的,在回收时需要通过算法判断该对象是否存活。
1.判断对象是否存活
- 算法一:引用计数算法
- 算法二:可达性分析算法
- 算法三:
2.垃圾回收相关算法
3.垃圾收集器
4.对象的引用
5.GC的触发
参考
类加载机制
maven
git
1.相关命令
git init
git reflog
git reset --hard 版本号
git status
git add
git commit
git push
git clone
git branch 分支名
git branch -v
git checkout 分支名
git merge 分支名
解决冲突
2.SSH免密设置
ssh-keygen -t rsa -C 835174530@qq.com
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/liqi/.ssh/id_rsa): /Users/liqi/.ssh/id_rsa
Created directory '/Users/liqi/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /Users/liqi/.ssh/id_rsa
Your public key has been saved in /Users/liqi/.ssh/id_rsa.pub
3.idea集成git
Redis
1.安装目录
2.启动命令
redis-server redis.conf路径名称=>/usr/local/etc/redis.conf
redis-cli
ping #测试 = pong
shutdown #关闭
3.五大常用数据类型+Redis6的新数据类型
4.Jedis
5.Redis事务特性
6.Redis持久化
- 过期策略
- 淘汰策略
7.Redis应用中的问题及解决方案 - 缓存穿透
大量查询不存在的数据,使得请求直达存储层,导致负载过大,甚至宕机,解决方案:
- 缓存空对象:存储层未命中后,仍然将空值存入缓存层。再次
- 布隆过滤器:将所有存在的key提前存入布隆过滤器,在访问存储层之前,先通过过滤器拦截,若请求的是不存在的key,则直接返回空值。
- 缓存击穿
对于高使用、高访问量的数据,在缓存失效的瞬间,会有大量的请求直达存储层,导致服务崩溃,解决方案:
- 加互斥锁,对数据的访问加互斥锁,当一个线程访问该数据时,其他线程只能等待。这个线程访问过后,缓存中的数据将被重建,届时其他线程就可以直接从缓存取数据。
- 永不过期,不设置过期时间,所以不会出现上述问题,这是物理上的不过气,为每个value设置过期时间,当发现该逻辑值过期时,使用单独的线程重建缓存
- 缓存雪崩
- 由于某些原因,缓存层不能提供服务,导致所有的请求直达存储层,造成存储层宕机。解决方案:
- 避免同时过期,设置过期时间时,附加一个随机数,避免大量key同时过期
- 构件高可用的Redis缓存,部署多个Redis实例,个别节点宕机,依然可以保持服务的整体可用
- 构件多级缓存,增加本地缓存,在存储层前面多加一级屏障,降低请求直达存储层的几率
- 启用限流和降级措施,对存储层增加限流措施,当请求超出限制时,对其提供降级服务。
- 分布式锁
场景:修改时,需要先将数据读到内存,在内存中修改后再存回去。分布式应用中,可能多个进程同时执行上述操作,而读取和修改非原子操作,所以会产生冲突。增加分布式锁,可以解决此类问题。
原理:同步锁,在多个线程都能访问的地方做一个标记,标识该数据的访问权限
分布式锁,在多个进程能访问到的地方做一个标记,标识该数据的访问权限
实现方式:基于数据库实现分布式锁,基于Redis实现分布式锁,基于Zookeeper实现分布式锁
8.集群
9.主从复制
10.redis commands
11.配置
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 设置key的序列化方式
template.setKeySerializer(RedisSerializer.string());
// 设置value的序列化方式
template.setValueSerializer(RedisSerializer.json());
// 设置hash的key的序列化方式
template.setHashKeySerializer(RedisSerializer.string());
// 设置hash的value的序列化方式
template.setHashValueSerializer(RedisSerializer.json());
template.afterPropertiesSet();
return template;
}
}
# RedisProperties
#总共16个库
spring.redis.database=11
spring.redis.host=localhost
spring.redis.port=6379
12.利用redis做缓存
Thymeleaf
Web/HTML
1.基本性质:简单、扩展,无状态有会话
2.cookie:服务器发起到浏览器并保存在浏览器的一小块数据,并且在浏览器下次访问该服务器时,会自动携带该数据,将其发送给服务器
- 创建cookie
- 设置cookie生效范围
- 设置cookie生存时间
- 发送cookie
3.session:服务端记录客户端信息,session依赖存于cookie
4.servlet生命周期
Linux
netty
kafka
参考
Kafka是一个分布式的、可分区的、可复制的消息系统。它提供了普通消息系统的功能,但具有自己独特的设计。在论坛项目中,Kafka主要用于对系统通知发布生产者和用户接收系统通知的消费者构建。
1.阻塞队列BlockingQueue=》Array/Linked/PriorityBlockingQueue-SynchronousQueue-DelayQueue
2.命令行启动kafka
/usr/local/opt/kafka/bin/zookeeper-server-start /usr/local/etc/kafka/zookeeper.properties
sudo /usr/local/opt/kafka/bin/kafka-server-start /usr/local/etc/kafka/server.properties
/usr/local/opt/kafka/bin/kafka-topics --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic my_test
/usr/local/opt/kafka/bin/kafka-topics --list --bootstrap-server localhost:9092
/usr/local/opt/kafka/bin/kafka-console-producer --broker-list localhost:9092 --topic my_test
/usr/local/opt/kafka/bin/kafka-console-consumer --bootstrap-server localhost:9092 --topic my_test --from-beginning
3.整合Spring
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=community-consumer-group
spring.kafka.consumer.enable-auto-commit=true
spring.kafka.consumer.auto-commit-interval=3000
4.生产消费模式
docker
dubbo
数据结构
常见排序算法
快速排序
核心思想:找到每个基准值的准确位置。
归并排序
希尔排序
敏感词过滤算法:KMP,前缀树,子字符串匹配算法
Kaptcha
使用:jar包,配置类
1.生成随机图片和文字
AJAX
异步处理,不用刷新页面直接显示
Elasticsearch
注意与jdk版本匹配,需要修改配置中data和log的路径
curl -X GET "localhost:9200/_cat/health?v"
#epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
#1648535023 06:23:43 my-application green 1 1 3 3 0 0 0 0 - 100.0%
curl -X GET "localhost:9200/_cat/nodes?v"
#ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
#127.0.0.1 12 99 68 17.59 cdfhilmrstw * liqideAir
curl -X GET "localhost:9200/_cat/indices?v"
#health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
#green open .geoip_databases AlAXVQbTRsqVLUoskWQWdQ 1 0 44 0 41.5mb 41.5mb
curl -X PUT "localhost:9200/test"
#{"acknowledged":true,"shards_acknowledged":true,"index":"test"}%
curl -X GET "localhost:9200/_cat/indices?v"
#health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
#green open .geoip_databases AlAXVQbTRsqVLUoskWQWdQ 1 0 44 0 41.5mb 41.5mb
#yellow open test BJYY6KxSS7mG6Qd5Jl5i-w 1 1 0 0 226b 226b
curl -X DELETE "localhost:9200/test"
spring.data.elasticsearch.cluster-name=nowcoder
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
NettyRuntime
//netty 启动冲突问题
搜索功能,条件
搜索功能
牛客网论坛项目逻辑梳理
项目整体框架
权限
一、会话管理
三种常用的会话管理
核心
一、敏感词算法
二、事务
性能
一、数据结构
二、Redis实现缓存、点赞
通知
一、kafka分布式系统消息通知发布
搜索
一、索引