面试题
- 零、开场介绍
- 一、JAVA基础
- 1. Java和C++,C#区别
- 2. 面向对象和面向过程的区别
- 3. JDK,JRE区别
- 4. ==和 equals 的区别?
- 5. 为什么重写equals还要重写hashcode?
- 6. 说说抽象类和接口
- 7. String,StringBuffer和StringBuilder。
- 8. final在java中的作用。
- 9. final修饰的对象什么时候被初始化?
- 10. final finally finalize()区别
- 11. 什么是反射?有什么作用?
- 12.常见的异常类有哪些?
- 13. Java如何序列化?
- 14.你知道java8的新特性吗,请简单介绍一下?
- 15. 什么是多态?如何实现?有什么好处?
- 二、JAVA容器
- 1. 集合了解吧,说说集合有几大类,分别介绍一下
- 2. hashmap和concurenthashmap区别
- 3. Array,ArrayList和LinkedList的区别?ArrayList如何扩容?
- 4. hashMap底层实现了解过吗?具体讲讲
- 5. 说说hashMap的jdk1.8的优化
- 6. HashMap 和 hashTable的区别?
- 7. HashSet和HashMap有什么区别?
- 8. 说说ConcurrentHashMap的底层实现
- 9. Jdk中map的实现都有什么:
- 10. LinkedHashMap跟HashMap的关系:
- 11. 红黑树和完全平衡二叉树(AVL)
- 三、多线程
- 1.为什么要使用多线程?多线程可能出现什么问题 ?
- 2. java实现多线程的方式有几种?
- 3. Runnable和Callable有什么区别?
- 4. 线程和进程的区别
- 5. 什么是守护线程?
- 6. java的线程大概有几种状态?
- 7. 说说与线程相关的方法
- 8. sleep 和 wait方法的区别?
- 9. 线程的 run() 和 start() 有什么区别?
- 10. sleep()和yield()有什么区别?
- 11. 死锁的四个条件?
- 12. 怎么在开发中避免死锁?
- 13. 怎么检测死锁?
- 14. 怎么解决死锁?
- 15.线程安全是什么?如何保证线程安全?
- 16. 10个线程,一个线程出错,怎么通知其它的线程。
- 17. 如何避免指令重排序
- 18. volatile除了避免指令重排序还有什么功能
- 19. 说说volatile关键字
- 20. ThreadLocal有什么作用?有哪些使用场景?
- 21. 高并发下,如何安全地修改同一行数据?
- 22. synchronized 和 volatile 的区别是什么?
- 23. synchronized 和 Lock 有什么区别?
- 24. synchronized 和 ReentrantLock 区别是什么?
- 四、计算机网络
- 1. get 和 post的区别
- 2. TCP和UDP的区别?tcp拥塞控制和流量控制如何实现?
- 3. 输入一次url过程,用到哪些协议?
- 5. HTTP和HTTPS的区别。
- 6. HTTPS加密认证过程。
- 7. TCP的三次握手,四次挥手
- 五、JVM
- 1. GC标记方法。
- 2. finalize方法。
- 3. 说一下垃圾回收机制?什么时候垃圾回收?
- 4. 堆内存和栈内存有什么区别?堆和栈哪个快?什么变量存在栈里面?
- 5. Java中类加载过程是什么样的?
- 6. JVM 如何确定垃圾对象:
- 7. 回收算法
- 8. 说说JVM内存区域分为几大块,分别讲一下
- 六、数据库
- 1. 说说mysql的存储引擎
- 2. 讲下索引以及应用场景
- 3. 索引的作用?索引有什么缺点?
- 4. 创建索引的原则
- 5. 为什么MySQL 没有使用Hash作为索引的数据结构呢?
- 6. MySQL索引使用的什么数据结构,B树和B+树的区别
- 7. 索引为什么采用B+树的数据结构,而不使用二叉树或者红黑树
- 8. 聚簇索引和非聚簇索引这两个概念怎么理解?
- 9. mysql索引优化相关方法,联合索引应该把什么字段放在第一个位置?
- 10. mysql的最左原则吗?
- 11. 脏读、幻读、不可重复读指什么?
- 12. 数据库事务的四个特性:
- 13. 说说sql的事务隔离级别,具体的应用场景
- 14. 说说数据库的乐观锁和悲观锁?
- 七、设计模式
- 1. 你最熟悉的设计模式 ?
- 2. 懒汉式你会怎么写,懒汉式实例化在哪,构造函数的权限?
- 3. 单例模式的饿汉式和懒汉式及区别
- 4. 常用的设计模式?
- 5. 简单工厂和抽象工厂有什么区别?
- 6. 说一说设计模式中的代理模式?
- 7. 说一说设计模式中的适配器模式?
- 八、框架
- 1. Spring的特点?
- 2.谈谈自己对于 Spring IoC 的了解
- 3.Spring AOP的实现原理?具体应用在哪些方面?举个例子?
- 4.Spring 框架中用到了哪些设计模式?
- 九、Redis
- 1. redis都有哪些数据结构?
- 2. Redis的应用场景
- 3. redis的延时队列怎么实现?
- 4. Redis如何实现持久化?
- 5. 什么是Redis的事务,用来干什么?
- 6. Redis是阻塞式IO吗?怎么做到请求一个一个进行处理?
- 7. Redis缓存淘汰策略?是失效时间到了就立即淘汰吗?淘汰控制?
- 8. 什么是缓存雪崩和缓存穿透?
- 9. 如何解决 Redis 缓存雪崩问题
- 10. 如何解决 Redis 缓存穿透问题
- 11. Redis如何实现分布式锁
- 十、Kafka
- 1. 问一下kafka的问题吧,kafka是怎么进行数据备份的?
- 2. 消费者是从leader中拿数据,还是从follow中拿数据?
- 3. 说说kafka的ISR机制?
- 4. kafka里面存的数据格式都是什么样的?
- 5. kafka中存的一个是数据文件,一个是索引文件,说说这个?
- 6. kafka 是如何清理过期数据的?
- 7. 一条message中包含哪些信息?
- 十一、项目
- 1. 校园论坛项目介绍
- 2. 这个项目最具挑战的是什么?
- 3. 项目中如何使用多线程?
- 4. 登录注册是如何实现的?用Cookie做了什么? Cookie被窃取了该怎么办?
- 5. Kafka做什么的?
- 6. 项目Kafka使用场景。为什么要用Kafka? Kafka为什么吞吐量高?
- 7. Kafka怎么保证顺序性?
- 8. 说一下kafka的内部结构,消息队列存在的意义?
- 9. 如何做到显示首页的热度最高帖子?如何更新缓存?
- 10. Redis存了什么数据?缓存过期时间是多少?如何解决缓存一致性问题?
- 11. 如何识别热点数据?热度如何计算?如何更新热度?
- 12. Redis如何淘汰过期数据?
- 13. 项目里redis做持久化处理了吗
- 14. redis宕机了怎么办
- 15. zset的底层数据结构?什么是跳表?
- 15. ES如何实现全文搜索的功能?ES的底层数据结构?
- 16. ES倒排索引为什么能够加速搜索?
- 17. 项目中SpringSecurity的权限模型是怎么样的?
- 18 .用户的授权信息如何存储?
- 十二、算法
- 1. 稳定排序有哪些?
- 2. 排序的时空复杂度
零、开场介绍
一、JAVA基础
1. Java和C++,C#区别
- 都是面向对象的语言,都支持封装、继承和多态
- Java 不提供指针来直接访问内存,程序内存更加安全
- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
- Java 有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存。
- C ++同时支持方法重载和操作符重载,但是 Java 只支持方法重载(操作符重载增加了复杂性,这与 Java 最初的设计思想不符)。
2. 面向对象和面向过程的区别
面向过程: 面向过程性能比面向对象高。 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发。但是,面向过程没有面向对象易维护、易复用、易扩展。
面向对象: 面向对象易维护、易复用、易扩展。 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,面向对象性能比面向过程低。
3. JDK,JRE区别
Java运行时环境(JRE)是将要执行Java程序的Java虚拟机。它同时也包含了执行applet需要的浏览器插件。Java开发工具包(JDK)是完整的Java软件开发包,包含了JRE,编译器和其他的工具(比如:JavaDoc,Java调试器),可以让开发者开发、编译、执行Java应用程序。
4. ==和 equals 的区别?
==比较
- 基本数据类型比较的是值;
- 引用类型比较的是地址值。
equals(Object o):
1)不能比较基本数据类型,基本数据类型不是类类型;
2)比较引用类型时(该方法继承自Object,在object中比较的是地址值)等同于”==”;
Object类中的方法,所以,在每一个java类中,都会有这个方法,因为每一个java类都是直接或者间接的Object类的子类,会继承到这个方法。
5. 为什么重写equals还要重写hashcode?
如果两个对象相等,则 hashcode 一定也是相同的。两个对象相等,对两个对象分别调用 equals 方法都返回 true。但是,两个对象有相同的 hashcode 值,它们也不一定是相等的 。因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖。
6. 说说抽象类和接口
- 抽象类可以有构造方法;接口中不能有构造方法。
- 抽象类中可以有普通成员变量;接口中没有普通成员变量。
- 抽象类中可以包含非抽象普通方法;JDK1.8 以前接口中的所有方法默认都是抽象的,JDK1.8 开始方法可以有 default 实现和 static 方法。
- 抽象类中的抽象方法的访问权限可以是 public、protected 和 default;接口中的抽象方法只能是 public 类型的,并且默认即为 public abstract 类型。
- 抽象类中可以包含静态方法;JDK1.8 前接口中不能包含静态方法,JDK1.8 及以后可以包含已实现的静态方法。
- 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量可以是任意访问权限;接口中变量默认且只能是 public static final 类型。
- 一个类可以实现多个接口,用逗号隔开,但只能继承一个抽象类。
- 接口不可以实现接口,但可以继承接口,并且可以继承多个接口,用逗号隔开。
7. String,StringBuffer和StringBuilder。
相同点:
- 都可以储存和操作字符串
- 都使用 final 修饰,不能被继承
- 提供的 API 相似
区别:
- String 是只读字符串,String 对象内容是不能被改变的
- StringBuffer 和 StringBuilder 的字符串对象可以对字符串内容进行修改,在修改后的内存地址不会发生改变
- StringBuilder 线程不安全;StringBuffer 线程安全
- 方法体内没有对字符串的并发操作,且存在大量字符串拼接操作,建议使用 StringBuilder,效率较高。
8. final在java中的作用。
final 语义是不可改变的。
- 被 final 修饰的类,不能够被继承
- 被 final 修饰的成员变量必须要初始化,赋初值后不能再重新赋值(可以调用对象方法修改属性值)。对基本类型来 说是其值不可变;对引用变量来说其引用不可变,即不能再指向其他的对象
- 被 final 修饰的方法不能重写
9. final修饰的对象什么时候被初始化?
final类型的静态变量(即编译期常量)在类加载时就会被初始化放入常量池中,其他的非编译期常量是在运行期初始化的。
10. final finally finalize()区别
- final 表示最终的、不可改变的。用于修饰类、方法和变量。final 修饰的类不能被继承;final 方法也同样只能使用,不能重写,但能够重载;final 修饰的成员变量必须在声明时给定初值或者在构造方法内设置初始值,只能读取,不可修改;final 修饰的局部变量必须在声明时给定初值;final 修饰的变量是非基本类型,对象的引用地址不能变,但对象的属性值可以改变
- finally 异常处理的一部分,它只能用在 try/catch 语句中,表示希望 finally 语句块中的代码最后一定被执行(存在一些情况导致 finally 语句块不会被执行,如 jvm 结束)
- finalize() 是在 java.lang.Object 里定义的,Object 的 finalize() 方法什么都不做,对象被回收时 finalize() 方法会被调用。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要清理工作,在垃圾收集器删除对象之前被调用的。一般情况下,此方法由JVM调用。特殊情况下,可重写 finalize() 方法,当对象被回收的时候释放一些资源,须调用 super.finalize() 。
11. 什么是反射?有什么作用?
Java 反射,就是在运行状态中
- 获取任意类的名称、package 信息、所有属性、方法、注解、类型、类加载器、modifiers(public、static)、父类、现实接口等
- 获取任意对象的属性,并且能改变对象的属性
- 调用任意对象的方法
- 判断任意一个对象所属的类
- 实例化任意一个类的对象
Java 的动态就体现在反射。通过反射我们可以实现动态装配,降低代码的耦合度;动态代理等。反射的过度使用会严重消耗系统资源。
JDK 中 java.lang.Class 类,就是为了实现反射提供的核心类之一。
一个 jvm 中一种 Class 只会被加载一次。
12.常见的异常类有哪些?
java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类 Exception(异常)和 Error(错误)。Exception 能被程序本身处理(try-catch), Error 是无法处理的(只能尽量避免)。
Exception 和 Error 二者都是 Java 异常处理的重要子类,各自都包含大量子类。
-
Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 受检查异常(必须处理) 和 不受检查异常(可以不处理)。
-
Error :Error 属于程序无法处理的错误 ,我们没办法通过 catch 来进行捕获 。例如,Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
13. Java如何序列化?
序列化:将 Java 对象转换成字节流的过程。
反序列化:将字节流转换成 Java 对象的过程。
当 Java 对象需要在网络上传输 或者 持久化存储到文件中时,就需要对 Java 对象进行序列化处理。
序列化的实现:类实现 Serializable 接口,这个接口没有需要实现的方法。实现 Serializable 接口是为了告诉 jvm 这个类的对象可以被序列化。
注意事项:
- 某个类可以被序列化,则其子类也可以被序列化
- 对象中的某个属性是对象类型,需要序列化也必须实现 Serializable 接口
- 声明为 static 和 transient 的成员变量,不能被序列化。static 成员变量是描述类级别的属性,transient 表示临时数据
- 反序列化读取序列化对象的顺序要保持一致
14.你知道java8的新特性吗,请简单介绍一下?
- Lambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中。
- 方法引用− 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
- 默认方法− 默认方法就是一个在接口里面有了一个实现的方法。
- 新工具− 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
- Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
- Date Time API − 加强对日期与时间的处理。
- Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
- Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。
15. 什么是多态?如何实现?有什么好处?
多态:
同一个接口,使用不同的实例而执行不同操作。同一个行为具有多个不同表现形式或形态的能力。
实现多态有三个条件:
- 继承
- 子类重写父类的方法
- 父类引用变量指向子类对象
实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
Java 中使用父类的引用变量调用子类重写的方法,即可实现多态。
二、JAVA容器
1. 集合了解吧,说说集合有几大类,分别介绍一下
Java 集合, 也叫作容器,主要是由两大接口派生而来:一个是 Collecton接口,主要用于存放单一元素;另一个是 Map 接口,主要用于存放键值对。对于Collection 接口,下面又有三个主要的子接口:List、Set 和 Queue。
- List(对付顺序的好帮手): 存储的元素是有序的、可重复的。
- Set(注重独一无二的性质): 存储的元素是无序的、不可重复的。
- Queue(实现排队功能的叫号机): 按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的。
- Map(用 key 来搜索的专家): 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),“x” 代表 key,“y” 代表 value,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。
2. hashmap和concurenthashmap区别
HashMap和ConcurentHashMap的主要区别是HashMaP是线程不安全,ConcurentHashMap是线程安全
JDK1.7
(1) HashMap的线程不安全主要是发生在扩容函数中,即根源是在transfer函数中,由于采用头插法,在多线程高并发环境下会造成死循环或数据丢失问题。
(2) ConcurentHashMap采用分段锁,可重入锁Segment类,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
JDK1.8
(1) HashMap在JDK 1.8中采用尾插法修复了1.7中由于头插法引起的线程不安全(死循环和数据丢失)。在JDK 1.8中进行put操作会引起线程不安全而导致数据覆盖。
(2) ConcurentHashMap采用CAS和synchronized来保证线程安全,使用的是锁分离思想,只是锁住的是一个node,而锁住Node之前的操作是基于在volatile和CAS之上无锁并且线程安全的,并且大量使用了U.compareAndSwapXXX的方法,这个方法是利用一个CAS算法实现。
3. Array,ArrayList和LinkedList的区别?ArrayList如何扩容?
Array 即数组
定义一个 Array 时,必须指定数组的数据类型及数组长度,即数组中存放的元素个数固定并且类型相同。
ArrayList 是动态数组,长度动态可变,会自动扩容。不使用泛型的时候,可以添加不同类型元素。
ArrayList和LinkedList的区别
- ArrayList 基于动态数组实现的非线程安全的集合;LinkedList 基于双向链表实现的非线程安全的集合。
- 扩容问题:ArrayList 使用数组实现,无参构造函数默认初始化长度为 10,数组扩容是会将原数组中的元素重新拷贝到新数组中,长度为原来的 1.5 倍(扩容代价高);LinkedList 不存在扩容问题,新增元素放到集合尾部,修改相应的指针节点即可。
- LinkedList 比 ArrayList 更占内存,因为 LinkedList 为每一个节点存储了两个引用节点,一个指向前一个元素,一个指向下一个元素。
- 对于随机 index 访问的 get 和 set 方法,一般 ArrayList 的速度要优于 LinkedList。因为 ArrayList 直接通过数组下标直接找到元素;LinkedList 要移动指针遍历每个元素直到找到为止。
- 新增和删除元素,一般 LinkedList 的速度要优于 ArrayList。因为 ArrayList 在新增和删除元素时,可能扩容和复制数组;LinkedList 实例化对象需要时间外,只需要修改节点指针即可。
- LinkedList 集合不支持高效的随机访问(RandomAccess)
- ArrayList 的空间浪费主要体现在在list列表的结尾预留一定的容量空间;LinkedList 的空间花费则体现在它的每一个元素都需要消耗存储指针节点对象的空间。
- 都是非线程安全,允许存放 null
4. hashMap底层实现了解过吗?具体讲讲
- HashMap 基于 Hash 算法实现,通过 put(key,value) 存储,get(key) 来获取 value
- 当传入 key 时,HashMap 会根据 key,调用 hash(Object key) 方法,计算出 hash 值,根据 hash 值将 value 保存在 Node 对象里,Node 对象保存在数组里
- 当计算出的 hash 值相同时,称之为 hash 冲突,HashMap 的做法是用链表和红黑树存储相同 hash 值的 value
- 当 hash 冲突的个数:小于等于 8 使用链表;大于 8 且 tab length 大于等于 64 时,使用红黑树解决链表查询慢的问题
ps:
- 上述是 JDK 1.8 HashMap 的实现原理,并不是每个版本都相同,比如 JDK 1.7 的 HashMap 是基于数组 + 链表实现,所以 hash 冲突时链表的查询效率低
- hash(Object key) 方法的具体算法是 (h = key.hashCode()) ^ (h >>> 16),经过这样的运算,让计算的 hash 值分布更均匀
5. 说说hashMap的jdk1.8的优化
JDK1.8在JDK1.7的基础上针对一个链上数据过多(即拉链过长的情况)导致性能下降,增加了红黑树来进行优化。即当链表超过8时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。
6. HashMap 和 hashTable的区别?
JDK 1.8 中 HashMap 和 Hashtable 主要区别如下:
- 线程安全性不同。HashMap 线程不安全;Hashtable 中的方法是 synchronized 的。
- key、value 是否允许 null。HashMap 的 key 和 value 都是可以是 null,key 只允许一个 null;Hashtable 的 key 和 value 都不可为 null。
- 迭代器不同。HashMap 的 Iterator 是 fail-fast 迭代器;Hashtable 还使用了 enumerator 迭代器。
- hash的计算方式不同。HashMap 计算了 hash值;Hashtable 使用了 key 的 hashCode方法。
- 默认初始大小和扩容方式不同。HashMap 默认初始大小 16,容量必须是 2 的整数次幂,扩容时将容量变为原来的2倍;Hashtable 默认初始大小 11,扩容时将容量变为原来的 2 倍加 1。
- 是否有 contains 方法。HashMap 没有 contains 方法;Hashtable 包含 contains 方法,类似于 containsValue。
- 父类不同。HashMap 继承自 AbstractMap;Hashtable 继承自 Dictionary。
7. HashSet和HashMap有什么区别?
HashMap
- 实现 Map 接口
- 键值对的方式存储
- 新增元素使用 put(K key, V value) 方法
- 底层通过对 key 进行 hash,使用数组 + 链表或红黑树对 key、value 存储
HashSet
- 实现 Set 接口
- 存储元素对象
- 新增元素使用 add(E e) 方法
- 底层是采用 HashMap 实现,大部分方法都是通过调用 HashMap 的方法来实现
8. 说说ConcurrentHashMap的底层实现
ConcurrentHashMap1.7 实现原理
ConcurrentHashMap 采用分段锁设计、将一个大的 HashMap 集合拆分成 n 多个不同的小的 HashTable(Segment),默认的情况下是分成 16 个不同的 Segment,每个 Segment 中都有自己独立的 HashEntry<K,V>[] table;
数组+Segments 分段锁+HashEntry 链表实现
使用 Lock 锁+CAS 乐观锁+UNSAFE 类
PUT 方法流程
- 第一次需要计算出:key 出存放在那个 Segment 对象中
- 还需要计算 key 存放在 Segment 对象中具体 index 位置。
ConcurrentHashMap1.8 实现原理
Put 原理 锁的粒度非常小,对每个数组 index 位置上锁 对 1.7ConcurrentHashMap 实现优化
- 取消 segment 分段设计,使用 synchronized 锁
- synchronized 在 JDK1.6 开始做了优化 默认实现锁的升级过程
JDK 1.7 到 JDK 1.8 中的 ConcurrentHashMap 最大的改动:
链表上的 Node 超过 8 个改为红黑树,查询复杂度 O(logn)
ReentrantLock 显示锁改为 synchronized,说明 JDK 1.8 中 synchronized 锁性能赶上或超过 ReentrantLock
9. Jdk中map的实现都有什么:
HashMap、TreeMap、Hashtable、LinkedHashMap。
10. LinkedHashMap跟HashMap的关系:
LinkedHashMap维护了一个双向循环链表,是有序的,保留了元素的插入顺序。
11. 红黑树和完全平衡二叉树(AVL)
红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。
平衡二叉树(AVL)的性质
它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
区别:
1、红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。
2、平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知
三、多线程
1.为什么要使用多线程?多线程可能出现什么问题 ?
由于创建和销毁线程都需要很大的开销,运用线程池就可以大大的缓解这些内存开销很大的问题;可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存 。
多线程并发编程并不总是能提高程序的执行效率和运行速度,而且可能存在一些问题,包括内存泄漏、上下文切换、死锁以及受限于硬件和软件的资源限制问题等。
2. java实现多线程的方式有几种?
有4种方式可以用来创建线程:
- 继承Thread类
class MyThread extends Thread{
public void run(){
System.out.println("线程运行");
}
}
public class Test{
public static void main(String[] args){
MyThread thread=new MyThread();
thread.start();//开启线程
}
}
- 实现Runnable接口
class MyThread implements Runnable
{
public void run(){
System.out.println("线程运行");
}
}
public class Test{
public static void main(String[] args){
MyThread thread=new MyThread();
Thread t=new Thread(thread);
t.start();//开启线程
}
}
- 还有一种方式是实现Callable接口
实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。
import java.util.concurrent.*;
public class CallableAndFuture{
//创建线程
public static class CallableTest implements Callable<String>{
public String call() throws Exception{
return "Hello World";
}
}
public static void main(String[] args){
ExecutorService threadPool=Executors.newSingleThreadExecutor();
//启动线程
Future<String> future=threadPool.submit(new CallableTest());
try{
System.out.println("等待线程执行完成");
System.out.println(future.get());//等待线程结束,并获取返回结果
}
catch(Exception e){
e.printStackTrace();
}
}
}
3. Runnable和Callable有什么区别?
主要区别
- Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,支持泛型
- Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息
4. 线程和进程的区别
-
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即为一个进程的创建、运行以及消亡的过程。
-
线程是比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程,多个线程共享进程的堆和方法区内存资源,每个线程都有自己的程序计数器、虚拟机栈和本地方法栈。由于线程共享进程的内存,因此系统产生一个线程或者在多个线程之间切换工作时的负担比进程小得多,线程也称为轻量级进程。
-
进程和线程最大的区别是,各进程是独立的,而各线程则不一定独立,因为同一进程中的多个线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护,进程则相反
5. 什么是守护线程?
Java线程分为用户线程和守护线程。
- 守护线程是程序运行的时候在后台提供一种通用服务的线程。所有用户线程停止,进程会停掉所有守护线程,退出程序。
- Java中把线程设置为守护线程的方法:在 start 线程之前调用线程的 setDaemon(true) 方法。
6. java的线程大概有几种状态?
线程在运行的生命周期中的任何时刻只能是 6 种不同状态的其中一种。
- 初始状态(NEW):线程已经构建,尚未启动。
- 运行状态(RUNNABLE):包括就绪(READY)和运行中(RUNNING)两种状态,统称为运行状态。
- 阻塞状态(BLOCKED):线程被锁阻塞。
- 等待状态(WAITING):线程需要等待其他线程做出特定动作(通知或中断)。
- 终止状态(TERMINATED):当前线程已经执行完毕。
7. 说说与线程相关的方法
- 加锁对象的 wait() 方法,使一个线程处于等待状态,并且释放所持有的对象的锁
- 加锁对象的 notify() 方法,由 JVM 唤醒一个处于等待状态的线程,具体哪个线程不确定,且与优先级无关
- 加锁对象的 notityAll() 方法,唤醒所有处入等待状态的线程,让它们重新竞争对象的锁
- 线程的 sleep() 方法,使一个正在运行的线程处于睡眠状态,是静态方法,调用此方法要捕捉 InterruptedException 异常
- JDK 1.5 开始通过 Lock 接口提供了显式锁机制,丰富了锁的功能,可以尝试加锁和加锁超时。Lock 接口中定义了加锁 lock()、释放锁 unlock() 方法 和 newCondition() 产生用于线程之间通信的 Condition 对象的方法
- JDK 1.5 开始提供了信号量 Semaphore 机制,信号量可以用来限制对某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须调用 Semaphore 对象的 acquire() 方法得到信号量的许可;在完成对资源的访问后,线程必须调用 Semaphore 对象的 release() 方法向信号量归还许可
8. sleep 和 wait方法的区别?
- sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
- wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
9. 线程的 run() 和 start() 有什么区别?
-
start方法用于启动线程,真正实现了多线程运行。在调用T线程的sturt方法后.线程会在后台执行,无须等待run方法体的代码执行完毕,就可以继续执行下面的代码。
-
在通过调用Thrend 类的start方法启动一个线程时,此线程处于就绪状态,并没有运行。
-
run方法也叫作线程体。包含了要执行的线程的逻辑代码,在调用run 方法后.线程会进人运行状态,开始运行run方法中的代码。在run 方法运行结束后,该线程终止,CPU再次调度其他线程。
10. sleep()和yield()有什么区别?
- sleep() 方法给其他线程运行机会时不考虑线程的优先级;yield() 方法只会给相同优先级或更高优先级的线程运行的机会
- 线程执行 sleep() 方法后进入超时等待状态;线程执行 yield() 方法转入就绪状态,可能马上又得得到执行
- sleep() 方法声明抛出 InterruptedException;yield() 方法没有声明抛出异常
- sleep() 方法需要指定时间参数;yield() 方法出让 CPU 的执行权时间由 JVM 控制
11. 死锁的四个条件?
- 互斥条件:一个锁一次只能由一个进程占有
- 不可剥夺条件:一个进程占有的资源在使用完之前不可以被其他进程剥夺,只能由该进程释放之后才能被其他进程获取。
- 请求和保持条件:一个进程在申请资源的同时保持已经占有的资源不释放。
- 循环等待条件:同时需要A、B两个资源的进程分别占有了A和B,形成了两个进程都阻塞并等待对方释放资源的状态。
12. 怎么在开发中避免死锁?
避免死锁:
对于以上 4 个条件,只要破坏其中一个条件,就可以避免死锁的发生。
对于第一个条件 “互斥” 是不能破坏的,因为加锁就是为了保证互斥。
其他三个条件,我们可以尝试
- 一次性申请所有的资源,破坏 “占有且等待” 条件
- 占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源,破坏 “不可抢占” 条件
- 按序申请资源,破坏 “循环等待” 条件
编程中的最佳实践:
使用 Lock 的 tryLock(long timeout, TimeUnit unit)的方法,设置超时时间,超时可以退出防止死锁
尽量使用并发工具类代替加锁
尽量降低锁的使用粒度
尽量减少同步的代码块
13. 怎么检测死锁?
jstack -l可以查看堆栈运行的状态,-l会显示锁状态,里面会报告死锁。
14. 怎么解决死锁?
1、系统重启
2、撤销代价比较低的线程,例如低优先级的线程
15.线程安全是什么?如何保证线程安全?
当多个线程访问某个方法时,不管你通过怎样的调用方式或者说这些线程如何交替的执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。
synchronized关键字,就是用来控制线程同步的,保证我们的线程在多线程环境下,不被多个线程同时执行,确保我们数据的完整性,使用方法一般是加在方法上。
就是我们在需要的时候去手动的获取锁和释放锁,甚至我们还可以中断获取以及超时获取的同步特性,但是从使用上说Lock明显没有synchronized使用起来方便快捷。
16. 10个线程,一个线程出错,怎么通知其它的线程。
重写了自定义线程组的uncaughtException()方法后,加上相应的中断操作和判断,是可以做到当某个线程出现异常然后中断时,其他的线程也会马上运行结束,不过这里的其他线程指得是当前和出现异常的线程在同一线程组的线程们,而在异常线程之后新加入线程组的线程就不会被影响到的,从正常线程可以持续运行下去就可以证明这点,所以即使采取了异常中断的手段,但是当线程组内的某个线程出现异常,只会影响到当前在线程组内的线程的运行情况,异常之后才加入到线程组的线程就不会被停止了。
17. 如何避免指令重排序
观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
18. volatile除了避免指令重排序还有什么功能
Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
从实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic包下的类,比如AtomicInteger。
19. 说说volatile关键字
对于可见性,Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
从实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic包下的类,比如AtomicInteger。
20. ThreadLocal有什么作用?有哪些使用场景?
ThreadLocal 是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。通过这种方式,避免资源在多线程间共享。
经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。
21. 高并发下,如何安全地修改同一行数据?
- 可以将数据加载到缓存中,利用 CAS 方式进行更新
- 也可以将所有请求放到同一个消息队列里,异步返回,按顺序执行更新
注意:
- 如果使用悲观锁,在并发请求量很大的情况下,会导致服务和数据连接数耗尽,系统卡死
22. synchronized 和 volatile 的区别是什么?
作用:
- synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。
- volatile 表示变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性;禁止指令重排序。
区别:
- synchronized 可以作用于变量、方法、对象;volatile 只能作用于变量。
- synchronized 可以保证线程间的有序性(个人猜测是无法保证线程内的有序性,即线程内的代码可能被 CPU 指令重排序)、原子性和可见性;volatile 只保证了可见性和有序性,无法保证原子性。
- synchronized 线程阻塞,volatile 线程不阻塞。
- volatile 本质是告诉 jvm 当前变量在寄存器中的值是不安全的需要从内存中读取;sychronized 则是锁定当前变量,只有当前线程可以访问到该变量其他线程被阻塞。
- volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。
23. synchronized 和 Lock 有什么区别?
- 实现层面不一样。synchronized 是 Java 关键字,JVM层面 实现加锁和释放锁;Lock 是一个接口,在代码层面实现加锁和释放锁
- 是否自动释放锁。synchronized 在线程代码执行完或出现异常时自动释放锁;Lock 不会自动释放锁,需要再 finally {} 代码块显式地中释放锁
- 是否一直等待。synchronized 会导致线程拿不到锁一直等待;Lock 可以设置尝试获取锁或者获取锁失败一定时间超时
- 获取锁成功是否可知。synchronized 无法得知是否获取锁成功;Lock 可以通过 tryLock 获得加锁是否成功
- 功能复杂性。synchronized 加锁可重入、不可中断、非公平;Lock 可重入、可判断、可公平和不公平、细分读写锁提高效率
24. synchronized 和 ReentrantLock 区别是什么?
- synchronized 竞争锁时会一直等待;ReentrantLock 可以尝试获取锁,并得到获取结果
- synchronized 获取锁无法设置超时;ReentrantLock 可以设置获取锁的超时时间
- synchronized 无法实现公平锁;ReentrantLock 可以满足公平锁,即先等待先获取到锁
- synchronized 控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll();ReentrantLock 控制等待和唤醒需要结合 Condition 的 await() 和 signal()、signalAll() 方法
- synchronized 是 JVM 层面实现的;ReentrantLock 是 JDK 代码层面实现
- synchronized 在加锁代码块执行完或者出现异常,自动释放锁;ReentrantLock 不会自动释放锁,需要在 finally{} 代码块显示释放
补充一个相同点:都可以做到同一线程,同一把锁,可重入代码块。
四、计算机网络
1. get 和 post的区别
- Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的。 但是这种做法也不时绝对的,大部分人的做法也是按照上面的说法来的,但是也可以在get请求加上 request body,给 post请求带上 URL 参数。
- Get请求提交的url中的数据最多只能是2048字节,这个限制是浏览器或者服务器给添加的,http协议并没有对url长度进行限制,目的是为了保证服务器和浏览器能够正常运行,防止有人恶意发送请求。Post请求则没有大小限制。
- Get限制Form表单的数据集的值必须为ASCII字符;而Post支持整个ISO10646字符集。
- Get执行效率却比Post方法好。Get是form提交的默认方法。
- GET产生一个TCP数据包;POST产生两个TCP数据包。
- 对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
- 而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
2. TCP和UDP的区别?tcp拥塞控制和流量控制如何实现?
TCP,Transmission Control Protocol 的缩写,即传输控制协议。
- 面向连接,即必须在双方建立可靠连接之后,才会收发数据
- 信息包头 20 个字节
- 建立可靠连接需要经过3次握手
- 断开连接需要经过4次挥手
- 需要维护连接状态
- 报文头里面的确认序号、累计确认及超时重传机制能保证不丢包、不重复、按序到达
- 拥有流量控制及拥塞控制的机制
UDP,User Data Protocol 的缩写,即用户数据报协议。
- 不建立可靠连接,无需维护连接状态
- 信息包头 8 个字节
- 接收端,UDP 把消息段放在队列中,应用程序从队列读消息
- 不受拥挤控制算法的调节
- 传送数据的速度受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制
- 面向数据报,不保证接收端一定能收到
流量控制
TCP 利用滑动窗口实现流量控制。
流量控制是为了控制发送方发送速率,保证接收方来得及接收。
接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。
拥塞控制
为了进行拥塞控制,TCP 发送方要维持一个 拥塞窗口(cwnd) 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。
TCP的拥塞控制采用了四种算法,即 慢开始 、 拥塞避免 、快重传 和 快恢复。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。
3. 输入一次url过程,用到哪些协议?
-
DNS 解析:浏览器查询 DNS,获取域名对应的 IP 地址:具体过程包括浏览器搜索自身的 DNS 缓存、搜索操作系统的 DNS 缓存、读取本地的 Host 文件和向本地 DNS 服务器进行查询等。对于向本地 DNS 服务器进行查询,如果要查询的域名包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析(此解析具有权威性);如果要查询的域名不由本地 DNS 服务器区域解析,但该服务器已缓存了此网址映射关系,则调用这个 IP 地址映射,完成域名解析(此解析不具有权威性)。如果本地域名服务器并未缓存该网址映射关系,那么将根据其设置发起递归查询或者迭代查询;
-
TCP 连接:浏览器获得域名对应的 IP 地址以后,浏览器向服务器请求建立链接,发起三次握手;
-
发送 HTTP 请求:TCP 连接建立起来后,浏览器向服务器发送 HTTP 请求;
-
服务器处理请求并返回 HTTP 报文:服务器接收到这个请求,并根据路径参数映射到特定的请求处理器进行处理,并将处理结果及相应的视图返回给浏览器;
-
浏览器解析渲染页面:浏览器解析并渲染视图,若遇到对 js 文件、css 文件及图片等静态资源的引用,则重复上述步骤并向服务器请求这些资源;浏览器根据其请求到的资源、数据渲染页面,最终向用户呈现一个完整的页面。
-
连接结束。
5. HTTP和HTTPS的区别。
安全性上,HTTPS是安全超文本协议,在HTTP基础上有更强的安全性。简单来说,HTTPS是使用TLS/SSL加密的HTTP协议
申请证书上,HTTPS需要使用ca申请证书
传输协议上, HTTP是超文本传输协议,明文传输;HTTPS是具有安全性的 SSL 加密传输协议
连接方式与端口上,http的连接简单,是无状态的,端口是 80; https 在http的基础上使用了ssl协议进行加密传输,端口是 443
6. HTTPS加密认证过程。
(1)发起请求:客户端在通过TCP和服务器建立连接之后(默认使用443端口),发出一个请求证书的消息给服务器,在该请求消息里包含自己可实现的算法列表和其他需要的消息。
(2)证书返回:服务器端在收到消息后回应客户端并返回证书,在证书中包含服务器信息、域名、申请证书的公司、公钥、数据加密算法等。
(3)证书验证:客户端在收到证书后,判断证书签发机构是否正确,并使用该签发机构的公钥确认签名是否有效,客户端还会确保在证书中列出的域名为正在连接的域名。如果客户端确认证书有效,则生成对称密钥,并使用公钥将对称密钥加密。
(4)密钥交换:客户端将加密后的对称密钥发送给服务器,服务器在接收到对称密钥后使用私钥解密。
(5)数据传输:经过上述步骤,客户端和服务器就完成了密钥对的交换,在之后的数据传输过程中,客户端和服务端就可以基于对称加密(加密和解密使用相同密钥的加密算法)将数据加密后在网络上传输,保证了网络数据传输的安全性。
7. TCP的三次握手,四次挥手
客户端–发送带有 SYN 标志的数据包–一次握手–服务端
服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端
客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端
客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送
服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加 1 。和 SYN 一样,一个 FIN 将占用一个序号
服务器-关闭与客户端的连接,发送一个 FIN 给客户端
客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加 1
五、JVM
1. GC标记方法。
引用计数和可达性分析。
- 引用计数实现起来比较简单,就是给对象添加一个引用计数器,每当有一个地方引用它时就加1,引用失效时就减1,当计数器为0的时候就标记为可回收。
- 可达性分析的基本思路就是:通过将一些称为”GC Roots”的对象作为起始点,从这些节点开始搜索,搜索和该节点发生直接或者间接引用关系的对象,将这些对象以链的形式组合起来,形成一张“关系网”,又叫做引用链。最后垃圾收集器就回收那些不在这张关系网上的对象。
2. finalize方法。
Java提供finalize()方法,垃圾回收器准备释放内存的时候,会先调用finalize()。
(1).对象不一定会被回收。
(2).垃圾回收不是析构函数。
(3).垃圾回收只与内存有关。
(4).垃圾回收和finalize()都是靠不住的,只要JVM还没有快到耗尽内存的地步,它是不会浪费时间进行垃圾回收的。
3. 说一下垃圾回收机制?什么时候垃圾回收?
垃圾回收机制,简称 GC
- Java 语言不需要程序员直接控制内存回收,由 JVM 在后台自动回收不再使用的内存
- 提高编程效率
- 保护程序的完整性
- JVM 需要跟踪程序中有用的对象,确定哪些是无用的,影响性能
特点
- 回收 JVM 堆内存里的对象空间,不负责回收栈内存数据
- 无法处理一些操作系统资源的释放,如数据库连接、输入流输出流、Socket 连接
- 垃圾回收发生具有不可预知性,程序无法精确控制垃圾回收机制执行
- 可以将对象的引用变量设置为 null,垃圾回收机制可以在下次执行时回收该对象。
- JVM 有多种垃圾回收 实现算法,表现各异
- 垃圾回收机制回收任何对象之前,会先调用对象的 finalize() 方法
- 可以通过 System.gc() 或 Runtime.getRuntime().gc() 通知系统进行垃圾回收,会有一些效果,但系统是否进行垃圾回收依然不确定
- 不要主动调用对象的 finalize() 方法,应该交给垃圾回收机制调用
4. 堆内存和栈内存有什么区别?堆和栈哪个快?什么变量存在栈里面?
栈内存和堆内存都是存储数据的地方。
栈内存中存储的值的大小是固定的,堆内存中存储值的大小不固定的。
栈:由系统自动分配,速度较快。但程序员是无法控制的。
堆:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。
栈: 在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
5. Java中类加载过程是什么样的?
类加载的步骤为,加载 -> 验证 -> 准备 -> 解析 -> 初始化。
1、加载:
获取类的二进制字节流
将字节流代表的静态存储结构转化为方法区运行时数据结构
在堆中生成class字节码对象
2、验证:连接过程的第一步,确保 class 文件的字节流中的信息符合当前 JVM 的要求,不会危害 JVM 的安全
3、准备:为类的静态变量分配内存并将其初始化为默认值
4、解析:JVM 将常量池内符号引用替换成直接引用的过程
5、初始化:执行类构造器的初始化的过程
6. JVM 如何确定垃圾对象:
JVM 采用的是可达性分析算法,通过 GC Roots 来判定对象是否存活,从 GC Roots 向下追溯、搜索,会产生 Reference Chain。当一个对象不能和任何一个 GC Root 产生关系时,就判定为垃圾。
软引用和弱引用,也会影响对象的回收。内存不足时会回收软引用对象;GC 时会回收弱引用对象。
7. 回收算法
判断对象是否可回收的算法有两种:
-
Reference Counting GC,引用计数算法
-
Tracing GC,可达性分析算法
JVM 各厂商基本都是用的 Tracing GC 实现
大部分垃圾收集器遵从了分代收集(Generational Collection)理论。
针对新生代与老年代回收垃圾内存的特点,提出了 3 种不同的算法:
1、标记-清除算法(Mark-Sweep)
标记需回收对象,统一回收;或标记存活对象,回收未标记对象。
缺点:
大量对象需要标记与清除时,效率不高
标记、清除产生的大量不连续内存碎片,导致无法分配大对象
2、标记-复制算法(Mark-Copy)
可用内存等分两块,使用其中一块 A,用完将存活的对象复制到另外一块 B,一次性清空 A,然后改分配新对象到 B,如此循环。
缺点:
不适合大量对象不可回收的情况,换句话说就是仅适合大量对象可回收,少量对象需复制的区域
只能使用内存容量的一半,浪费较多内存空间
3、标记-整理算法(Mark-Compact)
标记存活的对象,统一移到内存区域的一边,清空占用内存边界以外的内存。
缺点:
移动大量存活对象并更新引用,需暂停程序运行
8. 说说JVM内存区域分为几大块,分别讲一下
Java 虚拟机在执行 Java 程序的过程中会把他所管理的内存划分为若干个不同的数据区域:
- 程序计数器:可以看作是当前线程所执行的字节码文件(class)的行号指示器,它会记录执行痕迹,是每个线程私有的
- 方法区:主要存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据,该区域是被线程共享的,很少发生垃圾回收
- 栈:栈是运行时创建的,是线程私有的,生命周期与线程相同,存储声明的变量
- 本地方法栈:为 native 方法服务,native 方法是一种由非 java 语言实现的 java 方法,与 java 环境外交互,如可以用本地方法与操作系统交互
- 堆:堆是所有线程共享的一块内存,是在 java 虚拟机启动时创建的,几乎所有对象实例都在此创建,所以经常发生垃圾回收操作
JDK8 之前,Hotspot 中方法区的实现是永久代(Perm)
JDK8 开始使用元空间(Metaspace),以前永久代所有内容的字符串常量移至堆内存,其他内容移至元空间,元空间直接在本地内存分配。
六、数据库
1. 说说mysql的存储引擎
InnoDB
- 默认事务型引擎,被广泛使用的存储引擎
- 数据存储在共享表空间,即多个表和索引都存储在一个表空间中,可通过配置文件修改
- 主键查询的性能高于其他类型的存储引擎
- 内部做了很多优化,如:从磁盘读取数据时会自动构建hash索引,插入数据时自动构建插入缓冲区
- 通过一些机制和工具支持真正的热备份
- 支持崩溃后的安全恢复
- 支持行级锁
- 支持外键
MyISAM
- 拥有全文索引、压缩、空间函数
- 不支持事务和行级锁、不支持崩溃后的安全恢复
- 表存储在两个文件:MYD 和 MYI
- 设计简单,某些场景下性能很好,例如获取整个表有多少条数据,性能很高
2. 讲下索引以及应用场景
- 当我们使用order by将查询结果按照某个字段排序时,如果该字段没有建立索引,那么执行计划会将查询出的所有数据使用外部排序,这个操作是很影响性能的。但是如果我们对该字段建立索引,那么由于索引本身是有序的,因此直接按照索引的顺序和映射关系逐条取出数据即可。
- 对join语句匹配关系(on)涉及的字段建立索引能够提高效率。
- 查找符合where条件的记录时
- 如果要查询的字段都建立过索引,那么引擎会直接在索引表中查询而不会访问原始数据(否则只要有一个字段没有建立索引就会做全表扫描),这叫索引覆盖。因此我们需要尽可能的在select后只写必要的查询字段,以增加索引覆盖的几率。
3. 索引的作用?索引有什么缺点?
索引是一种用于快速查询和检索数据的数据结构。常见的索引结构有: B 树, B+树和 Hash。
索引的作用就相当于目录的作用。打个比方: 我们在查字典的时候,如果没有目录,那我们就只能一页一页的去找我们需要查的那个字,速度很慢。如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。
优点 :
- 使用索引可以大大加快 数据的检索速度(大大减少检索的数据量), 这也是创建索引的最主要的原因。
- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
缺点 :
- 创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。
- 索引需要使用物理文件存储,也会耗费一定空间。
4. 创建索引的原则
索引虽好,但也不是无限制的使用,最好符合一下几个原则
1) 最左前缀匹配原则,组合索引非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
2)较频繁作为查询条件的字段才去创建索引
3)更新频繁字段不适合创建索引
4)若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)
5)尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
6)定义有外键的数据列一定要建立索引。
7)对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
8)对于定义为text、image和bit的数据类型的列不要建立索引。
5. 为什么MySQL 没有使用Hash作为索引的数据结构呢?
1.Hash 冲突问题 :我们上面也提到过Hash 冲突了,不过对于数据库来说这还不算最大的缺点。
2.Hash 索引不支持顺序和范围查询(Hash 索引不支持顺序和范围查询是它最大的缺点: 假如我们要对表中的数据进行排序或者进行范围查询,那 Hash 索引可就不行了。
6. MySQL索引使用的什么数据结构,B树和B+树的区别
目前大部分数据库系统及文件系统都采用 B-Tree 或其变种 B+Tree 作为索引结构。
在B树中,你可以将键和值存放在内部节点和叶子节点;但在B+树中,内部节点都是键,没有值,叶子节点同时存放键和值。
B+树的叶子节点有一条链相连,而B树的叶子节点各自独立。
B树可以在内部节点同时存储键和值,因此,把频繁访问的数据放在靠近根节点的地方将会大大提高热点数据的查询效率。这种特性使得B树在特定数据重复多次查询的场景中更加高效。
由于B+树的内部节点只存放键,不存放值,因此,一次读取,可以在内存页中获取更多的键,有利于更快地缩小查找范围。 B+树的叶节点由一条链相连,因此,当需要进行一次全数据遍历的时候,B+树只需要使用O(logN)时间找到最小的一个节点,然后通过链进行O(N)的顺序遍历即可。而B树则需要对树的每一层进行遍历,这会需要更多的内存置换次数,因此也就需要花费更多的时间
7. 索引为什么采用B+树的数据结构,而不使用二叉树或者红黑树
Hash的存储结构是key-value形式存在数组中,对数据进行Hash(散列)运算,然后将哈希结果作为文件指针,可以从索引文件中获得数据的文件指针,再到数据文件中获取到数据,查询效率非常高,主流的Hash算法有MD5、SHA256等等。无法解决范围查询的场景,比如 select count(id) from sus_user where id >10;因此Hash这种索引结构只能针对字段名=目标值的场景使用。不适合模糊查询的场景。
红黑树也叫平衡二叉树,它不仅继承了二叉树的优点,而且解决了上面二叉树遇到的自增整形索引的问题,而且红黑树会左旋、右旋对结构进行调整,始终保证左子节点数 < 父节点数 < 右子节点数的规则。但在数据量大的时候,深度也很大。如果我们有很多数据,那么树的深度依然会很大,可能就会超过十几二十层以上,对我们的磁盘寻址不利,依然会花费很多时间查找。
B+树存储结构,只有叶子节点存储数据。B+树结构没有在所有的节点里存储记录数据,而是只在最下层的叶子节点存储,上层的所有非叶子节点只存放索引信息,这样的结构可以让单个节点存放下更多索引值,提高命中目标记录的几率。这种结构会在上层非叶子节点存储一部分冗余数据,但是这样的缺点都是可以容忍的,因为冗余的都是索引数据,不会对内存造成大的负担。
8. 聚簇索引和非聚簇索引这两个概念怎么理解?
聚集索引
聚集索引即索引结构和数据一起存放的索引。主键索引属于聚集索引。
在 Mysql 中,InnoDB 引擎的表的 .ibd文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引(B+树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。
聚集索引的优点
聚集索引的查询速度非常的快,因为整个 B+树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。
聚集索引的缺点
依赖于有序的数据 :因为 B+树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或 UUID 这种又长又难比较的数据,插入或查找的速度肯定比较慢。
更新代价大 : 如果对索引列的数据被修改时,那么对应的索引也将会被修改, 而且况聚集索引的叶子节点还存放着数据,修改代价肯定是较大的, 所以对于主键索引来说,主键一般都是不可被修改的。
非聚集索引
非聚集索引即索引结构和数据分开存放的索引。
二级索引属于非聚集索引。
MYISAM 引擎的表的.MYI 文件包含了表的索引, 该表的索引(B+树)的每个叶子非叶子节点存储索引, 叶子节点存储索引和索引对应数据的指针,指向.MYD 文件的数据。
非聚集索引的叶子节点并不一定存放数据的指针, 因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。
非聚集索引的优点
更新代价比聚集索引要小 。非聚集索引的更新代价就没有聚集索引那么大了,非聚集索引的叶子节点是不存放数据的
非聚集索引的缺点
跟聚集索引一样,非聚集索引也依赖于有序的数据
可能会二次查询(回表) :这应该是非聚集索引最大的缺点了。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。
9. mysql索引优化相关方法,联合索引应该把什么字段放在第一个位置?
索引优化的方法有以下,第一:字段选择性。查询条件含有多个字段时,不要在选择性很低字段上创建索引,可通过创建组合索引来增强低字段选择性和避免选择性很低字段创建索引带来副作用。正确索引会提高sql查询速度,过多索引会增加优化器选择索引的代价,不要滥用索引;第二:Explain优化查询检测。EXPLAIN可以帮助开发人员分析SQL问题,explain显示了mysql如何使用索引来处理select语句以及连接表,可以帮助选择更好的索引和写出更优化的查询语句。
在建立联合索引的时候应该注意索引列的顺序,一般情况下,将查询需求频繁或者字段选择性高的列放在前面。
10. mysql的最左原则吗?
- 顾名思义,就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。
- 最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
- =和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式
11. 脏读、幻读、不可重复读指什么?
- 脏读:一个事务读取另外一个事务还没有提交的数据。
- 不可重复读:一个事务内,两次相同条件的查询返回了不同的结果。
- 幻读:同一个事务中,一条数据出现在这次查询的结果集里,却没有出现在之前的查询结果集中。例如,在一个事务中进行了同一个查询运行了两次,期间被另外一个事务提交插入一行或修改查询条件匹配的一行。它比不可重复读更难防范,因为锁定第一个查询结果集的所有行并不能阻止导致幻象出现的更改。
12. 数据库事务的四个特性:
事务具备ACID四种特性,ACID是Atomic(原子性)、Consistency(一致性)、Isolation(隔离性)和Durability(持久性)的英文缩写。
-
原子性(Atomicity)
事务最基本的操作单元,要么全部成功,要么全部失败,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。 -
一致性(Consistency)
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。 -
隔离性(Isolation)
指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。 -
持久性(Durability)
指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
13. 说说sql的事务隔离级别,具体的应用场景
- 读未提交(Read Uncommitted):是最低的事务隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。会出现脏读,幻读,不可重复读,所有并发问题都可能遇到。
- 读已提交(Read Committed):保证一个事物提交后才能被另外一个事务读取。另外一个事务不能读取该事物未提交的数据。不会出现脏读现象,但是会出现幻读,不可重复读。
- 可重复读(Repeatable Read):这种事务隔离级别可以防止脏读,不可重复读,但是可能会出现幻象读。它除了保证一个事务不能被另外一个事务读取未提交的数据之外还避免了不可重复读。
- 串行化(Serializable):这是花费最高代价但最可靠的事务隔离级别。事务被处理为顺序执行。防止脏读、不可重复读、幻象读。
14. 说说数据库的乐观锁和悲观锁?
1.乐观锁
乐观锁在读数据时,认为别人不会去写其所读的数据:悲观锁就刚好相反,觉得自己读数据时,别人可能刚好在写自己刚读的数据,态度比较保守;时间戳在操作数据时不加锁,而是通过时间戳来控制并发出现的问题。
2.悲观锁
悲观锁指在其修改某条数据时,不允许别人读取该数据,直到自己的整个事务都提交并释放锁,其他用户才能访问该数据。悲观锁又可分为排它锁(写锁)和共享锁(读锁)。
七、设计模式
1. 你最熟悉的设计模式 ?
单例模式
保证一个类只有一个实例,并且提供一个访问该全局访问点
2.那些地方用到了单例模式
网站的计数器,一般也是采用单例模式实现,否则难以同步。
应用程序的日志应用,一般都是单例模式实现,只有一个实例去操作才好,否则内容不好追加显示。
多线程的线程池的设计一般也是采用单例模式,因为线程池要方便对池中的线程进行控制
Windows的(任务管理器)就是很典型的单例模式,他不能打开俩个
windows的(回收站)也是典型的单例应用。在整个系统运行过程中,回收站只维护一个实例。
3.单例优缺点
优点:
在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就防止其它对象对自己的实例化,确保所有的对象都访问一个实例
单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
提供了对唯一实例的受控访问。
由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
允许可变数目的实例。
避免对共享资源的多重占用。
缺点:
不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
单例类的职责过重,在一定程度上违背了“单一职责原则”。
滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
4.单例模式使用注意事项:
使用时不能用反射模式创建单例,否则会实例化一个新的对象
使用懒单例模式时注意线程安全问题
饿单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式模式)
5.单例创建方式
(主要使用懒汉和懒汉式)
饿汉式:类初始化时,会立即加载该对象,线程天生安全,调用效率高。
懒汉式: 类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象,具备懒加载功能。
静态内部方式:结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。
枚举单例: 使用枚举实现单例模式 优点:实现简单、调用效率高,枚举本身就是单例,由jvm从根本上提供保障!避免通过反射和反序列化的漏洞, 缺点没有延迟加载。
双重检测锁方式 (因为JVM本质重排序的原因,可能会初始化多次,不推荐使用)
2. 懒汉式你会怎么写,懒汉式实例化在哪,构造函数的权限?
1.饿汉式
饿汉式:类初始化时,会立即加载该对象,线程天生安全,调用效率高。
package com.lijie;
//饿汉式
public class Demo1 {
// 类初始化时,会立即加载该对象,线程安全,调用效率高
private static Demo1 demo1 = new Demo1();
private Demo1() {
System.out.println("私有Demo1构造参数初始化");
}
public static Demo1 getInstance() {
return demo1;
}
public static void main(String[] args) {
Demo1 s1 = Demo1.getInstance();
Demo1 s2 = Demo1.getInstance();
System.out.println(s1 == s2);
}
}
2.懒汉式
懒汉式: 类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象,具备懒加载功能。
package com.lijie;
//懒汉式
public class Demo2 {
//类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
private static Demo2 demo2;
private Demo2() {
System.out.println("私有Demo2构造参数初始化");
}
public synchronized static Demo2 getInstance() {
if (demo2 == null) {
demo2 = new Demo2();
}
return demo2;
}
public static void main(String[] args) {
Demo2 s1 = Demo2.getInstance();
Demo2 s2 = Demo2.getInstance();
System.out.println(s1 == s2);
}
}
3. 单例模式的饿汉式和懒汉式及区别
饿汉式:类初始化时,会立即加载该对象,线程天生安全,调用效率高。
懒汉式: 类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象,具备懒加载功能。
4. 常用的设计模式?
创建型
- 工厂模式与抽象工厂模式 (Factory Pattern)(Abstract Factory Pattern)
- 单例模式 (Singleton Pattern)
- 建造者模式 (Builder Pattern)
- 原型模式 (Prototype Pattern)
结构型
- 适配器模式 (Adapter Pattern)
- 装饰器模式 (Decorator Pattern)
- 桥接模式 (Bridge Pattern)
- 外观模式 (Facade Pattern)
- 代理模式 (Proxy Pattern)
- 过滤器模式 (Filter、Criteria Pattern)
- 组合模式 (Composite Pattern)
- 享元模式 (Flyweight Pattern)
行为型
- 责任链模式(Chain of Responsibility Pattern)
- 观察者模式(Observer Pattern)
- 模板模式(Template Pattern)
- 命令模式(Command Pattern)
- 解释器模式(Interpreter Pattern)
- 迭代器模式(Iterator Pattern)
- 中介者模式(Mediator Pattern)
- 策略模式(Strategy Pattern)
- 状态模式(State Pattern)
- 备忘录模式(Memento Pattern)
- 空对象模式(Null Object Pattern)
5. 简单工厂和抽象工厂有什么区别?
简单工厂模式
是由一个工厂对象创建产品实例,简单工厂模式的工厂类一般是使用静态方法,通过不同的参数的创建不同的对象实例
可以生产结构中的任意产品,不能增加新的产品
抽象工厂模式
提供一个创建一系列相关或相互依赖对象的接口,而无需制定他们具体的类,生产多个系列产品
生产不同产品族的全部产品,不能新增产品,可以新增产品族
6. 说一说设计模式中的代理模式?
代理模式指为对象提供-种通过代理的方式来访问并控制该对象行为的方法。在客户端不适合或者不能够直接引用一-个对象时,可以通过该对象的代理对象来实现对该对象的访问,可以将该代理对象理解为客户端和目标对象之间的中介者。
在现实生活也能看到代理模式的身影,比如企业会把五险一金业务交给第三方人力资源公司去做,因为人力资源公司对五险一金 业务更加熟悉。
在代理模式下有两种角色,一种是被代理者,一种是代理( Proxy),在被代理者需要做一项工作时,不用自己做,而是交给代理做。比如企业在招人时,不用自己去人才市场上找,可以通过代理(猎头公司)去找,代理有候选人池,可根据企业的需求筛选出合适的候选人去返回给企业。
7. 说一说设计模式中的适配器模式?
我们常常在开发中遇到各个系统之间的对接问题,然而每个系统的数据模型或多或少均存在差别,因此可能存在改变现有对象模型的情况,这将影响到系统的稳定。若想在不改变原有代码结构(类的结构)的情况下完成友好对接,就需要用到适配器模式。
适配器模式(Adapter Pattern)通过定义一个适配器类作为两个不兼容的接口之间的桥梁,将一个类的接口转换成用户期望的另一个接口,使得两个或多个原本不兼容的接口可以基于适配器类一起工作。
适配器模式主要通过适配器类实现各个接口之间的兼容,该类通过依赖注人或者继承实现各个接口的功能并对外统一提供服务。
在适配器模式的实现中有三种角色: Source、 Targetable、 Adapter。 Source 是待适配的类,Targetable 是目标接口,Adapter 是适配器。我们在具体应用中通过Adapter 将Source的功能扩展到Targetable,以实现接口的兼容。适配器的实现主要分为三类:类适配器模式、对象适配器模式、接口适配器模式。
八、框架
1. Spring的特点?
Spring 是一款开源的轻量级 Java 开发框架,旨在提高开发人员的开发效率以及系统的可维护性。
我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。
比如说 Spring 自带 IoC(Inverse of Control:控制反转) 和 AOP(Aspect-Oriented Programming:面向切面编程)、可以很方便地对数据库进行访问、可以很方便地集成第三方组件(电子邮件,任务,调度,缓存等等)、对单元测试支持比较好、支持 RESTful Java 应用程序的开发。
2.谈谈自己对于 Spring IoC 的了解
IoC(Inverse of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spirng 特有,在其他语言中也有应用。
控制:指的是对象创建(实例化、管理)的权力
反转:控制权交给外部环境(Spring 框架、IoC 容器)
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。
在实际项目中一个 Service 类可能依赖了很多其他的类,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。
Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。
3.Spring AOP的实现原理?具体应用在哪些方面?举个例子?
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib ,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理
4.Spring 框架中用到了哪些设计模式?
工厂设计模式: Spring 使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
代理设计模式: Spring AOP 功能的实现。
单例设计模式: Spring 中的 Bean 默认都是单例的。
模板方法模式: Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
包装器设计模式: 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
适配器模式: Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
九、Redis
1. redis都有哪些数据结构?
-
String字符串:字符串类型是 Redis 最基础的数据结构,首先键都是字符串类型,而且 其他几种数据结构都是在字符串类型基础上构建的,我们常使用的 set key value 命令就是字符串。常用在缓存、计数、共享Session、限速等。
-
Hash哈希:在Redis中,哈希类型是指键值本身又是一个键值对结构,哈希可以用来存放用户信息,比如实现购物车。
-
List列表(双向链表):列表(list)类型是用来存储多个有序的字符串。可以做简单的消息队列的功能。
-
Set集合:集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一 样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。利用 Set 的交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
-
Sorted Set有序集合(跳表实现):Sorted Set 多了一个权重参数 Score,集合中的元素能够按 Score 进行排列。可以做排行榜应用,取 TOP N 操作。
2. Redis的应用场景
- 缓存
- 共享Session
- 消息队列系统
- 分布式锁
3. redis的延时队列怎么实现?
1、使用zset数据结构存储,订单号为key,时间为score。
2、新增订单的时候,将订单号插入zset。
3、设定轮询,每分钟轮询一次zset,找出score小于当前秒数的数据,进行处理,然后将key在zset内删除。
4. Redis如何实现持久化?
Redis支持RDB和AOF两种持久化方式。
(1) RDB (Redis DataBase): rDB在指定的时间间隔内对数据进行快照存储。RDB的特点在于:文件格式紧凑,方便进行数据传输和数据恢复;在保存.rdb快照文件时父进程会fork 出一个子进程,由子进程完成具体的持久化工作,所以可以最大化Redis 的性能;同时,与AOF相比,在恢复大的数据集时会更快一些。
(2) AOF ( Append Of Flie): AOF记录对服务器的每次写操作,在Redis重启时会重放这些命令来恢复原数据。AOF命令以Redis 协议追加和保存每次写操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。AOF的特点有:可以使用不同的fsync 策略(无fsync、每秒fsync、每次写的时候fsync )将操作追加命令到文件中,操作效率高;同时,AOF文件是日志的格式,更容易被理解和操作。
5. 什么是Redis的事务,用来干什么?
Redis事务就是一个命令执行的队列,将一系列预定义命令包装成一个整体,就是一个队列,当执行的时候,一次性按照添加顺序依次执行,中间不会被打断或者干扰。
一个队列中,一次性,顺序性,排他性的执行一系列命令。
6. Redis是阻塞式IO吗?怎么做到请求一个一个进行处理?
(1) 绝大部分请求是纯粹的内存操作(非常快速)
(2) 采用单线程,避免了不必要的上下文切换和竞争条件
(3) 非阻塞IO - IO多路复用
内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间 这3个条件不是相互独立的,特别是第一条,如果请求都是耗时的,采用单线程吞吐量及性能可想而知了。应该说redis为特殊的场景选择了合适的技术方案。
7. Redis缓存淘汰策略?是失效时间到了就立即淘汰吗?淘汰控制?
FIFO (First In First Out)先进先出原则
最先进入的缓存数据在缓存空间不够的情况下(超出最大元素限制时)会首先被清理出去
LFU (Less Frequently Uesd)最少使用原则
一直以来最少被使用的元素会被清理掉。意味着,要求缓存的元素有一个hit属性,在缓存空间不够的情况下,hit值最小的将会被清理出去
LRU (Least Recently Used)最近最少使用原则
缓存的元素有个时间戳,当缓存容量满了,而又要腾出新地方来缓存新的元素的时候,则现有缓存元素中时间戳离当前时间最远的元素将被清除出去
8. 什么是缓存雪崩和缓存穿透?
缓存雪崩
缓存雪崩指在同一时刻由于大量缓存失效,导致大量原本应该访问缓存的请求都去查询数据库,而对数据库的CPU和内存造成巨大压力,严重的话会导致数据库宕机,从而形成一-系列连锁反应,使整个系统崩溃。
缓存穿透
缓存穿透指由于级存系统故陈或者用户频繁查询系统中不存在(在系统中不存在,在自然数据库和级存中都不存在)的数据,而这时请水穿过缓存不断被发送到数据库,导致数据库过载,进而引发一连串非发问题。
9. 如何解决 Redis 缓存雪崩问题
- 请求加锁:对于并发量不是很多的应用,使用请求加锁排队的方案防止过多请求数据库。
- 失效更新:为每一个缓存数据都增加过期标记来记录缓存数据是否失效,如果缓存标记失效,则更新数据缓存。
- 设置不同的失效时间:为不同的数据设置不同的缓存失效时间,防止在同-时刻有大量的数据失效。
10. 如何解决 Redis 缓存穿透问题
常用的解决级存穿透问题的方法有布隆过滤器和eachenull策略。
-
布隆过池器:指将所有可能存在的数据都映射列一个足够大的Bitmap中,在用户发起请求时首先经过布隆过滤器的拦截,一个一定不存在的数据会被这个布隆过泄器拦做,从而避免对底层存储系统业来企询上的压力。
-
cache null 策略:指如果一个查询返回的结果为null (可能见数据不存在,也可能是系统故陈),我们仍然级存这个nul结果,但它的过期时间会很短,通常不旭过5分钟;在用户再次青水该数据时血接返回nul,而不会继续访问数据库,从而有效保陈数据库的安全。其实cache null 策略的核心原理是:在级存中记录一个短暂的(数据过则时间内)数据在系统中是否存在的状态,如果不存在,则直接返回nl,不再在询数据内,从而避免复存穿遇列数据库上。
11. Redis如何实现分布式锁
实现思路与注意事项:
- 设置合理的过期时间,解决忘记释放锁、甚至服务器宕机未释放锁的问题
- 获取锁和设置过期时间,需要具有原子性,使用指令
SET key value NX PX milliseconds
NX 代表只有当键key不存在的时候才会设置key的值
PX 表示设置键 key 的过期时间,单位是毫秒
- value 值随机设置,删除 value 前判断是否相等,解决当前线程可能释放其他线程加的锁的问题
- lua 脚本可以解决,删除 value 时判断-删除,非原子操作的问题
十、Kafka
1. 问一下kafka的问题吧,kafka是怎么进行数据备份的?
2. 消费者是从leader中拿数据,还是从follow中拿数据?
3. 说说kafka的ISR机制?
4. kafka里面存的数据格式都是什么样的?
5. kafka中存的一个是数据文件,一个是索引文件,说说这个?
6. kafka 是如何清理过期数据的?
7. 一条message中包含哪些信息?
十一、项目
1. 校园论坛项目介绍
2. 这个项目最具挑战的是什么?
3. 项目中如何使用多线程?
4. 登录注册是如何实现的?用Cookie做了什么? Cookie被窃取了该怎么办?
5. Kafka做什么的?
6. 项目Kafka使用场景。为什么要用Kafka? Kafka为什么吞吐量高?
7. Kafka怎么保证顺序性?
8. 说一下kafka的内部结构,消息队列存在的意义?
9. 如何做到显示首页的热度最高帖子?如何更新缓存?
10. Redis存了什么数据?缓存过期时间是多少?如何解决缓存一致性问题?
11. 如何识别热点数据?热度如何计算?如何更新热度?
12. Redis如何淘汰过期数据?
13. 项目里redis做持久化处理了吗
14. redis宕机了怎么办
15. zset的底层数据结构?什么是跳表?
15. ES如何实现全文搜索的功能?ES的底层数据结构?
16. ES倒排索引为什么能够加速搜索?
17. 项目中SpringSecurity的权限模型是怎么样的?
18 .用户的授权信息如何存储?
十二、算法
1. 稳定排序有哪些?
不稳定:快排,堆排序,希尔排序,直接选择排序。
稳定:直接插入排序,冒泡,归并排序,基数排序