1.4.4.1.1. JDK
1.4.4.1.1.1. 数据结构
1.4.4.1.1.2. 类加载的时机?
被动调用:
1、当访问静态字段时,声明这个字段类的字段会被初始化(子类引用父类静态变量除外)
2、数组定义类引用不会触发类初始化
3、引用常量不会触发此类或接口初始化,在链接阶段已显式赋值
4、getClassLoader()不会
主动使用情况如下:
1、当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化
2、当调用类的静态方法时,即当使用了字节码invokestatic指令。
3、当使用类、接口的静态字段时(final修饰特殊考虑),比如,使用getstatic或者 putstatic指令。
4、当使用java.lang.reflect包中的方法反射类的方法时。Class. forName ( "com.Test ")
5、当初始化子类时,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
6、如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化, 该接口要在其之前被初始化。
7、当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
1.4.4.1.1.3. HashMap
hashMap 结构:1.7 数组+ 链表 1.8 数组+链表+红黑树
链表主要是解决hash冲突,解决hash冲突通过拉链法
数组默认长度16,扩容因子0.75,即达到长度12扩容。初始大小时2的n次幂
工作原理 :
存储对象时,将K/V键值传给put()方法:
①、调用hash(K)方法计算K的hash值,然后结合数组长度,计算得数组下标;
②、调整数组大小(当容器中的元素个数大于capacity*loadfactor时,容器会进行扩容resize为2n);
③
i.如果K的hash值在HashMap中不存在,则执行插入,若存在,则发生碰撞;
ii.如果K的hash值在HashMap中存在,且它们两者equals返回true,则更新键值对;
iii.如果K的hash值在HashMap中存在,且它们两者equals返回false,则插入链表的尾部(尾插法)或者红黑树中(树的添加方式)。
hash过程:
先调用hashcode(),通过值进行无符号右移
,在对无符号右移结果进行异或
运算获取hash值,最终根据hash值和长度-1进行与
运算
导致死循环
jdk7的hashmap的死循环,主要在多线程情况下,多个线程对map的同时扩容造成
头插法+链表+多线程并发+HashMap扩容导致死循环
1.4.4.1.1.4. concurrentHashMap
保证线程安全
jdk1.7:Segment+HashEntry来进行实现的;
jdk1.8:放弃了Segment臃肿的设计,采用Node+CAS+Synchronized
来保证线程安全;
get方法采用了unsafe方法,不需要加锁
1.4.4.1.1.5. 双亲委派原理
核心类库由bootstrap加载,恶意重写会被preDefineClass()保护机制
优势:避免类的重复加载,确保一个类的全局唯一性,保护核心api被串改
缺点:加载类的过程是单向,顶层classloader无法访问底层classloader加载的类
热替换tomcat jsp、Jdbc.jar spi接口实现类破坏了双亲委派机制
1.4.4.1.1.6. 内存溢出?内存泄漏?
内存溢出:应用程序占用内存增长速度非常快,造成垃圾回收已经跟不上内存消耗速度,否则不太容易出现OOM。
内存泄漏:
- 静态集合类:长生命周期对象持有短生命周期对象的引用,尽管短生命周期对象不再使用,但因为长生命周期对象持有它的引用导致不能被回收
- 单例模式
- 内部类持有外部类
- 各种链接,如数据库链接\网络链接、io链接等
- 变量不合理的作用域
- 改变哈希值
- 缓存泄漏
- 监听器和回调
1.4.4.1.1.7. 安全点和安全区域?
安全点:并非所有地方都能停顿下来开始GC,只有特定位置能停顿开始GC,这些位置称为安全点。
- 抢先式中断:目前没有虚拟机采用
- 主动式中断:设置中断标志,各个线程运行到safe point时主动轮询这个标志,如果中断标识为true,则将自己进行中断挂起。
安全区域:是指一段代码片段中,对象引用关系不会发生变化,在这个区域中任何位置gc都是安全的.
1.4.4.1.1.8. Java 5种引用区别?
强引用:不回收
软引用:内存不足时回收
弱引用:发现即回收
虚引用:对象回收跟踪
终结器引用:实现对象finalize()方法。无需手动编码,其内部配合引用队列使用。
1.4.4.1.2. mybatis
1.4.4.1.2.1. 工作原理:
- 在 MyBatis 的初始化过程中,会生成一个 Configuration 全局配置对象,里面包含了所有初始化过程中生成对象
- 根据 Configuration 创建一个 SqlSessionFactory 对象,用于创建 SqlSession “会话”
- 通过 SqlSession 可以获取到 Mapper 接口对应的动态代理对象,去执行数据库的相关操作
- 动态代理对象执行数据库的操作,由 SqlSession 执行相应的方法,在他的内部调用 Executor 执行器去执行数据库的相关操作
- 在 Executor 执行器中,会进行相应的处理,将数据库执行结果返回
1.4.4.1.2.2. Mybatis 缓存机制
MyBatis中默认定义了两级缓存,分别是一级缓存和二级缓存。 (1) 默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。 (2)二级缓存需要手动开启和配置,二级缓存是基于namespace级别的缓存。 (3)为了提高扩展性,MyBatis定义了缓存接口Cache。可以通过实现Cache接口来自定义二级缓存。
缓存回收策略:
(1) LRU – 最近最少使用的:移除最长时间不被使用的对象。eviction的默认值 是 LRU。 (2) FIFO – 先进先出:按对象进入缓存的顺序来移除它们。 (3) SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。 (4) WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
1.4.4.1.2.3. Spring中Mapper接口如何被注入?
通过实现 Spring 的BeanDefinitionRegistryPostProcessor 接口,实现它的
postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) 方法,也就是在 Spring 完成 BeanDefinition 的初始化工作后,会将 Mapper 接口也解析成 BeanDefinition 对象注册到 registry 注册表中,并且会修改其 beanClass 为 MapperFactoryBean 类型,还添加了一个入参为 Mapper 接口的 Class 对象的名称
这样 Mapper 接口会对应一个 MapperFactoryBean 对象,由于这个对象实现了 FactoryBean 接口,实现它的 getObject() 方法,该方法会通过 SqlSession 的 getMapper(
Class
1.4.4.1.2.4. 有哪些Executor执行器?
SimpleExecutor(默认):每执行一次数据库的操作,就创建一个 Statement 对象,用完立刻关闭 Statement 对象。 ReuseExecutor:执行数据库的操作,以 SQL 作为 key 查找缓存的
Statement 对象,存在就使用,不存在就创建;用完后,不关闭 Statement 对象,而是放置于缓存 Map
1.4.4.1.2.5. 延迟加载原理
延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定方法,执行数据加载。
MyBatis延迟加载主要使用:Javassist,Cglib实现
1.4.4.1.3. JVM(内存优化总篇)
1.4.4.1.3.1. 内存结构
1.4.4.1.3.2. 创建对象的方式
1、new \ 静态方法
2、class.newInstantce():反射方式,只能调用空无参构造,权限必须public
3、Constructor的newInstantce():反射方式,可以调用空参\带参构造器,权限没有要求
4、使用clone()当前类实现cloneable接口,实现clone(),默认浅拷贝
5、使用反序列化:从文件\数据库\网络中获取对象二进制流,反序列化内存中的对象
6、第三方库Objenesis,利用asm字节码技术,动态生成Constructor对象
1.4.4.1.3.3. 创建对象的步骤
1、判断对象对应的类是否加载、链接、初始化
2、为对象分配内存:指针碰撞、空闲列表
指针碰撞(Bump the pointer):用过内存在一边,空闲在一边,中间放着指针作为分界点指示器. 使用compact整理过程收集器时使用指针碰撞。分配内存仅仅是把指针指向空闲那边挪动一段与对象大小相等距离。Serial\ParNew采用该分配
空闲列表:cms有碎片的标记清楚算法。
3、处理并发安全问题(CAS)
CAS: 失败重试、区域加锁:保证指针更新操作的原子性;
TLAB:把内存分配动作按照线程划分在不同空间之中运行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲区
4、初始化分配到的空间:初始化为零值(不包括对象头)
5、设置对象头(元数据信息):对象hashCode、gc信息、锁信息、对象数组长度等数据存储在对象头
父类数据会先存储
6、执行init方法进行初始化
1.4.4.1.3.4. JIT 和 AOT编译
jdk9 引入aot静态编译:借助Graal编译器,程序运行之前将字节码转换为机器码
.java ->.class -> .so
缺点:破坏了java一次编译到处运行,必须为每个不同硬件\os编译对应的发行包。降低了java链接过程动态性,加载代码在编译期就必须全部已知。最初只支持linux x64 java base
C1(client)和C2(Server)编译器不同的优化策略:
•在不同的编译器上有不同的优化策略,C1编译器上主要有方法内联,去處拟化、冗余消除。
•方法内联:将引用的函数代码编译到引用点处,这样可以减少栈帧的生成,减少参数传递以及 跳转𨑬程 •去虚拟化:对唯一的实现类进行内联 •冗余消除:在运行期间把一些不会执行的代码折叠掉
•c2的优化主要是在全局层面,逃逸分析是优化的基础。基于逃逸分析在C2上有如下几种优化:
•标量替换:用标量值代替聚合对象的属性值
•栈上分配:对于未逃逸的对象分配对象在栈而不是堆
•同步消除:清除同步操作,通常指synchronized 总结: 一般来讲,JIT编译出来的机器码性能比解释器高。
C2编译器启动时长比C1编译器慢,系统稳定执行以后,C2编译器执行速度远远快于C1编译器.
1.4.4.1.3.5. GCroot 有哪些?
- 虚拟机栈中引用对象(各个线程方法中使用到的参数、局部变量等)
- 本地方法栈内JNI引用的对象
- 类静态属性引用对象
- 方法区中常量引用对象(字符串常量池里的引用)
- 同步锁synchronized持有的对象
- java虚拟机内部引用(基本类型class对象,常驻异常对象,系统类加载器)
- java虚拟机内部JMXBean\ JVMT中注册的回调、本地代码缓存等
1.4.4.1.3.6. 垃圾回收
什么是垃圾:运行程序中没有任何指针指向的对象;不及时清理会造成内存溢出
垃圾回收算法
引用技术算法(java没有使用)
引用计数器属性,用于记录对象被引用的情况。
优点:实现简单,垃圾对象别与识别;判定效率高,回收没有延迟性。
缺点:
- 需要单独字段存储计数器,这样做法增加了存储空间开销
- 每次赋值都需要更新计数器,伴随着加法和减法操作,增加了时间开销
- 引用计数器无法处理循环引用问题。
可达性分析算法(或根搜索算法\追踪性垃圾收集)
该算法也实现简单执行高效,同时解决在引用计数算法中循环引用的问题,防止内存泄漏。
标记清楚算法
标记:Collector从引用根节点开始遍历,标记所有被引用对象,一般都在header中记录可达对象
清楚:Collector对堆内存从头到尾进行线性遍历,如果没有标记为可达对象,则将回收
缺点:
- 效率低:递归与全堆对象遍历2遍
- 进行GC时,需要停止整个应用程序,用户体验差
- 清理出的内存不连续,产生内存碎片
复制算法(应用在新生代)
将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,
之后清除正在使用的内存块的所有对象,交互两个内存的角色,最后完成垃圾回收
- 优点:
没有标记和清除过程,实现简单,运行高效复制过去以后保证空间的连续性,不会出现“碎片” 问题。
- 缺点: 此算法的缺点也是很明盛的,就是需要两倍的内存空间。 对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小
- 特别的: 如果系统中的存活对象很多,复制算法不会很理想。因为复制算法需要复制的存活对象数量并不会太大,或者说非常低才行
标记-压缩算法(标记整理)
执行过程:第一阶段和标记清除一样,从根节点开始标记所有被引用对象。
第二阶段将所有存活对象压缩到内存的一端,按顺序排放。之后清理边界外所有的空间
优点:此算法消除了 “标记-清除”和“复制”两个算法的弊端。) •消除了标记/清除算法当中,内存区域分散的缺点,我们需要给新对象分配内存时,JvM只需要持有一个内存的起始地址即可。 •消除了复制算法当中,内存减半的高额代价。
缺点:从效率上来说,标记-压缩算法要低于复制算法。 •效率不高,不仅要标记所有存活对象,还要整理所有在活对象的引用地址。 •对于老年代每次都有大量对象存活的区域来说,极为负重。 •移动对象的同时,如果对象被其他对象引用,则还高要调整引用的地址。 移动过程中,需要全程暂停用户应用程序。即:STW
分代收集算法
不同生命周期不一样,择优选择;
在Hotspot中,基于分代的概念,GC所使用的内存回收算法必须结合年轻代和老年代各自的特点。 •年轻代 (Young Gen) 年轻代特点:区域相对老年代较小,对象生命周期短、存活率低,回收频繁。 这种情況复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对象大小有关,因此很适 用于年轻代的回收。而复制算法内存利用率不高的问题,通过hotspot中的两个survivor的设计得到 缓解。 •老年代 (Tenured Gen) 老年代特点:区城较大,对象生命周期长、存活率高,回收不及年轻代频繁。 这种情况存在大量仔活率高的对象,复制算法明显变得不合适。一般是由标记-清除或者是标记-清除与 标记-整理的混合实现。 •Mark阶段的开销与存活对象的数量成正比。 sweep阶段的开销与所管理区域的大小成正相关。 compact阶段的开销与存活对象的数据成正比。 以Hotspot中的CMS回收器为例,CMS是基于Mark-sweep实现的,对于对象的回收效率很高。而对于 碎片问题,CMS采用基于Mark-Compact算法的serial old回收器作为补偿措施:当内存回收不佳(
增量收集算法
垃圾收集线程只收集一小片区域的内存空间,接着切换应用程序线程,一次反复,直到垃圾收集完成。
总的来说通过对线程间冲突的妥善处理,允许垃圾收集线程以分阶段方式完成标记、清理或复制工作
缺点:造成系统吞吐量下降。
分区算法
1.4.4.1.3.7. 垃圾回收器(7种经典收集器)
jvm调优标准:在最大吞吐量优先的情况下,降低停顿时间
- 串行回收器:Serial 、Serial Old
- 并行回收器:ParNew、Parallel Scavenge 、Parallel Old
- 并发回收器:CMS 、G1
1.4.4.1.3.8. OOM
Java heap space
java.lang.OutOfMemoryError:: Java heap space //
原因:代码中有大对象分配,可能存在内存泄漏,无法找到一块足够大的内存容纳当前对象
解决:
- 检查是否有大对象分配,最有可能是大数组分配.
- 通过jmap把堆内存dump,使用mat查看是否存在内存泄漏
- 没有内存泄漏使用-Xmx加大堆内存
- 检查是否有finalizale对象,考虑其存在的必要性
Metaspace
java.lang.OutOfMemoryError:Metaspace
原因:生成大量类、方法区撑暴,无法卸载,元空间内存小
解决:检查是否有反射操作,是否元空间或永久代内存较小
GC overHead limit exceeded
超过98%时间在做GC并且回收不到2%的堆内存会抛出该异常。本质是预判性异常,实际系统没有真正内存溢出。
解决:
- 检查是否使用死循环或者使用大内存的代码
- 使用参数:-XX:-UseGCOverheadLimit 禁用这个检查
- dump内存,检查是否内存泄漏,如没有则加大内存
线程溢出
创建大量线程,超出系统限制15315
1.4.4.1.3.9. 哪些类不会生成clinit 方法?
- 一个类中没有声明任何类变量,也没有静态代码块时
- 一个类中声明类变量,但是没有明确使用类变量初始化语句以及静态代码块来执行操作时
- 一个类中包含static final 修饰的基本类型字段,这些类字段初始化语句采用编译时常量表达式
1.4.4.1.3.10. class.forName()和getClassLoader().loadClass()有什么区别?
类的加载= 装载+链接(1\2\3)+初始化
class.forName会执行到初始化环节,getClassLoader()只会执行装载
1.4.4.1.4. RocketMQ
1.4.4.1.4.1. RocketMQ消息顺序性
RocketMQ支持生产者在投放消息的时候自定义投放策略,我们实现一个MessageQueueSelector接口
// 并发消息消费逻辑实现类
org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService;
// 顺序消息消费逻辑实现类
org.apache.rocketmq.client.impl.consumer.ConsumeMessageOrderlyService;
1.4.4.1.5. java
1.4.4.1.5.1. 58同城和java字符串比较?
java关键字在sun.misc包已经初始化
package sun.misc;
import java.io.PrintStream;
public class Version {
private static final String launcher_name = "java";
1.4.4.1.5.2. String、StringBuffer、StringBuilder区别?
可变性:
String不可变类,每次新增都会产生新对象
Stringbuffer 、StringBuilder可变类,新增不会产生新对象
线程安全:
String不可变所以是线程安全\StringBuffer也是线程安全,每个操作方法都用了synchronized同步关键字
StringBuilder不是线程安全
性能方面:
string是性能最低(不可变需要重新创建对象分配内存)、StringBuffer比String性能高,它的可变性
性能最高是StringBuilder
存储方面:
String存储在字符串常量池
Stringbuffer和StringBuild存储在堆内存中
1.4.4.1.5.3. JVM为什么使用元空间替换了永久代?
1.7永久代内存是有上限,jvm加载class总数大小很难确定,永久代是通过fullgc回收
元空间存储在本地内存,内存上限比较大. 简化了fullgc过程,可以在不暂停情况下并发释放类数据
需要hostpot和jrockit合并代码,而JRockit里面没有永久代
1.4.4.1.5.4. 并发和并行有什么区别?
并行在多核cpu下同一时刻同时可以执行多个线程能力
并发是cpu能够处理任务数量
1.4.4.1.5.5. 令牌桶限流算法?
1.4.4.1.5.6. java SPI机制原理
原理:接口的扩展机制,把装配的控制权移到程序之外,完成标准和实现的解耦,提供一个动态可插拔的能力。
缺点:不能按需加载,每次都需要加载所有实例进行实例化,造成性能开销
类似:Spring 中 springFactorIesLoader dubbo: ExtensionLoader(激活扩展点、自适应)
1.4.4.1.5.7. MyISAM 和InnoDB 区别?
myIsam数据是通过二进制存储在磁盘上。.MYD数据文件 .MYI索引文件
InnoDB磁盘文件只有一个.ibd文件包含索引和数据
区别:
1、数据存储方式不同. myisam是数据和索引分开,innodb是索引数据合在一起
2、对于事务支持不同 myisam不支持 innoDB支持ACID事务
3、对于锁的支持不同 myisam只支持表锁 innodb可以根据不同情况去支持(行锁、表锁、间隙锁、临建锁)
4、myisam不支持外键、innodb支持外键
1.4.4.1.5.8. 性能最好的单例模式?
需要满足:
1、私有化构造方法(防止被外部实例化造成多实例)
2、提供一个静态方法作为全局访问点
实现:
1、使用双重检查锁方式(延迟实例化)。因加锁会有性能问题
2、通过静态内部类方式实现(延迟实例化)
3、通过枚举类方式实现,即是线程安全,又能反序列化导致破坏单例问题
1.4.4.1.5.9. 为什么concurrentHash中key不允许为null?
避免在多线程模式下出现歧义问题
1.4.4.1.5.10. 避免死锁问题
四个条件都发生时才会出现死锁:
1、互斥,共享资源 X 和 Y 只能被一个线程占用;
2、占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
3、不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
4、循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。
破坏其中一个,就可以成功避免死锁的发生
1.4.4.1.5.11. 为什么加索引能提升效率?
b+树多路平衡查找树存储索引,在千万数据情况下索引高度为3,而层高代表磁盘io次数。基于索引可以减少磁盘io次数
1.4.4.1.5.12. redis的理解?
基于内存存储实现key-value数据结构nosql数据库。redis也提供了持久化策略
提供了5中常用数据类型(string,map ,list,set,zset ),因在内存存储io性能比较好,会在应用是缓存中间件,集群模式提供了主从复制+哨兵实现高可用,redis通过hash槽的方式实现了数据分片,进一步提升了性能和扩展性
1.4.4.1.5.13. 怎么防止缓存击穿的问题?
产生原因:
1、保存的热点key在缓存过期的瞬间有大量请求进来,导致大量请求打到数据库里面
2、客户端恶意发起大量不存在key的请求,因不存在会大量传透到数据库层,导致缓存成一个摆设
解决:
1、对于热点数据,我们可以不设置过期时间,或者在访问数据的时候对数据过期时间进行续期
2、对于访问量较高缓存数据,可以设计多级缓存,尽量减少后端存储设备压力
3、使用布隆过滤器,在应用启动时候把不存在数据缓存到布隆过滤器里面
1.4.4.1.5.14. 一致性Hash算法的理解
是为了解决分布式下动态扩容和缩容的问题,
1.4.4.1.5.15. 什么是链路追踪?
请求链路追踪故障快速定位。可视化了解各个请求链路中耗时提升性能的分析和调优。依赖优化:了解各个调用环节可用性,梳理服务依赖关系以及进行相关优化。数据分析:可以得到用户行为路径,去汇总分析应用在很多场景中一些价值提现。
是一种分布式下实现请求链路可视化监控技术,核心去了解分布式下用户调用行为,去详细展示各个系统指标,去实现故障快速定位和缩短故障排除时间。还有可视化、服务依赖关系梳理、数据分析能力等
1.4.4.1.5.16. RabbitMQ如何实现高可用?
普通集群模式下一个queue消息只会存在于集群的一个节点上,集群里面其他节点会同步queue所在节点的元数据,消息在生产和消费时候,不管请求发送到集群的哪个节点,最终都会路由到queue所在节点上去存储和拉取消息。这种方式并不能保证queue高可用性,但是可以提升吞吐能力
镜像集群下可以通过keepalived + HAProxy 实现集群的负载均衡
镜像集群每个节点都会存储queue的数据副本,每次上产消息时都会把消息内容同步给集群中其他节点,这种方式能保证queue高可用性,集群中副本同步会带来性能损耗。因每个节点都存储了副本可以通过haProxy实现负载均衡
1.4.4.1.5.17. Java有几种文件拷贝方式,哪一种效率最高?
1、java.io包下的库, fileInputStream读取,在使用fileOutPutStream 写出
2、java.nio包下,transferTo或者TransfFrom方法实现(零拷贝实现),能避免不必要拷贝和上下文切换
3、java标准库本身提供files.copy实现
1.4.4.1.5.18. ArrayList的自动扩容机制的实现原理
ArrayList是数组结构容器,默认长度10,可以在构建时指定长度。容量满会触发自动扩容:
创建一个新的数组,这个数组长度是原来数组长度的1.5倍
然后使用Arrays.CopyOf方法把老数组的数据拷贝到新的数组中
扩容后在把新数据添加到新数组中
1.4.4.1.5.19. IO多路复用机制?
核心是让单个线程去监视多个链接,一旦某个链接就绪就触发读/写事件,就会通知对应应用程序主动获取就绪链接去进行读写操作.也就是说在应用程序中可以使用单个线程同时去处理多个客户端链接
Select poll 都是基于轮询去获取就绪链接, 而epoll 是基于时间驱动方式获取就绪链接
1.4.4.1.5.20. java提供了哪几种线程池,分别的提点?
1、newCachedThreadPool 一种可以缓存的线程池,它可以用来处理大量短期的突发流量
特点:
- 最大线程数是Integer.MaxValue
- 线程存活时间是60s
- 阻塞队列用的synchronousQueue 不存放元素的队列
- 每个线程存活60s
2、newFixedThreadPool 固定线程数量的线程池
特点:
核心线程和最大线程数量都是一个固定值.任务处理不过来就会加入阻塞队列等待
3、newSingleThreadExecutor 只有一个工作线程的线程池,并且线程数量无法动态更改,所有任务都按照FIFO顺序方式执行
4、newScheduledThreadPool 具有延迟执行功能的线程池,可以实现定时调度
5、newWorkStealingPool java8新加入的线程池,内部构建forkjoinPool利用工作窃取算法并行处理请求
线程状态:
1、new 线程创建还没有调用start启动
2、Runnable 线程运行状态,也可能是在就绪队列等待操作系统进行调度分配cpu资源
3、blocked线程处于锁等待状态
4、waiting 表示线程处于条件等待状态
5、Time_wait 和waiting状态相同,只是多了一个超时条件触发
6、Terminated 线程执行结束
1.4.4.1.6. JUC
JMM(java内存模型)一种抽象概念。规范需要保证:可见性、原子性、有序性;
CountdownLatch 做减法 countDown() \ await()
CyclicBarrier 做加法 await()
Semaphore : 伸缩 acquire()抢占 release()释放
BlockingQueue阻塞队列:put满时阻塞 take空的时候取阻塞
ArryBlockingQueue : 由数组结构组成有界阻塞队列
LinkedBlockingQueue:由链表结构组成有界(默认Integer.MAX_VALUE)阻塞队列
PriorityBlockQueue:支持优先级排序的无界阻塞队列
DelayQueue: 使用优先级队列实现的延迟无界阻塞队列
SynchronousQueue: 不存储元素的阻塞队列,也即单个元素队列,(生产者消费者模型)
LinkedTransferQueue: 由链表结构组成的无界阻塞队列
LinkedBlockingDeque:由链表结构组成的双向阻塞队列
1.4.4.1.7. lockSupport
Object中的 wait 和notify 必须在synchronized内执行 ,顺序先wait 在notify
Lock 中condition 在lock()内执行 await() signal() ,顺序 await(),在signal
lockSupport park unpark
没有顺序问题,最大凭证
是1,多次调用不会累加
LockSupport.park() 被阻塞
LockSupport.unpark(a) 释放线程a
1.4.4.1.7.1. AQS
Lock()-> sync.lock(公平和非公平) ->
fairSync.lock()
NonFairSyncLock()-> cas获取锁->
失败-> acquire(1) -> tryAcquire()-> acqureQueured(addWaiter()) -> enq(node)->加入队列
1.4.4.1.7.2. volatile
轻量级同步机制, 三大特性:保证可见性、不保证原子性
、禁止指令重排
不保证原子性
是因为拷贝副本到各自工作空间赋值,未通知其他线程。解决原子性:使用atomic原子类型
保证指令重排:编译器优化重排、指令并行重排、内存系统的重排。
进行写时,会在写之后加入store屏障指令,将工作内容共享变量刷新回主内容。storestore storelad
进行读时, 会加入load屏障指令,从主内容读取共享变量。loadload loadstore
使用场景:(保证多线程情况语义不进行指令重排)
单例模式(DCL)不一定线程安全,会有指令重排的存在,加入volatile可以禁止指令重排。
1.4.4.1.7.3. CAS
自旋 + unsafe , getAndAddInt()
缺点:
1、循环时间长开销大
2、只能保证一个共享变量的原子操作
3、引来ABA问题,原子引用更新,如何规避ABA
ABA:取出内容中某时刻的数据并在当下时刻比较并替换.在这个时间差类会导致数据变化。
原子引用:atomicReference
时间戳原子引用 :atomicStampedReference
1.4.4.1.7.4. ArrayList实现线程安全
异常:ConcurrentModificatioinException
1、Vector
2、Collections.synchronizedList(new ArrayList()); //底层使用synchronzied互斥锁
3、CopyOnWriteArryList
1.4.4.1.7.5. 锁
ReentrantLock/Synchronized是典型的可重入锁(又名递归锁).可重入最大作用是避免死锁
1.4.4.1.7.6. Synchronized 和 lock 有什么区别?
Synchronize 是java关键字,jvm层面。是monitor指令。不需要用户手动释放。不可中断。非公平锁
lock是接口是api类。需要用户手动释放锁。可中断。公平和非公平都可以。可以实现分组唤醒,精确唤醒
1.4.4.1.7.7. 伪共享
cpu设计了三级缓存、一次性读取64个子节数据作为缓存行、在java中一个long类型占8个子节,也就是缓存行说可以存放8个long类型变量。如果一个存储器位置被引用,那么将来它附近的位置也可能会被引用.缓存行的设计可以有效的减少和内存交互次数,从而去避免cpu和io的一个等待。恰好基于这个设计,修改同一个缓存行里面多个独立变量时候,基于缓存一致性会影响彼此功的性能。
解决方法:
1、对齐填充
2、jdk8提供了@contented注解
1.4.4.1.8. JVM
gc垃圾回收算法:
引用计数法缺点:每次对象赋值均要维护引用计数器,且计数器本身有一定损耗,较难处理循环引用。一般不采用
复制算法:新生代算法,复制->清空->交换(复制之后有交换,谁空谁是to) 缺点:浪费空间
- 标记清楚算法:产生内存碎片
- 标记整理 标记-清除,压缩 ;耗时比较多
1.4.4.1.8.1. 哪些对象可以作为GC root ?
内存不在被使用空间即为垃圾。
引用计数法:判断是否无引用;
枚举根节点可达性分析(根搜索路径):gc root开始扫描,能够遍历通过算可达。不可达即为垃圾。
GC ROOT对象:
虚拟机栈(栈桢中局部变量区)中引用对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI 引用的对象
1.4.4.1.8.2. jvm参数
jvm参数类型:
- 标配参数 -version -help
- x参数 xXint (解释执行) -Xcomp(第一次使用就编译成本地代码) -Xmixed (混合模型)
- xx参数
- boolean 类型 : -XX:+ 或者- 某个属性值 +标识开启 - 表示关闭
- KV设值类型 -XX:属性key=属性value
- jinfo举例 jinfo -flag PrintGCDetails pid(查看是否有该参数) jinfo -flags pid(查所有)
- 坑题 -Xms1024m -Xmx1024m 属于XX参数别名
- -Xms 等价于 -XX:InitialHeapSize
- -Xmx 等价于 -XX:MaxHeapSize
查看jvm默认初始值:java -XX:+PrintFlagsInitial
-Xms 初始内存大小,默认物理内存1/64 等价于 -XX:InitialHeapSize
-Xmx 最大分配内存 默认物理内存1/4 等价于-XX:MaxHeapSize
-Xss 单个线程栈大小 一般默认为512k ~ 1024k 等价于 -XX:ThreadStackSize
-Xmn 设置年轻代大小
-XX:MeataspaceSize 设置元空间大小
-XX:SurvivorRatio: 设置新生代eden和s0/s1空间比例 默认8:1:1 -XX:SurvivorRatio=4 eden:s0:s1 = 4:1:1
-XX:NewRatio 配置年轻代和老年代占比 默认-XX:NewRatio=2 新生代1老年代2 年轻代占整个堆的1/3
-XX:MaxTenuringThreshold 设置垃圾最大年龄 默认15 区间0-15
-XX:+PrintGCDetails GC日志
1.4.4.1.8.3. 强、软、弱、虚引用的区别
强:内存不足时,出现了OOM也不会对该对象进行回收
软(softReference):内存不足时才会被回收。内存敏感会用到该类型,例如:高速缓存
弱(weakReference): 当发生gc时就回收 weakHashMap(gc回收后也会被回收)
虚(PhantomReference) : 又名幽灵引用。虚引用不能单独使用,必须和ReferenceQueue 联合使用
1.4.4.1.8.4. OOM
java.lang.StackOverFlowError 栈溢出
java.lang.OutOfMemoryError: java heap space 堆内存溢出
java.lang.OutOfMemoryError: GC overhead limit exceeded 超过98%时间在GC并且回收不到2%的堆内存
超过98%时间在做GC并且回收不到2%的堆内存会抛出该异常。本质是预判性异常,实际系统没有真正内存溢出。使用字符串intern()方法
解决:
- 检查是否使用死循环或者使用大内存的代码
- 使用参数:-XX:-UseGCOverheadLimit 禁用这个检查
- dump内存,检查是否内存泄漏,如没有则加大内存
java.lang.OutOfMemoryError:Direct buffer memory 直接内存溢出 nio分配堆外内存不足时造成
- java.lang.OutOfMemoryError:unable to create new native thread 线程创建数超过了系统上限
- java.lang.OutOfMemoryError:Metaspace
原因:生成大量类、方法区撑暴,无法卸载,元空间内存小
解决:检查是否有反射操作,是否元空间或永久代内存较小
1.4.4.1.8.5. 垃圾回收器和垃圾算法?
GC算法(引用计数/复制拷贝/标记清理/标记整理)是内存回收方法论,垃圾收集器时算法落地实现。
5种垃圾收集器:serial(串)、parallel(并行) cms(并发标记清楚) 、 G1(并发对区域清理)、ZGC
- 并发(concurrency):把任务在不同的时间点交给处理器进行处理。在同一时间点,任务并不会同时运行。
- 并行(parallelism):把每一个任务分配给每一个处理器独立完成。在同一时间点,任务一定是同时运行。
(UseSerialGC 、 UseParNewGC、UseParallelGC 、UseConcMarkSweepGC、UseParallel0ldGC、UseG1GC)
-XX+UseSerialGC 配置时,新生代和老年代都会使用串行垃圾回收器
-XX:+UseParNewGC 只影响新生代收集,不影响老年代 ParNew(Yong区) +Serial Old(不在推荐) 组合 = 不在推荐
-XX:+UseParallelGC : 在新生代和老年代并行收集
-XX:+UseConcMarkSweepGC ParNew(young区)+CMS(old区)+Serial old组合使用。SerialOld 作为cms出错后备收集器
G1(jdk9默认) 每个regin从1m到32m不等。
1.4.4.1.8.6. 查看机器变慢流程?
- 整机:top
- CPU:vmstat vmstat -n 2 3 (每两秒采样一次,共采样3次
us+sy 参考值为80% ,如果大于说明cpu不足
mpstat -P all 2 //查看所有cpu核的信息
pidstat -u 1 -p
内存:free -m
硬盘:df -h
磁盘IO : iostat -xdk 2 3
网络io: ifstat
cpu高定位
ps -mp
printf "%x\n" tid
jstack 进程ID | grep tid -A60
1.4.4.1.9. Spring
轻量 2m 、ioc/di 容器实现bean生命周期管理、aop把业务功能和系统功能进行切分、mvc框架、事务管理
生态庞大、社区活跃度技术成熟度高
1.4.4.1.9.1. spring中bean的作用域
Singleton
Prototype
1、Request 每次http请求会创建一个新的bean
2、session会话维度,同一个session共享同一个bean实例不同的session产生不同的bean实例
3、globalSession
针对全局session维度共享同一个bean实例
1.4.4.1.9.2. IOC?
控制反转:对象是由使用者控制,有了容器可以交给容器帮忙进行管理
DI:依赖注入;按属性注入、构造器注入、方法注入;@autowired,@Resource PopulateBean
容器:存储对象使用map结构存储对象有三级缓存。
singletonObjects:存放完整对象 ConcurrentHashMap
earlySingletonObjects :存放半成品对象 HashMap
singletonFactories:表达式和对象映射关系 HashMap
bean生命周期
:spring容器帮助管理对象,对象从产生到销毁主要包含两个主要环节:实例化、初始化,过程中会有一些扩展点。各个环节步骤:
1、实例化bean,通过反射生成,在createBeanInstance中生成
2、对象创建后属性都是默认值,通过populateBean方法来填充属性,中间会存在循环依赖问题
3、向bean对象中设置容器属性,会调用invokeAwareMethods
4、调用BeanPostProcessor前置处理进行bean扩展处理
5、调用invokeInitMethods完成初始化,会判断是否实现InitalingBean接口
6、调用BeanPostProcessor后置处理方法,aop就是在这个接口实现。AbstractAutoProxyCreator
7、获取到完整对象使用
8、对象销毁会判断是否实现接口dispoablesBean接口
bean都是通过反射生成.有两个重要的扩展点:BeanFactoryPostProcessor,BeanPostProcessor,aop就是在ioc基础上实现的扩展,通过BeanPostProcessor实现
1.4.4.1.9.3. BeanFactory和FactoryBean区别?
都可以用来创建对象,只是流程方法不同;
beanFactory:必须遵循bean生命周期,可以创建单例对象
FactoryBean: 自定义bean对象创建。isSingleton判断是否单例对象 getObject()创建对象。比如Feign
1.4.4.1.9.4. BeanFactory 和ApplicationContext区别?
beanFactory是访问spring容器根接口,规定了一些规范
ApplicationContext是实现了beanFactory接口,实现了扩展功能,提供了丰富的api调用
1.4.4.1.9.5. Spring中用到的设计模式
单例:springbean都是单例
工厂:BeanFactory
模板:PostProcessorBeanFactory ,onRefresh
代理:aop
策略:xmlBeanDefinitionReader
观察:listenter ,event
适配:adapter
装饰:beanWrapper
责任链:使用aop是用到
1.4.4.1.9.6. 什么是循环依赖?
构造方法注入会造成循环依赖。
属性注入时原型(prototype)场景会报循环依赖。单例不会有循环依赖(singleon),spring内部使用3级缓存
解决循环依赖(DefaultSingletonBeanRegistry)
创建属性依赖bean过程
1 、A 创建过程中需要B,于是A将自己放到三级缓里面,去实例化 B 2 、B 实例化的时候发现需要 A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A 然后把三级缓存里面的这个 A 放到二级缓存里面,并删除三级缓存里面的 A 3、B 顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态) 然后回来接者创建 A,此时 B己经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。
1.4.4.1.9.7. AOP
spring 4 执行顺序:
正常 @before @after @afterReturning
异常 @before @after @afterThrowing
spring 5 执行顺序:
正常 @before @afterReturning @after
异常 @before @afterThrowing @after
1.4.4.1.9.8. spring事务传播?
传播特性 7种
Required(默认),
requires_new,
nested(当前存在事务就嵌套在当前事务中,没有的话新建),
support(支持当前事务,当前事务不存在就以非事物方式执行)
not_support(以非事物方式执行,如果存在事务就需要把当前事务挂起)
never(以非事务执行,存在事务就抛异常)
mandatory(强制事务执行,不存在事务就抛出异常)
嵌套事务怎么办?
如果外层是required,内层是required,rquires_new,nested
如果外城是required_new ,内层是required,rquires_new,nested
如果外层nested,内层是required,rquires_new,nested
判断内外方法是否同一个事务:
同一个事务异常统一外城处理
不在同一个事务,内层方法可能影响外层方法
1.4.4.1.10. Redis
命令不区分大小写,但key区分大小写
1.4.4.1.10.1. 使用场景:
String:单key\多key赋值 、数值增减、字符串长度(strLen) 、分布式锁、点赞
hash: 简单购物车场景
list : 微信文章订阅公众号
set: 无序无重复、集合运算(差集、交集、并集),抽奖小程序,朋友圈点赞
Zset : 排名
1.4.4.1.10.2. AOF过程
Aof 采用指令追加方式几乎实时去实现数据指令持久化,因会把每个数据变更都用指令保存,
会造成aof文件过大,redis为解决使用了指令重写机制,aof采用了相同指令压缩方式,只保留最新操作的指令
步骤:
1、根据当前redis内存里面的数据重新构建一个新的aop文件
2、读取当前redis里面的数据写入到新的aof文件中
3、重写完成后用新的aof文件覆盖现有的aof文件
1.4.4.1.10.3. 分布式锁有哪些坑?
误删别人锁?判断自己锁才能删除、不用lua脚本怎么删除,可以使用事务.
redis分布式续期?redisson 分布式通用组件、分布式锁
1.4.4.1.10.4. Redis 淘汰策略?
redis默认内存maxmemory 32位最大3G , 一般为物理内存的四分之三,通过配置文件设置最大内存或者通过config命令
redis打满会有什么问题?会报OOM
redis有8中缓存策略,默认noevication(等着内存打满),过期不一定立即删除
定时删除(cpu不友好)、惰性删除(对内存不友好)、定期删除(过期数据占比控制频度)
lru: 最近最少使用 lfu: 最近最少频率 配置策略:maxmemory-policy allkeys-lrn
noeviction: 默认配置。不会驱逐任何key,等内存打满
allkey-lru: 对所有key使用lru算法删除。一般采用
volatile-lru:对所有设置了过期时间的key使用lru
allkeys-random: 对所有key进行随机删除
volatile-rasndom:对所有设置过期时间key进行随机
volatile-ttl:删除马上要过期key
allkeys-lfu: 对所有key使用lfu算法进行删除
volatile-lfu:对设置过期时间的key进行lfu算法
1.4.4.1.11. dubbo
高性能轻量级开源RPC框架,由10层模型组成
负载算法:随机、加权、最短响应随机
1.4.4.1.11.1. 工作原理:
1、服务启动时,生产者和注册会根据配置信息链接注册中心,分别注册和订阅
2、注册中心会根据订阅关系返回服务提供者信息到服务消费者,同时服务消费者会把注册信息缓存到本地,如果提供新变更,消费者会收到注册中心一个推送进行更新本地缓存
3、服务消费者会生成代理对象,同时根据负载均衡策略去选择提供者,并且定时想monitor发送记录接口调用次数和时间信息
4、拿到代理对象后,服务消费者通过代理对象发起接口调用
5、服务提供者收到请求后会根据数据进行反序列化,然后经过代理调用具体接口的一个实现
1.4.4.1.11.2. Dubbo是如何动态感知服务下线
多个dubbo服务使用zookeeper维护,在zk上会使用树形结构维护dubbo服务提供方的协议地址,dubbo消费方会从zk上查找目标服务列表,从而完成整个服务注册和发现。zookeeper会通过心跳检测机制来判断是否dubbo服务提供端运行状态是否需要去剔除。当dubbo服务提供方出现故障被剔除时,dubbo消费方需要感知到地址变化,从而避免后续的请求发送到故障节点导致请求失败。watch机制来实现服务监听,一旦服务节点变化会发送事件给客户端,客户端会剔除本地服务,后续不会在请求改服务节点。
1.4.4.1.11.3. dubbo和springCloud区别:
dubbo是soa时代的产物,他关注点主要在于服务远程调用、流量分发\服务治理等方面,底层使用netty
springCloud是关注微服务架构生态解决方案,是一个微服务解决生态.底层是http+rest风格的接口
http有更多报文占用带宽会更多。
1.4.4.1.12. Netty
高性能nio网络模型框架,是对nio做了很多优化比如:零拷贝机制、高性能无锁队列、内存池等
netty支持多种通信协议,比如http、webSocket,针对数据拆包粘包问题内置多种策略
提升服务端吞吐量,提供了单线程reactor模型、多线程reactor模型、多线程多reactor模型
1、网络通信层 bootstrap(客户端启动) \serverBootstrap(服务端监听) \ channel(网络通信载体)
2、事件调度层 eventLoopGroup(线程池接收io请求并分配执行) eventLoop(具体线程)
3、服务编排层
channelPipLine(负责处理多个channelHanlder,把多个channel构成一个链)
channelHanlder(针对io数据的处理器,数据接收后通过指定handler处理)
channelHandlerContext(保存channelHandler上下文信息)
reactor把io分派对应的handler、acceptor 处理客户端连接请求、handlers 去执行业务逻辑读写操作
1.4.4.1.13. 主键id设计
1.4.4.1.13.1. 雪花算法:
1bit 符号位 4bit时间戳 10bit工作机器id 12bit序列号 = 64位长度 long类型数字
开启 NTP ,并设置stepback为0 禁止时间回调
优点:
- 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
- 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
- 可以根据自身业务特性分配bit位,非常灵活。
缺点:
- 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。
1.4.4.1.13.2. MongoDB中ObjectId
通过“时间+机器码+pid+inc”共12个字节,通过4+3+2+3的方式最终标识成一个24长度的十六进制字符。
优点:
- 非常简单,利用现有数据库系统的功能实现,成本小,有DBA专业维护。
- ID号单调自增,可以实现一些对ID有特殊要求的业务。
缺点:
- 强依赖DB,当DB异常时整个系统不可用,属于致命问题。配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号。
- ID发号性能瓶颈限制在单台MySQL的读写性能。
1.4.4.1.13.3. 微信的seqsvr
https://github.com/nebula-im/seqsvr
1.4.4.1.13.4. 美团Leaf-Segment
https://tech.meituan.com/2017/04/21/mt-leaf.html
优点:
- Leaf服务可以很方便的线性扩展,性能完全能够支撑大多数业务场景。
- ID号码是趋势递增的8byte的64位数字,满足上述数据库存储的主键要求。
- 容灾性高:Leaf服务内部有号段缓存,即使DB宕机,短时间内Leaf仍能正常对外提供服务。
- 可以自定义max_id的大小,非常方便业务从原有的ID方式上迁移过来。
缺点:
- ID号码不够随机,能够泄露发号数量的信息,不太安全。
- TP999数据波动大,当号段使用完之后还是会hang在更新数据库的I/O上,tg999数据会出现偶尔的尖刺。
- DB宕机会造成整个系统不可用。
Leaf-segment方案可以生成趋势递增的ID,同时ID号是可计算的,不适用于订单ID生成场景,
Leaf-snowflake方案完全沿用snowflake方案的bit位设计
1.4.4.1.13.5. UUID
优点:
- 性能非常高:本地生成,没有网络消耗。
缺点 :
- 不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。
- 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。
- ID作为主键时在特定的环境会存在一些问题,比如做DB主键的场景下,UUID就非常不适用:36个字符长度的UUID不符合要求
1.4.4.1.14. 算法题
1.4.4.1.14.1. 两个线程交替输出字母和数字?123456... ABCDEF.... 输出1A2B3C
LookSupport.park()不带参数代表当前线程阻塞,unpark(t2)叫醒某一个线程
1.4.4.1.14.2. 两数之和?LeetCode-1
输入:nums = [2,7,11,15] target = 9 ;
输出:[0,1] 解释: nums[0] + nums[1] = 9
//方法1 穷举法
public int[] twoSum(int[] nums,int target){
int [] result = new int[2];
for(int i = 0;i<nums.length ;i++){
for(int j=j+1;j<nums.length;j++){
if(nums[i] + nums[j] == target){
result[0] = i ;
result[1] = j ;
return result;
}
}
return result;
}
}
//方法2
public static int[] twoSum(int[] nums, int target) {
int[] result = new int[2];
Map<Integer, Integer> storeNums = new HashMap(nums.length, 1);
for (int i = 0; i < nums.length; i++) {
Integer another = target - nums[i];
Integer anotherIndex = storeNums.get(another);
if (anotherIndex != null) {
result[0] = anotherIndex;
result[1] = i;
break;
} else {
storeNums.put(nums[i], i);
}
}
return result;
}
1.4.4.1.14.3. 合并两个有序数组 LeetCode-88
输入:nums=[1,2,3,0,0,0],m = 3,nums2 = [2,5,6] ,n=3
输出:[1,2,2,3,5,6] 解释:合并2个数组结果
方法1:增加临时数组,比较并赋值到新数组
public void merge(int []num1,int m,int[]nums2,int n){
int k = m+n;//总长度
int[] temp = new int[k] ;
for(int index = 0;nums1Index = 0,nums2Index = 0;index<k; index++){
if(nums1Index >= m){ //nums1数组已经取完,完全取nums2数组值即可
temp[index] = nums2[nums2Index++] ;
}else if(nums2Index > n){//nums2数组已经取完,完全取nums1数组值即可
temp[index] = nums1[nums1Index++];
} else if(nums1[numsIndex] < nums2[nums2Index]){
temp[index] = nums1[nums1Index++];
}else{
temp[index] = nums2[nums2Index++] ;
}
}
for(int i=0;i<k ; i++){
nums1[i] = temp [i] ;
}
}
方法2:第一个数组长度有空余,按比较替换空余位的值进行
public static void merge(int[] nums1, int m, int[] nums2, int n) {
int k = m + n;
for (int index = k - 1, nums1Index = m - 1, nums2Index = n - 1; index >= 0; index--) {
if (nums1Index < 0) { //nums1 取完,完全取nums2值
nums1[index] = nums2[nums2Index--];
} else if (nums2Index < 0) { //nums2 取完,完全用nums1值
break;
} else if (nums1[nums1Index] > nums2[nums2Index]) {//nums1大于nums2 ,用nums1值
nums1[index] = nums1[nums1Index--];
} else { //nums2大于nums1 ,用nums2值
nums1[index] = nums2[nums2Index--];
}
}
}
1.4.4.1.14.4. 移动零 (LeetCode-283
输入:[0,1,0,3,12]
输出:[1,3,12,0,0]
思路1:双遍历,非零放前面,0放到后面
public void moveZeros(int[] nums){
if(nums == null){
return ;
}
int j = 0 ;
//非零移动到头
for(int i=0;i<nums.length; ++i){
if(nums[i]!=0){
nums[j++] = nums[i];
}
}
//剩下元素设置0
for(int i=j;i<nums.length;++i){
nums[i] = 0 ;
}
}
1.4.4.1.14.5. 找到消失的数字 LeetCode-448
输入:nums = [4,3,2,7,8,2,3,1]
输出:[5,6]
数组之外的数字进行比较
public List<Integer> findDisappearedNums(int[] nums) {
int n = nums.length;
for (int num : nums) {
int x = (num - 1) % n; // 对n取模还原本来的值
nums[x] += n;
}
List<Integer> result = new ArrayList<Integer>();
for (int i = 0; i < n; i++) {
if (nums[i] <= n) {
result.add(i + 1);
System.out.println(i + 1);
}
}
return result;
}
1.4.4.1.14.6. 爬楼梯?LeetCode-70
每次爬1或者2个台阶,问有多少中不同方法可以爬到楼顶?
规律: 输入1得1 、输入2得2 、 输入3 得 3(2阶 + 1)
第一种:递归
private Map<Integer,Integer> storeMap = new HashMap() ;//增加结果缓存,降低深度
public int climbStairs(int n){
if(n == 1) return 1 ;
if(n == 2) return 2 ;
if(null != storeMap.get(n)){
return storeMap.get(n);
}else{
int result = climbStairs(n-1) + climbStairs(n-2);
storeMap.put(n,result);
return result ;
}
}
第二种:非递归
public int climbStairs(int n){
if(n == 1)return 1;
if(n == 2)return 2;
int result = 0 ;
int pre = 2;
int prePre = 1 ;
for(int i = 3 ;i<=n; ++i){
result = pre + prePre ;
prePre = pre ;
pre = result ;
}
return result ;
}
1.4.4.1.14.7. 删除排序链表中重复项?LeetCode-83
输入 head = [1,1,3]
输出 [1,2]
public ListNode deleteDumplicates(ListNode head){
if(head == null){
return head;
}
ListNode currentNode = head;
while(null != currentNode.next){
if(currentNode.next.val == currentNode.val){
currentNode.next = currentNode.next.next;
}else{
currentNode = currentNode.next ;
}
}
}
1.4.4.1.14.8. 反转链表 LeetCode-206?
输入 head = [1,2,3,4,5]
输出 head = [5,4,3,2,1]
public ListNode reverseList(ListNode head){
ListNode preNode = null;
ListNode curr = head;
while(curr != null){
ListNode next = curr.next ;
curr.next = preNode ;
preNode = curr;
curr = next ;
}
return preNode ;
}
1.4.4.1.14.9. 回文链表 LeetCode-234
输入 head = [1,2,2,1]
输出 true
public boolean isPalindrome(ListNode head){
ListNode fast = head,slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next ;
slow = slow.next ;
}
if(fast != null){
slow = slow.next ;
}
slow = reverse(slow) ;
fast = head;
while(slow != null){
if(fast.val != slow.val){
return false;
}
fast = fast.next;
slow = slow.next ;
}
return true;
}
1.4.4.1.14.10. 判断链表是否相交?LeetCode-160
public ListNode getIntersectionNode(ListNode A,ListNode B){
if(A == null || B == null){
return null;
}
ListNode tempA = A ,tempB = B ;
while(tempA != teampB){
tempA == tempA == null ? B : tempA.next ;
tempB == tempB == null ? A : tempB.next ;
}
return tempA ;
}
1.4.4.1.14.11. 环形链表?LeetCode-141
给定一个链表,判断是否有环:根据快慢指针,判断是否有交集,有交集即有环
public boolean hasCycle(ListNode head){
if(head == null) return false;
ListNode slow =head,fast = head ;
while(fast.next != null && fast.next.next != null){
slow = slow.next ;
fast = fast.next.next ;
if(slow == fast){
return true;
}
}
return false ;
}
1.4.4.1.14.12. 链表中间节点?LeetCode-876
快慢指针,慢指针走1,快指针走2,快指针走到最后返回慢指针即中点
public ListNode middleNode(ListNode head){
ListNode slow = head,fast =head ;
while(fast != null && fast.next != null){
slow = slow.next ;
fast = fast.next.next ;
}
return slow ;
}
1.4.4.1.15. github
- 关键词搜索
seckill in:name,readme,description //关键词搜索
- Star/fork数排序搜索
springboot stars:>=5000
springboot forks:>500
Springboot forks:100..200 stars:80..100 //组合使用
- awesome 关键词搜索
awesome redis //搜索收藏redis的项目
代码高亮定位 #L
github代码地址加#L行号
Github 快捷键
https://docs.github.com/en/get-started/using-github/keyboard-shortcuts
按地区搜索
location:地区 language:语言
location:beijing language:java
1.4.4.1.16. 书籍
深入理解java虚拟机
架构设计核心理念
方法论:复杂度驱动、风险驱动、领域驱动
合适原则
简单原则:奥卡姆剃刀:若无必要,勿增实体
演进原则