java基础

重写hashcode和equals需要注意什么?
重写equals的时候必须重写hashcode方法;
重写的equals方法需要保证下面5个特性:

  1. 自反性:对于任何非空引用 x,x.equals(x) 应该返回 true
  2. 对称性:对于任何引用 x 和 y,当且仅当 y.equals(x) 返回 true,x.equals(y) 也应该返回 true。
  3. 传递性:对于任何引用 x、y 和 z,如果 x.equals(y)返回 true,y.equals(z) 也应返回同样的结果。
  4. 一致性:如果 x 和 y 引用的对象没有发生变化,反复调用 x.equals(y) 应该返回同样的结果。
  5. 对于任意非空引用 x,x.equals(null) 应该返回 false。

重写hashcode的注意事项:

  1. 返回的hash值是int类型,防止溢出。
  2. 不同的对象返回的hash值尽量不同。
  3. 如果 x.equals(y)返回 true,那么x和y的hashcode也应该相同,反之不一定。
  4. 如果引用的对象没有发生变化,那么他们的hashcode也不应该发生变化。

object你知道的方法
1.clone方法
保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
主要是JAVA里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用传递,我们有时候不希望在方法里将参数改变,这是就需要在类中复写clone方法。
2.getClass方法
final方法,获得运行时类型。
3.toString方法
该方法用得比较多,一般子类都有覆盖。
4.finalize方法
该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。
5.equals方法
该方法是非常重要的一个方法。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法。
6.hashCode方法
该方法用于哈希查找,可以减少在查找中使用equals的次数,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。
一般必须满足obj1.equals(obj2)==true。可以推出obj1.hashCode()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。
如果不重写hashcode(),在HashSet中添加两个equals为true的对象,会将两个对象都加入进去。
7.wait方法
wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生。
(1)其他线程调用了该对象的notify方法。
(2)其他线程调用了该对象的notifyAll方法。
(3)其他线程调用了interrupt中断该线程。
(4)时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。
8.notify方法
该方法唤醒在该对象上等待的某个线程。
9.notifyAll方法
该方法唤醒在该对象上等待的所有线程。

设计模式

单例模式
单例模式

集合

HashMap的原理,源码,ConcurrentHashMap

HashMap增删的情况后端数据结构如何位移?

HashMap容量为什么是2的幂

多线程

线程与进程,并行与并发?
进程代表一个运行中的程序,是资源分配和调度的基本单位,有三大特性:
1、独立性:独立的资源,私有的地址空间,进程间互不影响;
2、动态性:进程具有生命周期;
3、并发性:多进程可以在单核CPU上并发运行;
线程代表一个进程中的一个顺序执行流,多线程就是一个进程中的多个顺序执行流。线程又被称为轻量级进程,是系统运行的基本单位。
多线程的优势(进程线程区别):
1、进程之间不能共享内存,线程之间共享内存更容易,多线程可协作完成进程工作;
2、创建进程进行资源分配的代价较创建线程要大得多,所以多线程在高并发环境中效率更高。

并行与并发:
并行是指:多核多CPU或多机器处理同一段处理逻辑的时候,同一时刻多个执行流共同执行。
并发是指:通过CPU的调度算法,使用户感觉像是同时处理多个任务,但同一时刻只有一个执行流占用CPU执行。即使多核多CPU环境还是会使用并发,以提高处理效率。
主要的CPU调度算法有如下两种:
1、分时调度:每个线程轮流获取CPU使用权,各个线程平均CPU时间片。
2、抢占式调度:Java虚拟机使用的就是这种调度模型。这种调度方式会根据线程优先级,先调度优先级高的线程,如果线程优先级相同,会随机选取线程执行。

创建线程的两种方式:
1、继承Thread类,重写run()方法,然后直接调用该类.start()方法。
步骤为:
(1)创建一个类继承Thread类,重写run()方法,将所要完成的任务代码写进run()方法中;
(2)创建Thread类的子类的对象;
(3)调用该对象的start()方法,该start()方法表示先开启线程,然后调用run()方法;

  1. public class Thread1 {
  2. public static void main(String[] args) {
  3. Thread.currentThread().setName("主线程");
  4. System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
  5. //创建一个新线程
  6. ThreadDemo1 thread1 = new ThreadDemo1();
  7. //为线程设置名称
  8. thread1.setName("线程一");
  9. //开启线程
  10. thread1.start();
  11. }
  12. }
  13. class ThreadDemo1 extends Thread{
  14. @Override
  15. public void run() {
  16. System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
  17. }
  18. }

