2021 Java面试题


JAVA 经典面试题目

本面试题包括以下十九个模块:Java 基础、容器、多线程、反射、对象拷贝、Java Web 模块、异常、网络、设计模式、Spring/Spring MVC、Spring Boot/Spring Cloud、Hibernate、Mybatis、RabbitMQ、Kafka、Zookeeper、 MySql、Redis、JVM、Vue、Linux。

目录

JAVA 经典面试题目……………………………………………………………………………………… 1

一、Java 基础……………………………………………………………………………………………. 14

\1. JDK 和 JRE 有什么区别?……………………………………………………………….. 14

\2. == 和 equals 的区别是什么?…………………………………………………………. 14

\3. 两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?………… 14

\4. final 在 java 中有什么作用?…………………………………………………………… 15

\5. java 中的 Math.round(-1.5) 等于多少?…………………………………………… 15

\6. String 属于基础的数据类型吗?…………………………………………………………… 15

\7. java 中操作字符串都有哪些类?它们之间有什么区别?……………………………. 15

\8. String str=”i”与 String str=new String(“i”)一样吗?………………………….. 16

\9. 如何将字符串反转?…………………………………………………………………………….. 16

\10. String 类的常用方法都有那些? (挑几个说即可)……………………………… 18

\11. 抽象类必须要有抽象方法吗?………………………………………………………………. 20

\12. 普通类和抽象类有哪些区别?………………………………………………………………. 20

\13. 抽象类能使用 final 修饰吗?……………………………………………………………. 20

\14. 接口和抽象类有什么区别?…………………………………………………………………. 20

\15. java 中 IO 流分为几种?……………………………………………………………….. 21

\16. 字符流和字节流的区别?…………………………………………………………………… 22

\17. BIO、NIO、AIO 有什么区别?………………………………………………………….. 22

\18. Files 的常用方法都有哪些?(挑几个说即可)……………………………………….. 23

二、容器…………………………………………………………………………………………………….. 23

\1. java 容器都有哪些?……………………………………………………………………………. 23

\2. Collection 和 Collections 有什么区别?…………………………………………… 24

\3. List、Set、Queue、Map 之间的区别是什么?……………………………………… 25

\4. HashMap 和 Hashtable 有什么区别?……………………………………………… 26

\5. 如何决定使用 HashMap 还是 TreeMap?………………………………………… 27

\6. 说一下 HashMap 的实现原理?…………………………………………………………. 28

\7. 说一下 HashSet 的实现原理?…………………………………………………………… 28

\8. ArrayList 和 LinkedList 的区别是什么?……………………………………………. 29

\9. 如何实现数组和 List 之间的转换?……………………………………………………… 29

\10. ArrayList 和 Vector 的区别是什么?………………………………………………. 29

\11. Array 和 ArrayList 有何区别?………………………………………………………. 30

\12. 在 Queue 中 poll()和 remove()有什么区别?………………………………… 30

\13. 哪些集合类是线程安全的?…………………………………………………………………. 30

\14. 迭代器 Iterator 是什么?………………………………………………………………… 30

\15. Iterator 怎么使用?使用时应注意什么?……………………………………………… 31

\16. Iterator 和 ListIterator 有什么区别?……………………………………………… 31

\17. 怎么确保一个集合不能被修改?…………………………………………………………… 32

三、多线程………………………………………………………………………………………………….. 33

\1. 并行和并发有什么区别?………………………………………………………………………. 33

\2. 线程和进程的区别?…………………………………………………………………………….. 34

\3. 守护线程是什么?……………………………………………………………………………….. 34

\4. 创建线程有哪几种方式?………………………………………………………………………. 35

\5. 说一下 runnable 和 callable 有什么区别?……………………………………… 35

\6. 线程有哪些状态?……………………………………………………………………………….. 36

\7. sleep() 和 wait() 有什么区别?………………………………………………………… 36

\8. notify()和 notifyAll()有什么区别?………………………………………………………. 36

\9. 线程的 run()和 start()有什么区别?……………………………………………………. 37

\10. 创建线程池有哪几种方式?…………………………………………………………………. 37

\11. 线程池都有哪些状态?……………………………………………………………………….. 38

\12. 线程池中 submit()和 execute()方法有什么区别?………………………………. 38

\13. 在 java 程序中怎么保证多线程的运行安全?………………………………………. 39

\14. 多线程锁的升级原理是什么?………………………………………………………………. 39

\15. 什么是死锁?……………………………………………………………………………………. 40

\16. 怎么防止死锁?…………………………………………………………………………………. 41

\17. ThreadLocal 是什么?有哪些使用场景?…………………………………………….. 41

\18. 说一下 synchronized 底层实现原理?………………………………………………. 42

\19. synchronized 和 volatile 的区别是什么?……………………………………….. 42

\20. synchronized 和 Lock 有什么区别?………………………………………………. 43

\21. synchronized 和 ReentrantLock 区别是什么?……………………………….. 44

\22. 说一下 atomic 的原理?…………………………………………………………………. 44

四、反射…………………………………………………………………………………………………….. 45

\1. 什么是反射?……………………………………………………………………………………… 45

\2. 什么是 java 序列化?什么情况下需要序列化?…………………………………….. 45

五、对象拷贝………………………………………………………………………………………………. 45

\1. 为什么要使用克隆?…………………………………………………………………………….. 45

\2. 如何实现对象克隆?…………………………………………………………………………….. 45

\3. 深拷贝和浅拷贝区别是什么?………………………………………………………………… 46

六、Java Web……………………………………………………………………………………………… 46

\1. jsp 和 servlet 有什么区别?…………………………………………………………….. 46

\2. jsp 有哪些内置对象?作用分别是什么?……………………………………………….. 46

\3. 说一下 jsp 的 4 种作用域?…………………………………………………………… 47

\4. session 和 cookie 有什么区别?……………………………………………………… 48

\5. 说一下 session 的工作原理?……………………………………………………………. 49

\6. 如果客户端禁止 cookie 能实现 session 还能用吗?………………………….. 50

\7. 什么是 XSS 攻击,如何避免?…………………………………………………………… 50

\8. 什么是 CSRF 攻击,如何避免?…………………………………………………………. 51

七、异常…………………………………………………………………………………………………….. 51

\1. throw 和 throws 的区别?……………………………………………………………… 51

\2. final、finally、finalize 有什么区别?…………………………………………………… 52

\3. try-catch-finally 中哪个部分可以省略?……………………………………………….. 52

\4. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?. 52

八、网络…………………………………………………………………………………………………….. 52

\1. http 响应码 301 和 302 代表的是什么?有什么区别?…………………….. 52

\2. forward 和 redirect 的区别?…………………………………………………………. 53

\3. 简述 tcp 和 udp 的区别?………………………………………………………………. 53

\4. tcp 为什么要三次握手,两次不行吗?为什么?……………………………………… 55

\5. 说一下 tcp 粘包是怎么产生的?…………………………………………………………. 56

\6. get 和 post 请求有哪些区别?………………………………………………………… 57

\7. 如何实现跨域?…………………………………………………………………………………… 57

九、设计模式………………………………………………………………………………………………. 58

1.说一下你熟悉的设计模式?…………………………………………………………………… 58

十、Spring/Spring MVC………………………………………………………………………………. 60

\1. 为什么要使用 spring?………………………………………………………………………. 60

\2. 解释一下什么是 AOP?………………………………………………………………………. 60

\3. 解释一下什么是 IoC 和 DI?………………………………………………………………. 61

\4. spring 有哪些主要模块?……………………………………………………………………. 61

\5. spring 常用的注入方式有哪些?………………………………………………………….. 61

\6. spring 中的 bean 是线程安全的吗?………………………………………………… 61

\7. spring 支持几种 bean 的作用域?…………………………………………………… 62

\8. spring 自动装配 bean 有哪些方式?………………………………………………… 62

\9. spring 事务实现方式有哪些?……………………………………………………………… 62

\10. 说一下 spring 的事务隔离?……………………………………………………………. 62

\11. 说一下 spring mvc 运行流程?………………………………………………………… 63

\12. spring mvc 有哪些组件?…………………………………………………………………. 63

\13. @RequestMapping 的作用是什么?…………………………………………………. 64

\14. @Autowired 的作用是什么?……………………………………………………………. 64

十一、Spring Boot/Spring Cloud………………………………………………………………….. 64

\1. 什么是 spring boot?………………………………………………………………………… 64

\2. spring boot 配置文件有哪几种类型?它们有什么区别?…………………………. 65

\3. spring boot 有哪些方式可以实现热部署?……………………………………………. 65

\4. jpa 和 hibernate 有什么区别?……………………………………………………….. 65

\5. 什么是 spring cloud?………………………………………………………………………. 65

\6. spring cloud 断路器的作用是什么?……………………………………………………. 66

\7. spring cloud 的核心组件有哪些?……………………………………………………….. 66

十二、Hibernate…………………………………………………………………………………………. 66

\1. 什么是 ORM 框架?………………………………………………………………………… 66

\2. hibernate 中如何在控制台查看打印的 sql 语句?……………………………….. 67

\3. hibernate 有几种查询方式?………………………………………………………………. 67

\4. 在 hibernate 中使用 Integer 和 int 做映射有什么区别?……………….. 67

\5. hibernate 是如何工作的?………………………………………………………………….. 67

\6. get()和 load()的区别?………………………………………………………………………. 67

\7. 说一下 hibernate 的缓存机制?………………………………………………………… 68

\8. hibernate 对象有哪些状态?………………………………………………………………. 68

\9. 在 hibernate 中 getCurrentSession 和 openSession 的区别是什么? 68

\10. hibernate 实体类必须要有无参构造函数吗?为什么?…………………………… 69

十三、Mybatis…………………………………………………………………………………………….. 69

\1. mybatis 中 #{}和 ${}的区别是什么?………………………………………………… 69

\2. mybatis 有几种分页方式?…………………………………………………………………. 69

\3. RowBounds 是一次性查询全部结果吗?为什么?………………………………….. 69

\4. mybatis 逻辑分页和物理分页的区别是什么?………………………………………… 70

\5. mybatis 是否支持延迟加载?延迟加载的原理是什么? 70

\6. 说一下 mybatis 的一级缓存和二级缓存? 70

\7. mybatis 和 hibernate 的区别有哪些? 71

\8. mybatis 有哪些执行器(Executor)? 71

\9. mybatis 分页插件的实现原理是什么? 72

\10. mybatis 如何编写一个自定义插件? 72

十四、RabbitMQ…………………………………………………………………………………………………………………. 72

\1. rabbitmq 的使用场景有哪些? 72

\2. rabbitmq 有哪些重要的角色? 73

\3. rabbitmq 有哪些重要的组件? 73

\4. rabbitmq 中 vhost 的作用是什么? 74

\5. rabbitmq 的消息是怎么发送的? 74

\6. 消息堆积的影响和解决方案? 75

\7. rabbitmq 怎么避免消息丢失? 75

\8. 怎样避免消息重复消费?例如为了防止消息在消费者端丢失,会采用手动回复 MQ 的方式来解决,同时也引出了一个问题,消费者处理消息成功,手动回复 MQ 时由于网络不稳定,连接断开,导致 MQ 没有收到消费者回复的消息,那么该条消息还会保存在 MQ 的消息队列,由于MQ 的消息重发机制,会重新把该条消息发给和该队列绑定的消息者处理,这样就会导致消息重复消费。而有些操作是不允许重复消费的,比如 下单,减库存,扣款等操作。 76

\9. 要保证消息持久化成功的条件有哪些? 77

\10. rabbitmq 有几种工作模式? 77

\11. rabbitmq 怎么实现延迟消息队列? 78

\12. rabbitmq 集群有什么用? 79

\13. rabbitmq 节点的类型有哪些? 79

\14. rabbitmq 集群搭建需要注意哪些问题? 79

\15. rabbitmq 每个节点是其他节点的完整拷贝吗?为什么? 79

\16. rabbitmq 集群中唯一一个磁盘节点崩溃了会发生什么情况? 80

\17. rabbitmq 对集群节点停止顺序有要求吗? 80

十五、Kafka…………………………………………………………………………………………………………………. 80

\1. kafka 可以脱离 zookeeper 单独使用吗?为什么? 80

\2. kafka 有几种数据保留的策略? 80

\3. kafka 同时设置了 7 天和 10G 清除数据,到第五天的时候消息达到了 10G,这个时候 kafka 将如何处理? 80

\4. 什么情况会导致 kafka 运行变慢? 80

\5. 使用 kafka 集群需要注意什么? 81

十六、Zookeeper…………………………………………………………………………………………………………………. 81

\1. zookeeper 是什么? 81

\2. 简述 Zookeeper 的数据模型? 81

\3. zookeeper 都有哪些功能? 82

\4. zookeeper 有几种部署模式? 82

\5. watcher 架构及运行原理? 82

\6. watcher 特点? 82

\7. zookeeper 怎么保证主从节点的状态同步? 83

\8. 集群中为什么要有主节点? 83

\9. 集群中有 3 台服务器,其中一个节点宕机,这个时候 zookeeper 还可以使用吗?

………………………………………………………………………………………………………………………………… 83

\10. 说一下 zookeeper 的通知机制? 83

十七、MySql…………………………………………………………………………………………………………………. 84

\1. 数据库的三范式是什么? 84

\2. 一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 mysql 数据库,又插入了一条数据,此时 id 是几? 84

\3. 如何获取当前数据库版本? 85

\4. 说一下 ACID 是什么? 85

\5. char 和 varchar 的区别是什么? 85

\6. float 和 double 的区别是什么? 85

\7. mysql 索引是怎么实现的? 86

\8. 怎么验证 mysql 的索引是否满足需求? 86

\9. 说一下 mysql 常用的引擎? 86

\10. 说一下 mysql 的行锁和表锁? 87

\11. 说一下乐观锁和悲观锁? 87

\12. mysql 问题排查都有哪些手段? 88

\13. 如何做 mysql 的性能优化?(列出其中几条即可) 88

十八、Redis…………………………………………………………………………………………………………………. 89

\1. redis 是什么? 89

\2. redis 和 memecache 有什么区别? 89

\3. redis 为什么是单线程的?…………………………………………………………………… 90

\4. 什么是缓存穿透?怎么解决?………………………………………………………………… 90

\7. redis 支持的数据类型有哪些?…………………………………………………………….. 92

\8. jedis 和 redisson 有哪些区别?………………………………………………………. 92

\9. 怎么保证缓存和数据库数据的一致性?……………………………………………………. 92

\10. redis 持久化有几种方式?…………………………………………………………………. 93

\11. redis 怎么实现分布式锁?…………………………………………………………………. 94

\12. redis 如何做内存优化?……………………………………………………………………. 95

\13. redis 淘汰策略有哪些?及如何选择淘汰策略?…………………………………….. 95

\14. redis 常见的性能问题有哪些?该如何解决?………………………………………… 96

十九、JVM………………………………………………………………………………………………….. 96

1.说一下 jvm 的主要组成部分?及其作用?……………………………………………. 96

1. 说一下 jvm 运行时数据区?…………………………………………………………… 97

\3. 说一下堆栈的区别?…………………………………………………………………………….. 98

\4. 队列和栈是什么?有什么区别?…………………………………………………………….. 98

\5. 什么是双亲委派模型?…………………………………………………………………………. 99

\6. 说一下类加载的执行过程?…………………………………………………………………… 99

\7. 怎么判断对象是否可以被回收?…………………………………………………………….. 99

\8. java 中都有哪些引用类型?……………………………………………………………….. 100

\9. 说一下 jvm 有哪些垃圾回收算法?……………………………………………………. 101

\10. 说一下 jvm 有哪些垃圾回收器?…………………………………………………….. 102

\11. 详细介绍一下 CMS 垃圾回收器?…………………………………………………… 104

\12. 新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?………………. 104

\13. 简述分代垃圾回收器是怎么工作的?……………………………………………………. 105

\14. 说一下 jvm 调优的工具?………………………………………………………………. 105

\15. 常用的 jvm 调优的参数都有哪些?(列出几种就可以)……………………… 106

二十、算法题……………………………………………………………………………………………… 109

\1. 什么是快速排序算法?………………………………………………………………………… 109

\2. 算法的时间复杂度?…………………………………………………………………………… 109

\3. 二分法检索如何工作?………………………………………………………………………… 109

\4. 是否可以使用二分法检索链表?……………………………………………………………. 110

\5. 什么是堆排序?…………………………………………………………………………………. 110

\6. 什么是 Skip list?……………………………………………………………………………… 110

\7. 插入排序算法的空间复杂度是多少?……………………………………………………… 110

\8. 什么是“哈希算法”,它们用于什么?………………………………………………….. 111

\9. 如何查找链表是否有循环?………………………………………………………………….. 111

\10. 一个算法的最佳情况和最坏情况之间有什么区别?…………………………………. 111

\11. 什么是基数排序算法?………………………………………………………………………. 112

\12. 什么是递归算法?…………………………………………………………………………….. 112

\13. 提到递归算法的三个定律是什么?………………………………………………………. 112

\14. 什么是冒泡排序算法?………………………………………………………………………. 112

二十一、Vue……………………………………………………………………………………………… 113

\1. 简述 Vue 的双向绑定数据的原理?………………………………………………………. 113

\2. 简述 MVC、MVVM 的关系与区别?……………………………………………………. 113

\3. 简述 Vue 的生命周期?……………………………………………………………………… 113

\4. Vue 中组件如何通信的?……………………………………………………………………… 114

\5. 什么是闭包函数?js 中闭包函数的优缺点?………………………………………………. 114

\6. var、let 和 const 的区别是什么?………………………………………………………. 115

\7. 箭头函数 ()=> 和 function 定义函数的区别在哪里?…………………………… 115

\8. 简述 ES6 中的 Promise 对象?…………………………………………………………. 115

\9. null 和 undefined 的区别?…………………………………………………………….. 115

二十二、Linux……………………………………………………………………………………………. 116

一、Java 基础

1. JDK 和 JRE 有什么区别?

