Java基础
IO流分类
- 按数据单位:字节流、字符流
- 按数据流向:输入流、输出流
- 按流的角色:节点流、处理流
字节输入流:InputStream
字节输出流:OutputStream
字符输入流:Reader
字符输出流:Writer
File类常用API
- getAbsolutePath():获取绝对路径
- getPath():获取路径
- getName():获取文件名
- getParet():获取上层文件目录
- length():获取长度
- renameTo():重命名
- isDirectory():是否为目录
- isFile():是否为文件
- exists():是否存在
- canRead()/canWrite():是否可读/写
- isHidden():是否隐藏
String类
-
String被final修饰,因此是最终类,不能被继承,内部没有get、set方法,所以值也不能改变
-
JDK9之前,存储在char型数组中,JDK9及之后,存储在byte数组。
char=>byte的原因:节省空间,提高性能。用char存储时,每个字符都会占用2个字节,内部使用encoding判断使用那种编码(utf-16/Latin-l)
-
重写了equals方法,调用equals方法对比的是值是否相同
-
实现了serializable接口,可以序列化
-
实现了Comparable接口,可以比较大小
String a = “abc” 和 String a = new String(“abc”)的区别,分别创建了几个对象?
- String a = “abc” 在常量池中创建了1个对象
- String a = new String(“abc”)在常量池中创建了1个对象,在堆里创建了一个对象
如果是先String a = “abc” ,再new String(“abc”),则总共创建了两个对象
String a = “abc” 和 String b = “abc” ,a和b的地址值相同,都指向常量池
String常用方法
- length():获取字符串长度
- spilt():切割字符串为数组
- equals():对比字符串值是否相同
- replace():替换字符串
- toLawerCase()/toUooerCase():字符串全部变为小写/大写
- subString():截取字符串
装箱和拆箱
- 装箱:基本数据类型转包装类,使用包装类的构造器,构造器内部调用valueOf()方法
- 拆箱:包装类转基本数据类型:使用包装类的XXValue()方法
String转不同类型
- String转基本数据类型、包装类:包装类的parse()方法
- 基本数据类型、包装类转String:String的value()方法
数组与集合转换
- 数组转集合:Arrays.asList()方法
- 集合转数组:数组的toArray()方法
String、StringBuffer、StringBuilder的区别
- 相同点:都是字符串
- 不同点:
- String是final修饰的,值是不能改变的
- StringBuffer、StringBuilder是可以改变的
- StringBuffer是线程安全的(方法用synchronize修饰)
- StringBuilder是线程非安全的
Java比较器
实现Comparable或Comparator接口,实现compareTo()方法
多线程的创建
- 继承Thread类:重写run方法,调用Thread的start方法
- 实现Runable接口:实现run方法,把Runable作为参数传进Thread类,调用Thread的start方法
- 实现Callable接口:实现call方法,把Callable作为参数传进FeatureTask,再把FeatureTask作为参数传进Thread类,调用Thread的start方法
- 线程池
FeatureTask对象
FeatureTask同时实现了Runable和Feature接口,既可以被线程执行,也可以获取Callable接口的返回值。Feature接口可以对具体Runable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
方法的重载和重写
范围不同:重写一般在子类与父类之间,重载在同一个类
参数不同:重写的参数必须相同,重载一般用不同的参数来区分调用那个方法方法
返回类型不同:重载可以不同,重写必须相同或者是他的子类
异常与访问权限:重写时,异常不能大于父类,访问权限不能小于父类,重载与异常、访问权限无关
多态性不同:重载在编译时决定,重写在运行时决定
为什么使用线程池
- 减少了创建线程的时间,提高响应速度
- 重复利用线程池中的线程,减少创建、销毁线程的次数,降低资源消耗
- 便于管理线程,可以控制线程池的大小、最大线程数等
继承Thread类和实现Runable接口,选哪个?
- 实现Runable接口,因为Java是单继承的,如果继承了Thread类,这个类就没法继承其他类了,但是接口可以同时实现多个。
- 多个线程如果有共享数据,继承Thread类时,多个线程共用的属性必须是static的,而实现Runable接口则不用
如何理解Callable比Runable强大
- Callable接口可以有返回值
- 实现Callable接口,实现call方法可以抛出异常,被外面的操作捕获,获取异常信息
- Callable支持泛型
Thread类常用方法
- start():启动线程,并执行run方法
- run():线程被调度时执行的操作
- yield():释放cpu的执行权,一但释放,其他线程就能抢到执行权
- sleep():线程睡眠,线程进入阻塞状态
- isAlive():判断当前线程是否存活
- currentThread():返回当前线程
- getName()/setName():获取/修改线程名字
线程的调度
时间片,抢占式:优先高优先级的,优先级相同时,先进来的先执行
线程的生命周期
- 新建:当一个Thread类或其子类的对象被声明时
- 就绪:处于新建状态的线程被调用start方法后,进入线程队列等待执行权
- 运行:当就绪的线程获取执行权后便进入运行状态
- 阻塞:在某种情况下,被人为挂起或执行输入输出操作时并临时终止自己的执行,进入阻塞状态
- 死亡:线程已经执行完或者被强制终止、出现异常等
sleep方法和wait方法异同
- 相同点:都能使线程进入阻塞状态
- 不同点:
- 声明位置不同:sleep是Threa类的一个静态方法,wait是Object类的方法
- 调用要求不同:sleep方法可以在任何地方调用,wait只能在同步代码块或者同步方法中
- 是否会释放锁:如果两个方法同时在同步代码块或者同步方法中,sleep不会释放,wait会释放
线程的通信
- wait():调用wait方法,线程进入阻塞状态并释放锁,让其他线程先执行
- notify():唤醒被wait的线程,如果有多个线程被wait,优先唤醒优先级最高的。
- notifyAll():唤醒全部被wait的线程。
线程同步如何解决线程安全问题
1、同步代码块
2、同步方法
3、同步锁(Lock)
怎么避免线程死锁
- 避免使用多个锁
- 尽可能减少同步代码长度
- 尝试改变锁的顺序,避免线程之间互相等待
- 使用定时锁超时释放
sychronize和Lock的异同
- 相同点:都可以解决线程安全问题
- 不同点:synchronize在执行完同步代码块之后,自动释放锁,Lock需要手动调用unlock解锁,但是更加灵活
Java集合分类
- Collection接口
- List接口
- ArrayList
- LinkedList
- Vector
- Set接口
- HashSet
- LinkedHashSet
- ThreeSet
- List接口
- Map接口
- HashMap
- LinkedHashMap
- HashTable
- TreeMap
ArrayList和和HashMap的扩容
- ArrayList:
- JDK8之前,默认创建长度为10,扩容1.5倍
- JDK8之后,根据第一次调用add方法的长度进行创建,再次调用才会扩容
- HashMap:
- 创建长度为16的数组,默认加载因子为0.75,扩容临界值为:16 * 0.75 = 12
ArrayList和LinkedList区别
- 相同点:都是List接口实现类
- 不同点:
- ArrayList底层是数组,查找和遍历更快
- LinkedList底层是双向链表,插入和删除更快
HashMap与HashTable的区别
- 相同点:都是Map接口的实现类,存储的都是key-value键值对数据
- 不同点:
- HashMap是线程非安全的,执行效率高,可以存储null值
- HashTable是线程安全的,执行效率低,不能存储null值
异常的分类以及常见异常
- Error
- Exception
- IOException:IO异常
- ClassNotFoundException:类未发现异常
- NullPointerException:空指针异常
- IndexOutofBoundsException:角标超越异常
- ClassCastException:类型转换异常
- NumberFormatException:数值转换异常
悲观锁与乐观锁
- 悲观锁:认为线程安全一定发生,因此在操作之前先获取锁。例如:synchronize、Lock
- 乐观锁:认为线程安全不一定发生,因此不加锁。例如:版本号
final修饰
- 修饰基本数据类型,被称为常量,值无法被修改
- 修饰引用数据类型,对象本身内容可以修改,但是引用地址不能修改
- 修饰类的成员变量,必须当场赋值,否则编译报错
- 修饰方法:被称为最终方法,方法不能被子类重写,但是可以被继承
- 修饰类:被称为最终类,无法被继承
Mysql
索引失效
- 使用不等于时
- 对索引列进行函数操作
- 使用like且通配符开头
- 使用的or但是or的两边不全是索引列
- 使用了is null 或者 is not null
慢sql优化
- 避免使用select *
- union all 代替 union
- 小表驱动大表
- in中的值不能太多
- join的表不宜太多
- 用join代替子查询
- 选择合理的字段类型
explain列说明
- type:链接类型
- key:实际用到的索引
- key-len:实际索引长度
Mysql索引分类
Mysql默认B+tree
- 按数据结构分类:B+Tree、Hash索引、Full-Text
- 按物理存储分类:聚集索引、非聚集索引
- 按字段特性分类:主键索引、唯一索引、普通索引、全文索引
- 按字段个数分类:单列索引、联合索引(组合索引)
事务的特性(ACID)
- 原子性:事务的所有操作作为一个不可分割的单元,要么都执行,要么都不执行
- 一致性:保证事务开始前和开始后的数据的完整和一致
- 隔离性:事务的执行不会受其他事务的影响
- 持久性:事务的命令操作修改后,会被持久化保存,且不会回滚
事务的隔离级别
- 未提交读:最低级别的隔离,可能会读取到其他未提交的更改
- 提交读:只能读取到其他事物已提交的修改,但事务内多次读取可能结果不同
- 可重复读(默认):保证同一事务内多次读取一致,但某些情况下可能产生幻读
- 串行读:最高的隔离级别,通过锁表保证完全隔离,但会严重影响数据库性能
事务并发问题(脏读、幻读、不可重复读)
- 脏读:一个事务读到了另一个事务回滚之前的数据。
- 幻读:一个事务在前后两次查询中,另一个事务新增了几条数据,后面一次查到了前一次没有的行。
- 不可重复读:一个事务获取了值,另一个事务也获取值并进行修改,事务再次获取的值和第一次不同。
大分页
通过子查询先获取id,然后再关联查询
Spring
Spring Ioc和Aop
Ioc:工厂模式,aop:代理模式
- ioc思想:控制反转,强调创建bean的权利交给bean工厂
- aop思想:面向切面编程,功能横线抽取
- DI思想:依赖注入,强调bean之间的关系
BeanFactory和ApplicationContext的关系
- BeanFactory成为Bean工厂,ApplicationContext为Spring容器
- BeanFactory主要负责Bean的产生,ApplicationContext在BeanFactory的基础上进行拓展,对BeanFactory的API进行封装
- BeanFactory在调用getBean方法时才会创建Bean,ApplicationContext在加载配置文件时全部初始化好
- ApplicationContext内部继承了BeanFactory,内部维护着BeanFactory的引用,既有继承关系,也有融合关系
@Transaction注解失效
- 在方法中异常被 try catch 捕获并处理
- 方法不是public的
- 调用了本类的方法
- @Transaction的属性rollbackFor设置错误
- 父容器和子容器都管理了事务,子容器的事务不受父容器的影响
@Autowired和@Resource的区别
-
来源不同:@Autowired是Spring的,@Resource是Java的
-
注入方式不同:@Autowired是先根据类型再根据名称注入,@Resource是先根据名称再根据类型注入
-
用法:@Autowired支持构造器,属性和setter注入,@Resource只支持属性和setter注入
spring推荐setter注入
Spring Bean的生命周期
Bean加载配置,进入Bean工厂后置处理器,实例化,Bean后置处理器,加载到单例池,调用,销毁
Spring Aop的5种增强类型
- before:前置通知,目标方法执行前
- after-before:后置通知,目标方法执行后,方法异常时不会执行
- around:环绕通知,目标方法执行前后执行,方法异常时,环绕后方法不执行
- after-throwing:异常通知,目标方法抛出异常时执行
- after:最终通知,不管方法是否异常,最终都会执行
Spring MVC工作流程
用户发起请求,web服务器将请求转发给前端控制器(DispatcherServlet),调用HandlerMapping确定是哪个controller,controller执行对应的业务逻辑,返回一个ModleAndView对象,再通过视图解析器解析为具体的view,view根据model进行渲染,最后返回给客户端
过滤器与拦截器的区别
- 运行顺序不同:先过滤器,再拦截器
- 依赖不同:过滤器依赖于servlet,拦截器依赖于Spring
- 范围不同:过滤器只能对request和response响应,拦截器还能对SpringMVC生态下的组件做处理
SpringBoot
SpringBoot和SpringMVC的区别
- SpringMVC大量繁琐的配置,需要单独的Tomcat
- SpringBoot约定大于配置,自动装配,内置Tomcat
SpringBoot自动装配原理
主要通过@SpringBootApplication注解,@SpringBootApplication是由
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
三个注解组成的,@SpringBootConfiguration是代表这个类是SpringBoot的启动配置类,@EnableAutoConfiguration代表开启自动装配,@ComponentScan指定要扫描哪些包。
Mybatis
#和$的区别
- #是预编译占位符,可以防止sql注入,转换成sql时参数带双引号
- $是字符串替换,直接作为字符串拼接,有sql注入风险,转换成sql时参数不带双引号
Mybatis分页插件原理
通过拦截器来实现的,将分页参数传给插件,插件先查总条数,再把分页参数拼到sql中,最后吧数据和总条数一起返回。
Redis
Redis数据类型
- string 字符串
- hash 类似HashMap
- list 类似list
- set 类似set
- sortedset 可排序set
Redis三大问题
- 缓存穿透
- 原因:数据库和缓存中都不存在,每次请求都直接访问数据库并返回空
- 解决:
- 缓存空对象,如果没有则缓存空值,并设置过期时间
- 布隆过滤器,在客户端请求时,加一层过滤器来过滤请求
- 缓存雪崩
- 原因:大量缓存同时到期,导致大量请求同时访问数据库
- 解决:设置不同过期时间,或者建立集群
- 缓存击穿
- 原因:热key问题,热key突然消失,请求大量访问数据库
- 解决:
- 互斥锁,基于set nx命令,线程1使用set nx查询缓存,未命中则拿到锁,开始查询数据库然后加入缓存,线程2未命中进入休眠模式
- 逻辑过期,在互斥锁的基础上,缓存一个逻辑过期时间,获取锁成功后,直接吧数据返回,然后开启一个新的线程更新数据。
Redis缓存更新策略
- 内存淘汰,利用redis的内存淘汰机制,当内存不足时自动淘汰部分数据,下次查询时更新缓存
- 超时剔除,给缓存数据添加过期时间,到期后自动删除
- 主动更新,编写业务逻辑,在修改数据库的同时,更新缓存
主动更新策略
- 由缓存调用者,在更新数据库时同时更新缓存
- 缓存与数据库整合成一个服务,由服务来维护一致性,调用者调用该服务,无需关心缓存一致性问题
- 调用者只操作缓存,由其他线程异步的将缓存持久化到数据库,保证最终一致
Redis持久化
- RDB:将数据直接记录在磁盘
- save(默认):服务停止时创建新的RDB文件并保存到磁盘
- bgsave:在配置中配置save 60 1000,然后则会触发当60秒内修改1000次则更新RDB文件
- AOF:记录执行过的命令,根据命令进行恢复
Redis主从
实现读写分离
Redis哨兵
自动故障恢复
哨兵的作用
- 监控:sentinel会不断检查master和slave是否按预期工作。
- 自动恢复故障:如果master故障,sentinel会将一个slave提升为master。当故障实例恢复后也已新的master为主
- 通知:sentinel充当Redis客户端的服务发现来源,当集群发生故障偏移是,会将最新的消息推送给Redis的客户端。