2020年三、四月java面试题汇总 - Go语言中文社区

2020年三、四月java面试题汇总


文章目录

2020年3月份一些中小型互联网或计算机软件公司的社招题目,我会尽最大努力确保答案的正确性,但是我也才疏学浅,如果有错误或者遗漏的地方,欢迎指出。持续更新中……。

一、JavaSE基础

谈谈对OOP的理解

  1. 首先面向对象是一种编程思想
  2. 万物皆对象。我的电脑是一个对象,我的手机是一个对象等等,OOP可以理解为使用代码来模拟现实生活
  3. 三大特性:封装、继承和多态
    1. 封装就是隐藏类的内部信息,不允许外部程序直接访问,而是通过getter(获取)和setter(设置)方法完成,提高数据的安全性
    2. 继承是指:父类的基本特征和行为,子类也会有,子类也可以改变这些特征和行为。
    3. 多态就是多个对象调用同一个方法,可能会得到的是不同的结果。

Java基本类型所占字节和范围

类型 字节数 位数 取值范围
byte 1 8 -27 ~ 27-1
short 2 16 -215 ~ 215-1
char 2 16
int 4 32 -231 ~ 231-1
long 8 64 -263 ~ 263-1
float 4 32 3.402823e+38 ~ 1.401298e-45
double 8 64 1.797693e+308 ~ 4.9000000e-324
boolean 1(前7位是0) 1 0或者1

int(long) 和 float(double) 哪个表示的范围大?为什么?

float表示的 范围更大floatint 都是4个字节,而 float 还要表示小数,为什么 float 表示的数字范围大?

  • int 底层使用的是补码的方式表示一个数:1 位符号位 + 31 位二进制数
  • float 底层使用的是IEEE 754 浮点单精度数字格式,简单来说就是用指数的形式去表示一个数:1 位符号位 + 8 位指数 + 23位尾数

Int和Integer的区别,在使用时有什么区别?

  1. Integerint的包装类,int则是java的一种基本数据类型
  2. Integer变量必须实例化后才能使用,而int变量不需要
  3. Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
  4. Integer的默认值是nullint的默认值是0

重写,重载

  • 方法重载是指同一个类中的多个方法具有相同的名字,但这些方法具有不同的参数列表,即参数的数量或参数类型不能完全相同
  • 方法重写是存在子父类之间的,子类定义的方法与父类中的方法具有相同的方法名字,相同的参数表和相同的返回类型
    • 子类中不能重写父类中的final方法
    • 子类如果不是抽象类,则必须重写父类中的abstract方法

==与equals()

  • ==号在比较基本数据类型时比较的是值,而在比较两个对象时比较的是两个对象的地址值
  • equals()方法存在于Object类中,其底层依赖的是==号,如果类没有重写Object中的equals()方法的类中,则调用equals()方法其实和使用==号的效果一样

接口与抽象类

相同点:

  1. 都不能被实例化
  2. 接口的实现类或抽象类的子类都只有实现了抽象方法后才能实例化。

不同点:

  1. 意义不同:接口是功能的封装,解决对象能干什么;抽象类是事物的抽象,解决对象到底是什么。
  2. 内容不同:接口中的内容只能有:抽象方法(public abstract修饰),常量(public static final修饰),在JDK8中,接口还可以定义static静态方法和default方法;而抽象类可以有普通类的所有内容和抽象方法。
  3. 接口支持多继承,类只允许单继承

String底层使用什么实现的?为什么不可变?

String的底层使用的是char数组。这个char数组和String这个类都是final修饰的,所以不可变。

String,StringBuilder,StringBuffer的区别?

String是只读字符串,它并不是基本数据类型,而是一个对象。从底层源码来看是一个final类型的字符数组,所引用的字符串不能被改变,一经定义,无法再增删改。每次对String的操作都会生成新的String对象。

String的每次+操作 : 隐式在堆上new了一个跟原字符串相同的StringBuilder对象,再调用append方法 拼接
+后面的字符,最后再调用toString方法,返回String对象

StringBufferStringBuilder他们两都继承了AbstractStringBuilder抽象类,从AbstractStringBuilder抽象类中我们可以看到他们的底层都是可变的字符数组,所以在进行频繁的字符串操作时,建议使用StringBufferStringBuilder来进行操作。

另外StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的

什么是值传递?什么是引用传递?

  • 值传递:是对 基本类型 变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.
  • 引用传递:一般是对于 对象类型 变量而言的,传递的是该对象首地址的一个副本,并不是原对象本身,改变副本会影响原变量(本质都是同一个对象)。