JRE( Java Runtime Environment)是Java 运行时环境……它是运行编译后的 Java 程序所必需的一切包,包括 Java 虚拟机(JVM)、Java 基础类库、Java 命令和其他基础设施。但是,它不能用于创建新程序。

JDK 是Java 开发工具包……功能齐全的SDK for Java。它拥有 JRE 所拥有的一切,还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具:jconsole,jvisualvm 等工具软件, 还包含了 java 程序编写所需的文档和 demo 例子程序。它能够创建和编译程序,是提供给程序员使用的。

2. == 和 equals 的区别是什么?

1、功能不同

“==”是判断两个变量或实例是不是指向同一个内存空间的值”equals”是判断两个变量或实例所指向的内存空间的值是不是相同。2、定义不同

“equals”在JAVA 中是一个方法。”==”在 JAVA 中只是一个运算符号。

3. 两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?

两个对象 equals 相等,则它们的 hashcode 必须相等,反之则不一定。两个对象==相等,则其hashcode 一定相等, 反之不一定成立。

4. final 在 java 中有什么作用?

final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。

final 类中的成员变量可以根据需要设为 final,但是要注意 final 类中的所有成员方法都会被隐式地指定为 final 方法。

5. java 中的 Math.round(-1.5) 等于多少?

Math 的 round 方法是四舍五入,如果参数是负数,则往大的数。如,Math.round(-1.5)=-1,但是Math.round(1.5)=2 。

6. String 属于基础的数据类型吗?

String 类并不是基本数据类型,而是一个类(class),用来表示 java 语言中的字符串类型。

7. java 中操作字符串都有哪些类?它们之间有什么区别?

String、StringBuffer、StringBuilder。

String : final 修饰,String 类的方法都是返回 new String,即对 String 对象的任何改变都不影响到原对象,对字符串的修改操作都会生成新的对象。

StringBuffer : 对字符串的操作的方法都加了 synchronized,保证线程安全。

StringBuilder : 不保证线程安全,在方法体内需要进行字符串的修改操作,可以 new StringBuilder 对象,调用StringBuilder 对象的 append、replace、delete 等方法修改字符串。

8. String str=”i”与 String str=new String(“i”)一样吗?

img不一样。因为内存的分配方式不一样。String str=”i”的方式,Java 虚拟机会将其分配到常量池中; 而 String str=new String(“i”)方式,则会被分到堆内存中。

9. 如何将字符串反转?

\1. 递归

img

\2. 通过 charAt(int index)返回 char 值进行字符串拼接

img

\3. 把字符串转换成字符数组倒叙拼接然后返回值

img

\4. 调用 StringBuffer 中的 reverse 方法

img

\5. 把字符串转换成字符数组首位对调位置

img

10. String 类的常用方法都有那些? (挑几个说即可)

String 类的常用方法: equals:字符串是否相同

equalsIgnoreCase:忽略大小写后字符串是否相同compareTo:根据字符串中每个字符的 Unicode 编码进行比较

compareToIgnoreCase:根据字符串中每个字符的Unicode 编码进行忽略大小写比较indexOf:目标字符或字符串在源字符串中位置下标

lastIndexOf:目标字符或字符串在源字符串中最后一次出现的位置下标valueOf:其他类型转字符串

charAt:获取指定下标位置的字符

codePointAt:指定下标的字符的 Unicode 编码concat:追加字符串到当前字符串

isEmpty:字符串长度是否为 0 contains:是否包含目标字符串startsWith:是否以目标字符串开头endsWith:是否以目标字符串结束format:格式化字符串

getBytes:获取字符串的字节数组getChars:获取字符串的指定长度字符数组toCharArray:获取字符串的字符数组join:以某字符串,连接某字符串数组length:字符串字符数

matches:字符串是否匹配正则表达式replace:字符串替换

replaceAll:带正则字符串替换replaceFirst:替换第一个出现的目标字符串split:以某正则表达式分割字符串substring:截取字符串

toLowerCase:字符串转小写toUpperCase:字符串转大写trim:去字符串首尾空格

11. 抽象类必须要有抽象方法吗?

不必须。

抽象类必须有关键字 abstract 来修饰抽象类可以不含有抽象方法

如果一个类包含抽象方法,则该类必须是抽象类

12. 普通类和抽象类有哪些区别?

抽象类不能被实例化

抽象类可以有抽象方法,抽象方法只需申明,无需实现含有抽象方法的类必须申明为抽象类

抽象类的子类必须实现抽象类中所有抽象方法,否则这个子类也是抽象类抽象方法不能被声明为静态

抽象方法不能用 private 修饰抽象方法不能用 final 修饰

13. 抽象类能使用 final 修饰吗?

不能,抽象类是被用于继承的,final 修饰代表不可修改、不可继承的。

14. 接口和抽象类有什么区别?

他们都不能实例化对象,都可以包含抽象方法,而且抽象方法必须被继承的类全部实现。区别:

1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。

2、抽象类要被子类继承,接口要被类实现。

3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现

4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。

5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。6、抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结

7、抽象类里可以没有抽象方法

8、如果一个类里有抽象方法,那么这个类只能是抽象类

9、抽象方法要被实现,所以不能是静态的,也不能是私有的。

10、接口可继承接口,并可多继承接口,但类只能单根继承。

15. java 中 IO 流分为几种?

按照流的流向分,可以分为输入流和输出流;

按照操作单元划分,可以划分为字节流和字符流; 按照流的角色划分为节点流和处理流。

Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

16. 字符流和字节流的区别?

读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。

处理对象不同:字节流能处理所有类型的数据(如图片、avi 等),而字符流只能处理字符类型的数据。

字节流:一次读入或读出是 8 位二进制。通过字节的形式一个字节一个字节或者字节数组来操作文件中内容,可以操作一切文件。

字符流:一次读入或读出是 16 位二进制。通过单个字符或者是字符数组的形式来操作文件的,存在一定的局限性,是专门用于对文本文件操作的,默认的版本为 GBK

设备上的数据无论是图片或者视频,文字,它们都以二进制存储的。二进制的最终都是以一个 8 位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理字符数据。

17. BIO、NIO、AIO 有什么区别?

BIO 是一个连接一个线程。 NIO 是一个请求一个线程。 AIO 是一个有效请求一个线程。

BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求时才启动一个线程进行处理。

AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的 I/O 请求都是由 OS 先完成

了再通知服务器应用去启动线程进行处理。

18. Files 的常用方法都有哪些?(挑几个说即可)

Files.exists() 检测文件路径是否存在Files.createFile() 创建文件Files.createDirectory() 创建文件夹Files.delete() 删除文件或者目录Files.copy() 复制文件

Files.move() 移动文件Files.size()查看文件个数Files.read() 读取文件Files.write() 写入文件

二、容器

1.java 容器都有哪些?

数组,String,java.util 下的集合容器。

\1. 数组长度限制为 Integer.Integer.MAX_VALUE。

\2. String 的长度限制: 底层是 char 数组,长度 Integer.MAX_VALUE 线程安全的。

\3. Java 集合框架主要包括两种类型的容器,Collection 是最基本的集合接口。Set、List 和 Queue 继承了它。Map 是一种把键对象和值对象映射的集合。 Map 没有继承 Collection 接口。(可参考下图)

img

2.Collection 和 Collections 有什么区别?

\1. java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection 接口在 Java 类库中有很多具体的实现。Collection 接口的意义是为各种具体的集合提供了最大化的统一操作方式。

\2. java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于 Java 的 Collection 框架。

3. List、Set、Queue、Map 之间的区别是什么?

List:有序集合,元素可重复

Set:不重复集合,LinkedHashSet 按照插入排序,SortedSet 可排序,HashSet 无序Map:键值对集合

Queue:按照排队规则来确定对象产生的顺序

以下属于本题目相关扩展的内容:

\1. List 以线性方式存储元素,集合中可以存放重复对象,元素有序。最常用实现类:

ArrayList:随机访问元素快,增删元素慢。

Vector:Vector 与 ArrayList 相似。但 Vector 的方法是线程安全的,而 ArrayList 的方法不是,由于线程的同步必然要影响性能,因此 ArrayList 的性能比 Vector 好。

LinkedList:随机访问元素慢,顺序访问快,增删元素快。

Stack:栈,继承Vector,特点是先进后出(FILO, First In Last Out)。

\2. Set 不保存重复的元素。Set 与Collection 有完全一样的接口。Set 接口不保证维护元素的次序。最常用实现类:

HashSet : 随机查找快。存入 HashSet 的对象必须定义 hashCode()。HashSet 查找某个对象时, 首先用 hashCode()方法计算出这个对象的 Hash 码,然后再根据 Hash 码到相应的存储区域用equals()方法查找,从而提高了效率。

TreeSet : 保存次序的 Set, 底层为树结构。使用它可以从 Set 中提取有序的序列。LinkedHashSet : 具有 HashSet 的查询速度,且内部使用链表维护元素的顺序。于是在使用迭代器遍历 Set 时,结果会按元素插入的次序显示。

\3. 队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear) 进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。

队列中,最先插入的元素最先被删除,最后插入的元素最后被删除,因此队列又称为“先进先出”(FIFO

—first in first out)的线性表。

在 java5 中新增加了 java.util.Queue 接口,用以支持队列的常见操作。该接口扩展java.util.Collection 接口。

\4. Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。Map 没有继承于Collection 接口。

最常用的实现类:

HashMap:Map 基于哈希表的 Map 接口的实现。

HashTable:hashtable 和 hashmap,从存储结构和实现来讲基本上都是相同的,最大的不同就是hashtable 是线程安全的。

LinkedHashMap: LinkedHashMap 是 HashMap 的一个子类,是 Map 接口的哈希表和链接列表实现。它维护着一个双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。

TreeMap : 基于红黑树的实现。

4.HashMap 和 Hashtable 有什么区别?

\1. 继承不同。

public class Hashtable extends Dictionary implements Map public class HashMap extends AbstractMap implements Map

\2. Hashtable 中的方法是同步的,而 HashMap 中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用 Hashtable,但是要使用 HashMap 的话就要自己增加同步处理了。3.Hashtable 中,key 和 value 都不允许出现 null 值。在 HashMap 中,null 可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为 null。当 get()方法返回 null 值时,即可以表示HashMap 中没有该键,也可以表示该键所对应的值为null。因此,在 HashMap 中不能由 get()方法来判断 HashMap 中是否存在某个键, 而应该用 containsKey()方法来判断。

\4. 两个遍历方式的内部实现上不同。Hashtable、HashMap 都使用了 Iterator。而由于历史原因, Hashtable 还使用了 Enumeration 的方式 。

\5. 哈希值的使用不同,HashTable 直接使用对象的hashCode。而 HashMap 重新计算 hash 值。6.Hashtable 和 HashMap 它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable 中hash 数组默认大小是 11,增加的方式是 old*2+1。HashMap 中hash 数组的默认大小是 16,而且一定是 2 的指数。

5. 如何决定使用 HashMap 还是 TreeMap?

TreeMap<K,V>的 Key 值是要求实现 java.lang.Comparable,所以迭代的时候 TreeMap 默认是按照 Key 值升序排序的;TreeMap 的实现是基于红黑树结构。适用于按自然顺序或自定义顺序遍历键

(key)。

HashMap<K,V>的 Key 值实现散列 hashCode(),分布是散列的、均匀的,不支持排序;数据结构主要是桶(数组),链表或红黑树。适用于在 Map 中插入、删除和定位元素。

结论:

如果你需要得到一个有序的结果时就应该使用 TreeMap(因为 HashMap 中元素的排列顺序是不固定的)。除此之外,由于 HashMap 有更好的性能,所以大多不需要排序的时候我们会使用 HashMap。

6. 说一下 HashMap 的实现原理?

HashMap 使用数组加链表实现。每个数组中储存着链表。

当调用 put 方法时,第一步首先将 k,v 封装到 Node 对象当中(节点)。第二步它的底层会调用 K 的 hashCode()方法得出 hash 值。第三步通过哈希表函数/哈希算法,将 hash 值转换成数组的下标, 下标位置上如果没有任何元素,就把 Node 添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着 k 和链表上每个节点的 k 进行 equal。如果所有的 equals 方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个 equals 返回了 true,那么这个节点的 value 将会被覆盖。

当使用 get 方法获取 key 对应的 value 时,先调用 k 的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。第二步:通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上。重点理解如果这个位置上什么都没有,则返回 null。如果这个位置上有单向链表, 那么它就会拿着参数 K 和单向链表上的每一个节点的 K 进行 equals,如果所有 equals 方法都返回false,则 get 方法返回 null。如果其中一个节点的 K 和参数 K 进行 equals 返回 true,那么此时该节点的 value 就是我们要找的 value 了,get 方法最终返回这个要找的 value。

另外:HashMap 在 JDK1.8 之后引入红黑树结构。HashMap 是线程不安全的,线程安全的是CurrentHashMap,不过此集合在多线程下效率低。

7. 说一下 HashSet 的实现原理?

往 Haset 添加元素的时候,HashSet 会先调用元素的 hashCode 方法得到元素的哈希值 ,然后通过元素 的哈希值经过移位等运算,就可以算出该元素在哈希表中 的存储位置。

\1. 如果算出的元素存储的位置目前没有任何元素存储,那么该元素可以直接存储在该位置上

\2. 如果算出的元素的存储位置目前已经存在有其他的元素了,那么还会调用该元素的 equals 方法

与该位置的元素再比较一次,如果 equals 方法返回的是 true,那么该位置上的元素视为重复元素, 不允许添加,如果返回的是 false,则允许添加

8.ArrayList 和 LinkedList 的区别是什么?

\1. 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。

\2. 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。

\3. 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为ArrayList 增删操作要影响数组内的其他数据的下标。

综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时, 更推荐使用 LinkedList。

9. 如何实现数组和 List 之间的转换?

List 转数组:使用 List 的toArray 方法

数组转 List:使用 JDK 中 java.util.Arrays 工具类的 asList 方法

10. ArrayList 和 Vector 的区别是什么?

img

11. Array 和 ArrayList 有何区别?

Array 即数组,定义一个 Array 时,必须指定数组的数据类型及数组长度,即数组中存放的元素个数固定并且类型相同。

ArrayList 是动态数组,长度动态可变,会自动扩容。不使用泛型的时候,可以添加不同类型元素。

12. 在 Queue 中 poll()和 remove()有什么区别?

队列(queue)是一个典型的先进先出(FIFO)的容器。即从容器的一端放入事物,从另一端取出, 并且事物放入容器的顺序与取出的顺序是相同的。

相同点:都是返回第一个元素,并在队列中删除返回的对象。

不同点:remove() ,如果队列为空的时候,则会抛出异常。而 poll()只会返回 null。

13. 哪些集合类是线程安全的?

Vector

Stack

Hashtable

java.util.concurrent 包下所有的集合类 ArrayBlockingQueue、ConcurrentHashMap、

ConcurrentLinkedQueue、ConcurrentLinkedDeque…

14. 迭代器 Iterator 是什么?

迭代器模式,是 Java 中常用的设计模式之一。用于顺序访问集合对象的元素,无需知道集合对象的底层实现。Iterator 是可以遍历集合的对象,为各种容器提供了公共的操作接口,隔离对容器的遍历

操作和底层实现,从而解耦。缺点是增加新的集合类需要对应增加新的迭代器类,迭代器类与集合类成对增加。

15. Iterator 怎么使用?使用时应注意什么?

java.lang.Iterable 接口被 java.util.Collection 接口继承,功能比较简单,并且只能单向移动:

(1) 使用方法 iterator()要求容器返回一个 Iterator。第一次调用 Iterator 的 next()方法时,它返回序列的第一个元素。

(2) 使用 next()获得序列中的下一个元素。

(3) 使用 hasNext()检查序列中是否还有元素。

(4) 使用 remove()将迭代器新返回的元素删除。注意事项:

(1) Iterator 遍历集合元素的过程中不允许线程对集合元素进行修改,否则会抛出ConcurrentModifificationEception 的异常。

(2) Iterator 遍历集合元素的过程中可以通过 remove 方法来移除集合中的元素,删除的是上一次Iterator.next()方法返回的对象。

(3) Iterator 必须依附于一个集合类对象而存在,Iterator 本身不具有装载数据对象的功能。

(4) next()方法,该方法通过游标指向的形式返回 Iterator 下一个元素。

16. Iterator 和 ListIterator 有什么区别?

(1) ListIterator 继承 Iterator

(2) ListIterator 比 Iterator 多方法

add(E e) 将指定的元素插入列表,插入位置为迭代器当前位置之前

set(E e) 迭代器返回的最后一个元素替换参数 e hasPrevious() 迭代器当前位置,反向遍历集合是否含有元素previous() 迭代器当前位置,反向遍历集合,下一个元素

previousIndex() 迭代器当前位置,反向遍历集合,返回下一个元素的下标nextIndex() 迭代器当前位置,返回下一个元素的下标

(3) 使用范围不同,Iterator 可以迭代所有集合;ListIterator 只能用于 List 及其子类ListIterator 有 add 方法,可以向 List 中添加对象;Iterator 不能

ListIterator 有 hasPrevious() 和 previous() 方法,可以实现逆向遍历;Iterator 不可以ListIterator 有 nextIndex() 和 previousIndex() 方法,可定位当前索引的位置;Iterator 不可以

ListIterator 有 set()方法,可以实现对 List 的修改;Iterator 仅能遍历,不能修改

17. 怎么确保一个集合不能被修改?

使用 JDK 中java.util.Collections 类,unmodifiable*** 方法赋值原集合。当再修改集合时,会报错 java.lang.UnsupportedOperationException。从而确保自己定义的集合不被其他人修改。

