java

image.png

1、Arraylist和Linkedlist区别

(1)ArrayList 底层实现就是数组,且ArrayList实现了RandomAccess,表示它能快速随机访问存储的元素,通过下标 index 访问,只是我们需要用 get() 方法的形式, 数组支持随机访问, 查询速度快, 增删元素慢;
(2)LinkedList 底层实现是链表, LinkedList 没有实现 RandomAccess 接口,链表支持顺序访问, 查询速度慢, 增删元素快
arraylist是连续的内存,增删需要移动对应位置的其他部分所有数据都后移或前移。
linklist 是链表结构,增删就直接在对应位置断开链表,然后在对应链表设置新元素的前后节点即可

2、增加链表速度---跳表

(1)随机复制一个个数少的有序集合,一次向下查找

3、ArrayList如何进行扩容

数组实现,节约空间,数组有容量限制,超出后,增加50%容量。
get(i) ,set(i) 按索引得到元素,效率较高;
直接在数组末尾add(e)
but : remove(i), add(i)指定 索引i 处操作,效率较低,因为需要调用 System.arrayCopy(),移动受影响的元素,性能变差

arrays中的sort

1.对于很小的数组(长度小于27),会使用插入排序。
一个稳定的,自适应的迭代mergesort,在部分排序的数组上运行时,需要远远少于n lg(n)的比较,同时在随机数组上运行时性能堪比传统的mergesort。像所有合适的合并类型一样,这种类型是稳定的,并运行O(n log n)时间。

4、HashMap的红黑树和链表转化的阈值是8 小于6转为链表

如果元素小于8个,查询成本高,新增成本低
如果元素大于8个,查询成本低,新增成本高

如果选择6和8(如果链表小于等于6树还原转为链表,大于等于8转为树),中间有个差值7可以有效防止链表和树频繁转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。
还有一点重要的就是由于treenodes的大小大约是常规节点的两倍,因此我们仅在容器包含足够的节点以保证使用时才使用它们,当它们变得太小(由于移除或调整大小)时,它们会被转换回普通的node节点,容器中节点分布在hash桶中的频率遵循泊松分布,桶的长度超过8的概率非常非常小。所以作者应该是根据概率统计而选择了8作为阀值

5、hashmap 1.7 1.8

1.7中的HashMap是使用的数组加单链表实现。使用链表是为了解决Hash冲突,因为哪怕不同的key的时候也可能造成hashcode一样。
但是用链表查找数据的时间复杂度是O(n),链表越长很可能查询时间越长,因为需要依次遍历下去查找数据。

1.1.7使用头部插入链表entity性能更好但是容易死循环,

1.8使用链表与红黑树使用尾部插入。

1.7扩容每次都重新计算hash,1.8使用1.7计算hash的公式规律可以快速直接算出来

1.7的ConcurrentHashMap使用了Segment继承ReentrantLock来实现线程安全,也就是分段技术。
这里会有一个疑问为啥要锁段,而不是把粒度缩小到entity。
因为考虑到扩容。
然后还有一个问题只有写加了锁,读没有加锁。是利用缓存副本?不是!缓存副本会造成脏读,而且性能也不好。
1.8中放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized+红黑树 来保证并发安全进行实现,结构如下

6、线程池

corePoolSize:核心线程数量

maximumPoolSize:最大线程数量;

workQueue:等待队列,当任务提交时,如果线程池中的线程数量大于等于corePoolSize的时候,把该任务封装成一个Worker对象放入等待队列;

keepAliveTime:线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime;

TimeUnit :时间级别

threadFactory:它是ThreadFactory类型的变量,用来创建新线程。

handler:它是RejectedExecutionHandler类型的变量,表示线程池的饱和策略。如果阻塞队列满了并且没有空闲的线程,这时如果继续提交任务,就需要采取一种策略处理该任务。

ThreadFactory:定义线程池中创建的线程,如线程名称,优先级等,可以几次ThreadFactory重新newThread(Runnable r)方法。

使用无界队列的影响,这也是为什么使用Eexcutors来创建线程池存在一定风险的原因
delayWorkQueue 延时任务队列

JVM回收机制

1、判断对象是否可以回收
引用计数法 被引用+1 ,不被引用-1
2、可达性分析
3、永久代回收
该类所有实例都已经被回收(即在Java堆中不存在任何该类的实例)
加载该类的ClassLoader已经被回收
该类对应的Class对象没有在任何其他地方被引用,无法在任何地方通过反射访问该类的方法JVM可以对同时满足上述3个条件的无用类进行回收,也仅仅是"可以"而不是必然。在大量使用反射、动态代理等场景都需要JVM具备类卸载的功能来防止永久代的溢出

JDK中的快排