格式化日期用的什么?

使用的是 SimpleDateFormat

  • 把日期转换为指定格式的字符串
    //使用SimpleDateFormat类,在构造方法中指定日期的格式:y年,M月,d日,H小时,m分钟,s秒
    SimpleDateFormat sdf = new SimpleDateFormat("yyy y-MM-dd HH:mm:ss");
    //调用SimpleDateFormat实例方法format(Date)可以把日期转换为字符串
    String text = sdf.format(date);
    
  • 把字符串转换为日期
    text = "2068年5月12日 8:28:58";
    //先创建SimpleDateFormat对象, 指定的格式串要与字符串匹配
    SimpleDateFormat anotherSdf = new SimpleDateFormat("yyyy年M月dd日 H:mm:ss");
    //调用SimpleDateFormat的实例方法parse(String)可以把字符串转换为日期对象, 如果格式串与字符串不匹配就会产生异常
    date2 = anotherSdf.parse(text);
    

IO流中都有哪些类?

节点流(介质流)

父类 InputStream(输入流、字节流) OutputStream(输出流、字节流) Reader(输入流、字符流) Writer(输出流、字符流)
文件 FileInputStream FileOutputStream FileReader FileWriter
数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
字符串 StringReader StringWriter
管道 PipedInputStream PipedOutputStream PipedReader PipedWriter

处理流(装饰流)

父类 InputStream(输入流、字节流) OutputStream(输出流、字节流) Reader(输入流、字符流) Writer(输出流、字符流)
转换流 InputStreamReader OutputStreamWriter
缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
数据流 DataInputStream DataOutputStream
对象流 ObjectInputStream ObjectOutputStream
打印流 PrintStream PrintWriter

如何创建文件和目录?

File 对象可以表示文件或目录:

//第一个参数为父路径字符串,第二个参数为子路径名字
File f = new File("D:", "test");
f.mkdir();           //创建文件夹, 如果已存在重名的文件(夹),创建失败
f.createNewFile();   //创建文件

f.delete();          //删除文件

面向对象编程的六大原则

  1. 开闭原则

    开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类。

  2. 里氏替换原则

    所有引用基类的地方必须能透明地使用其子类的对象,也就是说子类可以扩展父类的功能,但不能改变父类原有的功能

    里氏替换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏替换原则是对实现抽象化的具体步骤的规范。

  3. 依赖倒转原则

    高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。简单的说就是尽量面向接口编程.

  4. 接口隔离原则

    客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。接口最小化,过于臃肿的接口依据功能,可以将其拆分为多个接口。

  5. 迪米特法则,又称最少知道原则

    一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。简单的理解就是高内聚,低耦合,一个类尽量减少对其他对象的依赖。

  6. 单一职责原则

    单一职责原则通俗来说就是一个类只负责一项职责。如果一个类有多个职责,这些职责就耦合在了一起。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起会影响复用性。

了解哪些设计模式?具体说说其中的某个?

  1. 单例模式:懒汉式、饿汉式、双重校验锁、静态加载,内部类加载、枚举类加载。保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  2. 代理模式:动态代理和静态代理,动态代理有jdk动态代理和cglib动态代理。
  3. 适配器模式:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
  4. 装饰者模式:动态给类加功能。
  5. 观察者模式:有时被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
  6. 策略模式:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
    外观模式:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
  7. 命令模式:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。
  8. 创建者模式:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
  9. 抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

二、集合

List,Set,Map区别(扩容过程)

(1)List集合

存储特点:有序,可重复。存储顺序与添加顺序一致,可以存储重复的数据

  • ArrayList
    底层数据结构是数组,访问快,添加,删除慢
    初始化容量:10
    扩容:1.5倍
  • Vector
    底层数据结构是数组,它是线程安全的,ArrayList不是线程安全的
    初始化容量:10
    扩容:2倍
  • LinkedList
    底层是双向链表,访问慢,添加和删除效率高

(2)Set集合