public class TestCollectionUnmodify {
static List<String> list = new ArrayList<String>(); static Set<String> set = new HashSet<String>();
static Map<String, String> map = new HashMap<String, String>();

static {
    list.add("1");
    list.add("2");
    list.add("3");

    set.add("1");
    set.add("2");
    set.add("3");
    map.put("1", "1");
    map.put("2", "2");
    map.put("3", "3");
}

public static void main(String[] args) {
    list = Collections.unmodifiableList(list); 
    set = Collections.unmodifiableSet(set); 
    map = Collections.unmodifiableMap(map); listModify();
    setModify(); mapModify();
}

public static void listModify() { 
    list.add("4");
}

public static void setModify() { 
    set.add("4");
}

public static void mapModify() { 
    map.put("3", "4");
}
}

三、多线程

1. 并行和并发有什么区别?

并发:指应用能够交替执行不同的任务,其实并发有点类似于多线程的原理,多线程并非是同时执行多个任务而是快速切换着执行。如果你开两个线程执行,就是在你几乎不可能察觉到的速度不断去切换这两个任务,已达到“同时执行效果”,其实并不是“同时”,只是计算机的速度太快,我们无法察觉到而已。例如,你吃一口饭、喝一口水、再吃一口饭、再喝一口水,以正常速度来看,完全能够看的出来,当你把这个过程以 n 倍速度执行时…可以想象一下是不是就是一直在喝水和吃饭了。

并行:指应用能够同时执行不同的任务。例如,吃饭的时候可以边吃饭边听音乐,这两件事情就可以同时执行。

总结:并发的关键是你有处理多个任务的能力,但不是同时。并行的关键是你在同时处理多个任务。两者区别:并发是交替执行,并行是同时执行。

2. 线程和进程的区别?

进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫轻量级进程。 线程的划分小于进程,线程是隶属于某个进程的。

进程是程序的一种动态形式,是 CPU,内存等资源占用的基本单位,而线程是不能占有这些资源的。进程之间相互独立,通信比较困难,而线程之间共享一块内存区域,通信比较方便。

进程在执行过程中包含比较固定的入口,执行顺序,出口,而线程的这些过程会被应用程序所控制。

3. 守护线程是什么?

守护线程是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。在 Java 中垃圾回收线程就是特殊的守护线程。

专门用于服务其他的线程,如果其他的线程(即用户自定义线程)都执行完毕,连 main 线程也执行完毕,那么jvm 就会退出(即停止运行),此时连 jvm 都停止运行了,守护线程当然也就停止执行了。

换一种通俗的说法,如果有用户自定义线程存在的话,jvm 就不会退出,此时守护线程也不能退出, 也就是它还要运行,为什么呢,就是为了执行垃圾回收的任务。

4. 创建线程有哪几种方式?

1、继承 Thread 类

这是最直观的一种方式,让一个类继承 Thread 重写 run 方法,然后把它 new 出来,这便是创建了一个新线程。

2、实现 Runnable 接口

通过实现 Runnable 接口的 run 方法,可以得到一个“可被执行的任务”,然后在 new Thread 的时候将这个任务传进去。

3、Callable+FutureTask

(1) 首先让一个类实现 Callable(泛型)接口的 call 方法,这一步是写一个“可被调用的任务”;

(2) 再 new 一个FutureTask(”未来的任务“),同时将上一步的 Callable 传进去;

(3) 最后 new 一个 Thread,同时将 Future 传进去。

请注意这种方式与上面两种方式的区别,此方式可以让你的任务返回一个返回值,类型任你定,都是可以的。在线程 start 之后,调用 FutureTask 的 get 即可得到返回值(线程任务需执行完成)。

4、借助线程池

这种方式就是你通过线程池间接地去创建线程,相当于把创建线程的任务托管给线程池。线程池可以统一管理线程,使得线程调度有序,且利用效率大大提高。线程池可以通过 Executors 提供的几个方法来创建,也可以通过 ThreadPoolExecutor 创建自定义线程池。

5. 说一下 runnable 和 callable 有什么区别?

Runnable 接口中的 run()方法的返回值是void,它做的事情只是纯粹地去执行 run()方法中的代码而已;

Callable 接口中的 call()方法是有返回值的,是一个泛型,和 Future、FutureTask 配合可以用来获

取异步执行的结果。

6. 线程有哪些状态?

线程状态有 5 种,新建,就绪,运行,阻塞,死亡

7. sleep() 和 wait() 有什么区别?

sleep()和 wait()都是线程暂停执行的方法。

1、这两个方法来自不同的类分别是 Thread 和 Object,sleep 方法属于 Thread 类中的静态方法, wait 属于 Object 的成员方法。

2、sleep()是线程类(Thread)的方法,不涉及线程通信,调用时会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait()是 Object 的方法,用于线程间的通信,调用时会放弃对象锁,进入等待队列,待调用 notify()/notifyAll()唤醒指定的线程或者所有线程,才进入对象锁定池准备获得对象锁进入运行状态。

3、wait,notify 和 notifyAll 只能在同步控制方法或者同步控制块里面使用,而 sleep 可以在任何地方使用(使用范围)。

4、sleep()方法必须捕获异常 InterruptedException,而 wait()\notify()以及 notifyAll()不需要捕获异常。

8. notify()和 notifyAll()有什么区别?

notify() 方法随机唤醒对象的等待池中的一个线程,进入锁池;notifyAll() 唤醒对象的等待池中的所有线程,进入锁池。

9. 线程的 run()和 start()有什么区别?

启动一个线程需要调用 Thread 对象的 start() 方法。调用线程的 start() 方法后,线程处于可运行状态,此时它可以由 JVM 调度并执行,这并不意味着线程就会立即运行。

run() 方法是线程运行时由 JVM 回调的方法,无需手动写代码调用。直接调用线程的 run() 方法, 相当于在调用线程里继续调用方法,并未启动一个新的线程。

10. 创建线程池有哪几种方式?

1、newFixedThreadPool

定长线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程数量不再变化,当线程发生错误结束时,线程池会补充一个新的线程

2、newCachedThreadPool

可缓存的线程池,如果线程池的容量超过了任务数,自动回收空闲线程,任务增加时可以自动添加新线程,线程池的容量不限制

3、newScheduledThreadPool 定长线程池,可执行周期性的任务4、newSingleThreadExecutor

单线程的线程池,线程异常结束,会创建一个新的线程,能确保任务按提交顺序执行5、newSingleThreadScheduledExecutor

单线程可执行周期性任务的线程池6、newWorkStealingPool

任务窃取线程池,不保证执行顺序,适合任务耗时差异较大。线程池中有多个线程队列,有的线程队

列中有大量的比较耗时的任务堆积,而有的线程队列却是空的,就存在有的线程处于饥饿状态,当一

个线程处于饥饿状态时,它就会去其它的线程队列中窃取任务。解决饥饿导致的效率问题。默认创建的并行 level 是 CPU 的核数。主线程结束,即使线程池有任务也会立即停止。

11. 线程池都有哪些状态?

线程池的 5 种状态:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED。

RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。

STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。 TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。

TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。

12. 线程池中 submit()和 execute()方法有什么区别?

execute 方法执行完成后没有返回值,而 submit 方法执行后有返回值方法所在的类不同,

execute 方法:java.util.concurrent.Executor; submit 方法:java.util.concurrent.ExecutorService 所需要的参数不同,

execute : java.util.concurrent.Executor#execute(java.lang.Runnable) submit: java.util.concurrent.ExecutorService#submit(java.lang.Runnable, T),

java.util.concurrent.ExecutorService#submit(java.lang.Runnable) java.util.concurrent.ExecutorService#submit(java.util.concurrent.Callable)

13. 在 java 程序中怎么保证多线程的运行安全?

解决办法:

JDK Atomic 开头的原子类、synchronized、LOCK,可以解决原子性问题。synchronized、volatile、LOCK,可以解决可见性问题。

Happens-Before 规则可以解决有序性问题。

14. 多线程锁的升级原理是什么?

1、锁的级别从低到高:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

2、锁分级别原因:没有优化以前,synchronized 是重量级锁(悲观锁),使用 wait 和 notify、notifyAll 来切换线程状态非常消耗系统资源;线程的挂起和唤醒间隔很短暂,这样很浪费资源,影响性能。所以 JVM 对 synchronized 关键字进行了优化,把锁分为无锁、偏向锁、轻量级锁、重量级锁状态。

(1) 无锁:没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功,其他修改失败的线程会不断重试直到修改成功。

(2) 偏向锁:对象的代码一直被同一线程执行,不存在多个线程竞争,该线程在后续的执行中自动获取锁,降低获取锁带来的性能开销。偏向锁,指的就是偏向第一个加锁线程,该线程是不会主动释放偏向锁的,只有当其他线程尝试竞争偏向锁才会被释放。偏向锁的撤销,需要在某个时间点上没有字节码正在执行时,先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态。如果线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁;如果线程处于活动状态,升级为轻量级锁的状态。

(3) 轻量级锁:轻量级锁是指当锁是偏向锁的时候,被第二个线程 B 所访问,此时偏向锁就会升级

为轻量级锁,线程 B 会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。当前只有一个

等待线程,则该线程将通过自旋进行等待。但是当自旋超过一定的次数时,轻量级锁便会升级为重量级锁;当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁。

(4) 重量级锁:指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。重量级锁通过对象内部的监视器(monitor)实现,而其中 monitor 的本质是依赖于底层操作系统的Mutex Lock 实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。

img

15. 什么是死锁?

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的 synchronized 代码块时,便占有了资源,直到它退出该代码块或者调用 wait 方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。

当然死锁的产生是必须要满足一些特定条件的:

\1. 互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放

\2. 请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。

\3. 不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用

\4. 循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。

16. 怎么防止死锁?

1、加锁顺序:当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。

2、加锁时限:另外一个可以避免死锁的方法是在尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。这段随机的等待时间让其它线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行

3、死锁检测:死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph 等等) 将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。当一个线程请求锁失败时, 这个线程可以遍历锁的关系图看看是否有死锁发生。

17. ThreadLocal 是什么?有哪些使用场景?

1、定义:ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

2、应用场景:

(1) 方便同一个线程使用某一对象,避免不必要的参数传递;

(2) 线程间数据隔离(每个线程在自己线程里使用自己的局部变量,各线程间的 ThreadLocal 对象互不影响);

(3) 获取数据库连接、Session、关联 ID(比如日志的 uniqueID,方便串起多个日志);其中 spring 中的事务管理器就是使用的 ThreadLocal:Spring 的事务管理器通过 AOP 切入业务代码,在进入业务代码前,会依据相应的事务管理器提取出相应的事务对象,假如事务管理器是DataSourceTransactionManager,就会从 DataSource 中获取一个连接对象,通过一定的包装后将其保存在ThreadLocal 中。而且 Spring 也将 DataSource 进行了包装,重写了当中的getConnection()方法,或者说该方法的返回将由 Spring 来控制,这样 Spring 就能让线程内多次获取到的 Connection 对象是同一个。

18. 说一下 synchronized 底层实现原理?

同步代码块是通过 monitorenter 和 monitorexit 指令获取线程的执行权。同步方法通过加 ACC_SYNCHRONIZED 标识实现线程的执行权的控制。

19. synchronized 和 volatile 的区别是什么?

1、作用:

synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。

volatile 表示变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性;禁止指令重排序。

2、区别:

synchronized 可以作用于变量、方法、对象;volatile 只能作用于变量。

synchronized 可以保证线程间的有序性(猜测是无法保证线程内的有序性,即线程内的代码可能被CPU 指令重排序)、原子性和可见性;volatile 只保证了可见性和有序性,无法保证原子性。synchronized 线程阻塞,volatile 线程不阻塞。

20. synchronized 和 Lock 有什么区别?

1、底层构成

Synchronized 是 JVM 的关键字,由 monitorEnter 和 monitorExit 组成,由于 wait 和notify 也依赖于 monitorEnter 和 monitorExit,所以 wait 和 notify 必须在 Synchronized 里面使用

Lock 是 jdk 提供的 JUC 包下的类,是 API 层面上的。2、使用方式

Synchronized 不需要手动释放Lock 必须手动释放

3、是否可中断Synchronized 不可中断Lock 可以中断

4、是否公平Synchronized 是非公平

Lock 是默认非公平,但是可以是公平锁5、绑定多个 condition

Synchronized 无法绑定多个 condition,无法精确唤醒Lock 可以

21. synchronized 和 ReentrantLock 区别是什么?

synchronized 竞争锁时会一直等待;ReentrantLock 可以尝试获取锁,并得到获取结果synchronized 获取锁无法设置超时;ReentrantLock 可以设置获取锁的超时时间synchronized 无法实现公平锁;ReentrantLock 可以满足公平锁,即先等待先获取到锁synchronized 控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll(); ReentrantLock 控制等待和唤醒需要结合 Condition 的 await() 和 signal()、signalAll() 方法synchronized 是 JVM 层面实现的;ReentrantLock 是 JDK 代码层面实现

synchronized 在加锁代码块执行完或者出现异常,自动释放锁;ReentrantLock 不会自动释放锁, 需要在 finally{} 代码块显示释放

22. 说一下 atomic 的原理?

1、直接操作内存,使用 Unsafe 这个类

img2、使用 getIntVolatile(var1, var2) 获取线程间共享的变量3、采用 CAS 的尝试机制(核心所在),代码如下:

可以看到这个 do …. while {!this.compareAndSwapInt(var1, var2, var5, var5 + var4)} 。不断地使用 CAS 进行重试,直到执行成功为止。这里是一个乐观锁的操作。

4、使用 Atomic ,是在硬件上、寄存器进行阻塞,而不是在线程、代码上阻塞。

5、这个是通俗说法 ABA 的问题

四、反射

1. 什么是反射?

反射是:指程序可以访问、检测和修改它本身状态或行为的一种能力。

我们平时用反射主要做:获取类型的相关信息;动态调用方法;动态构造对象;从程序集中获得类型。

2. 什么是 java 序列化?什么情况下需要序列化?

序列化:将 Java 对象转换成字节流的过程。反序列化:将字节流转换成 Java 对象的过程。

当 Java 对象需要在网络上传输 或者 持久化存储到文件中时,就需要对 Java 对象进行序列化处理。序列化的实现:类实现 Serializable 接口,这个接口没有需要实现的方法。实现 Serializable 接口是为了告诉 jvm 这个类的对象可以被序列化。

五、对象拷贝

1. 为什么要使用克隆?

想对一个对象进行处理,又想保留原有的数据进行接下来的操作,就需要克隆了。克隆分浅克隆和深克隆,浅克隆后的对象中非基本对象和原对象指向同一块内存,因此对这些非基本对象的修改会同时更改克隆前后的对象。深克隆可以实现完全的克隆,可以用反射的方式或序列化的方式实现。

2. 如何实现对象克隆?

1、实现 Cloneable 接口并重写Object 类中的 clone()方法

2、实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆

3. 深拷贝和浅拷贝区别是什么?

克隆分浅克隆和深克隆,浅克隆后的对象中非基本对象和原对象指向同一块内存,因此对这些非基本对象的修改会同时更改克隆前后的对象。深克隆可以实现完全的克隆,可以用反射的方式或序列化的方式实现。

六、Java Web

1. jsp 和 servlet 有什么区别?

Servlet:一种服务器端的 Java 应用程序,由 Web 容器加载和管理,用于生成动态 Web 内容, 负责处理客户端请求。

Jsp:是 Servlet 的扩展,本质上还是 Servlet。每个 Jsp 页面就是一个 Servlet 实例,Jsp 页面会被 Web 容器编译成 Servlet,Servlet 再负责响应用户请求。

区别:

Servlet 适合动态输出 Web 数据和业务逻辑处理,对于 html 页面内容的修改非常不方便;Jsp 是在 Html 代码中嵌入 Java 代码,适合页面的显示;

内置对象不同,获取内置对象的方式不同

2. jsp 有哪些内置对象?作用分别是什么?

1、HttpServletRequet 类的 Request 对象:代表请求对象,主要用于接受客户端通过 HTTP 协议连接传输服务器端的数据。

2、HttpSevletResponse 类的 Response 对象:代表响应对象,主要用于向客户端发送数据。

3、JspWriter 类的 out 对象:主要用于向客户端输出数据,out 的基类是 jspWriter

4、HttpSession 类的 session 对象:主要用来分别保存每个月的信息与请求关联的会话;会话状态的维持是 web 应用开发者必须面对的问题。

5、ServletContext 类的 application 对象:主要用于保存用户信息,代码片段的运行环境;它是一个共享的内置对象,即一个容器中的多个用户共享一个 application,故其保存的信息被所有用户所共享。

6、PageContext 类的 PageContext 对象:管理网页属性,为jsp 页面包装页面的上下文,管理对属于 jsp 的特殊可见部分中已经命名对象的访问,它的创建和初始化都是由容器来完成的。

7、ServletConfig 类的 Config 对象:代码片段配置对象,标识 Servlet 的配置。

8、Object 类的 Page 对象,处理 jsp 页面,是 object 类的一个实例,指的是 jsp 实现类的实例

9、Exception 对象:处理jsp 文件执行时发生的错误和异常,只有在错误页面里才使用,前提是在页面指令里要有 isErrorPage=true。

3. 说一下 jsp 的 4 种作用域?

1、page:代表与一个页面相关的对象和属性。

2、request:代表与客户端发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个 Web 组件;需要在页面显示的临时数据可以置于此作用域。

3、session:代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的 session 中。

4、application:代表与整个 Web 应用程序相关的对象和属性,它实质上是跨越整个 Web 应用程序,包括多个页面、请求和会话的一个全局作用域。

1、存储位置不同

cookie 的数据信息存放在客户端浏览器上。session 的数据信息存放在服务器上。

2、存储容量不同

单个 cookie 保存的数据<=4KB,一个站点最多保存 20 个 Cookie。

对于 session 来说并没有上限,但出于对服务器端的性能考虑,session 内不要存放过多的东西,并且设置 session 删除机制。

3、存储方式不同

cookie 中只能保管 ASCII 字符串,并需要通过编码方式存储为 Unicode 字符或者二进制数据。session 中能够存储任何类型的数据,包括且不限于 string,integer,list,map 等。

4、隐私策略不同