2、实现Runnable接口,实现run()方法。Runnable实例对象作为Thread构造方法中的target参数传入,充当线程执行体。这种方式适用于多个线程共享资源的情况。
执行的话必须new Thread(该类的实例).start();方法
步骤为:
(1)创建一个类并实现Runnable接口
(2)重写run()方法,将所要完成的任务代码写进run()方法中
(3)创建实现Runnable接口的类的对象,将该对象当做Thread类的构造方法中的参数传进去
(4)使用Thread类的构造方法创建一个对象,并调用start()方法即可运行该线程

public class Thread2 {
    public static void main(String[] args) {
        Thread.currentThread().setName("主线程");
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
        //创建一个新线程
        Thread thread2 = new Thread(new ThreadDemo2());
        //为线程设置名称
        thread2.setName("线程二");
        //开启线程
        thread2.start();
    }
}
class ThreadDemo2 implements Runnable { 
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
    }    
}

推荐使用实现接口的方式,他比继承类的方式更灵活,也能减少程序之间的耦合度,面向接口编程也是设计模式6大原则的核心
一般面试回答上面两种就可以,如果面试官还是接着问的话,可以扩展下面的两种:
3、实现Callable接口
步骤为:
(1)创建一个类并实现Callable接口
(2)重写call()方法,将所要完成的任务的代码写进call()方法中,需要注意的是call()方法有返回值,并且可以抛出异常
(3)如果想要获取运行该线程后的返回值,需要创建Future接口的实现类的对象,即FutureTask类的对象,调用该对象的get()方法可获取call()方法的返回值
 (4)使用Thread类的有参构造器创建对象,将FutureTask类的对象当做参数传进去,然后调用start()方法开启并运行该线程。

public class Thread3 {   
    public static void main(String[] args) throws Exception {
        Thread.currentThread().setName("主线程");
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
        //创建FutureTask的对象
        FutureTask<String> task = new FutureTask<String>(new ThreadDemo3());
        //创建Thread类的对象
        Thread thread3 = new Thread(task);
        thread3.setName("线程三");
        //开启线程
        thread3.start();
        //获取call()方法的返回值,即线程运行结束后的返回值
        String result = task.get();
        System.out.println(result);

    }
}

class ThreadDemo3 implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
        return Thread.currentThread().getName()+":"+"返回的结果";
    }
}

4、使用线程池创建
使用线程池创建线程的步骤:
(1)使用Executors类中的newFixedThreadPool(int num)方法创建一个线程数量为num的线程池
(2)调用线程池中的execute()方法执行由实现Runnable接口创建的线程;调用submit()方法执行由实现Callable接口创建的线程
(3)调用线程池中的shutdown()方法关闭线程池
这种实际上底层就是2、3方法然后通过线程池创建吧,不能算新的方法吧。

简述线程的生命周期
image.png
1、新建态,创建具有线程执行体的Thread对象,就进入了新建态。
2、就绪态,调用Thread对象的start()方法,就会为线程分配线程私有的方法栈、程序计数器资源,如果得到CPU资源,线程就会由就绪态转为运行态。换句话说,就绪态的线程获得了除CPU之外的所有必须资源。
3、运行态,就绪态线程得到CPU资源就会转为运行态,执行run()方法。当然,在调用yield()线程让步的情况,线程会由运行态转到就绪态,但这个过程可能是及其短暂的,如果当前线程拥有较高的优先级,即使让步后,它也会直接转为运行态。
4、阻塞态,会导致阻塞态的方法主要有:sleep()方法、wait()方法、join()方法、等待获取锁、等待IO等情况。在这些情况被处理后,就会转为就绪态,等待调度。
5、终止态,包括第四个问题所说的几种情况。