存储特点:无序,不可重复。存储顺序与添加顺序可能不一样;不能存储重复(通过equals()方法来判断)的数据

  • HashSet
    • 底层是HashMap
    • HashSet添加元素就是把元素作为键添加到底层的HashMap
  • TreeSet
    • TreeSet实现了SortedSet接口,可以根据元素自然排序,要求集合中的元素必须是可比较的(ComparatorComparable
    • TreeSet底层是TreeMap
    • TreeSet添加元素就是把元素作为键添加到底层的TreeMap

(3)Map

Map是按<键,值>对的形式存储数据,Mapkey不允许重复

HashMap遍历怎么遍历?

两种方式遍历:使用EntrySet遍历;使用KeySet 遍历

平时什么情况下使用HashMap?

比如说读取系统参数,在服务之间传递一些键值对时使用。

简单说一下什么是hash碰撞?怎么解决的?

首先根据键的哈希码,经过hash函数计算hash值,然后根据hash值计算数组下标,当下标相同时,就会出现hash碰撞。

HashMap采用链表法,将数组下标相同的键值对用链表存起来,解决了hash碰撞。

TreeMap和LinkedHashMap有什么区别?

  • HashMap 相较来说写入慢,读取快,上面介绍过了,就不赘述了;
  • LinkedHashMap它内部有一个链表,保持Key插入的顺序,写入快,读取慢。迭代的时候,也是按照插入顺序迭代;
  • TreeMap可以根据Key的自然顺序(如整数从小到大)或者自己定义的比较器,实现 Key 的有序排列。

如何删除一些集合中满足条件的元素?

不能使用for或者foreach循环加list.remove()进行删除,因为使用remove方法删除后,集合的长度会变,导致一些元素被漏掉。

  1. 使用外部迭代器删除集合中的所有奇数:

    List<Integer> list = new ArrayList<>();
    
    Iterator<Integer> iterator = list.iterator();
    
    while (iterator.hasNext()) {
        Integer i = iterator.next();
        if (i % 2 == 1) {
            iterator.remove();
        }
    }
    
  2. 使用内部迭代器(流),删除集合中的所有奇数:

    List<Integer> list = new ArrayList<>();
    
    list = list.stream().
                filter(x -> x % 2 == 0).
                collect(Collectors.toList());
    

三、多线程

1. 创建线程有哪些方法(4种)?

  1. 继承Thread类,重写run方法(其实Thread类本身也实现了Runnable接口)
  2. 实现Runnable接口,重写run方法
  3. 实现Callable接口,重写call方法(有返回值)
  4. 使用线程池(有返回值)

2. Runnable和Callable有什么不同?

  1. 最大的不同点:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
  2. Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

    Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取结果;当不调用此方法时,主线程不会阻塞!

3. start()和run()方法有什么区别?

  • start()方法来启动线程,而启动后的线程运行的是run()方法中的代码。
  • run()方法当作普通方法的方式调用时,并不会开启新的线程。

4. 线程池有哪些参数

最常用的三个参数:

  • corePoolSize:核心线程数
  • queueCapacity:任务队列容量(阻塞队列)
  • maxPoolSize:最大线程数

三个参数的作用:

  1. 当线程数小于核心线程数时,创建线程。
  2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
  3. 当线程数大于等于核心线程数,且任务队列已满
    1. 若线程数小于最大线程数,创建线程
    2. 若线程数等于最大线程数,抛出异常,拒绝任务

5. 线程池有几种(5种)?拒绝策略有几种(4种)?阻塞队列有几种(3种)?

五种线程池:

ExecutorService threadPool = null;
threadPool = Executors.newCachedThreadPool();//有缓冲的线程池,线程数 JVM 控制
threadPool = Executors.newFixedThreadPool(3);//固定大小的线程池
threadPool = Executors.newScheduledThreadPool(2);//一个能实现定时、周期性任务的线程池
threadPool = Executors.newSingleThreadExecutor();//单线程的线程池,只有一个线程在工作
threadPool = new ThreadPoolExecutor();//默认线程池,可控制参数比较多   

四种拒绝策略:

RejectedExecutionHandler rejected = null;
rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常
rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常
rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列
rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务

三种阻塞队列:

BlockingQueue<Runnable> workQueue = null;
workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出队列,有界
workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出队列,无界
workQueue = new SynchronousQueue<>();//无缓冲的等待队列,无界

6. 如何检测死锁?

利用Java自带工具JConsole
Java线程死锁查看分析方法

7. volatile底层是怎么实现的?

当一个变量定义为volatile后,它将具备两种特性:1. 可见性,2. 禁止指令重排序。

可见性:编译器为了加快程序运行速度,对一些变量的写操作会现在寄存器或CPU缓存上进行,最后写入内存。而在这个过程中,变量的新值对其它线程是不可见的。当对volatile标记的变量进行修改时,先当前处理器缓存行的数据写回到系统内存,然后这个写回内存的操作会使其他CPU里缓存了该内存地址的数据无效。

处理器使用嗅探技术保证它的内部缓存、系统内存和其他处理器的缓存的数据在总线上保持一致。如果一个正在共享的状态的地址被嗅探到其他处理器打算写内存地址,那么正在嗅探的处理器将使它的缓存行无效,在下次访问相同内存地址时,强制执行缓存行填充。

8. volatile与synchronized有什么区别?

  1. volatile仅能使用在变量上,synchronized则可以使用在方法、类、同步代码块等等。
  2. volatile只能保证可见性和有序性,不能保证原子性。而synchronized都可以保证。
  3. volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.

9. java中的线程有几种状态(6种)

  • NEW:新建状态,创建了一个线程对象
  • RUNNABLE:可以运行状态,线程调用了start()方法。
  • BLOCKED:阻塞状态,遇到了阻塞事件,或者等待锁对象.
  • WAITING:等待状态。wait()join()
  • TIMED_WAITING:等待状态,sleep()
  • TERMINATED:终止状态

在这里插入图片描述

更多多线程常见面试题及答案

四、MySql

sql优化

  1. sql优化主要是对查询进行优化,应尽量避免全表扫描,首先应考虑在 whereorder by 涉及的列上建立索引
  2. 任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
  3. 避免在 where 子句中使用!=<>操作符,否则将引擎放弃使用索引而进行全表扫描,
  4. 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描。可以在null上设置默认值,确保表中没有null值。
  5. 复合索引必须指定索引中第一个字段作为检索条件,否则不使用索引
  6. innot in 也要慎用,否则会导致全表扫描。对于连续的数值,能用 between 就不要用 in 了。
  7. 模糊查询时,以"%"开头,会导致全表扫描

更多sql优化的几种方法

索引

什么是索引

索引(Index)是帮助MySQL高效获取数据的数据结构,可以理解为一本字典的目录,提高程序的检索 和查询效率

索引的类型

  • 主键索引:数据列不允许重复,不允许为NULL,一个表只能有一个主键。

  • 唯一索引:数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。

    #创建唯一索引
    ALTER TABLE table_name ADD UNIQUE (column);
    #创建唯一组合索引
    ALTER TABLE table_name ADD UNIQUE (column1,column2);
    
  • 普通索引:基本的索引类型,没有唯一性的限制,允许为NULL值。

    #创建普通索引
    ALTER TABLE table_name ADD INDEX index_name (column);
    #创建组合索引
    ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);
    
  • 全文索引:是目前搜索引擎使用的一种关键技术。

    #创建全文索引
    ALTER TABLE table_name ADD FULLTEXT (column);
    