cookie 对客户端是可见的,别有用心的人可以分析存放在本地的 cookie 并进行 cookie 欺骗,所以它是不安全的。

session 存储在服务器上,对客户端是透明对,不存在敏感信息泄漏的风险。5、有效期上不同

开发可以通过设置 cookie 的属性,达到使 cookie 长期有效的效果。

session 依赖于名为JSESSIONID 的 cookie,而 cookie JSESSIONID 的过期时间默认为-1,只需关闭窗口该 session 就会失效,因而 session 不能达到长期有效的效果。

6、服务器压力不同

cookie 保管在客户端,不占用服务器资源。对于并发用户十分多的网站,cookie 是很好的选择。

session 是保管在服务器端的,每个用户都会产生一个 session。假如并发访问的用户十分多,会产生十分多的session,耗费大量的内存。

7、浏览器支持不同

假如客户端浏览器不支持cookie:cookie 是需要客户端浏览器支持的,假如客户端禁用了 cookie, 或者不支持cookie,则会话跟踪会失效。关于 WAP 上的应用,常规的 cookie 就派不上用场了。运用 session 需要使用 URL 地址重写的方式。一切用到 session 程序的 URL 都要进行 URL 地址重写, 否则 session 会话跟踪还会失效。

假如客户端支持 cookie:cookie 既能够设为本浏览器窗口以及子窗口内有效,也能够设为一切窗口内有效。session 只能在本窗口以及子窗口内有效。

8、跨域支持上不同cookie 支持跨域名访问。

session 不支持跨域名访问。

5. 说一下 session 的工作原理?

session 的工作原理是客户端登录完成之后,服务器会创建对应的 session,session 创建完之后, 会把 session 的 id 发送给客户端,客户端再存储到浏览器中。这样客户端每次访问服务器时,都会带着 sessionid,服务器拿到 sessionid 之后,在内存找到与之对应的 session 这样就可以正常工作了。

一般默认情况下,在会话中,服务器存储 session 的 sessionid 是通过 cookie 存到浏览器里。如果浏览器禁用了 cookie,浏览器请求服务器无法携带 sessionid,服务器无法识别请求中的用户身份,session 失效。但是可以通过其他方法在禁用 cookie 的情况下,可以继续使用 session。

1、通过 url 重写,把 sessionid 作为参数追加的原 url 中,后续的浏览器与服务器交互中携带sessionid 参数。

2、服务器的返回数据中包含 sessionid,浏览器发送请求时,携带 sessionid 参数。

3、 通过 Http 协议其他 header 字段,服务器每次返回时设置该 header 字段信息,浏览器中 js 读取该 header 字段,请求服务器时,js 设置携带该 header 字段。

7. 什么是 XSS 攻击,如何避免?

XSS 攻击,即跨站脚本攻击(Cross Site Scripting),它是 web 程序中常见的漏洞。攻击者往 web 页面里插入恶意的 HTML 代码(Javascript、css、html 标签等),当用户浏览该页面时,嵌入其中的 HTML 代码会被执行,从而达到恶意攻击用户的目的。如盗取用户 cookie 执行一系列操作, 破坏页面结构、重定向到其他网站等。

预防思路:

1、web 页面中可由用户输入的地方,如果对输入的数据转义、过滤处理

2、后台输出页面的时候,也需要对输出内容进行转义、过滤处理(因为攻击者可能通过其他方式把恶意脚本写入数据库)

3、前端对 html 标签属性、css 属性赋值的地方进行校验

8. 什么是 CSRF 攻击,如何避免?

跨站请求伪造(Cross-site request forgery),也被称为 one-click attack 或者 session riding, 通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。跟跨站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。

避免方法:

1、 CSRF 漏洞进行检测的工具,如 CSRFTester、CSRF Request Builder… 2、 验证 HTTP Referer 字段

3、 添加并验证 token

4、 添加自定义 http 请求头

5、 敏感操作添加验证码6、使用 post 请求

七、异常

1. throw 和 throws 的区别?

throw: 表示方法内抛出某种异常对象 如果异常对象是非 RuntimeException 则需要在方法声明时加上该异常的抛出,即需要加上 throws 语句或者在方法体内 try catch 处理该异常,否则编译报错。执行到 throw 语句则后面的语句块不再执行。

throws: 方法的定义上使用 throws 表示这个方法可能抛出某种异常,需要由方法的调用者进行异常处理。

2. final、finally、finalize 有什么区别?

1、final 可以用来修饰类、方法、变量,分别有不同的意义所在,final 修饰的 class 代表不可继续扩展,final 修饰的变量代表不可修改,final 修饰的方法代表不可重写。

2、finally 则是 java 保证某一段重点代码一定要被执行的修饰符,例如:我们需要用 try 块让 JDBC 保证连接,保证 unlock 锁等动作。

3、finalize 是基础类 java.lang.Object 的一个方法,它的设计目的是为了保证对象在垃圾回收之前完成特定资源的回收。

3. try-catch-finally 中哪个部分可以省略?

catch 和 finally 语句块可以省略其中一个。

4. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

会执行,在return 前执行

八、网络

1. http 响应码 301 和 302 代表的是什么?有什么区别?

301 重定向/跳转一般,表示本网页永久性转移到另一个地址。301 是永久性转移(Permanently Moved),SEO 常用的招式,会把旧页面的PR 等信息转移到新页面。

302 重定向表示临时性转移(Temporarily Moved ),当一个网页URL 需要短期变化时使用。区别:

301 重定向是永久的重定向,搜索引擎在抓取新内容的同时也将旧的网址替换为重定向之后的网址。

302 重定向是临时的重定向,搜索引擎会抓取新的内容而保留旧的网址。因为服务器返回 302 代码, 搜索引擎认为新的网址只是暂时的。

2. forward 和 redirect 的区别?

是 servlet 的主要两种跳转方式,forward 又叫转发,redirect 叫重定向。区别:

1、从地址栏显示来说:forward 是服务器内部重定向,客户端浏览器的网址不会发生变化;redirect 发生一个状态码,告诉服务器去重新请求那个网址,显示的的新的网址。

2、数据共享:在定向过程中 forward 使用的是同一个 request,可以共享;redirect 不可以。

3、应用场景:forward 一般用于用户登录:redirect 用于用户注销登录返回主页面或者跳转其他页面。

4、forward 效率更高。

5、本质上说:forward 转发是服务器上的行为,而 redirect 是客户端行为。6、次数:forward 只有一次,redirect 两次。

3. 简述 tcp 和 udp 的区别?

tcp 是面向连接的协议,也就是说,在收发数据前,必须和对方建立可靠的连接。一个 TCP 连接必须要经过三次“对话”才能建立起来。使用 TCP 协议传输数据,TCP 提供超时重发,丢弃重复数据, 检验数据,流量控制等功能,保证数据能从一端传到另一端。当数据从 A 端传到 B 端后,B 端会发送一个确认包(ACK 包)给 A 端,告知 A 端数据我已收到!

UDP 协议就没有这种确认机制,这就是为什么说 TCP 协议可靠,UDP 协议不可靠,提供这种可靠服务,会加大网络带宽的开销,因为“虚拟信道”是持续存在的,同时网络中还会出现大量的 ACK 和

FIN 包。TCP 协议提供了可靠的数据传输,但是其拥塞控制、数据校验、重传机制的网络开销很大, 不适合实时通信,所以选择开销很小的 UDP 协议来传输数据。UDP 协议是无连接的数据传输协议并且无重传机制,会发生丢包、收到重复包、乱序等情况。

1、基于连接与无连接。

2、UDP 不提供可靠性,不能保证数据能够到达目的地。

3、对系统资源的要求(TCP 较多,UDP 少)。

4、UDP 结构较简单。

5、TCP 面向字节流模式,TCP 会保证服务端按顺序接收到全部的字节流,UDP 面向数据报模式,不保证顺序性。

很明显,当数据传输的性能必须让位于数据传输的完整性、可控制性和可靠性时,选择 TCP 协议。当强调传输性能而不是传输的完整性时,如音频和多媒体应用,UDP 是最好的选择。在数据传输时间很短,以至于此前的连接过程成为整个流量主体的情况下,UDP 也是一个好的选择,如 DNS 交换。UDP 较低的开销使其有更好的机会去传送管理数据。TCP 丰富的功能有时会导致不可预料的性能低下。

4. tcp 为什么要三次握手,两次不行吗?为什么?

img

1、第一次握手

客户端向服务器发出连接请求报文,这时报文首部中的同部位 SYN=1,同时随机生成初始*** seq=x 此时,TCP 客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP 规定,SYN 报文段(SYN=1 的报文段)不能携带数据,但需要消耗掉一个序号。这是三次握手中的开始。表示客户端想要和服务端建立连接。

2、第二次握手

TCP 服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1, 确认号是 ack=x+1,同时也要为自己随机初始化一个*** seq=y。此时,TCP 服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。这个报文带有SYN(建立连接)和 ACK(确认)标志,询问客户端是否准备好。

3、第三次握手

TCP 客户进程收到确认后,还要向服务器给出确认。确认报文的 ACK=1,ack=y+1,此时,TCP 连接建立,客户端进入 ESTABLISHED(已建立连接)状态。TCP 规定,ACK 报文段可以携带数据,但是如果不携带数据则不消耗序号。这里客户端表示我已经准备好。

4、举例:已失效的连接请求报文段。

client 发送了第一个连接的请求报文,但是由于网络不好,这个请求没有立即到达服务端,而是在某个网络节点中滞留了,直到某个时间才到达 server。本来这已经是一个失效的报文,但是 server 端接收到这个请求报文后,还是会想 client 发出确认的报文,表示同意连接。假如不采用三次握手,那么只要server 发出确认,新的建立就连接了,但其实这个请求是失效的请求,client 是不会理睬server的确认信息,也不会向服务端发送确认的请求。但是server 认为新的连接已经建立起来了,并一直等待 client 发来数据,这样,server 的很多资源就没白白浪费掉了。采用三次握手就是为了防止这种情况的发生,server 会因为收不到确认的报文,就知道 client 并没有建立连接。这就是三次握手的作用。

5. 说一下 tcp 粘包是怎么产生的?

1、什么是 tcp 粘包

发送方发送的多个数据包,到接收方缓冲区首尾相连,粘成一包,被接收。2、原因

TCP 协议默认使用 Nagle 算法可能会把多个数据包一次发送到接收方。

应用程读取缓存中的数据包的速度小于接收数据包的速度,缓存中的多个数据包会被应用程序当成一个包一次读取。

3、处理方法

发送方使用 TCP_NODELAY 选项来关闭 Nagle 算法。数据包增加开始符和结束,应用程序读取、区分数据包。

在数据包的头部定义整个数据包的长度,应用程序先读取数据包的长度,然后读取整个长度的包字节数据,保证读取的是单个包且完整。

6. get 和 post 请求有哪些区别?

GET 在浏览器回退时是无害的,而 POST 会再次提交请求。GET 产生的 URL 地址可以被 Bookmark,而 POST 不可以。

GET 请求会被浏览器主动 cache,而 POST 不会,除非手动设置。GET 请求只能进行 url 编码,而 POST 支持多种编码方式。

GET 请求参数会被完整保留在浏览器历史记录里,而 POST 中的参数不会被保留。GET 请求在 URL 中传送的参数是有长度限制的,而 POST 么有。

对参数的数据类型,GET 只接受 ASCII 字符,而 POST 没有限制。

GET 比 POST 更不安全,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息。GET 参数通过 URL 传递,POST 放在 Request body 中。

7. 如何实现跨域?

1、jsonp,利用 “script” 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP 请求一定需要对方的服务器做支持才可以。

2、cors,需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。

3、postMessage,postMessage 是 HTML5 XMLHttpRequest Level 2 中的 API,且是为数不多可以跨域操作的 window 属性之一。

4、websocket,是 HTML5 的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。

5、Node 中间件代理(两次跨域),同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。

6、nginx 反向代理,实现原理类似于 Node 中间件代理,需要你搭建一个中转 nginx 服务器,用于转发请求。

7、window.name + iframe,window.name 属性的独特之处:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

8、location.hash + iframe,实现原理是a.html 欲与 c.html 跨域相互通信,通过中间页 b.html 来实现。 三个页面,不同域之间利用 iframe 的 location.hash 传值,相同域之间直接 js 访问来通信。9、document.domain + iframe,该方式只能用于二级域名相同的情况下,比如 a.test.com 和b.test.com 适用于该方式。两个页面都通过 js 强制设置 document.domain 为基础主域,就实现了同域。

九、设计模式

1.说一下你熟悉的设计模式?

1、创建型模式:

单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例, 其拓展是有限多例模式。

原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。

工厂方法(FactoryMethod)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。

抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。

建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建

它们,最后构建成该复杂对象。2、结构型模式:

代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。

适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。

装饰(Decorator)模式:动态地给对象增加一些职责,即增加其额外的功能。

外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。

组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。

3、行为型模式:

模板方法(Template Method)模式:定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。

策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。

命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。

迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的

内部表示。

访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式, 即每个元素有多个访问者对象访问。

解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。

十、Spring/Spring MVC

1.为什么要使用 spring?

\1. 方便解耦,便于开发(Spring 就是一个大工厂,可以将所有对象的创建和依赖关系维护都交给spring 管理)

\2. spring 支持 aop 编程(spring 提供面向切面编程,可以很方便的实现对程序进行权限拦截和运行监控等功能)

\3. 声明式事务的支持(通过配置就完成对事务的支持,不需要手动编程)

\4. 方便程序的测试,spring 对 junit4 支持,可以通过注解方便的测试 spring 程序5.方便集成各种优秀的框架

6.降低 javaEE API 的使用难度(Spring 对 javaEE 开发中非常难用的一些 API,例如 JDBC,javaMail, 远程调用等,都提供了封装,使这些 API 应用难度大大降低)

2. 解释一下什么是 AOP?

在业务系统中,总有一些不得不处理的事情,我们将这些重复性的代码抽取出来,放在专门的类中, 在通过 spring 的核心 AOP 对代码段进行增强处理。 在不改变原代码的基础上进行功能增强。有五种增强方式,前置增强,后置增强,环绕增强,最终增强,异常增强。

3. 解释一下什么是 IoC 和 DI?

IoC(Inversion of Control)控制反转,所谓控制反转就是把创建对象(bean)和维护对象(bean) 之间的关系的权利转移到Spring 容器中去了,程序本身不再维护。

DI(dependency injection )依赖注入,是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。

4. spring 有哪些主要模块?

Spring 有七大功能模块,分别是Spring Core,AOP,ORM,DAO,MVC,WEB,Context。

5. spring 常用的注入方式有哪些?

构造方法注入,setter 注入,基于注解的注入。

有四种注解可以注册 bean,每种注解可以任意使用,只是语义上有所差异: @Component:可以用于注册所有 bean

@Repository:主要用于注册dao 层的 bean @Controller:主要用于注册控制层的 bean @Service:主要用于注册服务层的bean

6. spring 中的 bean 是线程安全的吗?

容器本身并没有提供 bean 的线程安全策略,因此可以说 Spring 容器中的 Bean 本身不具备线程安全的特性,但还是要结合具体 scope 的 bean 去研究。

7. spring 支持几种 bean 的作用域?

singleton:单例模式,在整个 Spring IoC 容器中,使用 singleton 定义的 bean 只有一个实例prototype:原型模式,每次通过容器的 getbean 方法获取 prototype 定义的 bean 时,都产生一个新的 bean 实例

8. spring 自动装配 bean 有哪些方式?

(1) 在 XML 中进行显式配置

(2) 在 java 中进行显式配置

(3) 隐式的 bean 发现和自动装配一般推荐使用自动装配 bean 的方式

9. spring 事务实现方式有哪些?

\1. 编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、 commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。

\2. 基于 TransactionProxyFactoryBean 的声明式事务管理

\3. 基于@Transactional 的声明式事务管理

\4. 基于 Aspectj AOP 配置事务

10. 说一下 spring 的事务隔离?

READ UNCOMMITTED(读未提交数据):允许事务读取未被其他事务提交的变更数据,会出现脏读、不可重复读和幻读问题。

READ COMMITTED(读已提交数据):只允许事务读取已经被其他事务提交的变更数据,可避免脏读,仍会出现不可重复读和幻读问题。