Volatile关键字的作用?**
1.保证了线程的可见性,即在多线程中,每次读到volatile修饰的变量,一定是最新的数据;基于CPU的缓存一致性协议实现的
2.禁止指令重排序;基于内存屏障实现的
应用的话,单例模式中的双检锁,CAS

线程锁 lock synchronize

几种线程池的区别?
1、newFixedThreadPool 定长线程池
一个有指定的线程数的线程池,有核心的线程,里面有固定的线程数量,响应的速度快。正规的并发线程,多用于服务器。固定的线程数由系统资源设置。核心线程是没有超时机制的,队列大小没有限制,除非线程池关闭了核心线程才会被回收。
2、newCachedThreadPool 可缓冲线程池
只有非核心线程,最大线程数很大,每新来一个任务,当没有空余线程的时候就会重新创建一个线程,这边有一个超时机制,当空闲的线程超过60s内没有用到的话,就会被回收,它可以一定程序减少频繁创建/销毁线程,减少系统开销,适用于执行时间短并且数量多的任务场景。
3、ScheduledThreadPool 周期线程池
创建一个定长线程池,支持定时及周期性任务执行,通过过schedule方法可以设置任务的周期执行
4、newSingleThreadExecutor 单任务线程池
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行,每次任务到来后都会进入阻塞队列,然后按指定顺序执行。

并发工具包有哪些,具体什么用?

死锁的场景以及如何避免

jvm

jvm内存分区,为什么要有新生代和老年代

有做过jvm内存优化吗,怎么做

jvm场景问题,标记清楚多次后老年代产生内存碎片,引起full gc,接下来可能发生什么问题

jvm如何进行垃圾回收

算法

讲一下稳定的排序算法和不稳定的排序算法

讲一下快速排序的思想

说下对AIO,NIO,BIO的了解,nio的核心概念有哪些
AIO,同步非阻塞式IO,一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问。
BIO,同步阻塞式IO,一个连接一个线程,这个线程只针对这个连接而存在,专注于它的收发,如果没有数据读入它就一直阻塞等待。当然可以通过线程池改善。
NIO,异步非阻塞式IO,一个有效请求一个线程,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作。

框架

springmvc

springmvc的原理
image.png
1.用户发送请求至前端控制器(DispatcherServlet,也叫中央控制器);
2.前端控制器收到请求后,调用处理器映射器(HandlerMapping)请求获取Handler;
3.处理器映射器根据请求url找到具体的处理器(Handler,可以根据xml配置,注解进行查),生成处理器对象(Handler)及处理器拦截器(如果有则生成)返回给前端控制器(DispatcherServlet);
4.前端控制器调用处理器适配器(HandlerAdapter)请求执行Handler;
5.处理器适配器(HandlerAdaper)经过适配调用具体的Handler处理器(也叫后端控制器,Controller);
6.Handler(处理器)执行完成返回MondelAndView;
7.处理器适配器(HandlerAdatper)将处理器的执行结果ModelAndView返回给前端控制器(DispatcherServlet);
8.前端控制器将modelAndView传给视图解析器(viewResolver);
9.视图解析器解析后返回具体的View;
10.前端控制器根据View进行渲染视图(即将模型数据填充至视图);
11.前端处理器相应结果给用户;
其中:
1、前端控制器DispatcherServlet(不需要工程师开发),由框架提供
作用:接收请求,响应结果,相当于转发器,中央处理器。有了dispatcherServlet减少了其它组件之间的耦合度。
用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。
2、处理器映射器HandlerMapping(不需要工程师开发),由框架提供
作用:根据请求的url查找Handler
HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
3、处理器适配器HandlerAdapter
作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
4、处理器Handler(需要工程师开发)
注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler
Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。
5、视图解析器View resolver(不需要工程师开发),由框架提供
作用:进行视图解析,根据逻辑视图名解析成真正的视图(view)
View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。
一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。
6、视图View(需要工程师开发jsp…)
View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf…)

补充:
MVC:MVC是一种设计模式
MVC的原理图:
面试专题1 - 图3
分析:
M-Model 模型(完成业务逻辑:有javaBean构成,service+dao+entity)
V-View 视图(做界面的展示 jsp,html……)
C-Controller 控制器(接收请求—>调用模型—>根据结果派发页面)