如何判断一条sql语句是否使用了索引(执行计划)

通过执行计划,可以判断本次查询中对于数据行定位是否是从索引获取的

EXPLAIN  查询语句 

对于查询的评级由好到坏:const > primary(主键) > ref > range > index > all

  • 平时进行SQL优化时,最低要求达到range级别,尽可能达到ref级别

MySQL 服务占用 cpu 100%,如何排查问题?

  1. 一般情况下,mysql占用过高多是有慢查询,打开慢查询日志,找到执行较慢的语句。另一种方法:也可以在mysql命令行中调用 show processlist;,查看当前 mysql 使用频繁的 sql 语句。
  2. 分析查询效率低的原因,一般是因为没有使用索引,要么sql优化,要么给字段加上索引。

事务

Mysql事务的隔离级别

事务隔离级别 说明 脏读 不可重复读 幻读
读未提交(read-uncommitted) 读取数据的事务对象,可以读取到另一个事务对象尚未提交的数据
不可重复读(read-committed) 读取数据的事务对象,只能读取另一个事务对象提交过后的数据
可重复读(repeatable-read)
(默认)
读取数据的事务对象,在另一个事务对象提交前后读取的内容要保持一致
串行化(serializable) 串行读取

什么是脏读?什么是幻读?

  • 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据

  • 不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。

  • 幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。事务1在两次查询的过程中,事务2对该表进行了插入、删除操作,从而事务1第二次查询的结果发生了变化。

总结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

存储引擎

