接口(interface)和抽象类(abstract class)的区别是什么?
- 一个类实现(implemens)接口时必须实现接口的所有的方法
- 接口中的属性都是被
public static final
修饰的,只有常量没有变量 - 一个类可以实现多个接口
- 接口方法默认修饰符是
public abstract
,且不可以使用其他修饰符 - 不能有构造方法
- 接口强调的是功能
作用:降低耦合
- 一个类继承(extends)抽象类时,如果不实现所有的抽象方法,该类也必须是抽象类
- 抽象类既可以有变量,也可以有常量
- 一个类只能继承一个抽象类
- 抽象方法可以有
public、protected和default
来修饰 - 可以有构造方法、抽象方法和其他方法
- 抽象类强调的是从属关系
作用:把相同的东西提取出来,即重用
Servlet的生命周期
- Servlet 通过调用 init () 方法进行初始化。
- Servlet 调用 service() 方法来处理客户端的请求。
- Servlet 通过调用 destroy() 方法终止(结束)。
- 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
补充:
Servlet 调用的service() 方法:
- 每一次请求服务器都会开启一个新的线程并执行一次service方法,service根据客户端的请求类型,调用doGet、doPost等方法。
Servlet初始化
分三种情况:
- loadOnStartup < 0 或者 没有设置loadOnStartup
web容器启动的时候不做实例化处理,servlet首次被调用时做实例化 - loadOnStartup > 0
web容器启动的时候做实例化处理,顺序是由小到大,正整数小的先被实例化 - loadOnStartup = 0
web容器启动的时候做实例化处理,相当于是最大整数,因此web容器启动时,最后被实例化
Object类的常见方法总结
-
protected Object toString()
返回该对象的字符串表示,如果不重写是getClass().getName() + '@' + Integer.toHexString(hashCode())
-->com.leyou.Test@506c589e
-
protected Object getClass()
返回此 Object 的运行时类。class com.leyou.Test -
protected Object equals(Object object)
指示其他某个对象是否与此对象“相等”。
注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。 -
protected int hashCode()
返回该对象的哈希码值。
如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。 -
public final void wait(long timeout)
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。 -
void notifyAll()
唤醒在此对象监视器上等待的所有线程。 -
Object clone()
创建并返回此对象的一个副本。“副本”的准确含义可能依赖于对象的类。
x.clone() != x
为true
x.clone().getClass() == x.getClass()
为true
x.clone().equals(x)
为true
为什么equals()方法要重写?
- 判断两个对象在逻辑上是否相等,如根据类的成员变量来判断两个类的实例是否相等,而继承Object中的equals方法只能判断两个引用变量是否是同一个对象。这样我们往往需要重写equals()方法。
- 我们向一个没有重复对象的集合中添加元素时,集合中存放的往往是对象,我们需要先判断集合中是否存在已知对象,这样就必须重写equals方法。
equals重写有什么注意事项?
equals 方法在非空对象引用上实现相等关系:
- 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
- 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
- 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
- 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
- 非空性:对于任何非空引用值 x,x.equals(null) 都应返回 false。
spring ioc底层用的什么数据结构?
- xml配置文件
- dom4j解决xml
- 工厂设计模式
- 反射
HashMap
- HashMap的默认长度为16,是为了降低hash碰撞的几率
- 红黑树一直是O(logn),链表查找的时间复杂度为O(n)
HashMap的put流程
①.判断键值对数组是否为空或为null,若条件成立则执行resize()进行扩容;
②.根据键值key计算hash值得到插入的数组索引,如果索引位置的值为null,直接新建节点添加,转向⑥,如果不为空,转向③;
③.判断桶位的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
④.判断桶位是否为treeNode,即该节点是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
⑤.遍历该桶位的节点,判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作(这里用的尾插发);遍历过程中若发现key已经存在直接覆盖value即可;
⑥.插入成功后,判断实际存在的键值对数量size是否超过阈值,如果超过,进行扩容。
- 大约记得是它首先会看key为不为空,为空就调用一个put空值的函数,不为空的话它就调用hash()函数计算hash值吗,接着会先判断这个hash值对应的地址空间是否已经有值了,如果有的话,它会把新的存进去,把旧的返回,放在后面形成链表,在jdk1.8之后,当链表超过8的时候就变成了红黑树了
补充
- HashMap 允许插入键为 null 的键值对。
- 因为无法调用 null 的 hashCode() 方法,也就无法确定该键值对的桶下标,只能通过强制指定一个桶下标来存放。HashMap 使用第 0 个桶存放键为 null 的键值对。
hashMap为什么线程不安全?
比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:
- 在 Items[Size] 的位置存放此元素;
- 增大 Size 的值。
在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。
那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。
ConcurrentHashMap加的是什么锁?
- ConcurrentHashMap引入了一个“分段锁(1.7)”的概念,具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()来决定把key放到哪个HashTable中。
- 放弃了Segment(分段锁)臃肿的设计,采用Node(数组+链表+红黑树)+CAS+Synchronize来保证线程安全
ConcurrentHashMap的get()方法是否需要加锁,为什么?
- 1.8中ConcurrentHashMap的get操作全程不需要加锁,这也是它比其他并发集合如hashtable、用Collections.synchronizedMap()包装的hashmap;安全效率高的原因之一。
- get操作全程不需要加锁是因为Node的成员val是用volatile修饰的和数组用volatile修饰没有关系。
- 数组用volatile修饰主要是保证在数组扩容的时候保证可见性。
常见的异常整理
Java中的异常分为两大类:
1.Checked Exception(非Runtime Exception)
2.Unchecked Exception(Runtime Exception)
- 算数异常类:ArithmeticException
- 空指针异常类型:NullPointerException
- 类型强制转换类型:ClassCastException
- 下标越界异常:IndexOutOfBoundsExecption
- 数组下标越界异常:ArrayIndexOutOfBoundsException
- 类转换异常:ClassCastException
synchronize关键字的作用
synchronized的作用:
- Synchronized是Java中解决并发问题的一种最常用最简单的方法 ,他可以确保线程互斥的访问同步代码
可以用来修饰:
- 通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
- 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
- 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
举例
- 同一个对象在两个线程中分别访问该对象的两个同步方法,会产生互斥
- 不同对象在两个线程中调用同一个同步方法,不会产生互斥
- Synchronized修饰静态方法,用类直接在两个线程中调用两个不同的同步方法,会产生互斥
HashSet和TreeSet的区别
实现方式
- HashSet:HashSet是哈希表实现的。
- TreeSet:TreeSet是二叉树实现的。
数据是否有序
- HashSet:HashSet中的数据是无序的。
- TreeSet:Treeset中的数据是自动排好序的。
是否可以放入null值
- HashSet:可以放入null,但只能放入一个null。
- TreeSet:不允许放入null值。
Linux常用命令
- cd。切换项目目录
- pwd。用于显示工作目录
- ls。列出路径或当前目录下的所有文件信息
- cat <文件>。 表示读取文件内容
- rm命令。rm是remove 的缩写。用于删除文件或文件夹,常用参数-r -f,-r表示删除目录,也可以用于删除文件,-f表示强制删除,不需要确认。同样的,删除文件前需保证当前用户对当前路径有修改的权限。
- mkdir命令。用于创建文件夹
- cp命令。用于复制文件或文件夹。
- kill命令。结束当前进程
- ifconfig/ipaddr。查看IP地址
- touch。创建一个文件
SQL中IN和EXISTS用法的区别
- in()适合B表比A表数据小的情况 (not in会使索引失效)
select * from A where id in(select id from B)
- exists()适合B表比A表数据大的情况
select A.* from A where xx exists(select xx from B where A.id=B.id)
- 当A表数据与B表数据一样大时,in与exists效率差不多,可任选一个使用.
线程池
- new SingleThreadExecutor:创建一个单线程的线程池,保证所有任务的执行顺序按照任务的提交顺序执行。
- new FixedThreadPool:创建一个固定大小的线程池。调用execute()方法。每次提交任务就成捡一个线程,直到线程达到线程吃的最大大小。
- new CachedThreadPool:创建一个可缓存的线程池。大小不做限制
- new ScheduledThreadPool:创建一个大小无限的线程池。定时以及周期性的执行任务的操作。
创建线程的方法
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
static
使用场景
- 修饰成员变量和成员方法
- 静态代码块
- 修饰类(只能修饰内部类)
话术
- 被 static 修饰的成员属于类,不属于单个这个类的某个对象,该类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存的方法区。且仅有一份。
- 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
- 通过类名.来调用
Redis挂了怎么办?
- 因为redis它有持久化的特性,所以在购物车这个场景,用redis其实已经足够了,退一万步说,redis挂了,购物车它不会造成特别大的损失。(时间间隔机制)但是我知道redis它有三种集群模式,主从模式,哨兵模式,还有一个是分片的模式,但是我们这个商城没有用到redis集群。
- 万一Redis真的挂了,我们可以设置本地缓存(ehcache)+限流(hystrix),尽量避免我们的数据库被干掉(起码能保证我们的服务还是能正常工作的)。事发后:redis持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。
Redis持久化
- RDB
每隔多少秒生成快照存储到磁盘等介质上 - AOF
把所有的写指令记录下来,重启把写指令再重新执行一遍
排查系统遇到的问题
- 事前:流量达到高峰,加入来了大批量的请求,做一些限流、降级的操作
- 事中:出发了我们认定为Bug的逻辑,可以发一些邮件或者通知的报警
- 事后:问题产生后去追查错误日志,cat命令,错误日志的排查
事务运用的场景
- 操作多张表的时候
- 循环更新、插入
核心在于,某一个业务失败了是否可以回滚
ArrayList和LinkedList的插入,删除时间复杂度
操作 | ArrayList | LinkedList |
---|---|---|
读取 get() | 根据下标直接查询,时间复杂度O(1) | 遍历获取,时间复杂度O(n) |
添加 add(E) | 直接尾部添加,时间复杂度O(1) | 直接尾部添加,时间复杂度O(1) |
指定位置添加 add(index,E) | 后面元素需要逐个移动,时间复杂度O(n) | 指针指向操作,时间复杂度O(n) |
删除 remove(E) | 后面元素需要逐个移动,时间复杂度O(n) | 直接指针操作,时间复杂度O(1) |
之前用过Nginx吗? 你们一般都用它做什么?
- Nginx肯定用过,首先它本身是一款性能很高的Web服务器,我们在处理以静态资源都会利用到它,当然也听说Nginx也可以结合一些脚本语言处理动态应用,不过这方面倒是没有接触过,我们公司一般会将nginx和Tomcat相结合来使用。另外它还有一个很功能就是充当反向带服务器,在我们的微服务中就是利用它来反向代理Zuul网关,从而可以实现zuul的高可用,而且它自身携带负载均衡,我们也可以通过修改配置文件来实现自己的配置。
索引(底层存储结构B+Tree)
索引的分类
- 普通索引:加快查询
- 唯一索引:加速+唯一(不能为空)
- 主键索引:加速+唯一(可以为空)
- 组合索引:多列值组成一个索引,专门用于组合索引(左前匹配原则)
- 全文索引:利用分析技术等多种算法智能分析文本,筛选我们想要的结果
使用索引的条件:
- 字段的识别度不低于70%
- 经常作为where条件的字段适合创建索引
- 经常作为Order by条件的字段适合创建索引
- 经常作为表连接的字段适合创建索引
索引失效的条件:
- 使用 != < > not in等关键字
- 使用like,例如like %xxx%(失效),like xxx%(不会失效)
- 索引字段不能用于进行计算
- 不要在索引字段使用is null、is not null
在SpringCloud 体系你使用的Cookie 有没有遇到过什么问题
- 确实遇到过Cookie 丢失的问题,原因是Zuul内部有默认的过滤器,会对请求和响应头信息进行重组,过滤掉敏感的头信息,而Cookie默认就是敏感信息,所以就给过滤掉啦,怎么解决呢,其实解决很简单,就是覆盖sensitiveHeaders这个过滤请求头的最终属性的为null就可以啦!需要我们在配置文件中配置
zuul.sensitive-headers=
volatile 的特性
- 在Java中,每个线程都有一个独立的内存空间,称为工作内存; 它保存了用于执行操作的不同变量的值。在执行操作之后,线程将变量的更新值复制到主存储器,这样其他线程可以从那里读取最新值。
简单地说,volatile关键字标记一个变量,在多个线程访问它的情况下,总是转到主内存,读取和写入。 - 保证可见性(强制刷新在主存,其他线程缓存无效)和有序性(禁止指令重排序),这些都是上面的用内存屏障完成的。
- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)
- 禁止进行指令重排序。(实现有序性)
- volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。
你要悄悄拔尖,然后惊艳所有人!