REPEATABLE READ(可重复读):确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。SERIALIZABLE(序列化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,可避免所有并发问题,但性能非常低。

11. 说一下 spring mvc 运行流程?

用户发送请求到前端控制器 DispatcherServlet,

之后调用 HanderMapping 处理器找到具体的处理器返回, 再调用 HanderlerAdapter 处理适配器,

再调用具体的 Controller,执行完后返回 Modeandview, handlerAdate 将结果返回给 DispatcherServlet,

再传给 ViewResover 视图解析器解析后返回 View, DispatcherServlet 渲染后响应。

12. spring mvc 有哪些组件?

前端控制器(DispatcherServlet) 处理器映射器(HandlerMapping) 处理器适配器(HandlerAdapter) 拦截器(HandlerInterceptor)

语言环境处理器(LocaleResolver)

主题解析器(ThemeResolver) 视图解析器(ViewResolver)

文件上传处理器(MultipartResolver)

异常处理器(HandlerExceptionResolver) 数据转换(DataBinder)

消息转换器(HttpMessageConverter)

请求转视图翻译器(RequestToViewNameTranslator) 页面跳转参数管理器(FlashMapManager)

处理程序执行链(HandlerExecutionChain)

13. @RequestMapping 的作用是什么?

处理请求地址映射的注解,如果用于类上,表示类中的所有响应请求方法都是以该路径作为父路径

14. @Autowired 的作用是什么?

对类成员变量、方法和构造函数进行标注,完成自动装配的工作

十一、Spring Boot/Spring Cloud

1. 什么是 spring boot?

spring boot 其设计目的是用来简化 Spring 应用的创建、运行、调试、部署等。使用 Spring Boot 可以做到专注于 Spring 应用的开发,而无需过多关注 XML 的配置。Spring Boot 使用“习惯优于配置”的理念,简单来说,它提供了一堆依赖打包,并已经按照使用习惯解决了依赖问题。使用 Spring Boot 可以不用或者只需要很少的Spring 配置就可以让企业项目快速运行起来。

2. spring boot 配置文件有哪几种类型?它们有什么区别?

Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件。application 是用户级别的配置文件,主要用于 Spring Boot 项目的自动化配置。

bootstrap 是系统级别的配置文件,有以下几个应用场景:使用 Spring Cloud Config 配置中心时, 需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;一些固定的不能被覆盖的属性;一些加密/解密的场景。

3. spring boot 有哪些方式可以实现热部署?

模板热部署,使用调试模式 Debug 实现热部署,spring-boot-devtools,Spring Loaded,JRebel。

4. jpa 和 hibernate 有什么区别?

JPA 是规范,Hibernate 是对这个规范的实现。

5. 什么是 spring cloud?

\1. Spring Cloud 是一个微服务框架,相比Dubbo 等 RPC 框架, Spring Cloud 提供的全套的分布式系统解决方案。

\2. Spring Cloud 对微服务基础框架 Netflix 的多个开源组件进行了封装,同时又实现了和云端平台以及和 Spring Boot 开发框架的集成。

\3. Spring Cloud 为微服务架构开发涉及的配置管理,服务治理,熔断机制,智能路由,微代理,控制总线,一次性 token,全局一致性锁,leader 选举,分布式 session,集群状态管理等操作提供了一种简单的开发方式。

\4. Spring Cloud 为开发者提供了快速构建分布式系统的工具,开发者可以快速的启动服务或构建应用、同时能够快速和云平台资源进行对接。

6. spring cloud 断路器的作用是什么?

当一个服务调用另一个服务由于网络原因或者自身原因出现问题时 调用者就会等待被调用者的响应当更多的服务请求到这些资源时,导致更多的请求等待 这样就会发生连锁效应(雪崩效应),断路器就是解决这一问题。

断路器有完全打开状态:一段时间内达到一定的次数无法调用,并且多次监测没有恢复的迹象,断路器完全打开,那么下次请求就不会请求到该服务。

半开:短时间内有恢复迹象,断路器会将部分请求发给该服务,正常调用时,断路器关闭。关闭:当服务一直处于正常状态,能正常调用。

7. spring cloud 的核心组件有哪些?

服务发现——Netflix Eureka

客服端负载均衡——Netflix Ribbon 断路器——Netflix Hystrix

服务网关——Netflix Zuul

分布式配置——Spring Cloud Config

十二、Hibernate

1. 什么是 ORM 框架?

对象关系映射,通过类和数据库表的映射关系,将对象持久化到数据库中

2. hibernate 中如何在控制台查看打印的 sql 语句?

在配置文件里定义下面的配置: hibernate.show_sql=true hibernate.dialect=org.hibernate.dialect.MySQLDialect

3. hibernate 有几种查询方式?

三种,即 HQL,QBC,以及原生的 SQL

4. 在 hibernate 中使用 Integer 和 int 做映射有什么区别?

如果数据库中对应的存储数据为 null,使用 int 会出现类型转换异常,使用 Integer 则不会

5. hibernate 是如何工作的?

通过 Configuration 对象读取配置文件,

读取并解析映射信息创建SessionFactory 对象, 打开 session,

创建 Transaction,

对对象进行CRUD 操作, 提交事务,

关闭 session 和 SessionFactory 对象

6. get()和 load()的区别?

当 get()被调用时会立即发出 SQL 语句,并且返回对象也是实际对象

load()返回的是一个目标对象的代理对象,代理对象只有目标的 ID 值,只有调用 ID 值以外的属性值时才会查询

7. 说一下 hibernate 的缓存机制?

Hibernate 中的缓存分一级缓存和二级缓存。

一级缓存就是 Session 级别的缓存,在事务范围内有效是,内置的不能被卸载。二级缓存是SessionFactory 级别的缓存,从应用启动到应用结束有效。是可选的,默认没有二级缓存,需要手动开启。保存数据库后,缓存在内存中保存一份,如果更新了数据库就要同步更新。

什么样的数据适合存放到第二级缓存中? 1)很少被修改的数据

  1. 经常被查询的数据

  2. 不是很重要的数据,允许出现偶尔并发的数据

  3. 不会被并发访问的数据

  4. 常量数据

8. hibernate 对象有哪些状态?

Transient 瞬时,Persistent 持久,Detached 脱管

9. 在 hibernate 中 getCurrentSession 和 openSession 的区别是什么?

getCurrentSession 创建的 session 会和绑定到当前线程,而 openSession 不会。getCurrentSession 创建的线程会在事务回滚或事物提交后自动关闭,而openSession 必须手动关闭。

10. hibernate 实体类必须要有无参构造函数吗?为什么?

要,因为 hibernate 框架会调用这个默认构造方法来构造实例对象。

十三、Mybatis

1.mybatis 中 #{}和 ${}的区别是什么?

\1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。$将传入的数据直接显示生成在 sql 中。

\2. #方式能够很大程度防止 sql 注入。$方式无法防止Sql 注入。

2. mybatis 有几种分页方式?

数组分页,SQL 分页,拦截器分页,RowBounds 分页

3. RowBounds 是一次性查询全部结果吗?为什么?

RowBounds 表面是在“所有”数据中检索数据,其实并非是一次性查询出所有数据,因为 MyBatis 是对 jdbc 的封装,在 jdbc 驱动中有一个 Fetch Size 的配置,它规定了每次最多从数据库查询多少条数据,假如你要查询更多数据,它会在你执行 next()的时候,去查询更多的数据。就好比你去自动取款机取 10000 元,但取款机每次最多能取 2500 元,所以你要取 4 次才能把钱取完。只是对于 jdbc 来说,当你调用 next()的时候会自动帮你完成查询工作。这样做的好处可以有效的防止内存溢出。

4. mybatis 逻辑分页和物理分页的区别是什么?

逻辑分页是一次性查询很多数据,然后再在结果中检索分页的数据。这样做弊端是需要消耗大量的内存、有内存溢出的风险、对数据库压力较大。

物理分页是从数据库查询指定条数的数据,弥补了一次性全部查出的所有数据的种种缺点,比如需要大量的内存,对数据库查询压力较大等问题。

5. mybatis 是否支持延迟加载?延迟加载的原理是什么?

MyBatis 支持延迟加载,设置 lazyLoadingEnabled=true 即可。

延迟加载的原理的是调用的时候触发加载,而不是在初始化的时候就加载信息。比如调用 a. getB(). getName(),这个时候发现 a. getB() 的值为 null,此时会单独触发事先保存好的关联 B 对象的SQL,先查询出来 B,然后再调用 a. setB(b),而这时候再调用 a. getB(). getName() 就有值了, 这就是延迟加载的基本原理。

6. 说一下 mybatis 的一级缓存和二级缓存?

一级缓存:基于 PerpetualCache 的 HashMap 本地缓存,它的声明周期是和 SQLSession 一致的,有多个 SQLSession 或者分布式的环境中数据库操作,可能会出现脏数据。当 Session flush 或close 之后,该 Session 中的所有 Cache 就将清空,默认一级缓存是开启的。

二级缓存:也是基于 PerpetualCache 的 HashMap 本地缓存,不同在于其存储作用域为 Mapper 级别的,如果多个 SQLSession 之间需要共享缓存,则需要使用到二级缓存,并且二级缓存可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable 序列化接口(可用来保存对象的状态)。

开启二级缓存数据查询流程:二级缓存 -> 一级缓存 -> 数据库。

缓存更新机制:当某一个作用域(一级缓存 Session/二级缓存 Mapper)进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。

7. mybatis 和 hibernate 的区别有哪些?

灵活性:MyBatis 更加灵活,自己可以写 SQL 语句,使用起来比较方便。

可移植性:MyBatis 有很多自己写的 SQL,因为每个数据库的 SQL 可以不相同,所以可移植性比较差。

学习和使用门槛:MyBatis 入门比较简单,使用门槛也更低。

二级缓存:hibernate 拥有更好的二级缓存,它的二级缓存可以自行更换为第三方的二级缓存。

8. mybatis 有哪些执行器(Executor)?

Mybatis 有三种基本的 Executor 执行器:SimpleExecutor、ReuseExecutor、BatchExecutor。SimpleExecutor:每执行一次update 或 select,就开启一个Statement 对象,用完立刻关闭Statement 对象。

ReuseExecutor:执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map 内,供下一次使用。简言之,就是重复使用 Statement 对象。

BatchExecutor:执行 update(没有 select,JDBC 批处理不支持 select),将所有 sql 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement 对象,每个Statement 对象都是 addBatch()完毕后,等待逐一执行 executeBatch()批处理。与 JDBC 批处理相同。

9. mybatis 分页插件的实现原理是什么?

分页插件的基本原理是使用 Mybatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写sql,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。

10. mybatis 如何编写一个自定义插件?

新建类实现 Interceptor 接口,并指定想要拦截的方法签名MyBatis 配置文件中添加该插件

十四、RabbitMQ

1.rabbitmq 的使用场景有哪些?

\1. 解耦。比如说系统 A 会交给系统B 去处理一些事情,通过将 A,B 中间加入消息队列,A 将要处理的事情交给消息队列 ,B 的输入来源于与消息队列。

\2. 有序性。先来先处理,比如一个系统处理某件事需要很长一段时间,但是在处理这件事情时候,有其他人也发出了请求,可以把请求放在消息队里,一个一个来处理。

\3. 消息路由。按照不同的规则,将队列中消息发送到不同的其他队列中。

\4. 异步处理。处理一件事情,需要甲先做 A , 然后做乙丙丁分别处理 BCD ,BCD 这三件事情在 A 之后,但是相互之间没有关联。此时甲处理 A 之后,把事件发送到消息队列里边,乙丙丁接受到事件之后分别处理BCD。在甲处理能力比较大,BCD 处理能力比较小的时候,这样做,对于 A 的提升能力比较大。

2. rabbitmq 有哪些重要的角色?

RabbitMQ 中重要的角色有:生产者,消费者,代理。

生产者:消息的创建者,负责创建和推送数据到消息服务器。消费者:消息的接收方,用于处理数据和确认消息。

代理:就是RabbitMQ 本身,用于扮演“快递”的角色,本身不产生消息,只是扮演“快递”的角色。

3. rabbitmq 有哪些重要的组件?

\1. Server(broker): 接受客户端连接,实现 AMQP 消息队列和路由功能的进程。

\2. Virtual Host:其实是一个虚拟概念,类似于权限控制组,一个 Virtual Host 里面可以有若干个Exchange 和 Queue,但是权限控制的最小粒度是 Virtual Host。

\3. Exchange:接受生产者发送的消息,并根据Binding 规则将消息路由给服务器中的队列。ExchangeType 决定了Exchange 路由消息的行为,例如,在 RabbitMQ 中,ExchangeType 有direct、 Fanout 和 Topic 三种,不同类型的 Exchange 路由的行为是不一样的。

\4. Message Queue:消息队列,用于存储还未被消费者消费的消息。

\5. Message: 由 Header 和 Body 组成,Header 是由生产者添加的各种属性的集合,包括 Message 是否被持久化、由哪个 Message Queue 接受、优先级是多少等。而 Body 是真正需要传输的 APP 数据。

\6. Binding:Binding 联系了 Exchange 与 Message Queue。Exchange 在与多个 Message Queue 发生 Binding 后会生成一张路由表,路由表中存储着 Message Queue 所需消息的限制条件即Binding Key。当 Exchange 收到 Message 时会解析其Header 得到 Routing Key,Exchange 根

据 Routing Key 与 Exchange Type 将 Message 路由到 Message Queue。Binding Key 由

Consumer 在 Binding Exchange 与Message Queue 时指定,而 Routing Key 由 Producer 发送Message 时指定,两者的匹配方式由Exchange Type 决定。

\7. Connection:连接,对于RabbitMQ 而言,其实就是一个位于客户端和 Broker 之间的 TCP 连接。8.Channel:信道,仅仅创建了客户端到 Broker 之间的连接后,客户端还是不能发送消息的。需要为每一个 Connection 创建 Channel,AMQP 协议规定只有通过 Channel 才能执行 AMQP 的命令。一个 Connection 可以包含多个Channel。之所以需要Channel,是因为 TCP 连接的建立和释放都是十分昂贵的,如果一个客户端每一个线程都需要与 Broker 交互,如果每一个线程都建立一个 TCP 连接,暂且不考虑 TCP 连接是否浪费,就算操作系统也无法承受每秒建立如此多的TCP 连接。RabbitMQ 建议客户端线程之间不要共用Channel,至少要保证共用 Channel 的线程发送消息必须是串行的,但是建议尽量共用 Connection。

9.Command:AMQP 的命令,客户端通过 Command 完成与 AMQP 服务器的交互来实现自身的逻辑。例如在RabbitMQ 中,客户端可以通过publish 命令发送消息,txSelect 开启一个事务,txCommit提交一个事务。

4. rabbitmq 中 vhost 的作用是什么?

虚拟主机,一个 broker 里可以开设多个 vhost,用作不同用户的权限分离。

5. rabbitmq 的消息是怎么发送的?

首先客户端必须连接到 RabbitMQ 服务器才能发布和消费消息,客户端和 rabbit server 之间会创建一个 tcp 连接,一旦 tcp 打开并通过了认证(认证就是你发送给 rabbit 服务器的用户名和密码), 你的客户端就会和 RabbitMQ 创建一条 amqp 信道(channel),信道是创建在“真实”tcp 上的

虚拟连接,amqp 命令都是通过信道发送出去的,每个信道都会有一个唯一的id,不论是发布消息, 还是订阅队列都是通过个信道完成的。

6. 消息堆积的影响和解决方案?

当消息生产的速度长时间,远远大于消费的速度时。就会造成消息堆积。

\1. 产生堆积的情况主要有:生产者突然大量发布消息,消费者消费失败,消费者出现性能瓶颈, 消费者挂掉。

\2. 消息堆积的影响有:可能导致新消息无法进入队列,可能导致旧消息无法丢失,消息等待消 费的时间过长,超出了业务容忍范围。

\3. 解决办法:排查消费者的消费性能瓶颈,增加消费者的多线程处理,部署增加多个消费者。

7.rabbitmq 怎么避免消息丢失?

消息丢失的场景主要分为:消息在生产者丢失,消息在 RabbitMQ 丢失,消息在消费者丢失。1.消息在生产者丢失。场景:消息生产者发送消息成功,但是 MQ 没有收到该消息,消息在从生产者传输到 MQ 的过程中丢失,一般是由于网络不稳定的原因。解决方案:采用 RabbitMQ 发送方消息确认机制,当消息成功被 MQ 接收到时,会给生产者发送一个确认消息,表示接收成功。RabbitMQ 发送方消息确认模式有以下三种:普通确认模式,批量确认模式,异步监听确认模式。spring 整合 RabbitMQ 后只使用了异步监听确认模式。

\2. 消息在 RabbitMQ 丢失。场景:消息成功发送到 MQ,消息还没被消费却在 MQ 中丢失,比如 MQ 服务器宕机或者重启会出现这种情况。解决方案:持久化交换机,队列,消息,确保 MQ 服务器重启时依然能从磁盘恢复对应的交换机,队列和消息。 spring 整合后默认开启了交换机, 队列,消息的持久化,所以不修改任何设置就可以保证消息不在 RabbitMQ 丢失。但是为了以防

万一,还是可以申明下。

3.消息在消费者丢失。场景:消息费者消费消息时,如果设置为自动回复 MQ,消息者端收到消息后会自动回复 MQ 服务器,MQ 则会删除该条消息,如果消息已经在 MQ 被删除但是消费者的业务处理出现异常或者消费者服务宕机,那么就会导致该消息没有处理成功从而导致该条消息丢失。解决方案:设置为手动回复 MQ 服务器,当消费者出现异常或者服务宕机时,MQ 服务器不会删除该消息,而是会把消息重发给绑定该队列的消费者,如果该队列只绑定了一个消费者, 那么该消息会一直保存在 MQ 服务器,直到消息者能正常消费为止。本解决方案以一个队列绑 定多个消费者为例来说明,一般在生产环境上也会让一个队列绑定多个消费者也就是工作队列模式来减轻压力,提高消息处理效率。

8. 怎样避免消息重复消费?例如为了防止消息在消费者端丢失,会采用手动回复MQ 的方式来解决,同时也引出了一个问题,消费者处理消息成功,手动回复MQ 时由于网络不稳定,连接断开,导致 MQ 没有收到消费者回复的消息,那么该条消息还会保存在 MQ 的消息队列,由于 MQ 的消息重发机制,会重新把该条消息发给和该队列绑定的消息者处理,这样就会导致消息重复消费。而有些操作是不允许重复消费的,比如下单,减库存,扣款等操作。

解决方案:如果消费消息的业务是幂等性操作(同一个操作执行多次,结果不变)就算重复消费 也没问题,可以不做处理,如果不支持幂等性操作,如:下单,减库存,扣款等,那么可以在消 费者端每次消费成功后将该条消息 id 保存到数据库,每次消费前查询该消息 id,如果该条消息id 已经存在那么表示已经消费过就不再消费否则就消费。采用 redis 存储消息 id,因为 redis 是单线程的,并且性能也非常好,提供了很多原子性的命令,使用 setnx 命令存储消息 id。

9. 要保证消息持久化成功的条件有哪些?

声明队列必须设置持久化durable 为 true。

消息推送投递模式必须设置持久化,deliverymode 设置为 2。消息已经到达持久化交换器。

消息已经到达持久化队列。

10. rabbitmq 有几种工作模式?

\1. 场景 1:单发送单接收

使用场景:简单的发送与接收,没有特别的处理。

img

\2. 场景 2:单发送多接收

使用场景:一个发送端,多个接收端,如分布式的任务派发。为了保证消息发送的可靠性,不丢失消息,使消息持久化了。同时为了防止接收端在处理消息时 down 掉,只有在消息处理完成后才发送ack 消息。