SpringMVC的优点:
1.支持各种视图技术,不仅仅是JSP;
2.与spring框架集成(IOC容器,Aop等);
3.清晰的角色分配,前端控制器,处理器映射器,处理器适配器,处理器,视图解析器;
4.支持各种请求资源的映射策略;

Spring MVC的主要组件?

上面的简化描述版本
(1)前端控制器 DispatcherServlet(不需要程序员开发)
作用:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。
(2)处理器映射器HandlerMapping(不需要程序员开发)
作用:根据请求的URL来查找Handler
(3)处理器适配器HandlerAdapter
注意:在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以正确的去执行Handler。
(4)处理器Handler(需要程序员开发)
(5)视图解析器 ViewResolver(不需要程序员开发)
作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view)
(6)视图View(需要程序员开发jsp)
View是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf等等)
SpringMVC

SpringMVC怎么设定重定向和转发的
转发:在返回值前面加“forward:”,例如“forward:user.do?name=method4”;
重定向:在返回值前面加“redirect:”,例如“redirect:http://www.baidu.com”;

SpringMVC和AJAX怎么相互调用
通过Jackson框架就可以吧JAVA里面的对象直接转化成js可以识别的Json对象。具体步骤:
1.加入Jackson的jar包;
2.在配置文件配置json的映射;
3.在接收Ajax方法里面可以直接返回Object,List等,但在方法前面要加上@ResponseBody注解;

mybatis

1.什么是mybatis?
(1)mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注sql语句本身,不需要花精力去处理加载驱动、创建连接、创建statement等繁杂的过程,程序要直接编写原生态sql,可以严格控制sql的执行性能,灵活度高。
(2)mybatis可以使用xml或者注解来配置和映射原生信息,将POJO映射成数据库中的记录,避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。
(3)通过xml文件或者注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终的sql语句,最后又mybatis框架执行sql并将结果映射为JAVA对象并返回

2.mybatis的优缺点:
优点:
(1)基于sql语句编程,相当灵活,不会对应用程序或者数据库的设计造成任何影响,sql写在xml中,接触了sql和代码的耦合,便于统一管理;提供xml标签,支持编写动态sql,并可以重用;
(2)与JDBC相比,减少了大量的代码量,不需要手动开关连接;
(3)很好的与各种数据库兼容;
(4)能够和spring很好的集成;
(5)提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护;
总结下就是:方便,灵活,与spring集成好,和数据库兼容好,提供了映射标签等。
缺点:
(1)sql编写的工作了较大,尤其是字段多、关联表多的情况,对开发人员的编写sql语句的功底有一定的要求;
(2)sql语句依赖数据库,导致数据库移植性差,不能随便更改数据库。

3.适用场景:
(1)mybatis专注于sql本身,是一个足够灵活的DAO层解决方案;
(2)对性能要求高,或者需要变化较多的项目,如互联网项目;

4.mybatis与hibernate的异同:
(1)mybatis不是一个完全的ORM框架,需要程序员自己编写sql;
(2)mybatis直接编写原生态sql,可以严格控制sql性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁。但是mybatis无法做到数据库无关性,如果要实现支持多种数据库,则需要定义多套映射文件,工作量大。
(3)hibernate的对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件适合用hibernate,可以节省很多代码,提高效率;

5.#{ }和${ }的区别?
#{ } 相当于一个占位符,相当于JDBC的?,可以有效防止sql注入;
${ } 字符拼接指令,如果入参为普通数据类型,{ }内部只能写value;

6.通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
Dao接口即Mapper接口,接口的全限名,就是映射文件中的namespace的值;接口的方法名,就是映射文件中Mapper的Statement的ID值;接口方法内的参数,就是传递给sql的参数。Mapper接口的原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql的执行结果返回。方法不能被重载,因为使用 全限名+方法名 的保存和寻找策略。

7.Mybatis是如何进行分页的?分页插件的原理是什么?
使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。也可以在sql内直接书写带有物理分页的参数来完成物理分页,也可以使用分页插件(PageHelper)来完成物理分页。
分页插件的基本原理是使用Mybatis的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据数据库方言(dialect),添加对应的物理分页语句和物理分页参数。