存储引擎对比

  • MyIsam:

    • 使用三个文件表示每个表:

      • 格式文件 — 存储表的结构(mytable.frm)
      • 数据文件 — 存储表的数据(mytable.MYD)
      • 索引文件 — 存储表的索引(mytable.MYI)
    • 对表文件中数据行查询提速,有很大帮助。

    • 灵活的AUTO_INCREMENT字段处理

    • 可被转换为压缩、只读表来节省空间

    • 但是MyIsam不支持事务,因此在进行数据行添加,修改,删除时,无法保证数据安全性

  • INNODB:MySQL数据库的默认引擎;

    • 每个InnoDB表在数据库目录中以.frm格式文件表示
    • InnoDB表空间tablespace被用于存储表的内容
    • 提供一组用来记录事务性活动的日志文件
    • 支持事务处理
    • 提供全ACID兼容
    • MySQL服务器崩溃后提供自动恢复
    • 多版本(MVCC)和行级锁定
    • 支持外键及引用的完整性,包括级联更新和删除
  • MEMORY:其数据存储在内存中,且行的长度固定,这两个特点使得MEMORY存储引擎非常快;

    • 在数据库目录内,每个表均以.frm格式文件表示;
    • 表数据及索引被存储在内存中;
    • 表级锁机制;
    • 不能包含TEXT或BLOB字段;

如何选择合适的存储引擎

  1. MyISAM表最适合于大量的数据读而少量数据更新的混合操作。MyISAM表的另一种适用情形是使用压缩的中读表。
  2. 如果查询中包含较多的数据更新操作,应使用InnoDB。其行级锁机制和多版本的支持为数据读取和更新的混合提供了良好的并发机制。
  3. 可使用MEMORY存储引擎存储非永久需要的数据,或者是能够从基于磁盘的表中重新生成的数据。

Innodb有哪些特性?

  1. 插入缓冲
  2. 二次写
  3. 自适应哈希
  4. 预读

innodb引擎的4大特性

Innodb的事务锁有哪些?(4种)

行级锁

  • 共享锁(S Lock) : 允许事务读一行数据
  • 排它锁(X Lock) : 允许事务删除或更新一行数据

表级锁

  • 意向共享锁(IS Lock):事务想要获得一张表中某几行的共享锁
  • 意向排它锁(IX Lock):事务想要获得一张表中某几行的排它锁

由于Innodb引擎支持的均为行锁,所以意向锁其实不会阻塞除全表扫描之外的任何请求

数据库的乐观锁怎么实现的?

乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。

实现方式

  1. 在数据库中加入一个版本号字段,每次在执行数据的修改操作时,先查出相应的版本号,然后带上查出来的这个版本号去修改数据库,一旦该版本号和数据的版本号一致就可以执行修改操作并对版本号执行+1操作,否则就执行失败
  2. 上述方法中的版本号也可以用时间戳替换,时间戳不需要 +1 操作。

使用场景
乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

使用什么工具创建表?

使用Power Designer来创建表

表结构是怎么设计的?

  1. 首先,表结构的设计尽量要满足数据库三范式
  2. 但是还是根据实际需求进行取舍,有时可能会拿冗余换速度,最终用目的要满足客户需求。

第一范式:每一个字段是原子性不能再分;
第二范式:在第一范式基础之上,要求数据库中所有非主键字段完全依赖主键,不能产生部分依赖;
第三范式:在第二范式基础之上,要求非主键字段不能产生传递依赖于主键字段

数据库集群是怎么同步的?

在这里插入图片描述

  1. 当 master 主服务器上的数据发生改变时,则将其改变写入二进制事件日志文件中
  2. salve 从服务器会在一定时间间隔内对 master 主服务器上的二进制日志进行探测,探测其是否发生过改变,如果探测到 master 主服务器的二进制事件日志发生了改变,则开始一个 I/O Thread 请求 master 二进制事件日志
  3. 同时 master 主服务器为每个 I/O Thread 启动一个dump Thread,用于向其发送二进制事件日志
  4. slave 从服务器将接收到的二进制事件日志保存至自己本地的中继日志文件中
  5. salve 从服务器将启动 SQL Thread 从中继日志中读取二进制日志,在本地重放,使得其数据和主服务器保持一致;
  6. 最后 I/O Thread 和 SQL Thread 将进入睡眠状态,等待下一次被唤醒

如何防止SQL注入?

SQL注入产生的原因,就是用户提交的数据,被数据库系统编译而产生了开发者预期之外的动作。也就是,SQL注入是用户输入的数据,在拼接SQL语句的过程中,超越了数据本身,成为了SQL语句查询逻辑的一部分,然后这样被拼接出来的SQL语句被数据库执行,产生了开发者预期之外的动作。