img

\3. 场景 3:Publish/Subscribe

使用场景:发布、订阅模式,发送端发送广播消息,多个接收端接收。

img

\4. Routing (按路线发送接收)

使用场景:发送端按 routing key 发送消息,不同的接收端按不同的 routing key 接收消息。

img

\5. 场景 5:Topics (按 topic 发送接收)

使用场景:发送端不只按固定的 routing key 发送消息,而是按字符串“匹配”发送,接收端同样如此。

img

11. rabbitmq 怎么实现延迟消息队列?

AMQP 协议,以及 RabbitMQ 本身没有直接支持延迟队列的功能,但是可以通过 TTL 和 DLX 模拟出延迟队列的功能。

12. rabbitmq 集群有什么用?

高可用:某个服务器出现问题,整个 RabbitMQ 还可以继续使用。高容量:集群可以承载更多的消息量。

13. rabbitmq 节点的类型有哪些?

磁盘节点:消息会存储到磁盘。

内存结点:消息都存储在内存中,重启服务器消息丢失,性能高于磁盘类型。

14. rabbitmq 集群搭建需要注意哪些问题?

各节点之间使用“–link”连接,不能忽略。

各节点使用的 erlang cookie 值必须相同,此值相当于“密钥”的功能,用于各节点的认证。整个集群中必须包含一个磁盘节点。

15. rabbitmq 每个节点是其他节点的完整拷贝吗?为什么?

不是,原因有两个:

\1. 存储空间的考虑:如果每个节点都拥有所有队列的完全拷贝,这样的新增节点不但没有新增存储空间,反而增加了更多的冗余数据。

\2. 性能的考虑:如果每条消息都需要完整拷贝到每一个集群节点,那新增节点并没有提升处理消息的能力,最多是保持和单节点相同的性能甚至是更糟。

16. rabbitmq 集群中唯一一个磁盘节点崩溃了会发生什么情况?

不能创建队列,不能创建交换器,不能创建绑定,不能添加用户,不能更改权限,不能添加和删除集群节点。唯一磁盘节点崩溃了,集群是可以保持运行的,但你不能更改任何东西。

17. rabbitmq 对集群节点停止顺序有要求吗?

RabbitMQ 对集群的停止的顺序是有要求的,应该先关闭内存节点,最后再关闭磁盘节点。如果顺序恰好相反的话,可能会造成消息的丢失。

十五、Kafka

1. kafka 可以脱离 zookeeper 单独使用吗?为什么?

kafka 不能脱离 zookeeper 单独使用, 因为 kafka 使用 zookeeper 管理和协调 kafka 的节点服务器。

2. kafka 有几种数据保留的策略?

kafka 有两种数据保存策略: 按照过期时间保留 按照存储的消息大小保留。

3. kafka 同时设置了 7 天和 10G 清除数据,到第五天的时候消息达到了

10G****,这个时候 kafka 将如何处理?

这个时候 kafka 会执行数据清除工作,时间和大小不论那个满足条件,都会清空数据。

4. 什么情况会导致 kafka 运行变慢?

cpu 性能瓶颈,磁盘读写瓶颈,网络瓶颈。

5. 使用 kafka 集群需要注意什么?

集群的数量不是越多越好,最好不要超过 7 个,因为节点越多,消息复制需要的时间就越长,整个群组的吞吐量就越低。集群数量最好是单数,因为超过一半故障集群就不能用了,设置为单数容错率更高。

十六、Zookeeper

1.zookeeper 是什么?

zooKeeper 是一个经典的分布式数据一致性解决方案,致力于为分布式应用提供一 个高性能、高可用,且具有严格顺序访问控制能力的分布式协调存储服务。

\1. zooKeeper 将全量数据存储在内存中,并直接服务于客户端的所有非事务请求,尤其适用于以读为主的应用场景。

\2. zooKeeper 一般以集群的方式对外提供服务,一般 3 ~ 5 台机器就可以组成一个可用的Zookeeper 集群了,每台机器都会在内存中维护当前的服务器状态,并且每台机器之间都相互保持着通信。只要集群中超过一半的机器都能够正常工作,那么整个集群就能够正常对外服务。

\3. 对于来自客户端的每个更新请求,ZooKeeper 都会分配一个全局唯一的递增编号,这个编号反映了所有事务操作的先后顺序。

2.简述 Zookeeper 的数据模型?

ZooKeeper 数据模型的结构与 Unix 文件系统很类似,整体上可以看作是一棵树,每个节点称做一个ZNode,每个 ZNode 都可以通过其路径唯一标识。一个 znode 大体上分为 3 各部分:

\1. 节点的数据:即 znode data(节点path, 节点data)的关系就像是java map 中(key,value)的关系。

\2. 节点的子节点 children。

\3. 节点的状态stat:用来描述当前节点的创建、修改记录,包括 cZxid、ctime 等。

3. zookeeper 都有哪些功能?

数据发布和订阅,负载均衡,命名服务,分布式协调通知,集群管理,主节点选举,分布式锁, 分布式队列。

4. zookeeper 有几种部署模式?

单机部署,集群部署,伪集群部署。

5. watcher 架构及运行原理?

Watcher 实现由三个部分组成: Zookeeper 服务端,Zookeeper 客户端,客户端的ZKWatchManager 对象。

客户端首先将 Watcher 注册到服务端,同时将 Watcher 对象保存到客户端的 Watch 管理器中。当ZooKeeper 服务端监听的数据状态发生变化时,服务端会主动通知客户端,接着客户端的 Watch 管理器会触发相关 Watcher 来回调相应处理逻辑,从而完成整体的数据发布/订阅流程。

6. watcher 特点?

\1. 一次性:watcher 是一次性的,一旦被触发就会移除,再次使用时需要重新注册。

\2. 客户端顺序回调:watcher 回调是顺序串行化执行的,只有回调后客户端才能看到最新的数据状态。一个 watcher 回调逻辑不应该太多,以免影响别的 watcher 执行。

\3. 轻量级:WatchEvent 是最小的通信单元,结构上只包含通知状态、事件类型和节点路径,并不会

告诉数据节点变化前后的具体内容。

\4. 时效性:watcher 只有在当前 session 彻底失效时才会无效,若在 session 有效期内快速重连成功, 则 watcher 依然存在,仍可接收到通知。

7. zookeeper 怎么保证主从节点的状态同步?

Zookeeper 的核心是原子广播,这个机制保证了各个 Server 之间的同步。实现这个机制的协议叫做Zab 协议。Zab 协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。

恢复模式:当服务启动或者在领导者崩溃后,Zab 就进入了恢复模式,当领导者被选举出来,且大多数 Server 完成了和 leader 的状态同步以后,恢复模式就结束了。

因此,选举得到的 leader 保证了同步状态的进行,状态同步又保证了 leader 和 Server 具有相同的系统状态,当 leader 失去主权后可以在其他 follower 中选举新的 leader。

8. 集群中为什么要有主节点?

在分布式环境中,有些业务逻辑只需要集群中的某一台机器进行执行,其他的机器可以共享这个结果, 这样可以大大减少重复计算,提高性能,所以就需要主节点。

9. 集群中有 3 台服务器,其中一个节点宕机,这个时候 zookeeper 还可以使用吗?

可以继续使用,单数服务器只要没超过一半的服务器宕机就可以继续使用。

10. 说一下 zookeeper 的通知机制?

客户端会对某个 znode 建立一个 watcher 事件,当该 znode 发生变化时,这些客户端会收到zookeeper 的通知,然后客户端可以根据 znode 变化来做出业务上的改变。

十七、MySql

1.数据库的三范式是什么?

1.第一范式:

当关系模式R 的所有属性都不能在分解为更基本的数据单位时,称 R 是满足第一范式的,简记为 1NF。满足第一范式是关系模式规范化的最低要求,否则,将有很多基本操作在这样的关系模式中实现不了。2.第二范式:

如果关系模式 R 满足第一范式,并且 R 得所有非主属性都完全依赖于 R 的每一个候选关键属性,称R 满足第二范式,简记为 2NF。

3.第三范式:

设 R 是一个满足第一范式条件的关系模式,X 是 R 的任意属性集,如果 X 非传递依赖于 R 的任意一个候选关键字,称 R 满足第三范式,简记为 3NF。

2. 一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 mysql 数据库,又插入了一条数据,此时 id 是几?

一般情况下,我们创建的表的类型是 InnoDB,如果新增一条记录(不重启 mysql 的情况下),这条记录的 id 是 8;但是如果重启(文中提到的)MySQL 的话,这条记录的 ID 是 6。因为 InnoDB 表只把自增主键的最大 ID 记录到内存中,所以重启数据库或者对表 OPTIMIZE 操作,都会使最大 ID 丢失。 但是,如果我们使用表的类型是 MylSAM,那么这条记录的 ID 就是 8。因为 MylSAM 表会把自增主键的最大 ID 记录到数据文件里面,重启 MYSQL 后,自增主键的最大 ID 也不会丢失。

3. 如何获取当前数据库版本?

第一种方法:打开 mysql 在命令提示符上输入 select version() 第二种方法:在 cmd 里面输入 mysql -V 来获取 mysql 版本号

4. 说一下 ACID 是什么?

A:原子性(Atomicity),一个事务中所有操作,要么全部完成要么全部不完成。

C:一致性(Consistency),在事务执行之前和执行之后数据库都处于一致性状态。

I:隔离性(Isolation),在并发环境中当不同的事务同时操作相同数据时,事务之间互不影响。

D:持久性(Durability),只要事务成功结束数据库所做更新必须永久的保存下来。

5. char 和 varchar 的区别是什么?

char 类型的长度是固定的,varchar 的长度是可变的char 类型的效率比 varchar 的效率稍高

6. float 和 double 的区别是什么?

float:占 4 个字节,double: 占 8 个字节。

float 精度 7 位(可提供 7 位或 8 位有效数字,构成包括符号位、指数位和尾数位),double 精度高,有效数字 16 位。

double 消耗内存是 float 的两倍。

double 的运算速度比 float 慢得多,能用单精度时不要用双精度。

7. mysql 索引是怎么实现的?

在 MySQL 中,索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式是不同的。

\1. MyISAM 引擎使用 B+Tree 作为索引结构。MyISAM 会按照数据插入的顺序分配行号,从 0 开始, 然后按照数据插入的顺序存储在磁盘上。因为行是定长的,所以可以从表的开头跳过相应的字节找到需要的行。因此,MyISAM 中索引检索的算法为首先按照 B+Tree 搜索算法搜索索引,如果指定的Key 存在,则取出其data 域的值,然后以 data 域的值为地址,读取相应数据记录。MyISAM 的索引方式索引和数据存放是分开的,非聚集”的,所以也叫做非聚集索引。

\2. InnoDB 也使用 B+Tree 作为索引结构,但具体实现方式却与 MyISAM 截然不同。因为 InnoDB 支持聚簇索引(主键索引),聚簇索引就是表,所以InnoDB 不用像 MyISAM 那样需要独立的行存储。也就是说,InnoDB 的数据文件本身就是索引文件。因为 InnoDB 的索引的方式通过主键聚集数据,严重依赖主键。索引如果没有定义主键,那么 InnoDB 会选择一个唯一的非空索引代替。如果没有这样的索引,InnoDB 会隐式定义一个主键来作为聚簇索引。

8. 怎么验证 mysql 的索引是否满足需求?

使用 explain 查看 SQL 是如何执行查询语句的,从而分析你的索引是否满足需求。explain 语法:explain select * from table where type=1。

9. 说一下 mysql 常用的引擎?

\1. InnoDB

InnoDB 的存储文件有两个,后缀名分别是 .frm 和 .idb,其中 .frm 是表的定义文件,而 idb 是数据文件

InnoDB 中存在表锁和行锁,不过行锁是在命中索引的情况下才会起作用。InnoDB 支持事务,且支持四种隔离级别(读未提交、读已提交、可重复读、串行化),默认的为可重复读;而在 Oracle 数据库中,只支持串行化级别和读已提交这两种级别,其中默认的为读已提交级别。

\2. MyISAM

MyISAM 的存储文件有三个,后缀名分别是 .frm、.MYD、MYI,其中 .frm 是表的定义文件,.MYD 是数据文件,.MYI 是索引文件。

MyISAM 只支持表锁,且不支持事务。Myisam 由于有单独的索引文件,在读取数据方面的性能很高 。

10. 说一下 mysql 的行锁和表锁?

表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般.

11. 说一下乐观锁和悲观锁?

\1. 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制, 比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java 中 synchronized 和 ReentrantLock 等独占锁就是悲观锁思想的实现。

\2. 乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和 CAS 算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于 write_condition 机制,其实都是提供的乐观锁。在 Java 中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的。

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

12. mysql 问题排查都有哪些手段?

使用 show processlist 命令查看当前所有连接信息。使用 explain 命令查询 SQL 语句执行计划。

开启慢查询日志,查看慢查询的 SQL。

13. 如何做 mysql 的性能优化?(列出其中几条即可)

\1. 为查询缓存优化你的查询

\2. EXPLAIN 你的 SELECT 查询

\3. 当只要一行数据时使用 LIMIT 1

\4. 为搜索字段建索引

\5. 在 Join 表的时候使用相当类型的例,并将其索引

\6. 千万不要 ORDER BY RAND()

\7. 避免 SELECT *

\8. 永远为每张表设置一个 ID

\9. 尽可能的使用 NOT NULL

\10. 无缓冲的查询

\11. 把 IP 地址存成 UNSIGNED INT

\12. 固定长度的表会更快

\13. 垂直分割

\14. 拆分大的 DELETE 或 INSERT 语句

\15. 选择正确的存储引擎

\16. 越小的列会越快

十八、Redis

1. redis 是什么?

是一个高性能的(key/value)分布式内存数据库,基于内存运行,并支持持久化的NoSQL 数据库。

优点:

Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。Redis 不仅仅支持简单的key-value 类型的数据,同时还提供list,set,zset,hash 等数据结构的存储。

Redis 支持数据的备份,即 master-slave 模式的数据备份。

2. redis 和 memecache 有什么区别?

\1. 存储方式不同

memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小;redis 有部份存在硬盘上,这样能保证数据的持久性,支持数据的持久化(有快照和AOF 日志两种持久化方式,在实际应用的时候,要特别注意配置文件快照参数,要不就很有可能服务器频繁满载做 dump)。

\2. 数据支持类型不同

redis 在数据支持上要比 memecache 多的多。

3. redis 为什么是单线程的?

redis 核心就是:如果我的数据全都在内存里,我单线程的去操作,就是效率最高的,为什么呢?因为多线程的本质就是 CPU 模拟出来多个线程的情况,这种模拟出来的情况就有一个代价,就是上下文的切换。对于一个内存的系统来说,它没有上下文的切换就是效率最高的。redis 用单个 CPU 绑定一块内存的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个 CPU 上完成的,所以它是单线程处理这个事。

4. 什么是缓存穿透?怎么解决?

如果在请求数据时,在缓存层和数据库层都没有找到符合条件的数据,也就是说,在缓存层和数据库层都没有命中数据,那么,这种情况就叫作缓存穿透。造成缓存穿透的主要原因就是:查询某个 Key 对应的数据,Redis 缓存中没有相应的数据,则直接到数据库中查询。数据库中也不存在要查询的数据,则数据库会返回空,而 Redis 也不会缓存这个空结果。这就造成每次通过这样的 Key 去查询数据都会直接到数据库中查询,Redis 不会缓存空结果。这就造成了缓存穿透的问题。

第一种解决方案:就是把空对象缓存起来。当第一次从数据库中查询出来的结果为空时,我们就将这个空对象加载到缓存,并设置合理的过期时间,这样,就能够在一定程度上保障后端数据库的安全。

第二种解决缓存穿透问题的解决方案:就是使用布隆过滤器,布隆过滤器可以针对大数据量的、有规律的键值进行处理。一条记录是不是存在,本质上是一个 Bool 值,只需要使用 1bit 就可以存储。我们可以使用布隆过滤器将这种表示是、否等操作,压缩到一个数据结构中。比如,我们最熟悉的用户性别这种数据,就非常适合使用布隆过滤器来处理。

5. 什么是缓存击穿?怎么解决?

如果缓存中的数据在某个时刻批量过期,导致大部分用户的请求都会直接落在数据库上,这种现象就叫作缓存击穿。造成缓存击穿的主要原因就是:我们为缓存中的数据设置了过期时间。如果在某个时刻从数据库获取了大量的数据,并设置了相同的过期时间,这些缓存的数据就会在同一时刻失效,造成缓存击穿问题。

对于比较热点的数据,我们可以在缓存中设置这些数据永不过期;也可以在访问数据的时候,在缓存中更新这些数据的过期时间;如果是批量入库的缓存项,我们可以为这些缓存项分配比较合理的过期时间,避免同一时刻失效。

还有一种解决方案就是:使用分布式锁,保证对于每个 Key 同时只有一个线程去查询后端的服务, 某个线程在查询后端服务的同时,其他线程没有获得分布式锁的权限,需要进行等待。不过在高并发场景下,这种解决方案对于分布式锁的访问压力比较大。

6. 什么是缓存雪崩?怎么解决?

如果在某一时刻缓存集中失效,或者缓存系统出现故障,所有的并发流量就会直接到达数据库。数据存储层的调用量就会暴增,用不了多长时间,数据库就会被大流量压垮,这种级联式的服务故障,就叫作缓存雪崩。造成缓存雪崩的主要原因就是缓存集中失效,或者缓存服务发生故障,瞬间的大并发流量压垮了数据库。

解决缓存雪崩问题最常用的一种方案就是保证 Redis 的高可用,将 Redis 缓存部署成高可用集群(必要时候做成异地多活),可以有效的防止缓存雪崩问题的发生。

为了缓解大并发流量,我们也可以使用限流降级的方式防止缓存雪崩。例如,在缓存失效后,通过加锁或者使用队列来控制读数据库写缓存的线程数量。具体点就是设置某些 Key 只允许一个线程查询数据和写缓存,其他线程等待。则能够有效的缓解大并发流量对数据库打来的巨大冲击。