优化1:在小规模(size<7)数组中,直接插入排序的效率要比快速排序高。
没有一种排序在任何情况下都是最优的《基于比较的内部排序总结 》。 O(N^2)级别的排序看起来似乎比所有先进排序要差的多。但实际上也并非如此,Arrays中的sort()算法就给了我们一个很好的例子。当待排数组规模非常小的时候(JDK中规模的阈值为INSERTIONSORT_THRESHOLD=7),直接插入排序反而要比快排,归并排序要好。
这个道理很简单。数组规模小,简单算法的比较次数不会比先进算法多多少。相反,诸如快排,归并排序等先进算法使用递归操作,所付出的运行代价更高。
★ 优化2:精心选择划分元素,即枢轴。
快排有一种最差的情况,即蜕化成效率最差的起跑排序(见《 交换排序 》)。 导致这种情况产生的主要原因就是枢轴的选择并不能把整个数组划分成两个大致相等的部分。比如对于基本有序的数组,选择第一个元素作为枢轴就会产生这种蜕化。
既然如此,我们可以看看Arryas.sort()是如何为我们选择枢轴的。
● 如果是小规模数组(size<=7),直接取中间元素作为枢轴。
● 如果是中等规模数组(7= ● 如果是大规模数组(size>40),则在9个指定的数中取一个伪中数(中间大小的数s)

  中小规模时,这种取法尽量可以避免数组的较小数或者较大数成为枢轴。值得一提的是大规模的时候,首先在数组中寻找9个数据(可以通过源代码发现这9个数据的位置较为平均的分布在整个数组上);然后每3个数据找中位数;最后在3个中位数上再找出一个中位数作为枢轴。

  仔细想想,这种精心选择的枢轴,使得快排的最差情况成为了极小概率事件了。

★ 优化3:根据枢轴v划分,形成一个形如 (v)* 的数组

  普通快排算法,都是使得枢轴元素移动到数组的较中间位置。枢轴之前的元素全部小于或等于枢轴,之后的元素全部大于枢轴。但与枢轴相等的元素并不能移动到枢轴附近位置。这一点在Arrays.sort()算法中有很大的优化。

  阶段三,递归排序与枢轴不相等都元素区间{7、6}和{41、20、22、93}

JVM内存模型

image.png

JVM中的垃圾回收算法

https://blog.csdn.net/wangxiaotongfan/article/details/82389881
1、标记 -清除算法
2、复制算法
3、标记-整理算法
4、分代收集算法
“分代收集”(Generational Collection)算法,是一种划分的策略,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收

内存回收器

Serial

串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World(服务暂停)
参数控制:-XX:+UseSerialGC 串行收集器

2、ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-压缩
参数控制:-XX:+UseParNewGC ParNew收集器
-XX:ParallelGCThreads 限制线程数量参数

3、Parallel收集器

Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例;新生代复制算法、老年代标记-压缩
参数控制:-XX:+UseParallelGC 使用Parallel收集器+ 老年代串行

4Parallel Old 收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供
参数控制:-XX:+UseParallelOldGC使用Parallel收集器+ 老年代并行

5、CMS 收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。

从名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于“标记-清除”算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为4个步骤,包括:

初始标记(CMS initial mark)
并发标记(CMS concurrent mark)
重新标记(CMS remark)
并发清除(CMS concurrent sweep)
其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
​ 由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行。老年代收集器(新生代使用ParNew

G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。与CMS收集器相比G1收集器有以下特点:

空间整合,G1收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次GC。
可预测停顿,这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。

6、ZGC最新的 stop the world时间

image.png

线程池

线程池的创建
线程池可以通过ThreadPoolExecutor来创建。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) 

corePoolSize: 线程池核心线程数最大值
maximumPoolSize: 线程池最大线程数大小
keepAliveTime: 线程池中非核心线程空闲的存活时间大小
unit: 线程空闲存活时间单位
workQueue: 存放任务的阻塞队列
threadFactory: 用于设置创建线程的工厂,可以给创建的线程设置有意义的名字,可方便排查问题。
handler: 线城池的饱和策略事件,主要有四种类型。

redis持久化

redis持久化
RDB每次进行快照方式会重新记录整个数据集的所有信息。RDB在恢复数据时更快,可以最大化redis性能,子进程对父进程无任何性能影响。

AOF有序的记录了redis的命令操作。意外情况下数据丢失甚少。他不断地对aof文件添加操作日志记录,你可能会说,这样的文件得多么庞大呀。是的,的确会变得庞大,但redis会有优化的策略,比如你对一个key1键的操作,set key1 001 , set key1 002, set key1 003。那优化的结果就是将前两条去掉咯,那具体优化的配置在配置文件中对应的是

image

前者是指超过上一次aof重写aof文件大小的百分之多少,会再次优化,如果没有重写过,则以启动时为主。后者是限制了允许重写的最小aof文件大小。bgrewriteaof命令是手动重写命令,会fork子进程,在临时文件中重建数据库状态,对原aof无任何影响,当重建旧的状态后,也会把fork发生后的一段时间内的数据一并追加到临时文件,最后替换原有aof文件,新的命令继续向新的aof文件中追加。

myqsl的explian等级

image.png

你可能感兴趣的:(java)