解决办法:

  1. 最简单的就是采用预编译语句集(PreparedStatement),在SQL语句中放置占位符’?’,在执行前,数据库会先对含有占位符的SQL语句进行校验和预编译;执行时才会把参数传递进去,防止参数参与编译变成sql语句的一部分。
  2. 采用正则表达式将包含有 单引号('),分号(;) 和 注释符号(--)的语句给替换掉来防止SQL注入

五、Redis

1. Redis有哪些优势?

  1. 单线程,利用redis队列技术并将访问变为串行访问,消除了传统数据库串行控制的开销

  2. 支持数据持久化,支持AOF和RDB两种持久化方式。

  3. 分布式 读写分离模式

  4. 支持丰富数据类型

  5. 支持事务,操作都是原子性,所谓原子性就是对数据的更改要么全部执行,要不全部不执行。

2. 有几种数据类型?你用到了哪些数据类型(5种)?在哪儿用到的?

String类型

  • 计数:由于Redis单线程的特点,我们不用考虑并发造成计数不准的问题,通过 incrby 命令,我们可以正确的得到我们想要的结果。
  • 限制次数:比如登录次数校验,错误超过三次5分钟内就不让登录了,每次登录设置key自增一次,并设置该key的过期时间为5分钟后,每次登录检查一下该key的值来进行限制登录。

Hash类型

  • 缓存一些信息

List类型

  • 可以用作栈,队列等等

Set类型

  • 利用集合的交并集特性,比如在社交领域,我们可以很方便的求出多个用户的共同好友,共同感兴趣的领域等。

Zset类型

  • 和set数据结构一样,zset也可以用于社交领域的相关业务,并且还可以利用zset 的有序特性,还可以做类似排行榜的业务。

3. redis的过期策略有哪些?

定期删除+惰性删除

定期删除redis 默认是每隔 100ms 就会扫描一定数量的数据库的expires字典中的key,检查其是否过期,如果过期就删除。定期删除可能会导致很多过期 key 到了时间并没有被删除掉,所以就使用惰性删除。

expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。

惰性删除:获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。

4. 持久化

(1)RDB

Redis Database(RDB),就是在指定的时间间隔内将内存中的数据集快照写入磁盘,数据恢复时将快照文件直接再读到内存。

RDB 保存了在某个时间点的数据集(全部数据)。存储在一个二进制文件中,只有一个文件。默认是 dump.rdb。 RDB 技术非常适合做备份,可以保存最近一个小时,一天,一个月的全部数据。保存数据是在单独的进程中写文件,不影响 Redis 的正常使用。 RDB 恢复数据时比其他 AOF 速度快。

  • 优点:由于存储的是数据快照文件,恢复数据很方便,也比较快
  • 缺点:
    • 会丢失最后一次快照以后更改的数据。 如果你的应用能容忍一定数据的丢失,那么使用 rdb 是不错的选择; 如果你不能容忍一定数据的丢失,使用 rdb 就不是一个很好的选择。
    • 由于需要经常操作磁盘, RDB 会分出一个子进程。如果你的 redis 数据库很大的话,子进程占用比较多的时间,并且可能会影响 Redis 暂停服务一段时间(millisecond 级别),如果你的数据库超级大并且你的服务器 CPU 比较弱,有可能是会达到一秒。

(2)AOF

Append-only File(AOF), Redis 每次接收到一条改变数据的命令时,它将把该命令写到一个 AOF 文件中(只记录写操作,读操作不记录),当 Redis 重启时,它通过执行 AOF 文件中所有的命令来恢复数据。

  • append-only 文件是另一个可以提供完全数据保障的方案;
  • AOF 文件会在操作过程中变得越来越大。比如,如果你做一百次加法计算,最后你只会在数据库里面得到最终的数值,但是在你的 AOF 里面会存在 100 次记录,其中 99 条记录对最终的结果是无用的;但 Redis 支持在不影响服务的前提下在后台重构 AOF 文件,让文件得以整理变小
  • 可以同时使用这两种方式,redis默认优先加载 aof文件(aof数据最完整);

5. Redis集群保证了CAP中的什么?

CAP原则又称CAP定理,指在一个分布式系统不可能同时满足 Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性)。由于分区容错性是分布式系统中必须要保证的,因此我们只能在A和C之间进行权衡

  • 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
  • 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/zyx1260168395/article/details/105088359
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