另外,我们也可以通过数据预热的方式将可能大量访问的数据加载到缓存,在即将发生大并发访问的时候,提前手动触发加载不同的数据到缓存中,并为数据设置不同的过期时间,让缓存失效的时间点尽量均匀,不至于在同一时刻全部失效。

7. redis 支持的数据类型有哪些?

1.string(字符串) 2.hash(哈希) 3.list(列表) 4.set(集合) 5.zset(有序集合)

8. jedis 和 redisson 有哪些区别?

Jedis 和 Redisson 都是 Java 中对 Redis 操作的封装。Jedis 只是简单的封装了 Redis 的 API 库, 可以看作是Redis 客户端,它的方法和 Redis 的命令很类似。Redisson 不仅封装了 redis ,还封装了对更多数据结构的支持,以及锁等功能,相比于 Jedis 更加大。但 Jedis 相比于 Redisson 更原生一些,更灵活。

9. 怎么保证缓存和数据库数据的一致性?

\1. 方案 1.写请求串行化

写请求

(1) 写请求更新之前先获取分布式锁,获得之后才能去数据库更新这个数据,获取不到就进行等待, 超时后就返回更新失败。

(2) 更新完之后去刷新缓存,如果刷新失败,放到内存队列中进行重试(重试时取数据库最新数据更新缓存)。

读请求

(1)读请求发现缓存中没有数据时,直接去读取数据库,读完更新缓存。

\2. 方案 2.先更新数据库,异步删除缓存,删除失败后重试

(1) 先更新数据库

(2) 异步删除缓存(如果数据库是读写分离的,那么删除缓存时需要延迟删除,否则可能会在删除缓存时,从库还没有收到更新后的数据,其他读请求就去从库读到旧数据然后设置到缓存中。)

(3) 删除缓存失败时,将删除的 key 放到内存队列或者是消息队列中进行异步重试

\3. 方案 3.业务项目更新数据库,其他项目订阅 binlog 更新

(1) 业务项目直接更新数据库。

(2) cannal 项目会读取数据库的 binlog,然后解析后发消息到 kafka。

(3) 然后缓存更新项目订阅 topic,从 kafka 接收到更新数据库操作的消息后,更新缓存,更新缓存失败时,新建异步线程去重试或者将操作发到消息队列,后续再进行处理。

10. redis 持久化有几种方式?

RDB:RDB 持久化机制,是对 redis 中的数据执行周期性的持久化。

AOF:AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,在 redis 重启的时候,可以通过回放 AOF 日志中的写入指令来重新构建整个数据集。

11. redis 怎么实现分布式锁?

分布式锁常见的三种实现方式:

\1. 数据库乐观锁;

\2. 基于 Redis 的分布式锁;

\3. 基于 ZooKeeper 的分布式锁。Redis 实现最简单的分布式锁:

\1. 加锁

最简单的方法是使用 setnx 命令。key 是锁的唯一标识,按业务来决定命名。比如想要给一种商品的秒杀活动加锁,可以给 key 命名为 “lock_sale_商品 ID”。而 value 设置成什么呢?锁的 value 值为一个随机生成的 UUID。我们可以姑且设置成 1。加锁的伪代码如下:

setnx(key,1) 当一个线程执行 setnx 返回 1,说明 key 原本不存在,该线程成功得到了锁;当一个线程执行 setnx 返回 0,说明 key 已经存在,该线程抢锁失败。

\2. 解锁

有加锁就得有解锁。当得到锁的线程执行完任务,需要释放锁,以便其他线程可以进入。释放锁的最简单方式是执行 del 指令,伪代码如下:

del(key) 释放锁之后,其他线程就可以继续执行 setnx 命令来获得锁。3.锁超时

锁超时是什么意思呢?如果一个得到锁的线程在执行任务的过程中挂掉,来不及显式地释放锁,这块资源将会永远被锁住,别的线程再也别想进来。 所以,setnx 的 key 必须设置一个超时时间,单位为 second,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放,避免死锁。setnx 不支持超时参数,所以需要额外的指令,伪代码如下:

expire(key, 30)

12.redis 如何做内存优化?

\1. 缩减键值对象

\2. 共享对象池

\3. 字符串优化

\4. 编码优化

\5. 控制 key 的数量

13.redis 淘汰策略有哪些?及如何选择淘汰策略?

\1. noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。 大多数写命令都会导致占用更多的内存(有极少数会例外, 如 DEL )。

\2. allkeys-lru: 所有 key 通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。

\3. volatile-lru: 只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的key。

\4. allkeys-random: 所有 key 通用; 随机删除一部分 key。

\5. volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key。

\6. volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的 key。策略选择:

\1. 如果分为热数据与冷数据, 推荐使用 allkeys-lru 策略。 也就是, 其中一部分 key 经常被读写. 如果不确定具体的业务特征, 那么 allkeys-lru 是一个很好的选择。

\2. 如果需要循环读写所有的key, 或者各个key 的访问频率差不多, 可以使用 allkeys-random 策略, 即读写所有元素的概率差不多。

\3. 假如要让 Redis 根据 TTL 来筛选需要删除的 key, 请使用 volatile-ttl 策略。

14.redis 常见的性能问题有哪些?该如何解决?

\1. Master 写内存快照,save 命令调度 rdbSave 函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以 Master 最好不要写内存快照。

\2. Master AOF 持久化,如果不重写 AOF 文件,这个持久化方式对性能的影响是最小的,但是 AOF 文件会不断增大,AOF 文件过大会影响 Master 重启的恢复速度。Master 最好不要做任何持久化工作,包括内存快照和AOF 日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave 开启 AOF 备份数据,策略为每秒同步一次。

\3. Master 调用 BGREWRITEAOF 重写 AOF 文件,AOF 在重写的时候会占大量的 CPU 和内存资源, 导致服务 load 过高,出现短暂服务暂停现象。

\4. Redis 主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave 和 Master 最好在同一个局域网内

十九、JVM

1.说一下 jvm 的主要组成部分?及其作用?

类加载器(ClassLoader)

运行时数据区(Runtime Data Area) 执行引擎(Execution Engine)

本地库接口(Native Interface)

组件的作用:首先通过类加载器(ClassLoader)会把 Java 代码转换成字节码,运行时数据区

(Runtime Data Area)再把字节码加载到内存中,而字节码文件只是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将

字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口

(Native Interface)来实现整个程序的功能。

1. 说一下 jvm 运行时数据区?

运行时数据区包括堆、方法区、栈、本地方法栈、程序计数器。

\1. 堆

堆解决的是对象实例存储的问题,垃圾回收器管理的主要区域。

\2. 方法区

方法区可以认为是堆的一部分,用于存储已被虚拟机加载的信息,常量、静态变量、即时编译器编译后的代码。

\3. 栈

栈解决的是程序运行的问题,栈里面存的是栈帧,栈帧里面存的是局部变量表、操作数栈、动态链接、方法出口等信息。

(1) 栈帧

每个方法从调用到执行的过程就是一个栈帧在虚拟机栈中入栈到出栈的过程。

(2) 局部变量表

用于保存函数的参数和局部变量。

(3) 操作数栈

操作数栈又称操作栈,大多数指令都是从这里弹出数据,执行运算,然后把结果压回操作数栈。

\4. 本地方法栈

与栈功能相同,本地方法栈执行的是本地方法,一个Java 调用非 Java 代码的接口。

\5. 程序计数器(PC 寄存器)

程序计数器中存放的是当前线程所执行的字节码的行数。JVM 工作时就是通过改变这个计数器的值来选取下一个需要执行的字节码指令。

3.说一下堆栈的区别?

堆栈与堆区别为:空间不同、地址方向来不同、释放不同。

\1. 空间不同

(1) 堆栈:堆栈是自动分配变量,以及函数调用的时候所使用的一些空间。

(2) 堆:堆是是由malloc 之类函数分配的空间所在地。

\2. 地址方向不同

(1) 堆栈:堆栈的地址方向是由高向低减少性扩展,有总长度自大小限制。

(2) 堆:堆的地址方向是由低向高增长性扩展,没有总长度大小限制。

\3. 释放不同

(1) 堆栈:堆栈由编译器自动释放,存放函数的参数值,局部变量的 zhidao 值等。

(2) 堆:堆由程序员人工进行释放, 若程序员不释放,程序结束时可能由 OS 回收 。

4. 队列和栈是什么?有什么区别?

\1. 队列(Queue):是限定只能在表的一端进行插入和在另一端进行删除操作的线性表

\2. 栈(Stack):是限定只能在表的一端进行插入和删除操作的线性表区别:队列先进先出(FIFO),栈先进后出(FILO)

5. 什么是双亲委派模型?

当需要加载一个类的时候,子类加载器并不会马上去加载,而是依次去请求父类加载器加载,一直往上请求到最高类加载器:启动类加载器。当启动类加载器加载不了的时候,依次往下让子类加载器进行加载。当达到最底下的时候,如果还是加载不到该类,就会出现ClassNotFound 的情况。

好处:保证了程序的安全性。例子:比如我们重新写了一个 String 类,加载的时候并不会去加载到我们自己写的 String 类,因为当请求上到最高层的时候,启动类加载器发现自己能够加载 String 类, 因此就不会加载到我们自己写的 String 类了。

6. 说一下类加载的执行过程?

加载:将 java 源代码编译后的.class 字节码文件以二进制流的方式加载进内存连接

验证:验证加载进来的二进制流是否符合虚拟机的规范,不会危害的虚拟机自身的安全准备:给类变量(静态变量)赋予初始值,基本数据/引用类型数据

解析:将字符串引用转换为直接引用

初始化:变量赋予初始值、执行静态语句块、执行构造函数等等。

7. 怎么判断对象是否可以被回收?

\1. 引用计数算法,判断对象的引用数量。通过判断对象的引用数量来决定对象是否可以被回收。每个对象实例都有一个引用计数器,被引用则+1,完成引用则-1;任何引用计数为 0 的对象实例可以被当作垃圾收集;

\2. 可达性分析算法,JVM 的主流实现是可达性分析,可达性分析在概念上其实也不难理解,它的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所

走过的路径称为引用链,当一个对象到GC Roots 没有任何引用链相连时,则证明对象是不可用的。

8.java 中都有哪些引用类型?

所以在 JDK.1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4 种, 这 4 种引用的强度依次减弱。

\1. 强引用

Java 中默认声明的就是强引用。只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM 也会直接抛出 OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为 null,这样一来,JVM 就可以适时的回收对象了。

\2. 软引用

软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。在 JDK1.2 之后, 用 java.lang.ref.SoftReference 类来表示软引用。如果一个对象惟一剩下的引用是软引用,那么该对象是软可及的(softly reachable)。垃圾收集器并不像其收集弱可及的对象一样尽量地收集软可及的对象,相反,它只在真正 “需要” 内存时才收集软可及的对象。

\3. 弱引用

弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用。

\4. 虚引用

虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个 null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。

9.说一下 jvm 有哪些垃圾回收算法?

常用的垃圾回收算法有四种:标记-清除算法、复制算法、标记-整理算法、分代收集算法。

\1. 标记-清除算法

分为标记和清除两个阶段,首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。缺点:标记和清除两个过程效率都不高;标记清除之后会产生大量不连续的内存碎片。

\2. 复制算法

把内存分为大小相等的两块,每次存储只用其中一块,当这一块用完了,就把存活的对象全部复制到另一块上,同时把使用过的这块内存空间全部清理掉,往复循环。缺点:实际可使用的内存空间缩小为原来的一半,比较适合。

\3. 标记-整理算法

先对可用的对象进行标记,然后所有被标记的对象向一段移动,最后清除可用对象边界以外的内存。

\4. 分代收集算法

把堆内存分为新生代和老年代,新生代又分为 Eden 区、From Survivor 和 To Survivor。一般新生代中的对象基本上都是朝生夕灭的,每次只有少量对象存活,因此采用复制算法,只需要复制那些少量存活的对象就可以完成垃圾收集;老年代中的对象存活率较高,就采用标记-清除和标记-整理算法来进行回收。

以下属于本题目相关扩展的内容:

扩展 01****:JVM 何时会进行全局 GC

\1. 手动调用 System.GC 但也不是立即调用

\2. 老年代空间不足

\3. 永生代空间不足

\4. 计算得知新生代前往老年代平均值大于老年代剩余空间

扩展 02:在压力测试时,发现 FullGC 频率很高,如何解决

\1. 观察GC 日志,判断是否有内存泄漏,或者存在内部不合理点

\2. 调整JVM 参数,如新生代、老年代大小 S0+S1 大小比例,选用不同的立即回收器

\3. Dump 内存,做进一步的对象分析

\4. 压测脚本的编写,性能问题解决前可以发现问题,并对解决方案进行验证

10.说一下 jvm 有哪些垃圾回收器?

\1. 串行垃圾回收器

在 JDK1.3 之前,单线程回收器是唯一的选择。它的单线程意义不仅仅是说它只会使用一个 CPU 或一个手机线程去完成垃圾收集工作。而且它进行垃圾回收的时候,必须暂停其它所有的工作线程

(Stop The World,STW),直到它收集完成。它适合Client 模式的应用,在单 CPU 环境下,它效率高效,由于没有线程交互的开销,专心垃圾收集自然可以获得最高的单线程效率。

串行的垃圾收集器有两种,Serial 和 Serial Old,一般两者搭配使用。

新生代采用Serial,是利用复制算法;老年代使用 Serial Old 采用标记-整理算法。Client 应用或者命令行程序可以通过-XX:+UseSerialGC 开启串行垃圾回收器。

\2. 并行垃圾回收器

并行垃圾回收器是通过多线程进行垃圾收集的。也会暂停其它所有的工作线程(Stop The World,STW)。适合 Server 模式以及多 CPU 环境。一般会和 JDK1.5 之后出现的 CMS 搭配使用。并行的垃圾回收器有以下几种:

(1)ParNew:Serial 收集器的多线程版本,默认开启的收集线程数和 CPU 数量一样,运行数量可以通过修改ParallelGCThreads 设定。用于新生代手机,复制算法。用-XX:+UseParNewGC,和 Serial Old 收集器组合进行内存回收。

(2)Parallel Scavenge: 关注吞吐量,吞吐量优先,吞吐量=代码运行时间/(代码运行时间+垃圾收集时间),也就是高效率利用 CPU 时间,尽快完成程序的运算任务可以升值最大停顿时间MaxGCPauseMillis 以及,吞吐量大小 GCTimeRatio。如果设置了-XX:+UseAdaptiveSizePolicy 参数,则随着 GC,会动态调整新生代的大小,Eden,Survivor 比例等,以提供最合适的停顿时间或者最大的吞吐量。用于新生代收集,复制算法。通过-XX:+UseParallelGC 参数,Server 模式下默认提供了其和 SerialOld 进行搭配的分代收集方式。

(3)Parllel Old:Parallel Scavenge 的老年代版本。JDK 1.6 开始提供的。在此之前 Parallel Scavenge 的地位也很尴尬,而有了Parllel Old 之后,通过-XX:+UseParallelOldGC 参数使用Parallel Scavenge + Parallel Old 器组合进行内存回收。

\3. CMS 收集器

CMS(Concurrent Mark Sweep)收集器是一种以获得最短回收停顿时间为目标的收集器。从名字就能知道它是标记-清除算法的。但是它比一般的标记-清除算法要复杂一些,分为以下 4 个阶段: 初始标记:标记一下 GC Roots 能直接关联到的对象,会”Stop The World”。

并发标记:GC Roots Tracing,可以和用户线程并发执行。

重新标记:标记期间产生的对象存活的再次判断,修正对这些对象的标记,执行时间相对并发标记短, 会“Stop The World”。

并发清除:清除对象,可以和用户线程并发执行。

\4. G1 垃圾收集器

G1 从整体看还是基于标记-清除算法的,但是局部上是基于复制算法的。这样就意味者它空间整合做的比较好,因为不会产生空间碎片。G1 还是并发与并行的,它能够充分利用多 CPU、多核的硬件环境来缩短“stop the world”的时间。G1 还是分代收集的,但是 G1 不再像上文所述的垃圾收集器, 需要分代配合不同的垃圾收集器,因为 G1 中的垃圾收集区域是“分区”(Region)的。G1 的分代收集和以上垃圾收集器不同的就是除了有年轻代的 ygc,全堆扫描的 full GC 外,还有包含所有年轻代以及部分老年代 Region 的 Mixed GC。G1 还可预测停顿,通过调整参数,制定垃圾收集的最大停顿时间。G1 收集器的运作大致可以分为以下步骤:初始标记、并发标记、最终标记、筛选回收。

11. 详细介绍一下 CMS 垃圾回收器?

CMS(Concurrent Mark Sweep)收集器是一种以获得最短回收停顿时间为目标的收集器。从名字就能知道它是标记-清除算法的。但是它比一般的标记-清除算法要复杂一些,分为以下 4 个阶段:

(1) 初始标记:标记一下 GC Roots 能直接关联到的对象,会”Stop The World”。

(2) 并发标记:GC Roots Tracing,可以和用户线程并发执行。

(3) 重新标记:标记期间产生的对象存活的再次判断,修正对这些对象的标记,执行时间相对并发标记短,会“Stop The World”。

(4) 并发清除:清除对象,可以和用户线程并发执行。

12. 新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?

新生代回收器:Serial、ParNew、Parallel Scavenge 老年代回收器:Serial Old、Parallel Old、CMS

整堆回收器:G1

新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。

13. 简述分代垃圾回收器是怎么工作的?

\1. 分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。

\2. 新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:

(1) 把 Eden + From Survivor 存活的对象放入 To Survivor 区;

(2) 清空 Eden 和 From Survivor 分区;

(3) From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变From Survivor。

(4) 每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15