8.Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
第一种是使用标签,逐一定义数据库列名和对象属性名之间的映射关系。第二种是使用sql别名,将列的别名与对象属性名对应,有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射对象的属性逐一赋值并返回,找不到映射关系的属性无法完成赋值。

9.一级缓存与二级缓存?**
一级缓存:
(1)Mybatis的一级缓存的作用域是session,在同一个sqlSession中(openSession()后),执行相同的sql查询(SQL语句和参数都一致),第一次会去查询数据库并写在缓存中,后面会直接从缓存中读取,其实就是每次查询前会先去缓存中找,如果找不到,就去数据库里查询再把结果写到缓存中。sqlSession在执行增删改操作后该sqlSession会被清空。mybatis默认开启一级缓存。mybatis的内部缓存是用了HashMap的数据结构,key为hashcode+statementId+sql语句,value为查询出来的结果映射成的java对象。
使用:sqlSession.clearCache();可以强制清除缓存

(2)二级缓存是mapper级别的,mybatis默认不开启二级缓存,第一次调用mapper下的sql去查询信息,查询到的信息会存放在该mapper对应的二级缓存区域,第二次调用namespace下的mapper映射文件,如果是相同的sql查询,会去对应的二级缓存中取结果。如果调用的namespace下的mapper映射文件中进行增删改并且执行了commit操作,此时
image.png
image.png

10.Mybatis如何避免后台重复保存数据
方式一:ignore
插入时检索主键列表,如存在相同主键记录,不更改原记录,只插入新的记录。

INSERT IGNORE INTO test (id,name,created_time) VALUES (2,'test',now());

中间件

分布式

1.dubbo里的zookeeper是做什么的?
zookeeper是Dubbo服务的注册中心,provider(服务提供方)提供服务后注册在zookeeper上, consumer(服务消费方)通过接口和版本信息从zookeeper中获取相应的服务,服务对于consumer来说完全透明,根本感知不到该接口是来自本地还是provider,就像引用本地的一个bean一样。属 zookeeper可以实现服务的分布式,同时可以监控每个服务的状态以及调用次数情况等。
https://www.zhihu.com/question/25070185
https://blog.csdn.net/only_musm/article/details/78604641
https://blog.csdn.net/wangzhanzheng/article/details/54880919

2.zookeeper的作用:分布式锁、注册服务中心

3.zookeeper如何实现分布式锁、其他分布式锁怎么实现

4.docker平时怎么使用的
5.分布式事务的解决方案
6.单点登录怎么实现

7.秒杀系统怎么来实现
秒杀系统

kafka

kafka如何解决数据堆积

kafka消息的存储机制

如何用kafka保证消息的有序性

kafka如何保证并发情况下消息只对消费一次

数据库

关系型数据库

mysql innodb的b+树索引,主键索引,聚簇索引有什么区别

数据库四大特性
原子性、一致性、隔离性、持久性。

数据库的事务的四大隔离级别
读未提交(Read uncommitted)、读已提交(Read committed)、可重复读(Repeatable read)、
串行化(Repeatable read)

https://blog.csdn.net/sinat_35322593/article/details/81040479 数据库的四大特性和隔离级别

mysql里有哪些锁,表锁、行锁、乐观锁、悲观锁的特点和区别

mysql的死锁是怎么产生的,举一两个例子

数据库的优化包含哪些,mysql的优化,谈两个你优化的例子

mysql数据量多大的时候需要分表

mysql重用的存储引擎和区别

数据库索引,主键和唯一索引有什么区别
1.主键为一种约束,唯一索引为一种索引,本质上就不同。
2.主键在表中只能有一个,唯一索引可以有多个。
3.主键创建后一定包含唯一性索引,而唯一索引不一定就是主键。
4.主键不能为null,唯一索引可以为null.
5.主键可以被其它表引用,唯一索引不能。
6.主键和索引都是键,主键是逻辑键,索引为物理键,即主键不实际存在。

聚集索引和非聚集索引的区别

Mysql存储引擎innoDB和MylSAM的区别

redis

关于redis源码及redis集群,如何创建redis集群

分布式下redis如何保证线程安全

redis持久化的方式以及区别

redis如何主从同步

redis分布式锁注意事项