(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。

(5) 老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。

14. 说一下 jvm 调优的工具?

Jconsole : jdk 自带,功能简单,但是可以在系统有一定负荷的情况下使用。对垃圾回收算法有很详细的跟踪。

JProfiler:商业软件,需要付费。功能强大。VisualVM:JDK 自带,功能强大,与 JProfiler 类似。推荐。

15. 常用的 jvm 调优的参数都有哪些?(列出几种就可以)

(1)-Xms20M

表示设置 JVM 启动内存的最小值为 20M,必须以 M 为单位

(2)-Xmx20M

表示设置 JVM 启动内存的最大值为 20M,必须以 M 为单位。将-Xmx 和-Xms 设置为一样可以避免JVM 内存自动扩展。大的项目-Xmx 和-Xms 一般都要设置到 10G、20G 甚至还要高

(3)-verbose:gc

表示输出虚拟机中 GC 的详细情况

(4)-Xss128k

表示可以设置虚拟机栈的大小为 128k

(5)-Xoss128k

表示设置本地方法栈的大小为 128k。不过 HotSpot 并不区分虚拟机栈和本地方法栈,因此对于HotSpot 来说这个参数是无效的

(6)-XX:PermSize=10M

表示 JVM 初始分配的永久代(方法区)的容量,必须以 M 为单位

(7)-XX:MaxPermSize=10M

表示 JVM 允许分配的永久代(方法区)的最大容量,必须以 M 为单位,大部分情况下这个参数默认为 64M

(8)-Xnoclassgc

表示关闭 JVM 对类的垃圾回收

(9)-XX:+TraceClassLoading 表示查看类的加载信息

(10)-XX:+TraceClassUnLoading 表示查看类的卸载信息

(11)-XX:NewRatio=4

表示设置 年轻代(包括Eden 和两个 Survivor 区)/老年代 的大小比值为 1:4,这意味着年轻代占整个堆的 1/5

(12)-XX:SurvivorRatio=8 表示设置 2 个 Survivor 区:1 个 Eden 区的大小比值为 2:8,这意味着Survivor 区占整个年轻代的 1/5,这个参数默认为 8

(13)-Xmn20M

表示设置年轻代的大小为 20M

(14)-XX:+HeapDumpOnOutOfMemoryError

表示可以让虚拟机在出现内存溢出异常时 Dump 出当前的堆内存转储快照

(15)-XX:+UseG1GC

表示让 JVM 使用 G1 垃圾收集器

(16)-XX:+PrintGCDetails

表示在控制台上打印出 GC 具体细节

(17)-XX:+PrintGC

表示在控制台上打印出 GC 信息

(18)-XX:PretenureSizeThreshold=3145728

表示对象大于 3145728(3M)时直接进入老年代分配,这里只能以字节作为单位

(19)-XX:MaxTenuringThreshold=1

表示对象年龄大于 1,自动进入老年代,如果设置为 0 的话,则年轻代对象不经过Survivor 区,直接进入老年代。对于老年代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对

象会在 Survivor 区进行多次复制, 这样可以增加对象在年轻代的存活时间,增加在年轻代被回收的概率。

(20)-XX:CompileThreshold=1000

表示一个方法被调用 1000 次之后,会被认为是热点代码,并触发即时编译

(21)-XX:+PrintHeapAtGC

表示可以看到每次 GC 前后堆内存布局

(22)-XX:+PrintTLAB

表示可以看到 TLAB 的使用情况

(23)-XX:+UseSpining 开启自旋锁

(24)-XX:PreBlockSpin

更改自旋锁的自旋次数,使用这个参数必须先开启自旋锁

(25)-XX:+UseSerialGC

表示使用 jvm 的串行垃圾回收机制,该机制适用于丹 cpu 的环境下

(26)-XX:+UseParallelGC

表示使用 jvm 的并行垃圾回收机制,该机制适合用于多 cpu 机制,同时对响应时间无强硬要求的环境下,使用-XX:ParallelGCThreads=设置并行垃圾回收的线程数,此值可以设置与机器处理器数量相等。

(27)-XX:+UseParallelOldGC

表示年老代使用并行的垃圾回收机制

(28)-XX:+UseConcMarkSweepGC

表示使用并发模式的垃圾回收机制,该模式适用于对响应时间要求高,具有多 cpu 的环境下

(29)-XX:MaxGCPauseMillis=100

设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM 会自动调整年轻代大小,以满足此值。

(30)-XX:+UseAdaptiveSizePolicy

设置此选项后,并行收集器会自动选择年轻代区大小和相应的 Survivor 区比例,以达到目标系统规定的最低响应时间或者收集频率等,此值建议使用并行收集器时,一直打开。

二十、算法题

1. 什么是快速排序算法?

随机找出一个数,可以随机取,也可以取固定位置,一般是取第一个或最后一个称为基准,然后就是比基准小的在左边,比基准大的放到右边,如何放做,就是和基准进行交换,这样交换完左边都是比基准小的,右边都是比较基准大的,这样就将一个数组分成了两个子数组,然后再按照同样的方法把子数组再分成更小的子数组,直到不能分解为止。

2. 算法的时间复杂度?

算法的时间复杂度表示程序运行完成所需的总时间,它通常用大 O 表示法来表示。

3. 二分法检索如何工作?

在二分法检索中,我们先确定数组的中间位置,然后将要查找的值与数组中间位置的值进行比较,若小于数组中间值,则要查找的值应位于该中间值之前,依此类推,不断缩小查找范围,直至得到最终结果。

4. 是否可以使用二分法检索链表?

由于随机访问在链表中是不可接受的,所以不可能到达 O(1)时间的中间元素。因此,对于链表来说,二分法检索是不可以的(对顺序链表或排序后的链表是可以用的)。

5. 什么是堆排序?

堆排序可以看成是选择排序的改进,它可以定义为基于比较的排序算法。它将其输入划分为未排序和排序的区域,通过不断消除最小元素并将其移动到排序区域来收缩未排序区域。

6. 什么是 Skip list?

Skip list(跳表)是一种可以代替平衡树的数据结构,默认是按照 Key 值升序的。Skip list 让已排序的数据分布在多层链表中,以 0-1 随机数决定一个数据的向上攀升与否,通过“空间来换取时间”的一个算法,在每个节点中增加了向前的指针,在插入、删除、查找时可以忽略一些不可能涉及到的结点, 从而提高了效率。在 Java 的 API 中已经有了实现:

ConcurrentSkipListMap(在功能上对应 HashTable、HashMap、TreeMap) ; ConcurrentSkipListSet(在功能上对应 HashSet);

7. 插入排序算法的空间复杂度是多少?

插入排序的原理是,将数组分为已排序区间和未排序区间两部分,从未排序区间中依次取元素跟已排序区间的元素一一对比,找到适合插入的位置。

在完全有序的情况下,插入排序每个未排序区间元素只需要比较 1 次,所以时间复杂度是 O(n)。而在极端情况完全逆序,时间复杂度为 O(n^2).就等于每次都把未排序元素插入到数组第一位。在数组中插入 1 个元素的时间复杂度为 O(n),那插入 n 个就是 o(n^2)了。

8. 什么是“哈希算法”,它们用于什么?

“哈希算法”又称散列算法,是指某种从任意长度的数据中创建数字“指纹”的算法。它可以将任意长度的数据映射为固定长度的数据,这个映射后的数据我们称之为哈希值。它用于密码有效性、消息和数据完整性以及许多其他加密系统。

9. 如何查找链表是否有循环?

\1. 快慢指针法

我们定义两个指针,初始位置都放在头节点的地方,然后快慢指针一起走,快指针一次走两步(需要注意边界条件),慢指针一次走一步,如果快指针走到 nullptr,该链表就不带环;如果快慢指针相遇,该链表就带环。

\2. 哈希表/map

我们遍历所有结点并在 map 中存储每个结点的引用(或内存地址)。如果当前结点为空结点 nullptr

(即已检测到链表尾部的下一个结点),那么我们已经遍历完整个链表,并且该链表不是环形链表。如果当前结点的引用已经存在于 map 中,那么返回 true(即该链表为环形链表)。

10. 一个算法的最佳情况和最坏情况之间有什么区别?

最佳情况:算法的最佳情况解释为算法执行最佳的数据排列。例如,我们进行二分法检索,如果目标值位于正在搜索的数据中心,则这就是最佳情况,最佳情况时间复杂度为 0。

最差情况:给定算法的最差输入参考。例如快速排序,如果选择关键值的子列表的最大或最小元素, 则会导致最差情况出现,这将导致时间复杂度快速退化到 O(n2)。

11. 什么是基数排序算法?

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort) 或 binsort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为 O (nlog(r)m),其中 r 为所采取的基数,而 m 为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

12. 什么是递归算法?

递归算法是一个解决复杂问题的方法,将问题分解成较小的子问题,直到分解的足够小,可以轻松解决问题为止。通常,它涉及一个调用自身的函数。

13. 提到递归算法的三个定律是什么?

\1. 递归算法必须有一个基点

\2. 递归算法必须有一个趋向基点的状态变化过程

\3. 递归算法必须自我调用

14.什么是冒泡排序算法?

冒泡排序算法也称为下沉排序。在这种类型的排序中,要排序的列表的相邻元素之间互相比较。如果它们按顺序排列错误,将交换值并以正确的顺序排列,直到最终结果“浮”出水面。

二十一、Vue

1. 简述 Vue 的双向绑定数据的原理?

是通过数据劫持结合发布者-订阅者模式的方式来实现的,首先是对数据进行监听,然后当监听的属性发生变化时则告诉订阅者是否要更新,若更新就会执行对应的更新函数从而更新视图。

2. 简述 MVC、MVVM 的关系与区别?

MVC:Model-View-Controller 模型-视图-控制器MVVM:Model-View-ViewModel 模型-视图-视图模型

相同点:都是为了分离 View 和 Model,M 注重数据,V 注重视图,使 Model 和View 更易于维护。不同:MVC 是系统架构级别的,MVVM 是用于单页面上的,MVVM 的灵活性大于 MVC。

MVC 是 Controller 从 View 视图层收集数据,然后向相关模型请求数据并返回相应的视图来完成交互请求。

MVVM 本质上是 MVC 的改进版,其最重要的特性是数据绑定,此外还包括依赖注入,路由配置, 数据模板等一些特性。

3. 简述 Vue 的生命周期?

\1. beforeCreate // 在实例初始化之前

\2. created // 在实例创建完成后被立即调用, data,computed,methods,watch 已可用, dom (ref) 不可用

\3. beforeMount // 挂载实例开始之前

\4. mounted // 挂载实例之后 (dom 节点已经建好), dom (ref)已可用

\5. beforeUpdata // 数据更新之前,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。

\6. updated // 组件 DOM 已经更新

\7. beforeDestory // 实例销毁之前,在这一步,vue 实例仍然完全可用。8.destoryed // 实例销毁之后,Vue 实例指示的所有东西都会解绑定

4.Vue 中组件如何通信的?

\1. props 和$emit:父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过

$emit 触发事件来做到的。2.$attrs 和$listeners

\3. 中央事件总线

\4. provide 和 inject

\5. v-model

\6. $parent 和$children 7.boradcast 和 dispatch 8.vuex 处理组件之间的数据交互

5.什么是闭包函数?js 中闭包函数的优缺点?

闭包就是能够读取其他函数内部变量的函数

\1. 闭包的优点:局部作用域,防止变量污染,函数内部的局部变量保存在内存中

\2. 闭包的缺点:性能差(函数 b 依赖于函数 a 中的变量,所以当函数 a 执行结束后,变量无法被垃圾回收机制回收,从而导致内存浪费)

6.var、let 和 const 的区别是什么?

\1. var 定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。

\2. let 定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。

\3. const 用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。

7.箭头函数 ()=> 和 function 定义函数的区别在哪里?

\1. 箭头函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。

\2. 箭头函数不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。

\3. 箭头函数不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 Rest 参数代替。

\4. 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。

8. 简述 ES6 中的 Promise 对象?

简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

9. null 和 undefined 的区别?

1、null 使用 typeof 输出的是 object, undefined 是 undefined

2、null 表示变量定义了但是赋值为空,undefined 表示变量定义了但是未赋值

3、null 可以转为数字 0,undefined 不可以转为数字 0,但是 null==undefined 为true

二十二、Linux

\1. 关机和重启关机

shutdown -h now 立刻关机shutdown -h 5 5 分钟后关机poweroff 立刻关机

重启

shutdown -r now 立刻重启shutdown -r 5 5 分钟后重启reboot 立刻重启

\2. 目录切换 cd 命令:cd 目录

cd / 切换到根目录

cd /usr 切换到根目录下的 usr 目录cd ../ 切换到上一级目录 或者 cd .. cd ~ 切换到 home 目录

cd - 切换到上次访问的目录3.目录查看 ls [-al]

命令:ls [-al]

ls 查看当前目录下的所有目录和文件

ls -a 查看当前目录下的所有目录和文件(包括隐藏的文件)

ls -l 或 ll 列表查看当前目录下的所有目录和文件(列表查看,显示更多信息) ls /dir 查看指定目录下的所有目录和文件 如:ls /usr

4.目录操作【增,删,改,查】

(1) 创建目录【增】 mkdir 命令:mkdir 目录

mkdir aaa 在当前目录下创建一个名为 aaa 的目录mkdir /usr/aaa 在指定目录下创建一个名为 aaa 的目录

(2) 删除目录或文件【删】rm 命令:rm [-rf] 目录

删除文件:

rm 文件 删除当前目录下的文件

rm -f 文件 删除当前目录的的文件(不询问) 删除目录:

rm -r aaa 递归删除当前目录下的 aaa 目录

rm -rf aaa 递归删除当前目录下的 aaa 目录(不询问) 全部删除:

rm -rf * 将当前目录下的所有目录和文件全部删除

rm -rf /* 【自杀命令!慎用!慎用!慎用!】将根目录下的所有文件全部删除

注意:rm 不仅可以删除目录,也可以删除其他文件或压缩包,为了方便大家的记忆,无论删除任何目录或文件,都直接使用 rm -rf 目录/文件/压缩包

(3) 目录修改【改】mv 和 cp

(4) 重命名目录

命令:mv 当前目录 新目录

例如:mv aaa bbb 将目录 aaa 改为 bbb

注意:mv 的语法不仅可以对目录进行重命名而且也可以对各种文件,压缩包等进行 重命名的操作

(5) 剪切目录

命令:mv 目录名称 目录的新位置

示例:将/usr/tmp 目录下的 aaa 目录剪切到 /usr 目录下面 mv /usr/tmp/aaa /usr 注意:mv 语法不仅可以对目录进行剪切操作,对文件和压缩包等都可执行剪切操作

(6) 拷贝目录

命令:cp -r 目录名称 目录拷贝的目标位置 -r 代表递归

示例:将/usr/tmp 目录下的 aaa 目录复制到 /usr 目录下面 cp /usr/tmp/aaa /usr

注意:cp 命令不仅可以拷贝目录还可以拷贝文件,压缩包等,拷贝文件和压缩包时不 用写-r 递归

(7) 搜索目录【查】find

命令:find 目录 参数 文件名称

示例:find /usr/tmp -name ‘a*’ 查找/usr/tmp 目录下的所有以 a 开头的目录或文件5.文件操作【增,删,改,查】

(1) 新建文件【增】touch 命令:touch 文件名

示例:在当前目录创建一个名为 aa.txt 的文件 touch aa.txt

(2) 删除文件 【删】 rm 命令:rm -rf 文件名

(3) 修改文件【改】 vi 或 vim

【vi 编辑器的 3 种模式】

基本上 vi 可以分为三种状态,分别是命令模式(command mode)、插入模式(Insert mode) 和底行模式(last line mode),各模式的功能区分如下:

  1. 命令行模式 command mode)

控制屏幕光标的移动,字符、字或行的删除,查找,移动复制某区段及进入 Insert mode 下, 或者到 last line mode。

命令行模式下的常用命令:

【1】控制光标移动:↑,↓,j

【2】删除当前行:dd

【3】查找:/字符

【4】进入编辑模式:i o a

【5】进入底行模式::

  1. 编辑模式(Insert mode)

只有在 Insert mode 下,才可以做文字输入,按「ESC」键可回到命令行模式。编辑模式下常用命令:

【1】ESC 退出编辑模式到命令行模式;

  1. 底行模式(last line mode)

将文件保存或退出 vi,也可以设置编辑环境,如寻找字符串、列出行号……等。底行模式下常用命令:

【1】退出编辑: :q

【2】强制退出: :q!

【3】保存并退出: :wq

(4) 打开文件

命令:vi 文件名

示例:打开当前目录下的aa.txt 文件 vi aa.txt 或者 vim aa.txt

注意:使用 vi 编辑器打开文件后,并不能编辑,因为此时处于命令模式,点击键盘 i/a/o 进入编辑模式。

(5) 编辑文件

使用 vi 编辑器打开文件后点击按键:i ,a 或者 o 即可进入编辑模式。i:在光标所在字符前开始插入

a:在光标所在字符后开始插入

o:在光标所在行的下面另起一新行插入

(6) 保存文件:

第一步:ESC 进入命令行模式第二步:: 进入底行模式

第三步:wq 保存并退出编辑

(7) 取消编辑:

第一步:ESC 进入命令行模式第二步:: 进入底行模式

第三步:q! 撤销本次修改并退出编辑

(8) 文件的查看【查】

文件的查看命令:cat/more/less/tail cat:看最后一屏

示例:使用cat 查看/etc/sudo.conf 文件,只能显示最后一屏内容cat sudo.conf

more:百分比显示

示例:使用 more 查看/etc/sudo.conf 文件,可以显示百分比,回车可以向下一行,空格可以向下一页,q 可以退出查看

more sudo.conf less:翻页查看

示例:使用 less 查看/etc/sudo.conf 文件,可以使用键盘上的 PgUp 和 PgDn 向上 和向下翻页, q 结束查看

less sudo.conf

tail指定行数或者动态查看

示例:使用tail -10 查看/etc/sudo.conf 文件的后 10 行,Ctrl+C 结束tail -10 sudo.conf


文章作者: Mr.Hang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mr.Hang !
  目录